Domanda

Mi dispiace non riesce quasi mai FP ancora, voglio dividere una sequenza di linee in una sequenza di sequenze di linee, assumendo una linea vuota come la divisione punto, potrei farlo in python in questo modo:

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

Come si va a farlo in Erlang o Haskell?

È stato utile?

Soluzione

Sono solo un inizio Haskell programmatore (e quel poco che ho imparato Haskell era di 5 anni fa), ma tanto per cominciare, mi piacerebbe scrivere la traduzione naturale della vostra funzione, con l'accumulatore ( "il paragrafo corrente") essere passati in giro (ho aggiunto i tipi, solo per chiarezza):

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

Questo funziona:

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

In modo che sia una soluzione. Ma poi, l'esperienza Haskell suggerisce che ci sono quasi sempre funzioni di libreria per fare le cose in questo modo :) Una funzione correlata è chiamato groupBy , ed è quasi funziona:

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

Spiacenti. Ciò di cui abbiamo veramente bisogno è un "splitBy", e non è nelle librerie , ma siamo in grado di filtrare i cattivi noi stessi:

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

o, se si vuole essere cool, si può sbarazzarsi dell'argomento e farlo nel modo inutile:

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

Sono sicuro che c'è un modo ancora più breve. :-)

Modifica : ephemient sottolinea che (not . null) è più pulita (/= ""). Così possiamo scrivere

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

Il (not . null) ripetuto è una forte indicazione che abbiamo davvero dovrebbe astratto questo fuori in una funzione, e questo è ciò che il modulo Data.List.Split fa, come indicato nella risposta qui sotto.

Altri suggerimenti

Sto anche cercando di imparare Haskell. Una soluzione per questa domanda potrebbe essere:

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

dove sto utilizzando le funzioni da dati .list. Quelli che sto usando sono già disponibili dal preludio, ma si possono trovare la loro documentazione nel collegamento.

L'idea è quella di trovare il primo paragrafo utilizzando span (/= ""). Ciò restituirà il punto, e le linee seguenti. Abbiamo poi Recurse sulla lista più piccola delle linee che io chiamo rest.

Prima di dividere il primo comma, abbiamo cadere eventuali righe vuote utilizzando dropWhile (== ""). Questo è importante mangiare riga vuota (s) che separa i paragrafi. Il mio primo tentativo è stato questo:

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

, ma questo viene a mancare, quando si raggiunge il punto finale in quanto rest è allora la stringa vuota:

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

cadente righe vuote risolve questo, e rende anche il codice di trattare qualsiasi numero di righe vuote come separatore paragrafo, che è quello che ci si aspetterebbe come utente.

La soluzione più pulita sarebbe quella di utilizzare qualcosa di appropriato dal spaccato pacchetto.

È necessario installare quella prima, ma poi Data.List.Split.splitWhen null dovrebbe fare il lavoro perfettamente.

Si pensi in modo ricorsivo.

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

Si desidera raggruppare le righe, in modo da groupBy Data.List sembra un buon candidato. Esso utilizza una funzione personalizzata per determinare quali linee sono "uguali" così si può fornire qualcosa che rende le linee nello stesso paragrafo "uguale". Ad esempio:

import Data.List( groupBy )

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

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

Questo ha alcune limitazioni, poiché inpara può confrontare solo due righe adiacenti e logica più complessa non adatta al quadro in groupBy. Una soluzione più elementare, se è più flessibile. Utilizzando uno di base ricorsione può scrivere:

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

span divide un elenco nel punto la funzione fornita diventa falsa (la prima riga vuota), dropWhile rimuove gli elementi principali per cui la funzione fornita è vero (nessun leader righe vuote).

Meglio tardi che mai.

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"]]
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top