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


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.


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"

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"

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 .


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]

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.


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

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

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)

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.


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.


2

Exemple dans le ghci:

>  import qualified Text.Regex as R
>  R.splitRegex (R.mkRegex "x") "2x3x777"
>  ["2","3","777"]

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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.