Как в Haskell обрезать пробелы в начале и в конце строки?

Как удалить пробелы в начале и конце строки?

trim "  abc " 

=>

"abc"

Редактировать:

Хорошо, позвольте мне быть немного яснее. Я не понимал, что строковые литералы обрабатываются иначе, чем строки.

Я бы хотел это сделать:

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

Возможно ли это в Haskell? Я использую -XOverloadedStrings, но это работает только для литералов.


person Eric Normand    schedule 07.06.2011    source источник


Ответы (13)


Если у вас есть серьезные потребности в обработке текста, воспользуйтесь пакетом text от hackage:

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

Я написал это в предположении, что длина пробелов будет минимальной, поэтому ваши O (n) из ++ и reverse не вызывают особого беспокойства. Но я еще раз чувствую необходимость сказать, что если вас действительно беспокоит производительность, вам вообще не следует использовать String - перейдите к Text.

РЕДАКТИРОВАТЬ, подтверждая мою точку зрения, быстрый тест Criterion сообщает мне, что (для особенно длинной строки слов с пробелами и ~ 200 пробелов до и после) моя обрезка занимает 1,6 мс, обрезка с использованием реверса занимает 3,5 мс, а Data.Text.strip занимает 0,0016 мс. ..

person Thomas M. DuBuisson    schedule 07.06.2011
comment
Спасибо за рекомендацию. Я убедил свою команду добавить текст в проект, и это избавляет от многих головных болей. - person Eric Normand; 15.06.2011
comment
+1 за эталон, это здорово, когда люди действительно доказывают свои утверждения - person epsilonhalbe; 28.02.2012

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

import Data.Char (isSpace)

trim :: String -> String
trim = f . f
   where f = reverse . dropWhile isSpace
person Eric Normand    schedule 07.06.2011
comment
И это самое простое. Для быстрого и грязного использования это хорошо. - person Elliot Cameron; 29.11.2012
comment
Это красиво, хотя я бы использовал let привязку. - person Carcigenicate; 21.10.2014

После того, как был задан этот вопрос (около 2012 г.) Data.List получил dropWhileEnd, что значительно упростило это:

trim = dropWhileEnd isSpace . dropWhile isSpace
person spopejoy    schedule 09.07.2016
comment
Для тех, кого смущает оператор точки (используемый для композиции функций), это эквивалент trim :: String -> String trim xs = dropWhile isSpace (dropWhileEnd isSpace xs). stackoverflow.com/questions/631284 / hackage.haskell .org / package / base-4.12.0.0 / docs /. - person Javad; 17.10.2018
comment
Для новичков (таких как я): это решение требует, чтобы вы сначала import Data.List и import Data.Char. - person MEMark; 02.01.2021

Неэффективно, но легко понять и вставить туда, где это необходимо:

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

Конечно, 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)

p.s. что касается бесконечных списков, это будет работать:

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

и по очевидной причине это не будет (будет работать вечно):

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

Вы можете комбинировать Data.Text strip с его функциями un / Packing, чтобы избежать перегрузки строк:

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"
person John J. Camilleri    schedule 26.02.2014

В настоящее время пакет MissingH поставляется с strip функция:

import           Data.String.Utils

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

Поэтому, если преобразование из String в Text и обратно не имеет смысла в вашей ситуации, вы можете использовать указанную выше функцию.

person Damian Nadales    schedule 03.11.2017

Я знаю, что это старый пост, но я не видел решений, реализующих старый добрый 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"
person eazar001    schedule 13.04.2014

Я считаю, что это должно быть верно в отношении 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
person Arild    schedule 16.11.2014

Я ничего не знаю о времени выполнения или эффективности, но как насчет этого:

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

Решение без использования каких-либо других модулей или библиотек, кроме Prelude.

Некоторые тесты:

>lrtrim ""
>""

>lrtrim "       "
>""

>lrtrim "haskell       "
>"haskell"

>lrtrim "      haskell       "
>"haskell"

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

Это может быть время выполнения O (n).

Но на самом деле я этого не знаю, потому что не знаю время выполнения функций last и init. ;)

person jimmyt    schedule 21.09.2012
comment
Оба являются O (n), хотя init имеет как минимум два раза больше, чем last, поскольку он копирует n - 1 элемент. Для этого используйте Data.Text. Создавать собственные с помощью функций Prelude легко, весело и медленно. - person nomen; 18.10.2013

Следуя тому, что предлагали другие люди, вы можете избежать необходимости переворачивать строку, используя:

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
person sommnium003    schedule 13.12.2013

Если вы хотите реализовать свою собственную trim функцию без импорта каких-либо необычных пакетов.

import Data.Char (isSpace)

trimLeft :: String -> String
trimLeft = dropWhile isSpace

trimRight :: String -> String
trimRight = dropWhileEnd isSpace

trim :: String -> String
trim = trimRight . trimLeft
person Microtribute    schedule 09.07.2021

Другое (стандартное) решение

import System.Environment
import Data.Text

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

main = getLine >>= Main.strip >>= putStrLn
person user3599138    schedule 03.05.2014
comment
зачем вам добровольно заключать свой результат в IO? - person Erik Kaplun; 05.01.2016
comment
Излишнее обертывание результата в монаду подает плохой пример, не делайте этого. - person Nelo Mitranim; 28.02.2016