在Haskell中,如何从字符串的开头和结尾删除空格?

65
如何从字符串的开头和结尾删除空格?
trim "  abc " 

=>

"abc"

编辑:

好的,让我再明确一下。我不知道字符串字面量和字符串被处理得如此不同。

我想要做到这一点:

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

在Haskell中是否可以实现这个功能?我正在使用-XOverloadedStrings,但它似乎只适用于字面量。
13个回答

64

如果您有严格的文本处理需求,则可以使用Hackage中的text包:

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

如果您太固执,不想使用 text,也不喜欢反转方法的低效率,那么也许(我是说可能)像下面这样做会更有效:

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

我写这篇文章的前提是空格长度最小,所以你的++reverse的O(n)并不重要。但我还是需要再次强调,如果你真的关心性能,那么就不应该使用String,而应该转向使用Text
编辑证明我的观点,一个快速的Criterion基准测试告诉我(对于一个特别长的带有空格和约200个前后空格的字符串),我的修剪花费1.6毫秒,使用反向修剪花费3.5毫秒,而Data.Text.strip只需0.0016毫秒...

4
感谢您的推荐。我说服了我的团队添加文本到项目中,这样做节省了很多麻烦。 - Eric Normand
5
+1 对于基准来说真的很棒,当人们真正证明他们的声明时。 - epsilonhalbe

51

18
这是最简单的方法。对于快速且不太正式的使用情况,这很好。 - Elliot Cameron
6
虽然我更喜欢使用“let”绑定,但这很美。 - Carcigenicate

46

在这个问题被提出后(大约在2012年),Data.List加入了dropWhileEnd函数,使得这个问题变得更容易解决:

trim = dropWhileEnd isSpace . dropWhile isSpace

3
对于那些被点运算符(用于函数组合)弄糊涂的人,这相当于 trim :: String -> Stringtrim xs = dropWhile isSpace (dropWhileEnd isSpace xs)。参考链接为:https://dev59.com/yHRB5IYBdhLWcg3wa2m2 ;具体可见于 http://hackage.haskell.org/package/base-4.12.0.0/docs/Data-List.html#v:dropWhileEnd。 - Javad
2
对于像我这样的初学者:此解决方案需要您首先导入Data.ListData.Char - MEMark

15

虽然效率低下,但易于理解和在需要的地方粘贴:

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

3
您可以结合 Data.Textstrip 函数和其打包/解包函数,避免使用重载字符串:
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

测试它:

> let s = "  hello  "
> strip s
"hello"
> lstrip s
"hello  "
> rstrip s
"  hello"

3

现在,MissingH 包中包含一个strip 函数:

import           Data.String.Utils

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

如果在您的情况下,将 String 转换为 Text 再转回来没有意义,那么您可以使用上述函数。


我得到了“找不到模块'Data.String.Utils'” - John Smith Optional

3
当然,Data.Text在性能方面更好。但是,正如之前提到的那样,使用列表做这件事很有趣。以下是一种版本,它可以在单次遍历中去除字符串的右侧空格(无需翻转和++),并支持无限列表:
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)

顺便提一句,对于无限列表,那也可以工作:

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

显然,这将导致无限运行:

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

1
这应该是O(n)级别的算法,我相信:
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

我知道这是一篇旧文章,但我没有看到任何实现好老的fold解决方案。

首先使用dropWhile去除前导空格。然后,使用foldl'和一个简单的闭包,您可以在一次遍历中分析其余的字符串,并基于该分析将有用的参数传递给take,而无需使用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'

变量c跟踪应该被吸收的组合空格和非空格字符,变量w跟踪应该被剥离的右侧空格。
测试运行:
print $ trim "      a   b c    "
print $ trim "      ab c    "
print $ trim "    abc    "
print $ trim "abc"
print $ trim "a bc    "

输出:

"a   b c"
"ab c"
"abc"
"abc"
"a bc"

0

如果您想要实现自己的 trim 函数而不导入任何花哨的包,请参考以下内容。

import Data.Char (isSpace)

trimLeft :: String -> String
trimLeft = dropWhile isSpace

trimRight :: String -> String
trimRight = dropWhileEnd isSpace

trim :: String -> String
trim = trimRight . trimLeft

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接