In Haskell, how do you trim whitespace from the beginning and end of a string?
Asked Answered
W

13

66

How do you trim whitespace from the start and end of a string?

trim "  abc " 

=>

"abc"

Edit:

Ok, let me be a little clearer. I did not understand that string literals were treated so differently from Strings.

I would like to do this:

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

Is this possible in Haskell? I am using -XOverloadedStrings but that appears only to work for literals.

Wappes answered 7/6, 2011 at 19:7 Comment(0)
F
65

If you have serious text processing needs then use the text package from hackage:

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

If you're too stubborn to use text and don't like the inefficiency of the reverse method then perhaps (and I mean MAYBE) something like the below will be more efficient:

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

I wrote this on the assumption that the length of spaces would be minimal, so your O(n) of ++ and reverse is of little concern. But once again I feel the need to say that if you actually are concerned about the performance then you shouldn't be using String at all - move to Text.

EDIT making my point, a quick Criterion benchmark tells me that (for a particularly long string of words with spaces and ~200 pre and post spaces) my trim takes 1.6 ms, the trim using reverse takes 3.5ms, and Data.Text.strip takes 0.0016 ms...

Fluent answered 7/6, 2011 at 19:12 Comment(2)
Thanks for the recommendation. I convinced my team to add text to the project and it is saving a lot of headaches.Wappes
+1 for the benchmark it is great when people really prove their claimsBaroscope
W
53

From: http://en.wikipedia.org/wiki/Trim_(programming)#Haskell

import Data.Char (isSpace)

trim :: String -> String
trim = f . f
   where f = reverse . dropWhile isSpace
Wappes answered 7/6, 2011 at 19:8 Comment(2)
And it is the simplest. For a quick-and-dirty use case, this is nice.Pandorapandour
This is beautiful, although I would have used a let binding.Suppurative
C
48

After this question was asked (circa 2012) Data.List got dropWhileEnd making this a lot easier:

trim = dropWhileEnd isSpace . dropWhile isSpace
Conspicuous answered 9/7, 2016 at 14:57 Comment(3)
For those getting confused by the dot operator (used for function composition), this is equivalent of trim :: String -> String trim xs = dropWhile isSpace (dropWhileEnd isSpace xs). #631784 hackage.haskell.org/package/base-4.12.0.0/docs/…Honewort
For beginners (like me): This solution require that you first import Data.List and import Data.Char.Villarreal
If one really wants to avoid importing anything, this one works too: trimall = dropWhile (`elem` " \t") . (reverse . dropWhile (`elem` " \t") . reverse) Which is a combination of the above answer and Simon Michael's below.Carcinomatosis
M
16

Inefficient but easy to understand and paste in where needed:

strip = lstrip . rstrip
lstrip = dropWhile (`elem` " \t")
rstrip = reverse . lstrip . reverse
Ministration answered 7/7, 2011 at 18:20 Comment(0)
R
4

You can combine Data.Text's strip with it's un/packing functions to avoid having overloaded strings:

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

Testing it:

> let s = "  hello  "
> strip s
"hello"
> lstrip s
"hello  "
> rstrip s
"  hello"
Radiosensitive answered 26/2, 2014 at 10:18 Comment(0)
M
3

For sure, Data.Text is better for performance. But, as was mentioned, it's just fun to do it with lists. Here is a version that rstrip's the string in single pass (without reverse and ++) and supports infinite lists:

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. as for infinite lists, that will work:

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

and, for obvious reason, that will not (will run forever):

List.length $ List.take n $ rstrip $ 'a':(cycle " ")
Mouthpart answered 16/1, 2014 at 5:55 Comment(0)
W
3

Nowadays the MissingH package ships with a strip function:

import           Data.String.Utils

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

So if the conversion from String to Text and back does not make sense in your situation, you could use the function above.

Washington answered 3/11, 2017 at 14:3 Comment(1)
I get "Could not find module ‘Data.String.Utils’"Helainehelali
T
1

I know this is an old post, but I saw no solutions that implemented good old fold.

First strip the leading white-space using dropWhile. Then, using foldl' and a simple closure, you can analyze the rest of the string in one pass, and based on that analysis, pass that informative parameter to take, without needing 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'

Variable c keeps track of combined white and non white-space that should be absorbed, and variable w keeps track of right side white-space to be stripped.

Test Runs:

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

Output:

"a   b c"
"ab c"
"abc"
"abc"
"a bc"
Thresathresh answered 13/4, 2014 at 9:5 Comment(0)
D
1

This should be right about O(n), I believe:

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
Dicephalous answered 16/11, 2014 at 1:43 Comment(0)
G
0

I don't know anything about the runtime or efficiency but what about this:

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

A solution without using any other module or library than the Prelude.

Some tests:

>lrtrim ""
>""

>lrtrim "       "
>""

>lrtrim "haskell       "
>"haskell"

>lrtrim "      haskell       "
>"haskell"

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

It could be runtime O(n).

But I actually don't know it because I don't know the runtimes of the functions last and init. ;)

Granivorous answered 21/9, 2012 at 10:51 Comment(1)
Both are O(n), though init has at least a factor of two over last, since it copies n - 1 elements. Use Data.Text for this kind of thing. Making your own with Prelude functions is easy and fun and slow.Endure
J
0

Along the lines of what other people have suggested, you can avoid having to reverse your string by using:

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
Jockstrap answered 13/12, 2013 at 7:25 Comment(0)
A
0

In case you want to implement your own trim function without importing any fancy packages.

import Data.Char (isSpace)

trimLeft :: String -> String
trimLeft = dropWhile isSpace

trimRight :: String -> String
trimRight = dropWhileEnd isSpace

trim :: String -> String
trim = trimRight . trimLeft
Architrave answered 9/7, 2021 at 14:55 Comment(0)
K
-3

Another (std) solution

import System.Environment
import Data.Text

strip :: String -> IO String
strip = return . unpack . Data.Text.strip . pack

main = getLine >>= Main.strip >>= putStrLn
Kapoor answered 3/5, 2014 at 12:53 Comment(2)
why would you voluntarily wrap your result in IO?Camelopardus
Unnecessarily wrapping result in a monad sets a bad example, don't do that.Fayalite

© 2022 - 2024 — McMap. All rights reserved.