Comment diviser une chaîne dans Haskell?

163

Existe-t-il un moyen standard de fractionner une chaîne dans Haskell?

lineset wordsfonctionne très bien en fractionnant sur un espace ou une nouvelle ligne, mais il existe sûrement un moyen standard de fractionner sur une virgule?

Je n'ai pas pu le trouver sur Hoogle.

Pour être précis, je cherche quelque chose où split "," "my,comma,separated,list"revient ["my","comma","separated","list"].

Eric Wilson
la source
21
J'aimerais vraiment une telle fonction dans une prochaine version de Data.Listou même Prelude. C'est tellement commun et méchant sinon disponible pour le code-golf.
fuz le

Réponses:

135

Il existe un package pour cela appelé split .

cabal install split

Utilisez-le comme ceci:

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

Il est livré avec de nombreuses autres fonctions pour diviser sur des délimiteurs correspondants ou avoir plusieurs délimiteurs.

Jonno_FTW
la source
9
Cool. Je n'étais pas au courant de ce paquet. Il s'agit du package fractionné ultime car il donne beaucoup de contrôle sur l'opération (découper l'espace dans les résultats, laisser des séparateurs dans le résultat, supprimer les séparateurs consécutifs, etc.). Il y a tellement de façons de diviser des listes, il n'est pas possible d'avoir une seule splitfonction qui répondra à tous les besoins, vous avez vraiment besoin de ce genre de paquet.
gawi
1
sinon, si les packages externes sont acceptables, MissingH fournit également une fonction de fractionnement: hackage.haskell.org/packages/archive/MissingH/1.2.0.0/doc/html / ... Ce package fournit également de nombreuses autres fonctions "sympas" et je trouve que certains packages en dépendent.
Emmanuel Touzery
41
Le package fractionné fait désormais partie de la plate-forme haskell depuis la dernière version.
Internet du
14
importez Data.List.Split (splitOn) et allez en ville. splitOn :: Eq a => [a] -> [a] -> [[a]]
Internet
1
@RussAbbott le package fractionné est inclus dans la plateforme Haskell lorsque vous le téléchargez ( haskell.org/platform/contents.html ), mais il n'est pas automatiquement chargé lors de la construction de votre projet. Ajoutez splità la build-dependsliste dans votre fichier cabal, par exemple si votre projet s'appelle bonjour, alors dans le hello.cabalfichier sous la executable helloligne, mettez une ligne comme `build-depend: base, split` (notez deux espaces en retrait). Puis construisez en utilisant la cabal buildcommande. Cf. haskell.org/cabal/users-guide/…
expz
164

N'oubliez pas que vous pouvez consulter la définition des fonctions Prelude!

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

En regardant là-bas, la définition de wordsest,

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

Alors, changez-le pour une fonction qui prend un prédicat:

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

Alors appelez-le avec le prédicat que vous voulez!

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

Si vous utilisez Data.Text, il existe splitOn:

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

Ceci est construit dans la plate-forme Haskell.

Donc par exemple:

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
la source
1
@RussAbbott vous avez probablement besoin d'une dépendance au textpackage ou de l'installer. Mais appartiendrait à une autre question.
Emmanuel Touzery
Impossible de faire correspondre le type "T.Text" avec "Char" Type attendu: [Char] Type réel: [T.Text]
Andrew Koster
19

Dans le module Text.Regex (qui fait partie de la plateforme Haskell), il y a une fonction:

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

qui divise une chaîne basée sur une expression régulière. L'API peut être trouvée sur Hackage .

mal de bonbons
la source
Could not find module ‘Text.Regex’ Perhaps you meant Text.Read (from base-4.10.1.0)
Andrew Koster le
18

Utilisation Data.List.Split, qui utilise 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]
antimatière
la source
14

Essaye celui-là:

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

Ne fonctionne que pour un seul caractère, mais devrait être facilement extensible.

fuz
la source
10

Sans importer quoi que ce soit une substitution directe d'un caractère pour un espace, le séparateur cible pour wordsest un espace. Quelque chose comme:

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"

Vous pouvez en faire une fonction avec des paramètres. Vous pouvez éliminer le paramètre character-to-match my matching many, comme dans:

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

Par exemple

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

Un seul délimiteur de fin sera supprimé:

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

J'ai commencé à apprendre Haskell hier, alors corrigez-moi si je me trompe mais:

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)

donne:

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

ou peut-être que tu voulais

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

ce qui serait:

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
la source
1
Je cherchais un intégré split, étant gâté par des langues avec des bibliothèques bien développées. Mais merci quand même.
Eric Wilson
3
Vous avez écrit ceci en juin, donc je suppose que vous avez avancé dans votre voyage :) En tant qu'exercice, essayer de réécrire cette fonction sans inversion ni longueur car l'utilisation de ces fonctions entraîne une pénalité de complexité algorithmique et empêche également l'application à une liste infinie. S'amuser!
Tony Morris
5

Je ne sais pas comment ajouter un commentaire sur la réponse de Steve, mais je voudrais recommander la
  documentation des bibliothèques GHC ,
et là-dedans spécifiquement les
  fonctions Sublist dans Data.List

Ce qui est bien mieux comme référence, que la simple lecture du rapport Haskell.

De manière générale, un pli avec une règle sur le moment de créer une nouvelle sous-liste à alimenter devrait également le résoudre.

Evi1M4chine
la source
2

En plus des fonctions efficaces et prédéfinies données dans les réponses, j'ajouterai les miennes qui font simplement partie de mon répertoire de fonctions Haskell que j'écrivais pour apprendre la langue à mon rythme:

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

Les solutions sont au moins récursives à la queue, donc elles n'entraîneront pas de débordement de pile.

Irfan Hamid
la source
2

Exemple dans le ghci:

>  import qualified Text.Regex as R
>  R.splitRegex (R.mkRegex "x") "2x3x777"
>  ["2","3","777"]
Andreï
la source
1
Veuillez ne pas utiliser d'expressions régulières pour diviser les chaînes. Je vous remercie.
kirelagin
@kirelagin, pourquoi ce commentaire? J'apprends Haskell et j'aimerais connaître le raisonnement derrière votre commentaire.
Enrico Maria De Angelis le
@Andrey, y a-t-il une raison pour laquelle je ne peux même pas exécuter la première ligne de mon ghci?
Enrico Maria De Angelis le
1
@EnricoMariaDeAngelis Les expressions régulières sont un outil puissant pour la correspondance de chaînes. Il est logique de les utiliser lorsque vous correspondez à quelque chose de non trivial. Si vous voulez simplement diviser une chaîne sur quelque chose d'aussi trivial qu'une autre chaîne fixe, il n'est absolument pas nécessaire d'utiliser des expressions régulières - cela ne fera que rendre le code plus complexe et, probablement, plus lent.
kirelagin le
"S'il vous plaît, n'utilisez pas d'expressions régulières pour séparer les chaînes." WTF, pourquoi pas ??? Fractionner une chaîne avec une expression régulière est une chose parfaitement raisonnable à faire. Il existe de nombreux cas triviaux où une chaîne doit être divisée mais le délimiteur n'est pas toujours exactement le même.
Andrew Koster le
2

Je trouve cela plus simple à comprendre:

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