P16 - 每隔 N 个删除一个元素

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

P16 - 每隔 N 个删除一个元素

Drop every N’th element from a list

官方模块: Problems.P16 核心函数: dropEvery


← P15 每个元素重复 N 次 | P17 在 N 处切分列表 →


题目描述

删除列表中每隔 N 个位置的元素(从 1 开始计数)。

函数签名

1dropEvery :: [a] -> Int -> [a]

思路

两种思路:

  1. 带计数器递归:维护一个下标计数器,每到 N 就跳过
  2. zip 标号过滤:给每个元素标上位置序号,只保留序号不是 N 的倍数的

实现

方法一:递归 + 计数器

1dropEvery :: [a] -> Int -> [a]
2dropEvery [] _ = []
3dropEvery (x:xs) n = go x 1 xs
4  where
5    go x i []     = if i == n then [] else [x]
6    go x i (y:ys)
7      | i == n    = go y 1 ys
8      | otherwise = x : go y (i + 1) ys

用一个计数器 i 跟踪当前位置,每到 N 就丢弃当前元素。

也可以把当前元素和计数器一起追踪,更简洁的写法:

1dropEvery :: [a] -> Int -> [a]
2dropEvery xs n = [x | (x, i) <- zip xs [1..], i `mod` n /= 0]

方法二:zip + 列表推导(推荐)

1dropEvery :: [a] -> Int -> [a]
2dropEvery xs n = map fst . filter (\(_, i) -> i `mod` n /= 0) $ zip xs [1..]

zip xs [1..] 给每个元素编号,filter 去掉序号是 n 的倍数的元素,map fst 取回原值。

方法三:take/drop 分块

1dropEvery :: [a] -> Int -> [a]
2dropEvery [] _ = []
3dropEvery xs n = take (n - 1) xs ++ dropEvery (drop n xs) n

取前 n-1 个保留,跳过第 n 个,递归处理剩余部分。这个实现最符合"间隔删除"的语义理解。

dropEvery 的求值过程

dropEvery "abcdefghik" 3 为例:

1dropEvery "abcdefghik" 3
2= take 2 "abcdefghik" ++ dropEvery (drop 3 "abcdefghik") 3
3= "ab" ++ dropEvery "defghik" 3
4= "ab" ++ ("de" ++ dropEvery "ghik" 3)
5= "ab" ++ ("de" ++ ("gh" ++ dropEvery "k" 3))
6= "ab" ++ ("de" ++ ("gh" ++ "k"))
7= "abdeghk"

测试

 1>>> dropEvery "abcdefghik" 3
 2"abdeghk"
 3
 4>>> dropEvery [1,2,3,4,5] 2
 5[1,3,5]
 6
 7>>> dropEvery "a" 3
 8"a"
 9
10>>> dropEvery [] 3
11[]

参考


← P15 每个元素重复 N 次 | P17 在 N 处切分列表 →