2009-05-30 7 views
4

Désolé, je ne reçois pas encore FP, je veux diviser une séquence de lignes dans une séquence de séquences de lignes, en supposant une ligne vide comme division de paragraphe, je pourrais le faire en python comme ceci:paragraphes fonctionnels

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 le feriez-vous dans Erlang ou Haskell?

Répondre

4

Je ne suis qu'un début programmeur Haskell (et le petit Haskell que j'ai appris il y a 5 ans), mais pour commencer, j'écrirais la traduction naturelle de votre fonction, avec l'accumulateur ("le paragraphe courant") é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"]] 

Voilà 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 liée 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 it's not in the libraries, mais nous pouvons filtrer les mauvais nous-mêmes ceux:

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 faire la manière inutile:

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

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

Modifier: ephemient souligne que (not . null)(/= "") est plus propre que. On peut donc écrire

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

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

+0

Avez-vous une référence pour ça? Le rapport de la bibliothèque Haskell 98 (http://www.cs.auckland.ac.nz/references/haskell/haskell-library-1.4-html/list.html) * dit * que c'est un prédicat d'égalité, mais donne une implémentation explicite de la fonction groupBy. Notez que la signature de type de groupBy n'a pas de contrainte "Eq", suggérant qu'il est supposé fonctionner avec une fonction de prédicat arbitraire (transitive?) ... Il semble commun de l'utiliser de cette façon: http: //www.haskell .org/haskellwiki/List_function_suggestions # Generalize_groupBy_and_friends – ShreevatsaR

+2

Je préférerais utiliser 'non. null' à l'aide de '(/ =" ")', ce qui conduirait à l'ajout d'un autre point 'map = (filter $ not. null). groupBy (const $ not. null) ' – ephemient

3

Pensez récursivement.

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

Oh mon dieu! Est-ce un [Paras] (http://bulbapedia.bulbagarden.net/wiki/Paras) que je vois? –

4

J'essaie aussi 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 Data.List. Ceux que j'utilise sont déjà disponibles dans le Prelude, mais vous pouvez trouver leur documentation dans le lien.

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

Avant de séparer le premier paragraphe, nous supprimons les lignes vides en utilisant dropWhile (== ""). C'est important de manger la ou les lignes vides 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 résout ce, et il fait également le traitement de code n'importe quel nombre de lignes vides comme un séparateur de paragraphe, ce qui est ce que je m'attendrais en tant qu'utilisateur.

+1

'null' est marginalement plus efficace et certainement plus général que le test de l'égalité (in) avec les listes et les chaînes vides. 'span (/ =" ")' => 'break null',' dropWhile (== "") '=>' dropWhile null', les deux améliorent la lisibilité. – ephemient

+0

Oui et non - lors de la recherche d'une chaîne vide dans une liste de chaînes, je pense qu'il est plus lisible de rechercher "". Au moins pour débutant comme moi :-) –

3

Vous souhaitez grouper les lignes, donc groupBy de Data.List semble être un bon candidat. Il utilise une fonction personnalisée pour déterminer quelles lignes sont "égales" afin que l'on puisse fournir quelque chose qui rende les lignes dans le même paragraphe "égales". 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 elle est plus flexible. En utilisant 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 principaux éléments pour lesquels la fonction fournie est vraie (les grandes lignes vides) .

4

La solution la plus propre serait d'utiliser quelque chose de approprié à partir de l'emballage split.

Vous devrez d'abord l'installer, mais alors Data.List.Split.splitWhen null devrait faire le travail parfaitement.

0

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