Pregunta

Lo siento, no entiendo muy FP sin embargo, quiero dividir una secuencia de líneas en una secuencia de secuencias de líneas, asumiendo una línea vacía como la división párrafo, que podría hacerlo en Python como esto:

def get_paraghraps(lines):
    paragraphs = []
    paragraph = []
    for line in lines:
        if line == "": # I know it could also be  "if line:"
            paragraphs.append(paragraph)
            paragraph = []
        else:
            paragraph.append(line)
    return paragraphs

¿Cómo se van haciendo sobre él en Erlang o Haskell?

¿Fue útil?

Solución

Soy sólo un comienzo Haskell programador (y el pequeño Haskell supe fue hace 5 años), pero para empezar, me gustaría escribir la traducción natural de su función, con el acumulador ( "el párrafo actual") que se pueden extender (he añadido tipos, simplemente para mayor claridad):

type Line = String
type Para = [Line]

-- Takes a list of lines, and returns a list of paragraphs
paragraphs :: [Line] -> [Para]
paragraphs ls = paragraphs2 ls []

-- Helper function: takes a list of lines, and the "current paragraph"
paragraphs2 :: [Line] -> Para -> [Para]
paragraphs2 [] para = [para]
paragraphs2 ("":ls) para = para : (paragraphs2 ls [])
paragraphs2 (l:ls)  para = paragraphs2 ls (para++[l])

Esto funciona:

*Main> paragraphs ["Line 1", "Line 2", "", "Line 3", "Line 4"]
[["Line 1","Line 2"],["Line 3","Line 4"]]

Así que esa es una solución. Pero entonces, la experiencia sugiere que Haskell casi siempre hay bibliotecas de funciones para hacer cosas como esta :) Una función relacionada se llama GroupBy , y casi funciona:

paragraphs3 :: [Line] -> [Para]
paragraphs3 ls = groupBy (\x y -> y /= "") ls

*Main> paragraphs3 ["Line 1", "Line 2", "", "Line 3", "Line 4"]
[["Line 1","Line 2"],["","Line 3","Line 4"]]

Vaya. Lo que realmente necesitamos es un "splitBy", y no está en las bibliotecas , pero podemos filtrar los malos a nosotros mismos:

paragraphs4 :: [Line] -> [Para]
paragraphs4 ls = map (filter (/= "")) (groupBy (\x y -> y /= "") ls)

o, si quieres ser fresco, usted puede deshacerse del argumento y hacerlo de la manera insustancial:

paragraphs5 = map (filter (/= "")) . groupBy (\x y -> y /= "")

Estoy seguro de que hay una manera aún más corto. :-)

Editar ephemient señala que (not . null) es más limpio que (/= ""). Así podemos escribir

paragraphs = map (filter $ not . null) . groupBy (const $ not . null)

El (not . null) repetida es una fuerte indicación de que realmente debería abstracta esto en una función, y esto es lo que el módulo Data.List.Split hace, como se señala en la respuesta a continuación.

Otros consejos

También estoy tratando de aprender Haskell. Una solución para esta pregunta podría ser:

paragraphs :: [String] -> [[String]]
paragraphs [] = []
paragraphs lines = p : (paragraphs rest)
    where (p, rest) = span (/= "") (dropWhile (== "") lines)

donde estoy usando las funciones de de Datos .list. Los que yo estoy usando ya están disponibles desde el preludio, pero se pueden encontrar su documentación en el enlace.

La idea es encontrar el primer párrafo usando span (/= ""). Esto devolverá el párrafo, y las líneas siguientes. A continuación, Recurse en la lista más pequeña de las líneas que llamo rest.

Antes de dividir a cabo el primer párrafo, que abandonan las líneas vacías usando dropWhile (== ""). Esto es importante para comer la línea (s) vacío que separa los párrafos. Mi primer intento fue la siguiente:

paragraphs :: [String] -> [[String]]
paragraphs [] = []
paragraphs lines = p : (paragraphs $ tail rest)
    where (p, rest) = span (/= "") lines

pero esto no funciona cuando se llega al punto final desde rest es entonces la cadena vacía:

*Main> paragraphs ["foo", "bar", "", "hehe", "", "bla", "bla"]
[["foo","bar"],["hehe"],["bla","bla"]*** Exception: Prelude.tail: empty list

Dejar caer las líneas vacías resuelve este, y también hace que el código de tratar cualquier número de líneas en blanco como separador de párrafos, que es lo que se puede esperar como un usuario.

La solución más limpia sería utilizar algo apropiado de la fracción paquete.

Usted necesitará instalar ese principio, pero luego Data.List.Split.splitWhen null debe hacer el trabajo perfectamente.

Piense de forma recursiva.

get_paragraphs []      paras para = paras ++ [para]
get_paragraphs ("":ls) paras para = get_paragraphs ls (paras ++ [para]) []
get_paragraphs (l:ls)  paras para = get_paragraphs ls paras (para ++ [l])

Se desea agrupar las líneas, por lo groupBy de Data.List parece ser un buen candidato. Se utiliza una función personalizada para determinar qué líneas son "iguales" lo que uno puede suministrar algo que hace que las líneas en el mismo párrafo "iguales". Por ejemplo:

import Data.List( groupBy )

inpara :: String -> String -> Bool
inpara _ "" = False
inpara _ _  = True

paragraphs :: [String] -> [[String]]
paragraphs = groupBy inpara

Esto tiene algunas limitaciones, ya que inpara sólo puede comparar dos líneas adyacentes y una lógica más compleja no encajar en el marco dado por groupBy. Una solución más elemental si es más flexible. Utilizando uno básica recursividad puede escribir:

paragraphs [] = []
paragraphs as = para : paragraphs (dropWhile null reminder)
  where (para, reminder) = span (not . null) as
                           -- splits list at the first empty line

span divide una lista en el punto de la función suministrada se convierte en falsa (la primera línea de vacío), dropWhile elimina elementos principales para los que la función suministrada es verdadera (las líneas vacías principales).

Mejor tarde que nunca.

import Data.List.Split (splitOn)

paragraphs :: String -> [[String]]
paragraphs s = filter (not . null) $ map words $ splitOn "\n\n" s

paragraphs "a\nb\n\nc\nd"                == [["a", "b"], ["c", "d"]]
paragraphs "\n\na\nb\n\n\nc\nd\n\n\n"    == [["a", "b"], ["c", "d"]]
paragraphs "\n\na\nb\n\n \n  c\nd\n\n\n" == [["a", "b"], ["c", "d"]]
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top