2011-03-19 4 views
7

Estoy tratando de hacer una función relativamente simple que es casi concat pero con un pequeño giro. Se supone que es binario o juntos los últimos y primeros elementos de cada lista y los combina en el proceso. Estoy trabajando para aprender a escribir código que puede aprovechar las capacidades de Stream Fusion en Data.List.StreamEn Haskell, concat construye una lista perezosamente pero mi propia versión con un giro, no lo hace

Comprobé que el concat en base hace lo que se necesita y construye la lista perezosamente, sin embargo, esta versión I creado no. en la base, concat se especifica de la siguiente manera:

concat :: [[a]] -> [a] 
concat = foldr (++) [] 

(++) :: [a] -> [a] -> [a] 
(++) []  ys = ys 
(++) (x:xs) ys = x : xs ++ ys 

Aquí está mi código:

bconcat :: [[Word8]] -> [Word8] 
bconcat = foldr1 bappend 

bappend :: [Word8] -> [Word8] -> [Word8] 
bappend as bs = init as ++ (last as .|. head bs) : tail bs 

La pregunta que tengo es, ¿Cómo escribo esto para que la lista se construye con pereza? Incluso traté de escribir al pasar imitando la definición (++) en la base, pero no hizo la diferencia.

Por el momento, uso el siguiente código, funciona de la manera que quiero, pero el rendimiento se queda atrás concat. Además, utiliza la recursión explícita que me gustaría evitar.

bconcat :: [[Word8]] -> [Word8] 
bconcat (a:b:xs) = init a ++ bconcat ((bappend (last a) b):xs) 
bconcat (a:[]) = a 
bconcat [] = [] 

bappend :: Word8 -> [Word8] -> [Word8] 
bappend !a bs = (a .|. head bs) : tail bs 

Por lo tanto, la pregunta que tengo es, ¿Cómo se escribe el código por lo que construye la lista con pereza y sin recursión explícita?

Gracias por su tiempo.

Editar:

Mi principal interés, por el momento, está haciendo un código limpio, conciso y understable con los combinadores estándar. Todavía soy un principiante con un pensamiento funcional y me encantaría ver una forma razonablemente eficiente de ponerlos en práctica aquí.

+0

Supongo que el problema es que tiene que evaluar la primera lista completa antes de poder hacerlo. En mi humilde opinión, no hay evaluación perezosa posible. – fuz

+0

Definitivamente hay una manera de hacerlo construir la lista resultante perezosamente. concat lo logra y hace casi lo mismo. Además, el último fragmento que publiqué lo hago de la manera que quiero. Simplemente no puede alcanzar el nivel de rendimiento de concat. – Elriel

+0

Veo la diferencia, la primera versión OR (última como) con el re –

Respuesta

4

Tu definición me parece estricta. Por ejemplo, intente evaluar

length $ bconcat [[1,2,3],[4,undefined,6]] 

Pero podría estar acumulando errores para el. |. expresión. Quizás quieras forzar eso.

Siempre se puede fusionar cosas por uno mismo si no lo hacen así fusible automáticamente:

bconcat [] = error "bconcat: empty outer list" 
bconcat (xs:xss) = loop xss xs 
    where loop [] ys = ys 
     loop ((x:xs):xss) [y] = let z = x .|. y in seq z $ z : loop xss xs 
     loop ([]:_) _ = error "bconcat: empty inner list" 
     loop xss (y:ys) = y : loop xss ys 
+0

No estoy seguro de cuál de mis definiciones de bconcat pretendía probar "length $ bconcat [[1,2,3], [4, undefined, 6]]" pero devuelve 5 en ambos casos, como esperaba . – Elriel

+0

Gracias por la versión fusionada de bconcat. Ese código realmente vuela. Es incluso más rápido que el concat regular. El programa que intento optimizar funciona un 40% más rápido con ese. – Elriel

+0

Ack, eso está roto: "bconcat [[1], [2], [4]]" da "[3 *** Excepción: /tmp/foo.hs:(5,9)-(8,41) : Patrones no exhaustivos en el bucle de función " –

2

Parece que tiene un error tipográfico, 'bapennd' debe ser 'bappend' en el primer fragmento. Y creo que 'combinar' en el último fragmento debería ser 'bconcat'.

Parece que el primero (foldr1 bappend) será bastante vago. ¿Podría explicar la falta de pereza?

En cuanto a la necesidad tanto "init x" y "últimos XS", al mismo tiempo, es posible que desee un solo recorrido de xs:

let un [] = error "no last element" 
    un (x:[]) = ([],x) 
    un (x:xs) = let (ys,z) = un xs 
       in (x:ys,z) 

Esto tiene un espacio y tiempo disyuntiva diferente. Si siempre fuerza la respuesta de bconcat en orden, entonces es probable que sea una mejora. El "último x" contiene una referencia al encabezado de la lista completa y evita que xs se recolecte como basura hasta que se fuerce.

+0

Gracias, arreglé los tipeos. falta de pereza, como comer toda la memoria en mi sistema, causada por tener que hacer toda la cadena en la memoria antes de que cualquier otro código pueda hacer algo con ella. La idea es hacer que este programa funcione en memoria constante. – Elriel

+0

Intenté usar tu función un para obtener la cabeza y la cola al mismo tiempo, pero terminó desacelerando el código. Sospecho que esto tiene algo que ver con la optimización de fusión de secuencias, ya que estoy usando eso. Esa "última x" que contiene la referencia explica por qué obtengo una ligera mejoría en el rendimiento para hacer el primer parámetro del bappend que estoy usando actualmente, estricto. – Elriel

2

puedo cambiar la respuesta por augustuss para que coincida más de cerca el problema original de la escritura:

bconcat2 [] = [] 
bconcat2 (xs:xss) = loop xss xs 
    where loop [] ys = ys 
     loop xss (y:[]) = gather xss y 
     loop xss (y:ys) = y : loop xss ys 
     loop (xs:xss) [] = loop xss xs 
     gather [] z = [z] 
     gather ([]:xss) z = gather xss z 
     gather ((x:[]):xss) z = gather xss $! z .|. x 
     gather ((x:xs):xss) z = let z' = z .|. x in z' : loop xss xs 

Lo anterior ignora cualquier lista interna vacía.

+0

Gracias, mi código actual no necesita manejar listas internas vacías, pero podría ser útil para otro propósito. – Elriel

Cuestiones relacionadas