Como dividir uma string em Haskell?

163

Existe uma maneira padrão de dividir uma string em Haskell?

linese wordsfunciona muito bem ao dividir em um espaço ou nova linha, mas certamente existe uma maneira padrão de dividir em uma vírgula?

Não encontrei no Hoogle.

Para ser específico, estou procurando algo em que split "," "my,comma,separated,list"retorne ["my","comma","separated","list"].

Eric Wilson
fonte
21
Eu realmente gostaria de ter essa função em uma versão futura Data.Listou até mesmo Prelude. É tão comum e desagradável se não estiver disponível para o código-golfe.
fuz

Respostas:

135

Há um pacote para esse chamado split .

cabal install split

Use-o assim:

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

Ele vem com muitas outras funções para dividir em delimitadores correspondentes ou ter vários delimitadores.

Jonno_FTW
fonte
9
Legal. Eu não estava ciente deste pacote. Este é o pacote dividido final, pois oferece muito controle sobre a operação (aparar espaço nos resultados, deixar separadores no resultado, remover separadores consecutivos, etc ...). Existem muitas maneiras de dividir listas, não é possível ter uma única splitfunção que atenda a todas as necessidades, você realmente precisa desse tipo de pacote.
Gawi
1
caso contrário, se pacotes externos forem aceitáveis, o MissingH também oferece uma função de divisão: hackage.haskell.org/packages/archive/MissingH/1.2.0.0/doc/html/… Esse pacote também fornece muitas outras funções "interessantes" e acho que alguns pacotes dependem disso.
Emmanuel Touzery
41
O pacote dividido agora faz parte da plataforma haskell da versão mais recente.
A Internet
14
importe Data.List.Split (splitOn) e vá para a cidade. splitOn :: Eq a => [a] -> [a] -> [[a]]
A Internet
1
@RussAbbott, o pacote dividido é incluído na Plataforma Haskell quando você o baixa ( haskell.org/platform/contents.html ), mas não é carregado automaticamente na criação do seu projeto. Adicione splità build-dependslista no seu arquivo cabal, por exemplo, se o seu projeto for chamado hello, no hello.cabalarquivo abaixo da executable hellolinha coloque uma linha como `build-depende: base, divisão` (observe o recuo de dois espaços). Em seguida, construa usando o cabal buildcomando Cf. haskell.org/cabal/users-guide/...
expz
164

Lembre-se de que você pode procurar a definição de funções do Prelude!

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

Olhando para lá, a definição de wordsé,

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

Portanto, altere-o para uma função que aceita um predicado:

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

Em seguida, chame-o com o predicado que você quiser!

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

Se você usar Data.Text, haverá splitOn:

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

Isso é construído na plataforma Haskell.

Então, por exemplo:

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

ou:

{-# LANGUAGE OverloadedStrings #-}

import qualified Data.Text as T
main = print $ T.splitOn " " "this is a test"
Emmanuel Touzery
fonte
1
@RussAbbott provavelmente você precisa de uma dependência do textpacote ou instalá-lo. Mas pertenceria a outra pergunta.
Emmanuel Touzery
Não foi possível corresponder o tipo 'T.Text' com 'Char' Tipo esperado: [Char] Tipo real: [T.Text]
Andrew Koster
19

No módulo Text.Regex (parte da plataforma Haskell), existe uma função:

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

que divide uma string com base em uma expressão regular. A API pode ser encontrada em Hackage .

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

Use Data.List.Split, que usa 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]
antimatéria
fonte
14

Tente este:

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

Funciona apenas para um único caractere, mas deve ser facilmente extensível.

difuso
fonte
10

Sem importar nada, uma substituição direta de um caractere por um espaço, o separador de destino para wordsé um espaço. Algo como:

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

ou

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

Você pode transformar isso em uma função com parâmetros. Você pode eliminar o parâmetro caractere para corresponder à minha correspondência, como em:

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

Por exemplo

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

Um único delimitador à direita será eliminado:

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

Comecei a aprender Haskell ontem, então me corrija se estiver errado, mas:

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

ou talvez você quisesse

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

qual seria:

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
fonte
1
Eu estava procurando por um built-in split, sendo mimado por idiomas com bibliotecas bem desenvolvidas. Mas obrigada mesmo assim.
Eric Wilson
3
Você escreveu isso em junho, então suponho que você seguiu em sua jornada :) Como exercício, tentar reescrever essa função sem reversão ou extensão, pois o uso dessas funções incorre em uma penalidade de complexidade algorítmica e também impede a aplicação em uma lista infinita. Diverta-se!
Tony Morris
5

Eu não sei como adicionar um comentário sobre a resposta de Steve, mas eu gostaria de recomendar a
  documentação GHC bibliotecas ,
e em lá especificamente as
  funções de sub-lista em Data.List

O que é muito melhor como referência do que apenas ler o relatório simples de Haskell.

Geralmente, uma dobra com uma regra sobre quando criar uma nova sublist para alimentar, também deve resolvê-la.

Evi1M4chine
fonte
2

Além das funções eficientes e pré-criadas fornecidas nas respostas, adicionarei as minhas, que são simplesmente parte do meu repertório de funções Haskell que eu estava escrevendo para aprender o idioma em meu próprio 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)

As soluções são pelo menos recursivas de cauda, ​​para que não ocorram um estouro de pilha.

Irfan Hamid
fonte
2

Exemplo no ghci:

>  import qualified Text.Regex as R
>  R.splitRegex (R.mkRegex "x") "2x3x777"
>  ["2","3","777"]
Andrey
fonte
1
Por favor, não use expressões regulares para dividir strings. Obrigado.
kirelagin
@kirelagin, por que esse comentário? Estou aprendendo Haskell e gostaria de saber o racional por trás do seu comentário.
Enrico Maria De Angelis
@ Andy, existe uma razão pela qual eu não posso nem executar a primeira linha na minha ghci?
Enrico Maria De Angelis
1
@EnricoMariaDeAngelis Expressões regulares são uma ferramenta poderosa para correspondência de strings. Faz sentido usá-los quando você está combinando algo não trivial. Se você apenas deseja dividir uma string em algo tão trivial quanto outra string fixa, não há absolutamente nenhuma necessidade de usar expressões regulares - isso apenas tornará o código mais complexo e, provavelmente, mais lento.
kirelagin 21/01
"Por favor, não use expressões regulares para dividir strings." WTF, por que não ??? Dividir uma string com uma expressão regular é uma coisa perfeitamente razoável de se fazer. Existem muitos casos triviais em que uma string precisa ser dividida, mas o delimitador nem sempre é exatamente o mesmo.
Andrew Koster
2

Acho isso mais simples de entender:

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