【问题标题】:Parsing String of parenthesis to nested List in Haskell将括号字符串解析为Haskell中的嵌套列表
【发布时间】:2018-05-14 14:22:20
【问题描述】:

我的目标是编写一个函数来将嵌套括号字符串解析为相应的列表:

parseParens "()" --> []
parseParens "(())" --> [[]]
parseParens "((()()))" --> [[[],[]]]

首先我发现我不能轻易指定定义返回值的类型。我可以这样做:

parseParens :: String -> [[[[t]]]]

但是我怎么说它是无限嵌套的呢?我猜 Haskell 不允许这样做。

我的解决方案

我想出了自己的数据类型:

data InfiniteList = EmptyList | Cons InfiniteList InfiniteList deriving (Show)

还有一个使用它的解析器函数:

parseParens :: String -> InfiniteList
parseParens ('(':xs) =
    if remainder == ""
        then result
        else error "Unbalanced parenthesis"
    where (result, remainder) = parseToClose EmptyList xs
parseParens _ = error "Unbalanced parenthesis"

parseToClose :: InfiniteList -> String -> (InfiniteList, String)
parseToClose acc "" = error "Unbalanced parenthesis!"
parseToClose acc (')':xs) = (acc, xs)
parseToClose acc ('(':xs) = parseToClose (concatInfLists acc (Cons result EmptyList)) remainder
    where (result, remainder) = parseToClose EmptyList xs

concatInfLists :: InfiniteList -> InfiniteList -> InfiniteList
concatInfLists EmptyList ys = ys
concatInfLists (Cons x xs) ys = Cons x (concatInfLists xs ys)

像这样工作:

parseParens "()" --> EmptyList
parseParens "(())" --> Cons EmptyList EmptyList
parseParens "((()()))" --> Cons (Cons EmptyList (Cons EmptyList EmptyList)) EmptyList

如何改进?

肯定有更好的方法来做到这一点。也许甚至有办法为此使用内置的 List 数据类型?

【问题讨论】:

  • data Nested a = Flat a | Nested (Nested [a])Nested a 值由 n Nested 构造函数组成,由包含 as 的 n 深度嵌套列表的 Flat 构造函数终止。

标签: list parsing haskell infinite


【解决方案1】:

编辑:修正了我对本杰明回答的错误描述。

@Benjamin Hodgson 评论中的答案:

data Nested a = Flat a | Nested (Nested [a]) deriving (Show)

提供了一种表示任意嵌套深度的同质列表的好方法(即,有点像 [a] 加上 [[a]] 加上 [[[a]]] 加上所有其余部分的总和类型),这似乎是一个不寻常的表示您的问题,尤其是在以下情况下:

parseParens "(()(()))"

“子节点”的嵌套深度不同。这将表示为:

Nested (Nested (Nested (Flat [[],[[]]]))) :: Nested a

所以它实际上允许您将解析的结果表示为所需的列表,给定足够的Nested 构造函数,但它有一些奇怪的属性。例如,最里面的空列表实际上有不同的类型:第一个是[[a]] 类型,而第二个是[a] 类型。

作为一种替代方法,我认为您实际上想要的数据类型可能只是:

data Nested = N [Nested] deriving (Show)

每个节点N 是一个(可能为空的)节点列表。然后,你会得到:

> parseParens "()"
N []
> parseParens "(())"
N [N []]
> parseParens "((()()))"
N [N [N [],N []]]
> parseParens "(()(()))"
N [N [],N [N []]]

如果您只是忽略这些结果中的 N 构造函数,则前三个与问题开头的“相应列表”测试用例匹配。

附带说明:上面的Nested 数据类型实际上是一棵不包含数据的“玫瑰树”,相当于使用containers 包中Data.Tree 中的Tree 数据类型的Tree ()

最后,我不能再强调学习和使用单子解析库的帮助了,即使对于简单的解析工作也是如此。例如,使用parsec 库,您可以在一行中为您的语法编写一个解析器:

nested = N <$> between (char '(') (char ')') (many nested)

我的parseParens 的完整代码是:

import Data.Tree
import Text.Parsec
import Text.Parsec.String

data Nested = N [Nested] deriving (Show)

nested :: Parser Nested
nested = N <$> between (char '(') (char ')') (many nested)

parseParens :: String -> Nested
parseParens str =
  let Right result = parse (nested <* eof) "" str
  in  result

【讨论】:

  • 谢谢。这个N []thingie 运作良好。我设法轻松地将它插入到我现有的代码中。我肯定也需要了解这些解析库......尽管首先我需要了解 monad。
  • 创建新数据类型是否有任何特殊原因。在我看来,解析到列表就足够了。
  • @user26732,最初的问题是决定给parseParens提供什么类型。例如,如果它的类型为 parseParens :: String -&gt; [[[[a]]]],您可以返回普通列表,但最多只能返回深度 4。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-21
  • 2012-01-28
  • 2014-06-04
  • 1970-01-01
  • 2022-12-10
  • 1970-01-01
相关资源
最近更新 更多