web-dev-qa-db-fra.com

Dans Haskell, comment découpez-vous les espaces blancs du début et de la fin d'une chaîne?

Comment couper les espaces blancs au début et à la fin d'une chaîne?

trim "  abc " 

=>

"abc"

Éditer:

Ok, permettez-moi d'être un peu plus clair. Je ne comprenais pas que les littéraux de chaînes étaient traités si différemment des chaînes.

Je voudrais faire ceci:

import qualified Data.Text as T
let s :: String = "  abc  "
in T.strip s

Est-ce possible à Haskell? J'utilise -XOverloadedStrings mais cela ne semble fonctionner que pour les littéraux.

47
Eric Normand

Si vous avez de sérieux besoins de traitement de texte, utilisez le package text de hackage:

> :set -XOverloadedStrings
> import Data.Text
> strip "  abc   "
"abc"

Si vous êtes trop têtu pour utiliser text et que vous n'aimez pas l'inefficacité de la méthode inverse, alors peut-être (et je veux dire PEUT-ÊTRE) quelque chose comme ci-dessous sera plus efficace:

import Data.Char

trim xs = dropSpaceTail "" $ dropWhile isSpace xs

dropSpaceTail maybeStuff "" = ""
dropSpaceTail maybeStuff (x:xs)
        | isSpace x = dropSpaceTail (x:maybeStuff) xs
        | null maybeStuff = x : dropSpaceTail "" xs
        | otherwise       = reverse maybeStuff ++ x : dropSpaceTail "" xs


> trim "  hello this \t should trim ok.. .I  think  ..  \t "
"hello this \t should trim ok.. .I  think  .."

J'ai écrit ceci en supposant que la longueur des espaces serait minimale, donc votre O(n) of ++ et reverse sont peu préoccupants. Mais encore une fois, je ressens le besoin de dire que si vous êtes réellement préoccupé par les performances, vous ne devriez pas du tout utiliser String - passez à Text.

EDIT pour faire valoir mon point de vue, un benchmark Criterion rapide me dit que (pour une chaîne de mots particulièrement longue avec des espaces et ~ 200 espaces pré et post), mon découpage prend 1,6 ms, le découpage utilisant l'inverse prend 3,5 ms et Data.Text.strip prend 0,0016 ms ...

55

De: http://en.wikipedia.org/wiki/Trim_ (programmation) #Haskell

import Data.Char (isSpace)

trim :: String -> String
trim = f . f
   where f = reverse . dropWhile isSpace
34
Eric Normand

Après que cette question a été posée (vers 2012) Data.List got dropWhileEnd ce qui facilite grandement les choses:

trim = dropWhileEnd isSpace . dropWhile isSpace
24
spopejoy

Inefficace mais facile à comprendre et à coller si besoin:

strip = lstrip . rstrip
lstrip = dropWhile (`elem` " \t")
rstrip = reverse . lstrip . reverse
15
Simon Michael

Pour sûr, Data.Text est meilleur pour les performances. Mais, comme cela a été mentionné, c'est juste amusant de le faire avec des listes. Voici une version qui rstrip est la chaîne en un seul passage (sans reverse et ++) et supporte des listes infinies:

rstrip :: String -> String
rstrip str = let (zs, f) = go str in if f then [] else zs
    where
        go [] = ([], True)
        go (y:ys) =
            if isSpace y then
                let (zs, f) = go ys in (y:zs, f)
            else
                (y:(rstrip ys), False)

p.s. quant aux listes infinies, cela fonctionnera:

List.length $ List.take n $ rstrip $ cycle "abc  "

et, pour une raison évidente, cela ne fonctionnera pas (fonctionnera pour toujours):

List.length $ List.take n $ rstrip $ 'a':(cycle " ")
3
wonder.mice

De nos jours, le package MissingH est livré avec une fonction strip :

import           Data.String.Utils

myString = "    foo bar    "
-- strip :: String -> String
myTrimmedString = strip myString
-- myTrimmedString == "foo bar"

Donc, si la conversion de String à Text et retour n'a pas de sens dans votre situation, vous pouvez utiliser la fonction ci-dessus.

3
Damian Nadales

Vous pouvez combiner Data.Text's strip avec ses fonctions un/packing pour éviter d'avoir des chaînes surchargées:

import qualified Data.Text as T

strip  = T.unpack . T.strip . T.pack
lstrip = T.unpack . T.stripStart . T.pack
rstrip = T.unpack . T.stripEnd . T.pack

Le tester:

> let s = "  hello  "
> strip s
"hello"
> lstrip s
"hello  "
> rstrip s
"  hello"
3
John J. Camilleri

Je sais que c'est un vieux post, mais je n'ai vu aucune solution qui implémente un bon vieux fold.

Supprimez d'abord le premier espace blanc à l'aide de dropWhile. Ensuite, en utilisant foldl' et une simple fermeture, vous pouvez analyser le reste de la chaîne en un seul passage, et sur la base de cette analyse, passer ce paramètre informatif à take, sans avoir besoin de reverse:

import Data.Char (isSpace)
import Data.List (foldl')

trim :: String -> String
trim s = let
  s'    = dropWhile isSpace s
  trim' = foldl'
            (\(c,w) x -> if isSpace x then (c,w+1)
                         else (c+w+1,0)) (0,0) s'
  in
   take (fst trim') s'

La variable c garde la trace des espaces combinés blancs et non blancs qui doivent être absorbés, et la variable w garde la trace des espaces blancs du côté droit à supprimer.

Exécutions de test:

print $ trim "      a   b c    "
print $ trim "      ab c    "
print $ trim "    abc    "
print $ trim "abc"
print $ trim "a bc    "

Production:

"a   b c"
"ab c"
"abc"
"abc"
"a bc"
1
eazar001

Cela devrait être vrai pour O (n), je crois:

import Data.Char (isSpace)

trim :: String -> String
-- Trimming the front is easy. Use a helper for the end.
trim = dropWhile isSpace . trim' []
  where
    trim' :: String -> String -> String
    -- When finding whitespace, put it in the space bin. When finding
    -- non-whitespace, include the binned whitespace and continue with an
    -- empty bin. When at the end, just throw away the bin.
    trim' _ [] = []
    trim' bin (a:as) | isSpace a = trim' (bin ++ [a]) as
                     | otherwise = bin ++ a : trim' [] as
1
Arild

À l'instar de ce que d'autres personnes ont suggéré, vous pouvez éviter d'avoir à inverser votre chaîne en utilisant:

import Data.Char (isSpace)

dropFromTailWhile _ [] = []
dropFromTailWhile p item
  | p (last items) = dropFromTailWhile p $ init items
  | otherwise      = items

trim :: String -> String
trim = dropFromTailWhile isSpace . dropWhile isSpace
0
sommnium003

Je ne sais rien du runtime ou de l'efficacité mais qu'en est-il:

-- entirely input is to be trimmed
trim :: String -> String
trim = Prelude.filter (not . isSpace')

-- just the left and the right side of the input is to be trimmed
lrtrim :: String -> String
lrtrim = \xs -> rtrim $ ltrim xs
  where
    ltrim = dropWhile (isSpace')
    rtrim xs
      | Prelude.null xs = []
      | otherwise = if isSpace' $ last xs
                    then rtrim $ init xs
                    else xs 

-- returns True if input equals ' '
isSpace' :: Char -> Bool
isSpace' = \c -> (c == ' ')

Une solution sans utiliser d'autre module ou bibliothèque que le Prelude.

Quelques tests:

>lrtrim ""
>""

>lrtrim "       "
>""

>lrtrim "haskell       "
>"haskell"

>lrtrim "      haskell       "
>"haskell"

>lrtrim "     h  a  s k e   ll       "
>"h  a  s k e   ll"

Il peut s'agir du runtime O (n).

Mais je ne le sais pas car je ne connais pas les durées d'exécution des fonctions last et init. ;)

0
jimmyt