P03 - 取列表第 k 个元素
Indexed element in a list
官方模块:
Problems.P03核心函数:elementAt
← P02 取列表倒数第二个元素 | P04 求列表长度 →
← P02 取列表倒数第二个元素 | P04 求列表长度 →
题目描述
找出列表的第 k 个元素(1-based indexing,第一个元素编号为 1)。如果 k 超出列表范围,返回 Nothing。
函数签名
1elementAt :: [a] -> Int -> Maybe a
思路
这是一个带下标的递归:递归遍历列表的同时递减 k,当 k 减到 1 时返回当前元素。
三种终止条件:
[]——列表提前耗尽,返回Nothingk < 1——非法下标,返回Nothingk == 1——命中目标,返回Just x
实现
方法一:递归递减
1elementAt :: [a] -> Int -> Maybe a
2elementAt [] _ = Nothing
3elementAt _ k | k < 1 = Nothing
4elementAt (x:_) 1 = Just x
5elementAt (_:xs) k = elementAt xs (k - 1)
用 guard k < 1 处理非法下标,模式 (x:_) 配合 1 命中目标,(_:xs) 配合 k 递归缩小。
语法点:Pattern + Guard 写在一行
Haskell 允许把 | guard 直接跟在模式后面写在同一行:
1elementAt _ k | k < 1 = Nothing
等价于更常见的换行写法:
1elementAt _ k
2 | k < 1 = Nothing
这是 Haskell 2010 报告允许的合法语法。适用场景:
- guard 条件短且只有一条时,一行更紧凑
- guard 条件长或者有多条(
| cond1 = ... | cond2 = ...)时,换行更清晰
标准库中也有这种写法,例如 Data.List.isPrefixOf:
1isPrefixOf (x:xs) (y:ys) | x == y = isPrefixOf xs ys
2 | otherwise = False
方法二:使用 drop + listToMaybe
1import Data.Maybe (listToMaybe)
2
3elementAt :: [a] -> Int -> Maybe a
4elementAt xs k
5 | k < 1 = Nothing
6 | otherwise = listToMaybe . drop (k - 1) $ xs
drop (k-1) xs 丢掉前 k-1 个元素(如果列表不够长,得到空列表),listToMaybe 取结果列表的第一个元素。
这种写法的好处是不需要手写递归,语义清晰。
方法三:使用 zip 过滤
1elementAt :: [a] -> Int -> Maybe a
2elementAt xs k = lookup k (zip [1..] xs)
zip [1..] xs 将列表标号为 [(1, x1), (2, x2), ...],lookup k 按 key 查找。简洁优雅,但效率是 O(n)。
测试
1>>> elementAt [1,2,3] 2
2Just 2
3
4>>> elementAt "haskell" 5
5Just 'e'
6
7>>> elementAt [1,2] 3
8Nothing
9
10>>> elementAt [] 1
11Nothing
12
13>>> elementAt [10,20,30] 0
14Nothing