Неожиданное поведение io: fread в Erlang - PullRequest
6 голосов
/ 23 января 2009

Это вопрос Эрланга.

Я столкнулся с неожиданным поведением 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).

Ответы [ 3 ]

9 голосов
/ 29 января 2009

Я собираюсь пойти на это, будучи ошибкой в ​​Эрланге, и странной. Изменение строки формата на «~ 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, и я уже углубляюсь в источник. Это может быть новая запись для меня ...

EDIT

Еще одно расследование подтвердило, что это ошибка. Вызов одного из внутренних методов, который используется в 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"}

По сути, если нужно прочитать несколько значений, тогда начинается новая строка, первая строка новой строки в части строки, которая еще должна быть прочитана. Другое тестирование предполагает, что, если вы добавляете пробел, все в порядке, и если вы начинаете строку с новой строки, она запрашивает больше.

Я собираюсь докопаться до сути, черт возьми ... (ухмыляясь) Не так много кода, через который нужно пройти, и не так уж много конкретно посвящено переводу строк, поэтому не стоит слишком много времени, чтобы сузить и исправить это.

EDIT ^ 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 и пожинать полученную славу и славу ...

1 голос
/ 27 марта 2009

Помимо того факта, что это кажется ошибкой в ​​одной из библиотек 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

Ура, ч.

0 голосов
/ 28 января 2009

Я заметил, что есть несколько случаев, когда два числа объединяются, и кажется, что оно находится на границах строк в каждой строке, начиная с четвертой строки и далее.

Я обнаружил, что если вы добавляете символ пробела в начало каждой строки, начинающейся с пятой, то есть:

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 так, чтобы нам не пришлось это делать.

UPDATE Вот обходной путь, который не заставит вас изменить файл. Предполагается, что все цифры состоят из двух символов (<100): </p>

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.

По сути, код перехватывает любые числа, представляющие собой конкатенацию двух через новую строку, и разбивает их на две части.

Опять же, это kludge, который подразумевает возможную ошибку в io: fread, но это должно сделать это.

ОБНОВЛЕНИЕ СНОВА Вышеприведенное будет работать только для двузначных входов, но поскольку в примере все цифры (даже те, что <10) упакованы в двузначный формат, который будет работать в этом примере. 1024 *

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...