В Haskell, как вы урезаете пробел от начала и конца строки? - PullRequest
47 голосов
/ 07 июня 2011

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

trim "  abc " 

=>

"abc"

Edit:

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

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

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

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

Ответы [ 12 ]

55 голосов
/ 07 июня 2011

Если у вас есть серьезные потребности в обработке текста, используйте пакет 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 мс ...

34 голосов
/ 07 июня 2011

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

import Data.Char (isSpace)

trim :: String -> String
trim = f . f
   where f = reverse . dropWhile isSpace
24 голосов
/ 09 июля 2016

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

trim = dropWhileEnd isSpace . dropWhile isSpace
15 голосов
/ 07 июля 2011

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

strip = lstrip . rstrip
lstrip = dropWhile (`elem` " \t")
rstrip = reverse . lstrip . reverse
3 голосов
/ 03 ноября 2017

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

import           Data.String.Utils

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

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

3 голосов
/ 26 февраля 2014

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

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 голосов
/ 16 января 2014

Конечно, Data.Text лучше для производительности.Но, как уже упоминалось, это просто весело делать со списками.Вот версия, в которой rstrip представляет собой строку за один проход (без реверса и ++) и поддерживает бесконечные списки:

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)

ps, как для бесконечных списков, которые будут работать:

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

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

List.length $ List.take n $ rstrip $ 'a':(cycle " ")
1 голос
/ 16 ноября 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
1 голос
/ 13 апреля 2014

Я знаю, что это старый пост, но я не видел решений, которые бы реализовали старый добрый 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 голосов
/ 03 мая 2014

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

import System.Environment
import Data.Text

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

main = getLine >>= Main.strip >>= putStrLn
...