Haskell 99 题学习路线

2026-09-07 00:00    #Haskell   #算法   #练习  

Ninety-Nine Haskell Problems 是 Haskell 社区经典的 99 道练习题,源自 Prolog 99 题的 Haskell 移植版。每个题目都有一个函数骨架,你只需要实现它,然后跑预置测试验证。

这套题的独特价值在于:难度梯度设计得好,从最基础的列表递归一路做到图同构、Huffman 编码、数独求解。把 LYAH 学到的语法点串成实战能力。

学习阶段划分

第一阶段:列表基础(P01-P10)

覆盖 LYAH:Ch1-5

题号题目核心语法推荐
P01取列表最后一个元素递归基条件、Maybe
P02取列表倒数第二个元素多条件递归
P03取列表第 k 个元素下标计数
P04求列表长度尾递归、fold
P05反转列表递归 + ++ vs 尾递归优化
P06判断回文==、列表比较
P07展平嵌套列表自定义 ADT、递归遍历
P08去除连续重复元素Eq 约束、guard
P09将连续重复元素打包成子列表group 的手动实现★★
P10游程编码 (run-length encoding)结合 P09、tuple★★
阶段要点

P01-P06 是热身,闭着眼也要能写出来。P07 让你第一次接触自定义 data 类型(NestedList),为后面树和图打基础。P08-P10 是 group 的三种变体,理解它们对第 7 章 Data.List 模块的理解有质的提升。

第二阶段:编码与列表变换(P11-P20)

覆盖 LYAH:Ch4-7

题号题目核心语法推荐
P11改进的游程编码自定义 data Encoding aSingle/Multiple
P12解码游程编码replicateconcatMap
P13直接实现游程编码不用中间子列表,边遍历边计数★★
P14复制每个元素concatMapreplicate
P15每个元素重复 N 次concatMap
P16每隔 N 个删除一个元素zip + 取模计数★★
P17在 N 处切分列表splitAt
P18提取子列表 [i, k]take/drop 组合★★
P19旋转列表 N 位正负数旋转、splitAt★★
P20删除第 k 个元素,返回 (元素, 剩余列表)splitAt 组合
阶段要点

P11-P12 教你用自定义代数数据类型(Encoding)表达业务语义——这是 Haskell 最核心的思维方式。P16-P20 在练 take/drop/splitAt/zip 的标准组合套路,对刷题帮助直接。

第三阶段:组合与随机(P21-P28)

覆盖 LYAH:Ch5-7

题号题目核心语法推荐
P21在指定位置插入元素splitAt
P22生成整数范围enumFromTo 或递归
P23从列表随机选 N 个元素RandomGen、ST monad★★★
P24从 1..M 中随机选 N 个不同数P23 的应用★★
P25随机排列列表Fisher-Yates 洗牌★★★
P26生成所有 N 选 K 的组合递归、list comprehension★★★
P27互斥分组组合 + 回溯★★★
P28按子列表长度排序 / 按出现频率排序sortOngroup . sortcomparing★★
阶段要点

P26 是经典回溯题,用 list comprehension 写比 C++ 的 for 循环嵌套清爽太多。P23-P25 涉及随机性,Haskell 的纯函数随机数(State monad)跟命令式语言完全不同,这一关是思维转换节点。如果卡住,可以暂时跳过 P23-P25 先往后走,回来再看。

第四阶段:数论(P29-P45)

这个阶段跟 LYAH 关系不大,但跟数学底子关系很大。

核心题说明推荐
P29欧拉函数 φ(n)
P30判断素数★★
P31互质判断、gcd
P32-P33质因数分解★★
P34改进版 φ(n)(用质因数)★★
P35-P36高 totient 数★★★
P37-P39素数生成(埃拉托色尼筛 + 流式无限列表)★★★
P40-P41哥德巴赫猜想验证
P42-P43乘法逆元、高斯整数★★★
P44-P45命题逻辑、真值表、合取范式★★★
阶段要点

P37 的埃拉托色尼筛在 Haskell 里有非常优雅的写法:primes = 2 : filter (isPrime primes) [3..]。这题跟你平时在 C++ 里写的 bitset 筛法完全不同——Haskell 用无限惰性列表表达筛法,是理解惰性求值优势的绝佳例子。P44-P45 涉及命题逻辑和自动定理证明,适合对形式逻辑感兴趣的做。

第五阶段:树(P54-P69)

覆盖 LYAH:Ch8(自定义类型)

核心题说明推荐
P54-P55完全平衡二叉树、对称树
P56从列表构造 BST★★
P57-P58高度平衡树(AVL 前身)★★★
P59-P60树的叶子/内部节点/按层遍历★★
P61-P62完全二叉树、布局算法★★★
P63-P66树 ↔ 字符串序列化★★
P67-P68树的 dotstring 表示、前序/中序 → 树重建★★
阶段要点

P57 的平衡树构造问题在 Haskell 里可以一行的 list comprehension 写出来,这也是 Haskell 数据处理最优雅的地方。P68 是经典的 “已知前序和中序,重建二叉树”,面试高频题。

第六阶段:图与难题(P80-P99)

难度最高,需要大量背景知识。

核心题说明推荐
P80-P81图的不同表示(邻接表/边列表)★★
P82-P83两点间所有路径、所有环★★★
P84最小生成树(Prim)★★★
P85图同构判断★★★★★
P86图着色★★★
P87-P88二分图判断、连通分量★★
P90-P91八皇后问题、骑士巡游(回溯)★★★
P92-P93数位谜题(arithmetic puzzle)★★★
P94-P95正则图生成、全词/标识符★★
P96数独求解★★★
阶段要点

P90(八皇后)和 P96(数独)是两种经典的回溯问题。用 Haskell 的 do-notation(list monad)可以不到 10 行写完八皇后,这大概是 Haskell 最令人震撼的表达力展示。图的部分整体可以放最后做,因为需要很多额外的数据结构知识。

实操建议

环境搭建

1cabal install ninetynine

安装后得到 Problems.* 模块,每个模块里有 myLastmyButLast 这样的函数骨架。

学习节奏

哪些可以跳过

总结

阶段题号范围对应 LYAH核心收获
列表基础P01-P10Ch1-5递归、模式匹配、Maybe
列表变换P11-P20Ch4-7自定义类型、take/drop 组合
组合P21-P28Ch5-7回溯、惰性列表、State monad
数论P29-P45数学惰性筛法、命题逻辑
P54-P69Ch8自定义 ADT、树遍历
P80-P99进阶回溯、list monad、图算法