7

这是一个 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 程序将此文件读入列表。

-模块(欧拉67)。
-作者(“凯尔·斯潘登”)。

-出口([解决/0])。

解决()->
    {ok, File} = file:open("triangle.txt", [read]),
    数据 = 读取文件(文件),
    确定 = 文件:关闭(文件),
    数据。

读取文件(文件)->
    读取文件(文件,[])。

读取文件(文件,数据)->
    case io:fread(File, "", "~d") of
        {好的,[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|...]

请注意第四行的最后一个数字 (34) 和第五行的第一个数字 (10) 是如何合并为一个数字 3410 的。

当我使用“od”转储文本文件时,这些行没有什么特别之处;它们以 cr-nl 结尾,就像任何其他行一样:

> od -ta 三角形.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)。

4

3 回答 3

9

我也打算把它当作 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",
 [...]|...]

因此,出于计数的目的,它似乎将换行符计数为常规字符,但在产生输出时却没有。Loopy 就像地狱一样。

一周的 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 的补丁(请记住重新编译并将新的梁文件放在旧文件的顶部):

--- ../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,并获得由此产生的名声和荣耀......

于 2009-01-29T00:22:10.153 回答
2

除了它似乎是其中一个 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

干杯,H。

于 2009-03-27T10:21:00.023 回答
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 设置格式字符串,这样我们就不必这样做了。

更新 这是一个不会强迫您更改文件的解决方法。这假设所有数字都是两个字符(< 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:fread 中可能存在错误的组合,但应该这样做。

再次更新上述内容仅适用于两位数输入,但由于该示例将所有数字(甚至小于 10 的数字)打包成两位数格式,因此适用于本示例。

于 2009-01-28T15:47:51.487 回答