Haskell 语法特性 OJ 入门题单
保留原 Haskell OJ 入门题目,按 Haskell 语法特性重新组织练习路径。
Haskell 语法特性 OJ 入门题单
这份题单保留原来的 30 道题,但学习主线从“算法阶段”改成“Haskell 语法特性”。目标不是把题写得最短,而是通过短题反复见到 Haskell 在 OJ 中最常用的写法。
每题复盘时重点看三件事:
- 这题用到了哪些 Haskell 语法或标准库函数?
- 输入解析那一行是怎么一步一步执行的?
- 能不能写出一个清楚版本,再尝试写得更 Haskell 一点?
建议每题至少保留两个版本的理解:
text
直白版本:变量多一点,步骤拆开,便于确认含义。
熟练版本:使用 <$>、函数组合、列表函数或标准库,让代码更紧凑。阶段 1:程序入口、do 与基础 IO
目标:
- 理解
main :: IO () - 理解
do块里的执行顺序 - 区分
<-绑定 IO 结果和let绑定普通表达式 - 熟悉
print、putStrLn、字符串拼接和if ... then ... else ...
小模板:
haskell
main :: IO ()
main = do
input <- getLine
let n = read input :: Int
print (n + 1)题目:
- 练习点:
do块中连续读取输入,<-取出 IO 结果,let计算普通值,putStrLn拼接输出。 - 练习点:最小
main :: IO ()程序,putStrLn输出固定字符串。 - 练习点:读入两个整数,使用模式匹配
[r1, s]拆列表,再用print输出表达式。 - 练习点:
if ... then ... else ...表达式,odd/even判断,read后指定整数类型。
阶段 2:输入解析、<$>、函数组合与类型标注
目标:
- 熟悉
getContents、getLine、lines、words - 理解
map read . words <$> getContents - 理解
<$>、.、$在输入解析中的作用 - 学会在解析处写类型标注,避免
read类型不明确
小模板:
haskell
main :: IO ()
main = do
xs <- map read . words <$> getContents :: IO [Int]
print (sum xs)这行可以按下面顺序理解:
text
getContents :: IO String
words :: String -> [String]
map read :: [String] -> [Int]
map read . words :: String -> [Int]
map read . words <$> input :: IO [Int]题目:
- 练习点:
getLine读取一行字符串,filter (== '1')过滤字符,length统计结果。 - 练习点:
map read . words <$> getContents读整数列表,用递归或takeWhile反复处理列表。 - 练习点:
show把数字转字符串,digitToInt处理字符数字,函数组合写出数位和。 - 练习点:
lines/words处理多组输入,把每一行映射成一个判断结果。
阶段 3:列表、字符串与列表推导式
目标:
- 熟悉
String本质是[Char] - 熟悉
map、filter、length、reverse - 会用列表推导式表达枚举
- 会用
takeWhile、dropWhile、字符串分割和反转处理短题
小模板:
haskell
main :: IO ()
main = do
xs <- map read . words <$> getContents :: IO [Int]
print (length [x | x <- xs, x < 0])题目:
- 练习点:把字符串看成字符列表,使用
takeWhile、dropWhile或递归统计连续合法段。
阶段 4:模式匹配、递归、where 与 case
目标:
- 用模式匹配拆列表和字符串
- 会写递归函数的终止条件和递推分支
- 熟悉
where放局部函数 - 在多分支判断时比较
if、guards 和case
小模板:
haskell
solve :: [Int] -> Int
solve [] = 0
solve (x:xs)
| x < 0 = 1 + solve xs
| otherwise = solve xs题目:
- 练习点:字符串递归匹配,
isPrefixOf/stripPrefix,从后往前消去时练习函数拆分。 - 练习点:用递归或
group压缩相邻相同字符,比较手写递归和标准库写法。
阶段 5:排序、元组、sortOn 与 Down
目标:
- 熟悉
sort和sortOn - 用元组作为排序 key
- 用
Down表示降序 - 用
zip [1..]保留原始编号
小模板:
haskell
import Data.List (sortOn)
import Data.Ord (Down(..))
main :: IO ()
main = do
let xs = [("tokyo", 80, 1), ("osaka", 90, 2)]
print (sortOn (\(city, score, _) -> (city, Down score)) xs)题目:
- 练习点:
lines读取多行字符串,sort排序字符串列表,concat拼接结果。 - 练习点:降序排序可以用
reverse . sort,也可以用sortOn Down;再配合下标奇偶分组。 - 练习点:
zip [1..]保留原始编号,sortOn使用元组排序键,Down实现分数降序,mapM_ print批量输出。
阶段 6:Set、Map.Strict、计数与查找
目标:
- 会用
Data.Set去重和查找 - 会用
Data.Map.Strict统计频率 - 理解
insertWith (+)的计数写法 - 区分普通列表扫描和集合 / 映射结构
小模板:
haskell
import qualified Data.Map.Strict as Map
freq :: Ord a => [a] -> Map.Map a Int
freq = foldl (\mp x -> Map.insertWith (+) x 1 mp) Map.empty题目:
- 练习点:
Set.fromList去重,Set.size统计不同元素数量。 - 练习点:用
Map.Strict统计字符串频率,找最大值,再输出所有达到最大频率的 key。
阶段 7:fold、scan 与入门 DP
目标:
- 熟悉
foldl'维护累计状态 - 熟悉
scanl生成前缀状态 - 把 DP 状态写成列表递推或局部递归
- 开始注意惰性求值下的空间问题
小模板:
haskell
import Data.List (foldl')
sumStrict :: [Int] -> Int
sumStrict = foldl' (+) 0题目:
后续扩展方向
这 30 题做完后,可以按语法继续扩展:
- 输入解析:多练
ByteString,解决大输入性能问题。 - 数据结构:继续练
Map.Strict、IntMap、Set、数组。 - 表达能力:每题写一个直白版,再写一个使用函数组合或标准库的版本。
- 算法进阶:从 Educational DP、ABC C/D 继续把 Haskell 写法和算法模型结合起来。
如果后续把某些外链题写进本仓库题解,可以再把外链逐步替换成 [[problem: oj,pid]] 形式。