2009-05-30 17 views
4

Tut mir leid, ich bekomme FP noch nicht ganz, ich möchte eine Folge von Zeilen in eine Sequenz von Zeilen aufteilen, unter der Annahme einer leeren Zeile als Absatzdivision, könnte ich es in Python wie folgt tun:Funktionale Absätze

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 

Wie würden Sie es in Erlang oder Haskell tun?

Antwort

4

Ich bin nur ein Anfänger Haskell Programmierer (und die kleine Haskell, die ich gelernt habe, war vor 5 Jahren), aber für den Anfang würde ich die natürliche Übersetzung Ihrer Funktion schreiben, mit dem Akku ("der aktuelle Absatz")) wird herumgereicht (ich habe Typen hinzugefügt, nur für Klarheit):

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

Dies funktioniert:

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

Damit eine Lösung ist. Aber dann schlägt Haskell Erfahrung, dass es fast immer Bibliotheksfunktionen für Dinge wie diese :) Eine verwandte Funktion tun wird groupBy genannt, und es funktioniert fast:

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. Was wir wirklich brauchen, ist ein „splitBy“, und it's not in the libraries, aber wir können die schlechten uns herauszufiltern:

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

oder, wenn man cool sein wollen, Sie loszuwerden, das Argument bekommen können und tun es die sinnloser Weg:

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

Ich bin mir sicher es gibt einen noch kürzeren Weg.:-)

bearbeiten: ephemient weist darauf hin, dass (not . null) sauberer als (/= "") ist. So können wir

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

Die wiederholten (not . null) ein starker Hinweis darauf ist, dass wir wirklich sollte abstrakt dies aus in eine Funktion, und das ist, was die Data.List.Split module tut, wie unten in der Antwort darauf hingewiesen.

+0

Haben Sie eine Referenz dafür? Der Haskell 98-Bibliotheksbericht (http://www.cs.auckland.ac.nz/references/haskell/haskell-library-1.4-html/list.html) * sagt * es ist ein Gleichheitsprädikat, gibt aber eine explizite Implementierung von die Funktion groupBy. Beachten Sie, dass die Typ-Signatur von groupBy keine "Eq" -Bedingung hat, was darauf hindeutet, dass sie mit einer beliebigen (transitiven?) Prädikatfunktion arbeiten soll ... Es scheint üblich, sie so zu verwenden: http: //www.haskell .org/haskellwiki/List_function_suggestions # Generalize_groupBy_and_friends – ShreevatsaR

+2

Ich würde 'nicht bevorzugen. null 'to using '(/ =" ")', was zu den noch weiteren point-free' parges = map (Filter $ not. null) führen würde. groupBy (const $ nicht. null) ' – ephemient

3

Denken Sie rekursiv.

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 mein! Ist das ein [Paras] (http://bulbapedia.bulbagarden.net/wiki/Paras) ich sehe? –

4

Ich versuche auch, Haskell zu lernen. Eine Lösung für diese Frage könnte sein:

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

, wo ich die Funktionen von Data.List bin mit. Die, die ich benutze, sind bereits vom Prelude verfügbar, aber du kannst ihre Dokumentation im Link finden.

Die Idee ist es, den ersten Absatz mit span (/= "") zu finden. Dies wird den Absatz und die folgenden Zeilen zurückgeben. Wir rekapitulieren dann die kleinere Liste der Zeilen, die ich rest nenne.

Vor dem Aufteilen des ersten Absatzes löschen wir alle leeren Zeilen mit dropWhile (== ""). Dies ist wichtig, um die Leerzeile (n) zwischen den Absätzen zu essen. Mein erster Versuch war:

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

aber dies nicht gelingt, wenn wir den letzten Absatz, da rest erreichen ist dann die leere Zeichenkette:

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

leere Zeilen Dropping diese löst, und es macht auch den Code treat beliebig viele leere Zeilen als Absatztrennzeichen, was ich als Benutzer erwarten würde.

+1

'null' ist marginal effizienter und definitiv allgemeiner als das Testen auf (Gleichheit) mit leeren Listen und Strings. 'span (/ =" ")' => 'break null',' dropWhile (== "") => 'dropWhile null', beide verbessern die Lesbarkeit. – ephemient

+0

Ja und Nein - wenn ich nach einer leeren Zeichenfolge in einer Liste von Zeichenfolgen suche, ist es besser, nach "" zu suchen. Zumindest für Anfänger wie mich :-) –

3

Sie möchten die Zeilen gruppieren, so scheint groupBy von Data.List scheint ein guter Kandidat. Es verwendet eine benutzerdefinierte Funktion, um zu bestimmen, welche Zeilen "gleich" sind, so dass man etwas liefern kann, das Linien in demselben Absatz "gleich" macht. Zum Beispiel:

import Data.List(groupBy) 

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

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

Dies hat einige Einschränkungen, da inpara nur zwei benachbarte Linien vergleichen und komplexere Logik paßt nicht in die von groupBy vorgegebenen Rahmen. Eine elementare Lösung, wenn sie flexibler ist. Basis-Rekursion eine Verwendung schreiben kann:

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

span spaltet eine Liste an der Stelle der bereitgestellte Funktion falsch (die erste leere Zeile) wird, entfernt dropWhile führende Elemente, für die der bereitgestellte Funktion wahr (irgendwelche führenden Leerzeilen) ist .

4

Die sauberste Lösung wäre die Verwendung von etwas aus dem split Paket.

Sie müssen zuerst installieren, aber dann sollte Data.List.Split.splitWhen null die Arbeit perfekt machen.

0

Besser spät als nie.

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