Come dividere una stringa in Haskell?


163

Esiste un modo standard per dividere una stringa in Haskell?

linese wordsfunziona alla grande partendo da uno spazio o da una nuova riga, ma sicuramente esiste un modo standard per dividere una virgola?

Non sono riuscito a trovarlo su Hoogle.

Per essere precisi, sto cercando qualcosa in cui split "," "my,comma,separated,list"ritorni ["my","comma","separated","list"].


21
Mi piacerebbe davvero una simile funzione in una versione futura Data.Listo addirittura Prelude. È così comune e brutto se non disponibile per il code-golf.
fuz,

Risposte:


135

Esiste un pacchetto per questo chiamato split .

cabal install split

Usalo in questo modo:

ghci> import Data.List.Split
ghci> splitOn "," "my,comma,separated,list"
["my","comma","separated","list"]

Viene fornito con molte altre funzioni per dividere i delimitatori corrispondenti o avere diversi delimitatori.


9
Freddo. Non ero a conoscenza di questo pacchetto. Questo è il pacchetto split definitivo in quanto offre molto controllo sull'operazione (tagliare lo spazio nei risultati, lasciare i separatori nei risultati, rimuovere i separatori consecutivi, ecc ...). Esistono molti modi per dividere gli elenchi, non è possibile avere un'unica splitfunzione in grado di rispondere a tutte le esigenze, è davvero necessario quel tipo di pacchetto.
gawi,

1
altrimenti, se i pacchetti esterni sono accettabili, MissingH fornisce anche una funzione split: hackage.haskell.org/packages/archive/MissingH/1.2.0.0/doc/html/… Questo pacchetto fornisce anche molte altre funzioni "belle da avere" e trovo che alcuni pacchetti dipendono da questo.
Emmanuel Touzery,

41
Il pacchetto diviso è ora parte della piattaforma haskell dalla versione più recente.
Internet

14
importa Data.List.Split (splitOn) e vai in città. splitOn :: Eq a => [a] -> [a] -> [[a]]
Internet

1
@RussAbbott il pacchetto split è incluso nella piattaforma Haskell quando lo si scarica ( haskell.org/platform/contents.html ), ma non viene caricato automaticamente durante la creazione del progetto. Aggiungere splitalla build-dependslista nel file cabala, ad esempio, se il progetto si chiama ciao, poi nel hello.cabalfile di sotto della executable hellolinea di mettere una linea come `accumulo dipende: base, split` (nota di due spazi trattino). Quindi compilare usando il cabal buildcomando Cf. haskell.org/cabal/users-guide/…
expz

164

Ricorda che puoi cercare la definizione delle funzioni di Prelude!

http://www.haskell.org/onlinereport/standard-prelude.html

Guardando lì, la definizione di wordsè,

words   :: String -> [String]
words s =  case dropWhile Char.isSpace s of
                      "" -> []
                      s' -> w : words s''
                            where (w, s'') = break Char.isSpace s'

Quindi, cambiarlo per una funzione che accetta un predicato:

wordsWhen     :: (Char -> Bool) -> String -> [String]
wordsWhen p s =  case dropWhile p s of
                      "" -> []
                      s' -> w : wordsWhen p s''
                            where (w, s'') = break p s'

Quindi chiamalo con qualunque predicato tu voglia!

main = print $ wordsWhen (==',') "break,this,string,at,commas"

31

Se usi Data.Text, c'è splitOn:

http://hackage.haskell.org/packages/archive/text/0.11.2.0/doc/html/Data-Text.html#v:splitOn

Questo è integrato nella piattaforma Haskell.

Quindi per esempio:

import qualified Data.Text as T
main = print $ T.splitOn (T.pack " ") (T.pack "this is a test")

o:

{-# LANGUAGE OverloadedStrings #-}

import qualified Data.Text as T
main = print $ T.splitOn " " "this is a test"

1
@RussAbbott probabilmente hai bisogno di una dipendenza dal textpacchetto o installarlo. Sarebbe comunque parte di un'altra domanda.
Emmanuel Touzery,

Impossibile abbinare il tipo 'T.Text' con 'Char' Tipo previsto: [Char] Tipo effettivo: [T.Text]
Andrew Koster

19

Nel modulo Text.Regex (parte della piattaforma Haskell), c'è una funzione:

splitRegex :: Regex -> String -> [String]

che divide una stringa in base a un'espressione regolare. L'API è disponibile su Hackage .


Could not find module ‘Text.Regex’ Perhaps you meant Text.Read (from base-4.10.1.0)
Andrew Koster,

18

Usa Data.List.Split, che utilizza split:

[me@localhost]$ ghci
Prelude> import Data.List.Split
Prelude Data.List.Split> let l = splitOn "," "1,2,3,4"
Prelude Data.List.Split> :t l
l :: [[Char]]
Prelude Data.List.Split> l
["1","2","3","4"]
Prelude Data.List.Split> let { convert :: [String] -> [Integer]; convert = map read }
Prelude Data.List.Split> let l2 = convert l
Prelude Data.List.Split> :t l2
l2 :: [Integer]
Prelude Data.List.Split> l2
[1,2,3,4]

14

Prova questo:

import Data.List (unfoldr)

separateBy :: Eq a => a -> [a] -> [[a]]
separateBy chr = unfoldr sep where
  sep [] = Nothing
  sep l  = Just . fmap (drop 1) . break (== chr) $ l

Funziona solo per un singolo carattere, ma dovrebbe essere facilmente estensibile.


10

Senza importare nulla una sostituzione diretta di un carattere per uno spazio, il separatore di destinazione per wordsè uno spazio. Qualcosa di simile a:

words [if c == ',' then ' ' else c|c <- "my,comma,separated,list"]

o

words let f ',' = ' '; f c = c in map f "my,comma,separated,list"

Puoi trasformarlo in una funzione con parametri. Puoi eliminare il parametro carattere-per-abbinare il mio abbinamento molti, come in:

 [if elem c ";,.:-+@!$#?" then ' ' else c|c <-"my,comma;separated!list"]

9
split :: Eq a => a -> [a] -> [[a]]
split d [] = []
split d s = x : split d (drop 1 y) where (x,y) = span (/= d) s

Per esempio

split ';' "a;bb;ccc;;d"
> ["a","bb","ccc","","d"]

Verrà eliminato un singolo delimitatore finale:

split ';' "a;bb;ccc;;d;"
> ["a","bb","ccc","","d"]

6

Ho iniziato a studiare Haskell ieri, quindi correggimi se sbaglio ma:

split :: Eq a => a -> [a] -> [[a]]
split x y = func x y [[]]
    where
        func x [] z = reverse $ map (reverse) z
        func x (y:ys) (z:zs) = if y==x then 
            func x ys ([]:(z:zs)) 
        else 
            func x ys ((y:z):zs)

dà:

*Main> split ' ' "this is a test"
["this","is","a","test"]

o forse volevi

*Main> splitWithStr  " and " "this and is and a and test"
["this","is","a","test"]

quale sarebbe:

splitWithStr :: Eq a => [a] -> [a] -> [[a]]
splitWithStr x y = func x y [[]]
    where
        func x [] z = reverse $ map (reverse) z
        func x (y:ys) (z:zs) = if (take (length x) (y:ys)) == x then
            func x (drop (length x) (y:ys)) ([]:(z:zs))
        else
            func x ys ((y:z):zs)

1
Stavo cercando un built-in split, viziato da lingue con librerie ben sviluppate. Ma grazie comunque.
Eric Wilson,

3
Lo hai scritto a giugno, quindi presumo che tu sia andato avanti nel tuo viaggio :) Come esercizio, provando a riscrivere questa funzione senza inversione o lunghezza poiché l'uso di queste funzioni comporta una penalità di complessità algoritmica e impedisce anche l'applicazione a un elenco infinito. Divertiti!
Tony Morris,

5

Non so come aggiungere un commento alla risposta di Steve, ma vorrei raccomandare la
  documentazione delle librerie GHC ,
e in particolare le
  funzioni di Sublist in Data.List

Il che è molto meglio come riferimento, piuttosto che leggere il semplice rapporto Haskell.

In genere, anche una piega con una regola su quando creare un nuovo elenco secondario da alimentare, dovrebbe risolverlo.


2

Oltre alle funzioni efficienti e predefinite fornite nelle risposte, aggiungerò le mie che sono semplicemente parte del mio repertorio di funzioni di Haskell che stavo scrivendo per imparare la lingua nel mio tempo:

-- Correct but inefficient implementation
wordsBy :: String -> Char -> [String]
wordsBy s c = reverse (go s []) where
    go s' ws = case (dropWhile (\c' -> c' == c) s') of
        "" -> ws
        rem -> go ((dropWhile (\c' -> c' /= c) rem)) ((takeWhile (\c' -> c' /= c) rem) : ws)

-- Breaks up by predicate function to allow for more complex conditions (\c -> c == ',' || c == ';')
wordsByF :: String -> (Char -> Bool) -> [String]
wordsByF s f = reverse (go s []) where
    go s' ws = case ((dropWhile (\c' -> f c')) s') of
        "" -> ws
        rem -> go ((dropWhile (\c' -> (f c') == False)) rem) (((takeWhile (\c' -> (f c') == False)) rem) : ws)

Le soluzioni sono almeno ricorsive alla coda, quindi non incorreranno in un overflow dello stack.


2

Esempio nel ghci:

>  import qualified Text.Regex as R
>  R.splitRegex (R.mkRegex "x") "2x3x777"
>  ["2","3","777"]

1
Per favore, non usare espressioni regolari per dividere le stringhe. Grazie.
Kirelagin,

@kirelagin, perché questo commento? Sto imparando Haskell e mi piacerebbe conoscere il razionale dietro il tuo commento.
Enrico Maria De Angelis,

@Andrey, c'è un motivo per cui non riesco nemmeno a eseguire la prima riga nella mia ghci?
Enrico Maria De Angelis,

1
@EnricoMariaDeAngelis Le espressioni regolari sono un potente strumento per la corrispondenza delle stringhe. Ha senso usarli quando abbini qualcosa di non banale. Se vuoi solo dividere una stringa su qualcosa di così banale come un'altra stringa fissa, non è assolutamente necessario utilizzare espressioni regolari: renderà il codice solo più complesso e, probabilmente, più lento.
Kirelagin il

"Per favore, non usare espressioni regolari per dividere le stringhe." WTF, perché no ??? Dividere una stringa con un'espressione regolare è una cosa perfettamente ragionevole da fare. Ci sono molti casi banali in cui una stringa deve essere divisa ma il delimitatore non è sempre esattamente lo stesso.
Andrew Koster,

2

Lo trovo più semplice da capire:

split :: Char -> String -> [String]
split c xs = case break (==c) xs of 
  (ls, "") -> [ls]
  (ls, x:rs) -> ls : split c rs
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.