这是一个Erlang问题。
我遇到了一些意想不到的行为:io:fread。
我想知道是否有人可以检查我使用io的方式是否有问题:fread或者io中是否有错误:fread。
我有一个文本文件,其中包含“数字三角形”,如下所示:
59 73 41 52 40 09 26 53 06 34 10 51 87 86 81 61 95 66 57 25 68 90 81 80 38 92 67 73 30 28 51 76 81 18 75 44 ...
每对数字之间只有一个空格,每一行以回车符换行结束。
我使用以下Erlang程序将此文件读入列表。
-module(euler67). -author('Cayle Spandon'). -export([solve/0]). solve() -> {ok, File} = file:open("triangle.txt", [read]), Data = read_file(File), ok = file:close(File), Data. read_file(File) -> read_file(File, []). read_file(File, Data) -> case io:fread(File, "", "~d") of {ok, [N]} -> read_file(File, [N | Data]); eof -> lists:reverse(Data) end.
该程序的输出是:
(erlide@cayle-spandons-computer.local)30> euler67:solve(). [59,73,41,52,40,9,26,53,6,3410,51,87,86,8161,95,66,57,25, 6890,81,80,38,92,67,7330,28,51,76,81|...]
注意如何将第四行(34)的最后一个数字和第五行(10)的第一个数字合并为一个数字3410。
当我使用“od”转储文本文件时,这些行没有什么特别之处;他们就像任何其他行一样以cr-nl结尾:
> od -t a triangle.txt 0000000 5 9 cr nl 7 3 sp 4 1 cr nl 5 2 sp 4 0 0000020 sp 0 9 cr nl 2 6 sp 5 3 sp 0 6 sp 3 4 0000040 cr nl 1 0 sp 5 1 sp 8 7 sp 8 6 sp 8 1 0000060 cr nl 6 1 sp 9 5 sp 6 6 sp 5 7 sp 2 5 0000100 sp 6 8 cr nl 9 0 sp 8 1 sp 8 0 sp 3 8 0000120 sp 9 2 sp 6 7 sp 7 3 cr nl 3 0 sp 2 8 0000140 sp 5 1 sp 7 6 sp 8 1 sp 1 8 sp 7 5 sp 0000160 4 4 cr nl 8 4 sp 1 4 sp 9 5 sp 8 7 sp
一个有趣的观察结果是,问题发生的一些数字恰好出现在文本文件中的16字节边界上(但不是全部,例如6890)。
答案 0 :(得分:9)
我将继续使用Erlang中的一个bug,这也是一个奇怪的错误。将格式字符串更改为“~2s”会产生同样奇怪的结果:
["59","73","4","15","2","40","0","92","6","53","0","6","34",
"10","5","1","87","8","6","81","61","9","5","66","5","7",
"25","6",
[...]|...]
因此,为了计算,它似乎将换行符计为常规字符,但在生成输出时则不然。懒散无比。
一周的Erlang编程,我已经深入研究了源代码。这对我来说可能是一个新纪录......
修改强>
我进一步调查证实这是一个错误。调用fread
中使用的内部方法之一:
> io_lib_fread:fread([], "12 13\n14 15 16\n17 18 19 20\n", "~d").
{done,{ok,"\f"}," 1314 15 16\n17 18 19 20\n"}
基本上,如果要读取多个值,那么换行符,第一个换行符将在字符串的“仍待读取”部分中被吃掉。其他测试表明,如果你在前面添加一个空格就可以了,如果你用一个换行符引导该字符串,它会要求更多。
我要深究这一点,gosh-darn-it ...(笑)没有那么多代码可以通过,而且没有太多代码专门处理换行符,所以它不应该需要很长时间才能缩小范围并修复它。
修改^ 2 强>
哈哈!得到了一点点。这是你想要的stdlib的补丁(记得重新编译并将新的beam文件放在旧的顶部):
--- ../erlang/erlang-12.b.3-dfsg/lib/stdlib/src/io_lib_fread.erl
+++ ./io_lib_fread.erl
@@ -35,9 +35,9 @@
fread_collect(MoreChars, [], Rest, RestFormat, N, Inputs).
fread_collect([$\r|More], Stack, Rest, RestFormat, N, Inputs) ->
- fread(RestFormat, Rest ++ reverse(Stack), N, Inputs, More);
+ fread(RestFormat, Rest ++ reverse(Stack), N, Inputs, [$\r|More]);
fread_collect([$\n|More], Stack, Rest, RestFormat, N, Inputs) ->
- fread(RestFormat, Rest ++ reverse(Stack), N, Inputs, More);
+ fread(RestFormat, Rest ++ reverse(Stack), N, Inputs, [$\n|More]);
fread_collect([C|More], Stack, Rest, RestFormat, N, Inputs) ->
fread_collect(More, [C|Stack], Rest, RestFormat, N, Inputs);
fread_collect([], Stack, Rest, RestFormat, N, Inputs) ->
@@ -55,8 +55,8 @@
eof ->
fread(RestFormat,eof,N,Inputs,eof);
_ ->
- %% Don't forget to count the newline.
- {more,{More,RestFormat,N+1,Inputs}}
+ %% Don't forget to strip and count the newline.
+ {more,{tl(More),RestFormat,N+1,Inputs}}
end;
Other -> %An error has occurred
{done,Other,More}
现在将我的补丁提交给erlang-patches,并获得由此产生的名声和荣耀......
答案 1 :(得分:1)
除了它似乎是其中一个erlang库中的错误之外,我认为你可以(非常)轻松地避开这个问题。
鉴于您的文件是面向行的,我认为最佳做法是您逐行处理它。
考虑以下结构。它适用于未修补的erlang,因为它使用延迟评估,它可以处理任意长度的文件,而不必先将所有文件读入内存。该模块包含一个应用于每一行的函数示例 - 将一行整数的文本表示形式转换为整数列表。
-module(liner).
-author("Harro Verkouter").
-export([liner/2, integerize/0, lazyfile/1]).
% Applies a function to all lines of the file
% before reducing (foldl).
liner(File, Fun) ->
lists:foldl(fun(X, Acc) -> Acc++Fun(X) end, [], lazyfile(File)).
% Reads the lines of a file in a lazy fashion
lazyfile(File) ->
{ok, Fd} = file:open(File, [read]),
lazylines(Fd).
% Actually, this one does the lazy read ;)
lazylines(Fd) ->
case io:get_line(Fd, "") of
eof -> file:close(Fd), [];
{error, Reason} ->
file:close(Fd), exit(Reason);
L ->
[L|lazylines(Fd)]
end.
% Take a line of space separated integers (string) and transform
% them into a list of integers
integerize() ->
fun(X) ->
lists:map(fun(Y) -> list_to_integer(Y) end,
string:tokens(X, " \n")) end.
Example usage:
Eshell V5.6.5 (abort with ^G)
1> c(liner).
{ok,liner}
2> liner:liner("triangle.txt", liner:integerize()).
[59,73,41,52,40,9,26,53,6,34,10,51,87,86,81,61,95,66,57,25,
68,90,81,80,38,92,67,73,30|...]
And as a bonus, you can easily fold over the lines of any (lineoriented) file w/o running out of memory :)
6> lists:foldl( fun(X, Acc) ->
6> io:format("~.2w: ~s", [Acc,X]), Acc+1
6> end,
6> 1,
6> liner:lazyfile("triangle.txt")).
1: 59
2: 73 41
3: 52 40 09
4: 26 53 06 34
5: 10 51 87 86 81
6: 61 95 66 57 25 68
7: 90 81 80 38 92 67 73
8: 30 28 51 76 81 18 75 44
干杯, 小时。
答案 2 :(得分:0)
我注意到有多个实例合并了两个数字,并且它似乎位于从第四行开始的每一行的行边界处。
我发现如果你在第五行开始的每一行的开头添加一个空白字符,那就是:
59
73 41
52 40 09
26 53 06 34
10 51 87 86 81
61 95 66 57 25 68
90 81 80 38 92 67 73
30 28 51 76 81 18 75 44
...
数字得到正确解析:
39> euler67:solve().
[59,73,41,52,40,9,26,53,6,34,10,51,87,86,81,61,95,66,57,25,
68,90,81,80,38,92,67,73,30|...]
如果您将空白添加到前四行的开头也是有效的。
这更像是一种解决方法,而不是一种实际解决方案,但它确实有效。我想弄清楚如何为io:fread设置格式字符串,以便我们不必这样做。
<强>更新强> 这是一种不会强制您更改文件的解决方法。这假定所有数字都是两个字符(&lt; 100):
read_file(File, Data) ->
case io:fread(File, "", "~d") of
{ok, [N] } ->
if
N > 100 ->
First = N div 100,
Second = N - (First * 100),
read_file(File, [First , Second | Data]);
true ->
read_file(File, [N | Data])
end;
eof ->
lists:reverse(Data)
end.
基本上,代码会捕获换行符中两个串联的任何数字,并将它们分成两行。
同样,这是一个暗示io中可能存在错误的kludge:fread,但是应该这样做。
再次更新上述内容仅适用于两位数输入,但由于该示例将所有数字(甚至是<10)数字打包成两位数格式,因此该示例适用于此示例