Question

Désolé, je ne comprends pas bien FP encore, je veux diviser une séquence de lignes en une séquence de séquences de lignes, en supposant une ligne vide comme division de paragraphe, je pouvais le faire en python comme ceci:

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

Comment feriez-vous le faire dans Erlang ou Haskell?

Était-ce utile?

La solution

Je ne suis qu'un début programmeur Haskell (et le petit Haskell j'ai appris était il y a 5 ans), mais pour un début, j'écrire la traduction naturelle de votre fonction, avec l'accumulateur ( « l'actuel paragraphe ») étant passé autour (je l'ai ajouté types, juste pour plus de clarté):

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

Cela fonctionne:

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

Alors, c'est une solution. Mais, l'expérience Haskell suggère qu'il ya presque toujours des fonctions de bibliothèque pour faire des choses comme ça :) Une fonction correspondante est appelée groupBy , et il fonctionne presque:

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

Oops. Ce dont nous avons besoin est un « splitBy », et ce n'est pas dans les bibliothèques , mais nous pouvons filtrer les mauvais nous:

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

ou, si vous voulez être cool, vous pouvez vous débarrasser de l'argument et de le faire de la façon inutile:

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

Je suis sûr qu'il existe un moyen encore plus court. :-)

Modifier : ephemient souligne que (not . null) est plus propre que (/= ""). Ainsi, nous pouvons écrire

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

Le (not . null) répété est une forte indication que nous devrions vraiment abstraite ceci en fonction, ce qui est ce que le module Data.List.Split fait, comme indiqué dans la réponse ci-dessous.

Autres conseils

Je suis aussi essayer d'apprendre Haskell. Une solution à cette question pourrait être:

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

où j'utilise les fonctions de données .list. Ceux que je utilise sont déjà disponibles à partir du Prélude, mais vous pouvez trouver leur documentation dans le lien.

L'idée est de trouver le premier paragraphe en utilisant span (/= ""). Ceci renvoie le paragraphe et les lignes suivantes. Nous RECURSE ensuite sur la petite liste de lignes que j'appelle rest.

Avant le fractionnement du premier alinéa, on laisse tomber des lignes vides à l'aide dropWhile (== ""). Il est important de manger la ligne vide (s) séparant les paragraphes. Ma première tentative était la suivante:

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

mais cela échoue lorsque nous atteignons le dernier paragraphe puisque rest est alors la chaîne vide:

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

Dropping lignes vides permet de résoudre cela, et il est également le code traiter un certain nombre de lignes vides comme séparateur de paragraphe, qui est ce que je me attends en tant qu'utilisateur.

La solution la plus propre serait d'utiliser quelque chose d'approprié de la fendu paquet.

Vous devez installer cette première, mais Data.List.Split.splitWhen null devrait faire le travail parfaitement.

Pensez récursive.

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

Vous voulez regrouper les lignes, donc groupBy de Data.List semble être un bon candidat. Il utilise une fonction personnalisée pour déterminer quelles lignes sont « égaux » si l'on peut fournir quelque chose qui fait des lignes dans le même paragraphe « égal ». Par exemple:

import Data.List( groupBy )

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

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

Cela a des limites, car inpara ne peut comparer deux lignes adjacentes et une logique plus complexe ne rentre pas dans le cadre donné par groupBy. Une solution plus élémentaire si est plus souple. L'utilisation d'une récursion de base peut écrire:

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

span divise une liste au moment où la fonction fournie devient fausse (la première ligne vide), dropWhile supprime des éléments de premier plan pour lequel la fonction est vraie (fourni toutes les grandes lignes vides).

Mieux vaut tard que jamais.

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"]]
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top