首页 > 解决方案 > Haskell Parsec - 解析两个东西列表

问题描述

我使用Advent of Code 第 16 部分作为学习如何使用 Parsec 的借口,但我在如何处理这种特定情况下磕磕绊绊。

输入采用以下格式:

Before: [3, 2, 3, 0]
2 3 1 1
After:  [3, 2, 3, 0]

Before: [1, 0, 2, 1]
7 0 1 1
After:  [1, 1, 2, 1]

...

Before: [0, 0, 2, 1]
6 2 3 1
After:  [0, 6, 2, 1]



5 0 2 3
5 1 3 1
...
5 3 2 2

换句话说,首先是一组三行,它们被解析成一个结构,用空行分隔,然后是三个空行,然后是四位数字的行数。

我为每个结构都有工作解析器 -Sample并且MaskedOperation,解析器samplemaskedOp分别为1 - 但我不知道如何将它们放在一起以将其解析为([Sample], [MaskedOperation]).

我尝试了以下方法:

parseInput :: GenParser Char st ([Sample], [MaskedOperation])
parseInput = do
  samples <- sample `sepBy` (count 2 newline) <* count 3 newline
  operations <- maskedOp `sepBy` newline
  return (samples, operations)

但是当它到达三个换行符时它会失败,期待另一个样本:

(line 3221, column 1):
unexpected "\n"
expecting "Before:"

我如何告诉 parsec 我想尽可能多地使用,然后使用分隔符(额外的换行符),然后开始阅读其他内容?


1阅读 Advent of Code 问题的上下文;名字并不重要。

标签: haskellparsec

解决方案


恐怕你不能sepBy在这里使用。

让我们将其简化为将“a”解析为样本,将“b”解析为换行符。您将解析字符串,例如

a b b a b b b c b c b c b

那么解析器在遍历这样的字符串时会怎么想呢?让我们来看看:


解析a或空序列

[a] b b a b b b c b c b c b

哦,一个a。该序列是非空的,所以我many "bb" >> "a"将从现在开始解析。

a [b b a] b b b c b c b c b

成功!让我们再吃点或完成

a b b a [b b] b c b c b c b

好的,我找到了另一个bb,所以顺序继续。解析a

a b b a b b [b] c b c b c b


问题是解析器不会回滚,除非明确要求这样做。要赋予解析器回滚能力,您必须使用try组合器对其进行标记,否则它将无法“取消消费”消耗的输入。

目前,我没有看到比重写sepBy组合器更好的方法,以使其意识到在解析每个分隔符后,如果解析separator >> target失败,可能需要将其返回到缓冲区:

sepTry a sep = sepTry1 a sep <|> pure []
sepTry1 a sep = liftA2 (:) a (many (try $ sep *> a))

请注意,解析a必须包含在try部分中——这是实际触发失败的地方。

为了可视化差异,让我们看一下相同的场景,但sepTry改为:


...

a [b b a] b b b c b c b c b

成功!如果可能的话,让我们试着再买一个

a b b a ![b b] b c b c b c b

好的,我找到了另一个bb,所以顺序继续。解析a

a b b a !b b [b] c b c b c b

不是我所期望的。返回失败并将光标移到后面的感叹号。

a b b a ![]b b b c b c b c b

解析失败bba,解析序列完成。解析bbb

a b b a [b b b] c b c b c b

成功!


在您的情况下,每个Sample解析器将尝试Sample在它们之后读取 2 个换行符,或者在失败的情况下读取 3 个换行符并继续MaskedOperations


推荐阅读