Parsec中sepBy的分隔符解析器优先级及组合式解析实现问询
用Parsec原生实现按行解析并计算结果乘积
核心思路
要实现按换行分隔的子串解析,关键是限制原解析器p仅作用于单行内容,而非整个输入流。我们可以通过Parsec的输入操作组合子,提取单行内容交给p处理,再将剩余输入设置为后续内容,最后组合多行结果计算乘积。
实现代码
首先导入必要的模块:
import Text.Parsec import Text.Parsec.String (Parser) import Data.Char (ord) import Control.Monad.Trans.Either (liftEither)
1. 辅助函数:将解析器限制为单行解析
这个函数会从当前输入中提取第一行(到换行符或输入结束),将其交给原解析器处理,同时更新输入为剩余内容:
parseLine :: Parser a -> Parser a parseLine p = do input <- getInput let (line, rest) = break (== '\n') input -- 更新输入:移除换行符(如果存在) setInput $ case rest of [] -> [] (_:remaining) -> remaining -- 用原解析器处理单行内容,转换为Parsec monad内的操作 result <- liftEither $ parse p "" line return result
2. 原解析器:计算字符串所有字符的ASCII值之和
sumAscii :: Parser Int sumAscii = sum <$> many (ord <$> anyChar)
3. 目标解析器:计算每行ASCII和的乘积
productOfLineSums :: Parser Int productOfLineSums = product <$> many (parseLine sumAscii)
测试验证
对于输入"AB\nZ\nCEF",解析结果为:
-- 运行 parse productOfLineSums "" "AB\nZ\nCEF" -- 得到 Right 2428740,即 (65+66)*(90)*(67+69+70) = 131*90*206 = 2428740
关键细节说明
- 为什么不用
sepBy p newline?
原解析器sumAscii会消费所有输入字符(包括换行符),导致sepBy无法匹配分隔符newline。通过parseLine,我们将sumAscii的作用范围限制为单行,避免了修改原解析器的逻辑,完全符合组合式编程思想——原解析器仍可单独用于解析任意字符串,无需为换行场景做特殊处理。 - 边界情况处理:
函数自动兼容最后一行无换行符的场景,break会将剩余所有输入作为最后一行,setInput设置为空,不影响后续解析。
通用扩展
如果你确实需要将Parsec String u a转换为Parsec [String] u [a](即输入为行的列表),可以直接用遍历函数实现:
parseLines :: Parsec String u a -> Parsec [String] u [a] parseLines p = traverse (parse p "")
不过多数场景下,直接在原String输入流上用parseLine组合更符合Parsec的原生使用方式。
内容的提问来源于stack exchange,提问作者Feryll
相关产品推荐
相关产品推荐

