从字符串中删除空格并将每个单词放在列表中,haskell

Removing whitespace from a string and putting each word separated in a list, haskell

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

输入

" A \t String with many\nspaces."

会输出

["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,所以不使用其他库的解决方案将是理想的!

需要自己动手吗?如果不是,请使用 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 ""

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

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

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

最终结果:

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

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

这不是执行此操作的最佳或最有效的方法,但它将演示该技术。而且它不需要任何库!

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

{-# LANGUAGE DeriveFunctor #-}

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

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

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

我们将为我们的解析器类型实现 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 需要的关键操作是绑定 (>>=),它获取第一个解析器的结果并将其提供给 return 第二个解析器的函数。这是组合解析器的最方便的方法。它让我们可以累积(或丢弃)结果,而无需通过解析器函数手动线程化输入。

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

接下来我们需要一种创建 "primitive" 解析器的方法。我们将创建一个接受 Char 谓词和 return 的解析器的函数,该解析器将接受传递谓词的单个字符。

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