Jak podzielić ciąg znaków w Haskell?

163

Czy istnieje standardowy sposób dzielenia łańcucha w Haskell?

linesi wordsdziała świetnie z dzieleniem na spację lub znak nowej linii, ale na pewno istnieje standardowy sposób dzielenia przecinkiem?

Nie mogłem go znaleźć w Hoogle.

Mówiąc konkretnie, szukam czegoś, co split "," "my,comma,separated,list"wraca ["my","comma","separated","list"].

Eric Wilson
źródło
21
Bardzo chciałbym mieć taką funkcję w przyszłej wersji Data.Listlub nawet Prelude. Jest tak powszechny i ​​paskudny, jeśli nie jest dostępny dla golfa kodowego.
fuz

Odpowiedzi:

135

Istnieje pakiet o nazwie split .

cabal install split

Użyj tego w ten sposób:

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

Zawiera wiele innych funkcji do dzielenia według pasujących separatorów lub posiadania kilku separatorów.

Jonno_FTW
źródło
9
Chłodny. Nie byłem świadomy tego pakietu. To ostateczny pakiet rozłam, ponieważ daje większą kontrolę nad operacją (trymowania miejsca w wynikach, separatory urlopu w wyniku, usuwanie kolejnych separatorów, etc ...). Jest tak wiele sposobów dzielenia list, że nie jest możliwe posiadanie w jednej funkcji, która będzie odpowiadała wszystkim potrzebom, naprawdę potrzebujesz tego rodzaju pakietu. split
gawi,
1
w przeciwnym razie, jeśli akceptowalne są pakiety zewnętrzne, MissingH udostępnia również funkcję dzielenia: hackage.haskell.org/packages/archive/MissingH/1.2.0.0/doc/html/… Ten pakiet zapewnia również wiele innych funkcji „miłych do posiadania” i stwierdzam, że niektóre pakiety zależą od tego.
Emmanuel Touzery
41
Pakiet podzielony jest teraz częścią platformy haskell w najnowszym wydaniu.
Internet
14
import Data.List.Split (splitOn) i udaj się do miasta. splitOn :: Eq a => [a] -> [a] -> [[a]]
The Internet
1
@RussAbbott podzielony pakiet jest dołączany do platformy Haskell podczas pobierania ( haskell.org/platform/contents.html ), ale nie jest automatycznie ładowany podczas budowania projektu. Dodaj splitdo build-dependslisty w swoim pliku cabal, np. Jeśli twój projekt nazywa się hello, to w hello.cabalpliku poniżej executable hellolinii umieść linię typu `build-depend: base, split` (zwróć uwagę na wcięcie dwóch spacji). Następnie zbuduj za pomocą cabal buildpolecenia. Por. haskell.org/cabal/users-guide/…
expz
164

Pamiętaj, że możesz sprawdzić definicję funkcji Prelude!

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

Patrząc tam, definicja wordsjest,

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

Więc zmień to na funkcję, która przyjmuje predykat:

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

Następnie nazwij to dowolnym predykatem, który chcesz!

main = print $ wordsWhen (==',') "break,this,string,at,commas"
Steve
źródło
31

Jeśli używasz Data.Text, istnieje splitOn:

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

Jest to wbudowane w platformę Haskell.

Na przykład:

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

lub:

{-# LANGUAGE OverloadedStrings #-}

import qualified Data.Text as T
main = print $ T.splitOn " " "this is a test"
Emmanuel Touzery
źródło
1
@RussAbbott prawdopodobnie potrzebujesz zależności od textpakietu lub go zainstalować. Jednak należałoby w innym pytaniu.
Emmanuel Touzery
Nie można dopasować typu „T.Text” do „Char” Oczekiwany typ: [Char] Rzeczywisty typ: [T.Text]
Andrew Koster
19

W module Text.Regex (część Platformy Haskell) znajduje się funkcja:

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

który dzieli ciąg na podstawie wyrażenia regularnego. Interfejs API można znaleźć pod adresem Hackage .

evilcandybag
źródło
Could not find module ‘Text.Regex’ Perhaps you meant Text.Read (from base-4.10.1.0)
Andrew Koster
18

Zastosowanie Data.List.Split, które wykorzystuje 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]
antymateria
źródło
14

Spróbuj tego:

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

Działa tylko dla jednego znaku, ale powinien być łatwo rozszerzalny.

fuz
źródło
10

Bez importowania czegokolwiek po prostym zastąpieniu jednego znaku spacją, separatorem docelowym dla wordsjest spacja. Coś jak:

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

lub

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

Możesz zrobić z tego funkcję z parametrami. Możesz wyeliminować parametr znak, aby dopasować moje pasujące wiele, na przykład:

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

Na przykład

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

Pojedynczy ogranicznik końcowy zostanie usunięty:

split ';' "a;bb;ccc;;d;"
> ["a","bb","ccc","","d"]
Frank Meisschaert
źródło
6

Zacząłem się uczyć Haskella wczoraj, więc popraw mnie, jeśli się mylę, ale:

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)

daje:

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

a może chciałeś

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

co byłoby:

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
źródło
1
Szukałem wbudowanego split, rozpieszczanego przez języki z dobrze rozwiniętymi bibliotekami. Ale i tak dzięki.
Eric Wilson,
3
Napisałeś to w czerwcu, więc zakładam, że ruszyłeś dalej w swoją podróż :) Jako ćwiczenie, próba przepisania tej funkcji bez cofania lub długości, ponieważ użycie tych funkcji pociąga za sobą karę za złożoność algorytmiczną, a także uniemożliwia zastosowanie do nieskończonej listy. Baw się dobrze!
Tony Morris
5

Nie wiem, jak dodać komentarz do odpowiedzi Steve'a, ale chciałbym polecić
  dokumentację bibliotek GHC ,
a w niej konkretnie funkcje listy
  podrzędnej w Data.List

Co jest o wiele lepsze jako odniesienie, niż zwykłe czytanie raportu Haskella.

Ogólnie rzecz biorąc, fałd z regułą określającą, kiedy utworzyć nową podlistę do karmienia, powinien również rozwiązać ten problem.

Evi1M4chine
źródło
2

Oprócz wydajnych i gotowych funkcji podanych w odpowiedziach dodam własne, które są po prostu częścią mojego repertuaru funkcji Haskell, które pisałem, aby nauczyć się języka w swoim czasie:

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

Rozwiązania są co najmniej rekurencyjne, więc nie powodują przepełnienia stosu.

Irfan Hamid
źródło
2

Przykład w ghci:

>  import qualified Text.Regex as R
>  R.splitRegex (R.mkRegex "x") "2x3x777"
>  ["2","3","777"]
Andrey
źródło
1
Nie używaj wyrażeń regularnych do dzielenia łańcuchów. Dziękuję Ci.
kirelagin
@kirelagin, dlaczego ten komentarz? Uczę się Haskella i chciałbym poznać uzasadnienie twojego komentarza.
Enrico Maria De Angelis
@Andrey, czy jest powód, dla którego nie mogę nawet uruchomić pierwszej linii w moim ghci?
Enrico Maria De Angelis
1
@EnricoMariaDeAngelis Wyrażenia regularne to potężne narzędzie do dopasowywania ciągów. Warto ich używać, gdy dopasowujesz coś nietrywialnego. Jeśli chcesz po prostu podzielić ciąg na coś tak trywialnego, jak inny ustalony ciąg, absolutnie nie ma potrzeby używania wyrażeń regularnych - spowoduje to tylko, że kod będzie bardziej złożony i prawdopodobnie wolniejszy.
kirelagin
„Proszę, nie używaj wyrażeń regularnych do dzielenia łańcuchów”. WTF, czemu nie ??? Dzielenie łańcucha za pomocą wyrażenia regularnego jest całkowicie rozsądną rzeczą do zrobienia. Istnieje wiele trywialnych przypadków, w których ciąg musi zostać podzielony, ale separator nie zawsze jest dokładnie taki sam.
Andrew Koster
2

Łatwiej mi to zrozumieć:

split :: Char -> String -> [String]
split c xs = case break (==c) xs of 
  (ls, "") -> [ls]
  (ls, x:rs) -> ls : split c rs
mxs
źródło