funktionale Absätze
-
06-09-2019 - |
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?
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"]]