Erlang中io:fread的意外行为

Erlang中io:fread的意外行为,erlang,Erlang,这是一个Erlang问题 我遇到了io:fread的一些意想不到的行为 我想知道是否有人能检查一下我使用io:fread的方式是否有问题,或者io:fread中是否有bug 我有一个文本文件,其中包含一个“数字三角形”,如下所示: 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 ... 59 73 41 52 40 09 26 5

这是一个Erlang问题

我遇到了io:fread的一些意想不到的行为

我想知道是否有人能检查一下我使用io:fread的方式是否有问题,或者io:fread中是否有bug

我有一个文本文件,其中包含一个“数字三角形”,如下所示:

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 ... 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. -模块(euler67)。 -作者('Cayle Spandon')。 -导出([solve/0])。 solve()-> {ok,File}=File:open(“triangle.txt,[read]), 数据=读取文件(文件), 确定=文件:关闭(文件), 数据。 读取文件(文件)-> 读取文件(文件,[])。 读取文件(文件、数据)-> 案例io:fread(文件“,“~d”)的 {好的,[N]}-> 读取文件(文件[N |数据]); eof-> 列表:反向(数据) 结束。 该程序的输出为:

(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|...] (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 >od-t三角形.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 10 sp 5 1 sp 8 7 sp 8 6 sp 8 1 0000060 cr nl 6 1 sp 9 5 sp 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 00001604 cr nl 8 4 sp 14 sp 9 5 sp 8 7 sp
一个有趣的观察结果是,出现问题的一些数字恰好位于文本文件中的16字节边界上(但不是全部,例如6890)。

我注意到有多个实例中两个数字合并,从第四条线开始,每一条线的边界都有

我发现,如果在每行的开头添加一个空格字符,从第五行开始,即:

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设置格式字符串,这样我们就不必这样做了

更新 这里有一个解决方法,它不会强迫您更改文件。假设所有数字都是两个字符(<100):

基本上,代码捕获任何一个数字,这些数字是换行符中两个数字的串联,并将它们拆分为两个

同样,这是一个含糊不清的问题,暗示io中可能存在一个bug:fread,但应该这样做


再次更新以上内容只适用于两位数的输入,但由于示例将所有数字(即使是<10)打包为两位数格式,因此在本示例中也适用。

我认为它也是Erlang中的一个bug,而且是一个奇怪的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编程,我已经在深入研究源代码了。那对我来说可能是一张新唱片

编辑

对我来说,更多的调查已经证实这是一个bug。调用
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"}
基本上,如果有多个值要读取,那么一个换行符,第一个换行符会在字符串的“仍然要读取”部分被吃掉。其他测试表明,如果你在空格前加上前缀就可以了,如果你在字符串前面加了一个换行符,它会要求更多

我要追根究底了,该死的。。。(露齿而笑)没有那么多代码需要处理,也没有多少代码专门处理换行符,所以不应该花太长时间来缩小范围并修复它

编辑^2

哈哈!!有个小坏蛋

下面是您想要的stdlib补丁(请记住重新编译并将新的beam文件放在旧文件的上方):


现在将我的补丁提交给erlang补丁,并收获由此产生的名声和荣耀……

除了它似乎是erlang libs中的一个bug之外,我认为您可以(非常)轻松地规避这个问题

鉴于您的文件是面向行的,我认为最好的做法是逐行处理它

考虑以下构造。它在未修补的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
干杯,
h、

这看起来像是一个bug。从数据长度来看,它是32个元素,而不是36个元素。以其他格式排列数据只会使故障四处移动。(我在Vista+Erland 5.6.5中试用过)。谢谢!这很有帮助。如果您找到一个有效的格式字符串,那当然也会很有帮助。但为了确保我们在同一页上:您认为我现在使用的格式字符串(即“~d”)应该与我的原始字符串一起使用吗
--- ../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}

-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