You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 09:30:38