P03 - 取列表第 k 个元素

2026-09-07 00:00    #Haskell   #99题   #列表  

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 时返回当前元素。

三种终止条件:

实现

方法一:递归递减

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

参考


← P02 取列表倒数第二个元素 | P04 求列表长度 →