【问题标题】:Removing whitespace from a string and putting each word separated in a list, haskell从字符串中删除空格并将每个单词分隔在列表中,haskell
【发布时间】:2016-05-26 12:59:48
【问题描述】:

感谢Remove white space from string,我可以成功删除字符串中的空格,但就我而言,我还需要将单词分开并将它们全部放在一个列表中,如下例所示。

输入

" 一个包含许多\n空格的 \t 字符串。"

会输出

["A","String","with","many","spaces."]

我可以输出这个

["","A","","","","String","with","many"]

使用以下代码

> splitWords :: String -> [String]
> splitWords [] =[]
> splitWords as =splitWord "" as


> splitWord _ []  = []
> splitWord word ('\n':as)   = word : splitWord "" as
> splitWord word ('\t':as)  = word : splitWord "" as
> splitWord word (' ':as)  = word : splitWord "" as
> splitWord word (a:as) = splitWord (word ++ [a]) as

由于我正在尝试学习haskell,因此不使用其他库的解决方案将是理想的!

【问题讨论】:

标签: string haskell char whitespace removing-whitespace


【解决方案1】:

你需要自己做吗?如果没有,请使用Data.String.words

λ words " A \t String with many\nspaces."
["A","String","with","many","spaces."] :: [String]

words 定义为:

words   :: String -> [String]
words s =  case dropWhile Char.isSpace s of
                  "" -> []
                  s' -> w : words s''
                        where (w, s'') = break Char.isSpace s'

编辑:不使用 Data.String 函数。

你离得不远了。

首先,您缺少输出中的最后一个单词。 您可以通过将splitWord _ [] = [] 更改为splitWord word [] = [word] 来解决这个问题。

下一个问题是添加到列表中的空字符串。您需要将它们过滤掉(我做了一个顶级函数来演示):

addIfNotEmpty :: String -> [String] -> [String]
addIfNotEmpty s l = if s == "" then l else s:l

使用此功能:

splitWord word []  = addIfNotEmpty word []
splitWord word ('\n':as)   = addIfNotEmpty word $ splitWord "" as
splitWord word ('\t':as)  = addIfNotEmpty word $ splitWord "" as
splitWord word (' ':as)  = addIfNotEmpty word $ splitWord "" as
splitWord word (a:as) = splitWord (word ++ [a]) as

还有啊!有用。但是等等,我们还没有完成!


整理

让我们从splitWords 开始。这里没什么可做的,但我们可以使用eta-reduction

splitWords :: String -> [String]
splitWords = splitWord ""

接下来,请注意,对于每种类型的空间,操作都是相同的。让我们删除重复:

splitWord word (c:cs)
    | c `elem` " \t\n" = addIfNotEmpty word $ splitWord "" cs
    | otherwise        = splitWord (word ++ [c]) cs

我在这里使用elem 来检查下一个字符是否是空格,可以说有更好的方法来做到这一点。

最终结果:

splitWords :: String -> [String]
splitWords = splitWord ""

splitWord :: String -> String -> [String]
splitWord word [] = addIfNotEmpty word []
splitWord word (c:cs)
    | c `elem` " \t\n" = addIfNotEmpty word $ splitWord "" cs
    | otherwise        = splitWord (word ++ [c]) cs

addIfNotEmpty :: String -> [String] -> [String]
addIfNotEmpty s l = if s == "" then l else s:l

【讨论】:

  • 是的,我想尽量避免使用库!
  • 您好,感谢您的精彩回复!我有一个关于你试图在那里做的 eta 减少的问题。我认为如果有空列表的情况会很好,但是当我尝试这样做时,它说它有不同数量的参数,例如 splitWords [] = []。有没有办法将这种情况包含在 splitWords 中?
  • @user6386278 空列表不需要大小写。我也不相信它会很好,主要是令人困惑。 splitWordsString 为参数,而不是 List。它会起作用,因为String 等同于[Char],但它会使事情变得混乱。所以用""[] 调用splitWords 会产生相同的结果,但使用字符串更一致。如果你调用splitWords "",它会依次调用splitWord "" "",它会匹配到splitWord word []。请注意,我自己犯了使用[] 的错误,而"" 更合适。
【解决方案2】:

我们需要的是解析器。这只是一个将字符串作为输入并返回数据结构作为输出的函数。我将向您展示一种以“组合器”样式创建解析器的简化方法。这意味着我们将从更小的解析器中构建我们想要的解析器(通过组合它们)。

这不是最好或最有效的方法,但它会展示这项技术。而且它不需要任何库!

我们将从语言编译指示开始,以减少一些样板:

{-# LANGUAGE DeriveFunctor #-}

现在让我们创建一个数据类型来表示解析函数。

data Parser a = P { parser :: String -> Maybe (String, a) } deriving Functor

基本上,解析器是数据包装器下的一个函数。它的工作方式是将一个字符串作为输入,如果它的条件与字符串开头的字符匹配,那么它将使用这些字符,创建a 类型的数据并返回一个包含未使用的Just输入和新项目。但是,如果条件失败,那么它只会返回 Nothing

我们将为 Parser 类型实现 Applicative 和 Monad,然后我们将能够使用 do 表示法。这是 Haskell(恕我直言)最酷的功能之一。我们不会使用 Applicative <*>,但我们需要实例来实现 Monad。 (尽管 Applicative 本身就很棒。)

instance Applicative Parser where
  pure x = P (\input -> Just (input, x))
  f <*> p = do
    f' <- f
    p' <- p
    return $ f' p'

Monad 需要的关键操作是绑定 (&gt;&gt;=),它获取第一个解析器的结果并将其提供给返回第二个解析器的函数。这是组合解析器最方便的方法。它让我们无需通过解析器函数手动线程化输入即可累积(或丢弃)结果。

instance Monad Parser where
  return = pure
  p >>= f = P (\input -> case parse p input of
                           Just (rest, x) -> parse (f x) rest
                           _ -> Nothing)

接下来我们需要一种方法来创建“原始”解析器。我们将创建一个函数,它接受一个Char 谓词并返回一个解析器,该解析器将接受一个通过谓词的字符。

satisfy :: (Char -> Bool) -> Parser Char
satisfy p = P (\input -> case input of
                  (x:xs) | p x -> Just (xs, x)     -- success!
                  _ -> Nothing                     -- failure :(

还有很多其他方法可以操纵解析器,但我们会坚持解决给定的问题。接下来我们需要的是一种重复解析器的方法。这就是while 函数派上用场的地方。它需要一个解析器来生成 a 类型的项目并重复它直到它失败,并将结果累积到一个列表中。

while :: Parser a -> Parser [a]
while p = P (\input -> case parse p input of
                Nothing -> Just (input, [])
                Just (rest, x) -> parse (fmap (x:) (while p)) rest)

我们快完成了。我们将创建谓词来区分空格和非空格。

isWhitespace c = c == ' ' || c == '\t' || c == '\n'
isNotWhiteSpace = not . isWhitespace

好的,现在我们来看看 do-notation 有多棒。首先,我们为单个单词创建一个解析器。

word :: Parser String
word = do
  c <- (satisfy isNotWhitespace)        -- grab the first character
  cs <- while (satisfy isNotWhitespace) -- get any other characters
  while (satisfy isWhitespace)          -- eat the trailing whitespace
  return (c:cs)

我们终于可以实现我们真正想要的解析器了!

splitWords :: Parser [String]
splitWords = do
  while (satisfy isWhitespace)   -- eat up any leading whitespace
  while word

最后,试试吧!

main :: IO ()
main = do
  let input = " A \t String with many\nspaces."
  case parse splitWords input of
    Nothing -> putStrLn "failed!"
    Just (_, result) -> putStrLn . show $ result

这是我在 ghci 中得到的:

λ main
["A","String","with","many","spaces."]

【讨论】:

    猜你喜欢
    • 2018-08-10
    • 1970-01-01
    • 2017-08-19
    • 2019-12-02
    • 1970-01-01
    • 1970-01-01
    • 2010-12-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多