Frage

Leider habe ich nicht ganz FP noch, ich eine Folge von Linien in eine Folge von Sequenzen von Linien geteilt werden soll, eine leere Zeile als Absatz Division unter der Annahme, ich habe es in Python wie dies tun könnte:

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 gehen über es in Erlang oder Haskell zu tun?

War es hilfreich?

Lösung

Ich bin nur ein Anfang Haskell Programmierer (und das kleine Haskell ich gelernt habe, war vor 5 Jahren), aber für den Anfang, würde ich die natürliche Übersetzung Ihrer Funktion, mit dem Akkumulator ( „der aktuelle Absatz“) schreibe werden 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])

Das funktioniert:

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

Das ist also eine Lösung. Aber dann schlägt Haskell Erfahrung, dass es fast immer Bibliotheksfunktionen für Dinge zu tun, wie dieses :) Eine ähnliche Funktion wird aufgerufen, groupBy , und es fast funktioniert:

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 es nicht in den Bibliotheken ist , aber wir können die schlechten herauszufiltern uns:

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

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

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

Ich bin sicher, es ist ein noch kürzerer 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 wiederholte (not . null) ist ein starker Hinweis darauf, dass wir wirklich sollte abstrakt dies aus in eine Funktion, und das ist, was die Data.List.Split Modul der Fall ist, wie unten in der Antwort darauf hingewiesen.

Andere Tipps

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 mit den Funktionen von Daten .List . Die, die ich verwende sind bereits aus dem Präludium, aber Sie können ihre Dokumentation in dem Link finden.

Die Idee ist, den ersten Absatz mit span (/= "") zu finden. Dadurch wird der Absatz zurück und die Linien folgen. Wir haben dann Rekursion auf der kleineren Liste der Linien, die ich rest nennen.

Bevor Sie den ersten Absatz Aufteilung aus, fallen wir alle leeren Zeilen mit dropWhile (== ""). Dies ist wichtig, um die leere Zeile zu essen (e) Trennen der Absätze. 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 erreichen, da rest ist dann die leere Zeichenkette:

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

Leerzeilen Dropping löst dieses Problem, und es macht auch der Code eine beliebige Anzahl von Leerzeilen als Absatz Separator behandeln, das ist, was ich als Benutzer erwartet.

Die sauberste Lösung wäre, etwas zu verwenden, sollte aus dem Split Paket.

Sie werden feststellen, dass zuerst installieren müssen, aber dann sollte Data.List.Split.splitWhen null den Job perfekt.

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

Sie möchten die Zeilen zu gruppieren, so groupBy von Data.List scheint wie ein guter Kandidat. Es verwendet eine benutzerdefinierte Funktion, um zu bestimmen, welche Linien sind „gleich“ so ein etwas liefern kann, die Linien im selben 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 ist flexibler. Grund Rekursion ein verwenden, kann schreiben:

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

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

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"]]
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top