¿Cómo dividir una cuerda en Haskell?

163

¿Hay una forma estándar de dividir una cadena en Haskell?

linesy wordsfunciona muy bien al dividir en un espacio o en una nueva línea, pero ¿seguramente hay una forma estándar de dividir en una coma?

No pude encontrarlo en Hoogle.

Para ser específico, estoy buscando algo donde split "," "my,comma,separated,list"regrese ["my","comma","separated","list"].

Eric Wilson
fuente
21
Realmente me gustaría una función de este tipo en una versión futura Data.Listo incluso Prelude. Es tan común y desagradable si no está disponible para el golf de código.
fuz

Respuestas:

135

Hay un paquete para esto llamado split .

cabal install split

Úselo así:

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

Viene con muchas otras funciones para dividir en delimitadores coincidentes o tener varios delimitadores.

Jonno_FTW
fuente
9
Frio. No estaba al tanto de este paquete. Este es el paquete dividido definitivo, ya que brinda mucho control sobre la operación (recortar el espacio en los resultados, dejar separadores en el resultado, eliminar separadores consecutivos, etc.). Hay tantas maneras de dividir las listas, que no es posible tener una sola splitfunción que responda a todas las necesidades, realmente necesita ese tipo de paquete.
gawi
1
de lo contrario, si los paquetes externos son aceptables, MissingH también proporciona una función dividida: hackage.haskell.org/packages/archive/MissingH/1.2.0.0/doc/html/… Ese paquete también ofrece muchas otras funciones "agradables" y encuentro que algunos paquetes dependen de ello.
Emmanuel Touzery
41
El paquete dividido ahora está separado de la plataforma haskell a partir del lanzamiento más reciente.
Internet
14
importa Data.List.Split (splitOn) y ve a la ciudad. splitOn :: Eq a => [a] -> [a] -> [[a]]
Internet
1
@RussAbbott, el paquete dividido se incluye en la plataforma Haskell cuando lo descarga ( haskell.org/platform/contents.html ), pero no se carga automáticamente al crear su proyecto. Agregue splita la build-dependslista en su archivo cabal, por ejemplo, si su proyecto se llama hola, entonces en el hello.cabalarchivo debajo de la executable hellolínea coloque una línea como `build-depend: base, split` (observe dos sangrías de espacio). Luego construya usando el cabal buildcomando. Cf. haskell.org/cabal/users-guide/…
expz
164

¡Recuerde que puede buscar la definición de las funciones de Preludio!

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

Mirando allí, la definición de wordses,

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

Entonces, cámbielo por una función que tome un 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'

¡Entonces llámalo con el predicado que quieras!

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

Si usa Data.Text, hay splitOn:

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

Esto está construido en la plataforma Haskell.

Entonces, por ejemplo:

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"
Emmanuel Touzery
fuente
1
@RussAbbott probablemente necesite una dependencia del textpaquete o instalarlo. Sin embargo, pertenecería a otra pregunta.
Emmanuel Touzery
No se pudo hacer coincidir el tipo 'T.Text' con 'Char' Tipo esperado: [Char] Tipo real: [T.Text]
Andrew Koster
19

En el módulo Text.Regex (parte de la plataforma Haskell), hay una función:

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

que divide una cadena basada en una expresión regular. La API se puede encontrar en Hackage .

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

Uso 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]
antimateria
fuente
14

Prueba 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

Solo funciona para un solo personaje, pero debe ser fácilmente extensible.

fuz
fuente
10

Sin importar nada una sustitución directa de un personaje por un espacio, el separador de destino para wordses un espacio. Algo como:

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"

Puede convertir esto en una función con parámetros. Puede eliminar el parámetro carácter para que coincida con mi coincidencia, como en:

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

P.ej

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

Se eliminará un solo delimitador final:

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

Empecé a aprender Haskell ayer, así que corrígeme si me equivoco pero:

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)

da:

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

o tal vez quisiste

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

cuál podría ser:

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
fuente
1
Estaba buscando un splitlenguaje incorporado , que se estropeaba con idiomas con bibliotecas bien desarrolladas. Pero gracias de todos modos.
Eric Wilson
3
Escribiste esto en junio, así que supongo que has seguido adelante en tu viaje :) Como ejercicio, tratar de reescribir esta función sin reversa o duración ya que el uso de estas funciones conlleva una penalización de complejidad algorítmica y también evita la aplicación a una lista infinita. ¡Que te diviertas!
Tony Morris
5

No sé cómo agregar un comentario a la respuesta de Steve, pero me gustaría recomendar la
  documentación de las bibliotecas de GHC ,
y allí específicamente las
  funciones Sublist en Data.List

Lo cual es mucho mejor como referencia, que simplemente leer el informe simple de Haskell.

Genéricamente, un pliegue con una regla sobre cuándo crear una nueva sublista para alimentar, también debería resolverlo.

Evi1M4chine
fuente
2

Además de las funciones eficientes y predefinidas que figuran en las respuestas, agregaré las mías, que son simplemente parte de mi repertorio de funciones de Haskell que estaba escribiendo para aprender el idioma en mi propio tiempo:

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

Las soluciones son al menos recursivas de cola para que no incurran en un desbordamiento de pila.

Irfan Hamid
fuente
2

Ejemplo en el ghci:

>  import qualified Text.Regex as R
>  R.splitRegex (R.mkRegex "x") "2x3x777"
>  ["2","3","777"]
Andrey
fuente
1
Por favor, no use expresiones regulares para dividir cadenas. Gracias.
kirelagin
@kirelagin, ¿por qué este comentario? Estoy aprendiendo Haskell, y me gustaría saber lo racional detrás de tu comentario.
Enrico Maria De Angelis
@Andrey, ¿hay alguna razón por la que ni siquiera puedo ejecutar la primera línea en mi ghci?
Enrico Maria De Angelis
1
@EnricoMariaDeAngelis Las expresiones regulares son una herramienta poderosa para la coincidencia de cadenas. Tiene sentido usarlos cuando estás haciendo coincidir algo no trivial. Si solo desea dividir una cadena en algo tan trivial como otra cadena fija, no hay absolutamente ninguna necesidad de usar expresiones regulares, solo hará que el código sea más complejo y, probablemente, más lento.
kirelagin
"Por favor, no use expresiones regulares para dividir cadenas". WTF, ¿por qué no? Dividir una cadena con una expresión regular es algo perfectamente razonable. Hay muchos casos triviales en los que una cadena debe dividirse, pero el delimitador no siempre es exactamente el mismo.
Andrew Koster
2

Encuentro esto más simple de entender:

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