Haskell 语法特性 OJ 入门题单

保留原 Haskell OJ 入门题目,按 Haskell 语法特性重新组织练习路径。

0 / 0 已完成

Haskell 语法特性 OJ 入门题单

这份题单保留原来的 30 道题,但学习主线从“算法阶段”改成“Haskell 语法特性”。目标不是把题写得最短,而是通过短题反复见到 Haskell 在 OJ 中最常用的写法。

每题复盘时重点看三件事:

  1. 这题用到了哪些 Haskell 语法或标准库函数?
  2. 输入解析那一行是怎么一步一步执行的?
  3. 能不能写出一个清楚版本,再尝试写得更 Haskell 一点?

建议每题至少保留两个版本的理解:

text
直白版本:变量多一点,步骤拆开,便于确认含义。
熟练版本:使用 <$>、函数组合、列表函数或标准库,让代码更紧凑。

阶段 1:程序入口、do 与基础 IO

目标:

  • 理解 main :: IO ()
  • 理解 do 块里的执行顺序
  • 区分 <- 绑定 IO 结果和 let 绑定普通表达式
  • 熟悉 printputStrLn、字符串拼接和 if ... then ... else ...

小模板:

haskell
main :: IO ()
main = do
    input <- getLine
    let n = read input :: Int
    print (n + 1)

题目:

阶段 2:输入解析、<$>、函数组合与类型标注

目标:

  • 熟悉 getContentsgetLinelineswords
  • 理解 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]

题目:

阶段 3:列表、字符串与列表推导式

目标:

  • 熟悉 String 本质是 [Char]
  • 熟悉 mapfilterlengthreverse
  • 会用列表推导式表达枚举
  • 会用 takeWhiledropWhile、字符串分割和反转处理短题

小模板:

haskell
main :: IO ()
main = do
    xs <- map read . words <$> getContents :: IO [Int]
    print (length [x | x <- xs, x < 0])

题目:

  • 练习点:把字符串看成字符列表,使用 takeWhiledropWhile 或递归统计连续合法段。

阶段 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

目标:

  • 熟悉 sortsortOn
  • 用元组作为排序 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)

题目:

阶段 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

题目:

阶段 7:fold、scan 与入门 DP

目标:

  • 熟悉 foldl' 维护累计状态
  • 熟悉 scanl 生成前缀状态
  • 把 DP 状态写成列表递推或局部递归
  • 开始注意惰性求值下的空间问题

小模板:

haskell
import Data.List (foldl')

sumStrict :: [Int] -> Int
sumStrict = foldl' (+) 0

题目:

后续扩展方向

这 30 题做完后,可以按语法继续扩展:

  • 输入解析:多练 ByteString,解决大输入性能问题。
  • 数据结构:继续练 Map.StrictIntMapSet、数组。
  • 表达能力:每题写一个直白版,再写一个使用函数组合或标准库的版本。
  • 算法进阶:从 Educational DP、ABC C/D 继续把 Haskell 写法和算法模型结合起来。

如果后续把某些外链题写进本仓库题解,可以再把外链逐步替换成 [[problem: oj,pid]] 形式。