2009-01-23 23 views
6

Esta es una pregunta de Erlang.Comportamiento inesperado de io: fread en Erlang

Me he encontrado con un comportamiento inesperado por io: fread.

Me preguntaba si alguien podría verificar si hay algún problema con la forma en que uso io: fread o si hay un error en io: fread.

I tienen un archivo de texto que contiene un "triángulo de números" como sigue:

 
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 
... 

Hay un único espacio entre cada par de números y cada línea termina con un par de nueva línea de retorno de carro .

Utilizo el siguiente programa Erlang para leer este archivo en una lista.

 
-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. 

La salida de este programa es:

 
([email protected])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|...] 

Nota cómo el último número de la cuarta línea (34) y el primer número de la quinta línea (10) se han fusionado en un único número 3410.

Cuando vuelco el archivo de texto usando "od" no hay nada de especial en esas líneas; terminan con cr-nl al igual que cualquier otra línea:

 
> 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 

Una observación interesante es que algunos de los números para los cuales se produce el problema pasa a ser el límite de 16 bytes en el archivo de texto (pero no todos, por ejemplo 6890).

+0

Esto busca todo como un error. Tomando la longitud de los datos muestra que son 32 elementos, no 36 elementos. Organizar los datos en otros formatos solo mueve el problema. (Lo probé en Vista + Erland 5.6.5). – Godeke

Respuesta

9

Voy a ser un error en Erlang, y extraño. Cambio de la cadena de formato a "~ 2s" da resultados igualmente extraños:

["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", 
[...]|...] 

lo que parece que se está contando un carácter de nueva línea como un personaje regular a los efectos de contar, pero no cuando se trata de producir la salida. Loopy como todo el infierno.

Una semana de programación de Erlang, y ya estoy profundizando en la fuente. Eso podría ser un nuevo récord para mí ...

EDITAR

Un poco más investigación ha confirmado que para mí esto es un error. Llamando a uno de los métodos internos que se utilizan en 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"} 

Básicamente, si hay varios valores que deben leerse, a continuación, una nueva línea, la primera nueva línea se come en la parte "aún no se ha leído" de la cadena. Otras pruebas sugieren que si antepones un espacio, está bien, y si lideras la cadena con una nueva línea, pide más.

Voy a llegar al fondo de esto, carajo ... (sonrisa) No hay tanto código para pasar, y no mucho de lo que se trata específicamente de líneas nuevas, por lo que no debería ' toma demasiado tiempo para reducirlo y arreglarlo.

EDITAR^2

HA HA! Tengo el pequeño blighter.

Aquí está el parche a la stdlib que desee (recuerde que recompilar y colocar el nuevo archivo de viga sobre la parte superior de la anterior):

--- ../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} 

ahora a presentar mi parche para Erlang-parches, y cosechar la fama y la gloria resultante ...

+0

Parece que se está calentando. Gracias por tomarse el tiempo para llegar al fondo de esto. –

0

Me di cuenta de que hay varias instancias donde se combinan dos números, y parece estar en los límites de la línea en cada línea que comienza en la cuarta línea y más allá.

He descubierto que si se agrega un carácter de espacio en blanco al principio de cada línea que comienza en el quinto, es decir:

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 
... 

Los números se hacen analizado sintácticamente correcta:

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|...] 

También funciona si agrega los espacios en blanco al comienzo de las primeras cuatro líneas, también.

Es más una solución que una solución real, pero funciona. Me gustaría descubrir cómo configurar la cadena de formato para io: fread de modo que no tengamos que hacer esto.

ACTUALIZACIÓN Aquí hay una solución alternativa que no lo forzará a cambiar el archivo. Esto supone que todos los dígitos son dos caracteres (< 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. 

Básicamente, el código detecta cualquiera de los números que son la concatenación de dos a través de una nueva línea y los divide en dos.

De nuevo, es un kludge que implica un posible error en io: fread, pero eso debería hacerlo.

ACTUALIZACIÓN De nuevo el anterior sólo funcionará para las entradas de dos dígitos, pero ya que el ejemplo paquetes de todos los dígitos (incluso las < 10) en un formato de dos dígitos, que funcione para este ejemplo.

+0

Gracias! Eso ayuda.Si encuentras una cadena de formato que funciona, eso también sería útil. Pero solo para asegurarnos de que estamos en sintonía: ¿cree que la cadena de formato que estoy usando ahora (a saber, "~ d") debería funcionar con mi archivo original? En otras palabras: hay un error en io: fread? –

+0

No veo ninguna razón por la que no debería funcionar con el archivo original, pero todavía soy un poco nuevo en Erlang, por lo que podría estar perdiendo algo. Ciertamente podría ser un error, pero no estoy seguro en este momento. – Vector

+0

Su solución también asume que todos los números tienen exactamente dos dígitos: no funcionará para 1 \ n2, ya que aparecerán como 12 y no se dividirán. – womble

1

Además del hecho de que parece ser un error en una de las libs de erlang, creo que podría eludir (muy) fácilmente el problema.

Dado que su archivo está orientado a líneas, creo que la mejor práctica es que también lo procesa línea por línea.

Considere la siguiente construcción. Funciona muy bien en un erlang no parcheado y debido a que utiliza evaluación diferida, puede manejar archivos de longitud arbitraria sin tener que leer todo en la memoria primero. El módulo contiene un ejemplo de una función para aplicar a cada línea, convirtiendo una línea de representaciones de texto de enteros en una lista de enteros.


-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 

Cheers, h.

Cuestiones relacionadas