P21 - 在指定位置插入元素
Insert an element at a given position into a list
官方模块:
Problems.P21核心函数:insertAt
← P20 删除第 k 个元素 | P22 生成整数范围 →
题目描述
把元素插入列表的第 个位置。题目使用从 1 开始的位置编号,因此位置 1 表示插到列表最前面。
函数签名
1insertAt :: a -> [a] -> Int -> [a]
实现
方法一:splitAt(推荐)
1insertAt :: a -> [a] -> Int -> [a]
2insertAt x xs n = front ++ x : back
3 where
4 (front, back) = splitAt (n - 1) xs
在位置 插入,等价于在前 个元素之后插入。splitAt 一步切出前后两段,再把新元素接在中间。
++ 的右侧是 x : back(不是 [x] ++ back),所以只复制前缀,不产生额外 : 操作。若位置超过列表长度,splitAt 让 back = [],新元素自然追加到末尾。
方法二:递归
1insertAt :: a -> [a] -> Int -> [a]
2insertAt x [] _ = [x]
3insertAt x xs 1 = x : xs
4insertAt x (y:ys) n = y : insertAt x ys (n - 1)
三种情况分别处理:
- 列表到头 → 新元素成为最后一个
- 位置 1 → 插到最前面
- 否则 → 保留当前元素,递归到下一位置
这是最符合 Haskell 列表递归直觉的写法。
方法三:zip + takeWhile
1insertAt :: a -> [a] -> Int -> [a]
2insertAt x xs n = take (n - 1) xs ++ x : drop (n - 1) xs
不依赖 splitAt,直接 take + drop。当 n 很大时 take 取到整个列表,drop 返回 [],新元素加在末尾。
方法对比
| 方法 | 核心操作 | 特点 |
|---|---|---|
| splitAt | splitAt | 一次切分,语义清晰 |
| 递归 | : + 递归 | 纯模式匹配,教学友好 |
| take/drop | take + drop + ++ | 工具函数组合 |
测试
1>>> insertAt 'X' "abcd" 2
2"aXbcd"
3>>> insertAt 0 [1,2,3] 1
4[0,1,2,3]
5>>> insertAt 4 [1,2,3] 99
6[1,2,3,4]