Wie teile ich einen String in Haskell?

163

Gibt es eine Standardmethode zum Teilen einer Zeichenfolge in Haskell?

linesund wordsfunktionieren gut, wenn Sie ein Leerzeichen oder eine neue Zeile teilen, aber es gibt sicherlich eine Standardmethode, um ein Komma zu teilen?

Ich konnte es bei Hoogle nicht finden.

Um genau zu sein, suche ich etwas, wo split "," "my,comma,separated,list"zurückkehrt ["my","comma","separated","list"].

Eric Wilson
quelle
21
Ich würde wirklich gerne eine solche Funktion in einer zukünftigen Version von Data.Listoder sogar Prelude. Es ist so üblich und böse, wenn es nicht für Code-Golf verfügbar ist.
Fuz

Antworten:

135

Hierfür gibt es ein Paket namens split .

cabal install split

Verwenden Sie es so:

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

Es verfügt über viele andere Funktionen zum Aufteilen auf übereinstimmende Trennzeichen oder auf mehrere Trennzeichen.

Jonno_FTW
quelle
9
Cool. Dieses Paket war mir nicht bekannt. Dies ist das ultimative Split-Paket, da es viel Kontrolle über den Vorgang bietet (Platz in den Ergebnissen kürzen, Trennzeichen im Ergebnis belassen, aufeinanderfolgende Trennzeichen entfernen usw.). Es gibt so viele Möglichkeiten, Listen aufzuteilen, dass es nicht möglich ist, eine einzige splitFunktion zu haben , die alle Anforderungen erfüllt. Sie benötigen wirklich ein solches Paket.
Gawi
1
Andernfalls, wenn externe Pakete akzeptabel sind, bietet MissingH auch eine Split-Funktion: hackage.haskell.org/packages/archive/MissingH/1.2.0.0/doc/html/… Dieses Paket bietet auch viele andere "nice-to-have" -Funktionen und ich finde, dass einige Pakete davon abhängen.
Emmanuel Touzery
41
Das geteilte Paket ist seit der letzten Version von der haskell-Plattform getrennt.
Das Internet
14
importiere Data.List.Split (splitOn) und gehe in die Stadt. splitOn :: Gl. a => [a] -> [a] -> [[a]]
Das Internet
1
@RussAbbott Das geteilte Paket ist beim Herunterladen in der Haskell-Plattform enthalten ( haskell.org/platform/contents.html ), wird jedoch beim Erstellen Ihres Projekts nicht automatisch geladen. Fügen Sie splitder build-dependsListe in Ihrer Kabalendatei hinzu, z. B. wenn Ihr Projekt Hallo heißt, und fügen Sie in die hello.cabalDatei unter der executable helloZeile eine Zeile wie "Build-Dependent: Base, Split" ein (beachten Sie zwei Leerzeichen). Erstellen Sie dann mit dem cabal buildBefehl. Vgl. haskell.org/cabal/users-guide/…
expz
164

Denken Sie daran, dass Sie die Definition der Prelude-Funktionen nachschlagen können!

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

Wenn man dort schaut, ist die Definition von words,

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

Ändern Sie es also für eine Funktion, die ein Prädikat akzeptiert:

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

Dann nenne es mit einem beliebigen Prädikat!

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

Wenn Sie Data.Text verwenden, gibt es splitOn:

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

Dies ist in der Haskell-Plattform integriert.

Also zum Beispiel:

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

oder:

{-# LANGUAGE OverloadedStrings #-}

import qualified Data.Text as T
main = print $ T.splitOn " " "this is a test"
Emmanuel Touzery
quelle
1
@RussAbbott wahrscheinlich benötigen Sie eine Abhängigkeit zum textPaket oder installieren es. Würde aber in eine andere Frage gehören.
Emmanuel Touzery
Typ 'T.Text' konnte nicht mit 'Char' übereinstimmen Erwarteter Typ: [Char] Tatsächlicher Typ: [T.Text]
Andrew Koster
19

Im Modul Text.Regex (Teil der Haskell-Plattform) gibt es eine Funktion:

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

Hiermit wird eine Zeichenfolge basierend auf einem regulären Ausdruck aufgeteilt. Die API finden Sie bei Hackage .

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

Verwenden Sie Data.List.Split, die verwendet 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]
Antimaterie
quelle
14

Probier diese:

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

Funktioniert nur für ein einzelnes Zeichen, sollte aber leicht erweiterbar sein.

fuz
quelle
10

Ohne etwas zu importieren, das ein Zeichen direkt durch ein Zeichen ersetzt, ist das Zieltrennzeichen für wordsein Leerzeichen. Etwas wie:

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

oder

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

Sie können dies zu einer Funktion mit Parametern machen. Sie können den Parameter Zeichen entfernen, der mit meinen übereinstimmenden vielen übereinstimmt, wie in:

 [if elem c ";,.:-+@!$#?" then ' ' else c|c <-"my,comma;separated!list"]
fp_mora
quelle
9
split :: Eq a => a -> [a] -> [[a]]
split d [] = []
split d s = x : split d (drop 1 y) where (x,y) = span (/= d) s

Z.B

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

Ein einzelnes nachfolgendes Trennzeichen wird entfernt:

split ';' "a;bb;ccc;;d;"
> ["a","bb","ccc","","d"]
Frank Meisschaert
quelle
6

Ich habe gestern angefangen, Haskell zu lernen, also korrigiere mich, wenn ich falsch liege, aber:

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)

gibt:

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

oder vielleicht wolltest du

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

welches sein würde:

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)
Robin Begbie
quelle
1
Ich suchte nach einem eingebauten splitSystem, das von Sprachen mit gut entwickelten Bibliotheken verwöhnt wurde. Danke trotzdem.
Eric Wilson
3
Sie haben dies im Juni geschrieben, also gehe ich davon aus, dass Sie auf Ihrer Reise weitergegangen sind :) Als Übung führt der Versuch, diese Funktion ohne Umkehrung oder Länge umzuschreiben, da die Verwendung dieser Funktionen zu einer Beeinträchtigung der algorithmischen Komplexität führt und auch die Anwendung auf eine unendliche Liste verhindert. Habe Spaß!
Tony Morris
5

Ich weiß nicht , wie ein Kommentar auf Steves Antwort hinzufügen, aber ich möchte den empfehlen
  GHC Bibliotheken Dokumentation ,
und dort speziell die
  Sublist Funktionen in Data.List

Was als Referenz viel besser ist, als nur den einfachen Haskell-Bericht zu lesen.

Im Allgemeinen sollte eine Falte mit der Regel, wann eine neue Unterliste für den Feed erstellt werden soll, diese ebenfalls lösen.

Evi1M4chine
quelle
2

Zusätzlich zu den effizienten und vorgefertigten Funktionen, die in den Antworten angegeben sind, füge ich meine eigenen hinzu, die einfach Teil meines Repertoires an Haskell-Funktionen sind, die ich geschrieben habe, um die Sprache in meiner Freizeit zu lernen:

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

Die Lösungen sind mindestens rekursiv, sodass kein Stapelüberlauf auftritt.

Irfan Hamid
quelle
2

Beispiel im ghci:

>  import qualified Text.Regex as R
>  R.splitRegex (R.mkRegex "x") "2x3x777"
>  ["2","3","777"]
Andrey
quelle
1
Bitte verwenden Sie keine regulären Ausdrücke, um Zeichenfolgen zu teilen. Danke dir.
Kirelagin
@ kirelagin, warum dieser Kommentar? Ich lerne Haskell und würde gerne wissen, was hinter Ihrem Kommentar steckt.
Enrico Maria De Angelis
@Andrey, gibt es einen Grund, warum ich nicht einmal die erste Zeile in meinem ausführen kann ghci?
Enrico Maria De Angelis
1
@EnricoMariaDeAngelis Reguläre Ausdrücke sind ein leistungsstarkes Werkzeug für den String-Abgleich. Es ist sinnvoll, sie zu verwenden, wenn Sie etwas nicht Triviales finden. Wenn Sie nur eine Zeichenfolge in etwas so Triviales wie eine andere feste Zeichenfolge aufteilen möchten, müssen Sie keine regulären Ausdrücke verwenden. Dadurch wird der Code nur komplexer und wahrscheinlich langsamer.
Kirelagin
"Bitte verwenden Sie keine regulären Ausdrücke, um Zeichenfolgen zu teilen." WTF, warum nicht ??? Das Teilen einer Zeichenfolge mit einem regulären Ausdruck ist durchaus sinnvoll. Es gibt viele triviale Fälle, in denen eine Zeichenfolge geteilt werden muss, der Begrenzer jedoch nicht immer genau gleich ist.
Andrew Koster
2

Ich finde das einfacher zu verstehen:

split :: Char -> String -> [String]
split c xs = case break (==c) xs of 
  (ls, "") -> [ls]
  (ls, x:rs) -> ls : split c rs
mxs
quelle