Pergunta

Desculpe eu não começ completamente FP ainda, eu quero dividir uma seqüência de linhas em uma seqüência de seqüências de linhas, assumindo uma linha em branco como a divisão parágrafo, eu poderia fazê-lo em python como este:

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

Como você vai fazer sobre isso em Erlang ou Haskell?

Foi útil?

Solução

Sou apenas um programador Haskell começando (e o pequeno Haskell que eu aprendi foi 5 anos atrás), mas para um começo, eu ia escrever a tradução natural de sua função, com o acumulador ( "o parágrafo atual") sendo repassados ??(Eu adicionei os tipos, apenas para maior clareza):

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

Isso funciona:

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

Então, isso é uma solução. Mas, em seguida, a experiência Haskell sugere que há quase sempre funções de biblioteca para fazer coisas como esta :) Uma função relacionada é chamada groupBy , e quase 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"]]

Opa. O que realmente precisamos é um "splitBy" e não é nas bibliotecas , mas podemos filtrar os maus-nos:

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

ou, se você quiser ser legal, você pode se livrar do argumento e fazê-lo da maneira inútil:

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

Eu tenho certeza que há uma maneira ainda mais curto. :-)

Editar : ephemient pontos que (not . null) é mais limpo do que (/= ""). Assim, podemos escrever

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

O (not . null) repetida é uma forte indicação de que nós realmente deveria abstrato isso em uma função, e é isso que o Data.List.Split módulo faz, como apontado na resposta abaixo.

Outras dicas

Eu também estou tentando aprender Haskell. Uma solução para esta questão poderia ser:

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

onde eu estou usando as funções de Dados .list. O que eu estou usando já estão disponíveis a partir do Prelude, mas você pode encontrar a sua documentação no link.

A idéia é encontrar o primeiro parágrafo usando span (/= ""). Isso irá retornar o parágrafo, e as linhas seguintes. Em seguida, recurse na lista menor de linhas que eu chamo rest.

Antes de divisão para fora no primeiro parágrafo, que descartar quaisquer linhas vazias usando dropWhile (== ""). Isto é importante para comer a linha de vazio (s) separando os parágrafos. Minha primeira tentativa foi o seguinte:

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

mas isso falhar quando chegarmos ao parágrafo final desde rest é então a string vazia:

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

Dropping linhas vazias resolve isso, e ele também faz o deleite código de qualquer número de linhas em branco como um separador de parágrafo, que é o que eu esperaria de um usuário.

A solução mais limpa seria usar algo apropriado a partir da cisão pacote.

Você precisará instalar esse início, mas depois Data.List.Split.splitWhen null deve fazer o trabalho perfeitamente.

Pense 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])

Você deseja agrupar as linhas, então groupBy de Data.List parece ser um bom candidato. Ele usa uma função personalizada para determinar quais linhas são "iguais" para que se possa fornecer algo que faz linhas do mesmo parágrafo "igual". Por exemplo:

import Data.List( groupBy )

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

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

Isto tem algumas limitações, uma vez inpara só pode comparar duas linhas adjacentes e lógica mais complexa não se encaixa no quadro dado por groupBy. Uma solução mais elementar se é mais flexível. Usando um básico recursão pode escrever:

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

span divide uma lista no ponto a função fornecida se torna falso (a primeira linha em branco), remove dropWhile principais elementos para os quais a função fornecida é verdadeira (vazias quaisquer linhas principais).

Melhor tarde do 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 em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top