P09 - 将连续重复元素打包成子列表

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

P09 - 将连续重复元素打包成子列表

Pack consecutive duplicates of list elements into sublists

官方模块: Problems.P09 核心函数: pack


← P08 去除连续重复元素 | P10 游程编码 →



← P08 去除连续重复元素 | P10 游程编码 →


题目描述

将列表中连续重复的元素打包成子列表。每个子列表包含一组连续相同的元素。

函数签名

1pack :: Eq a => [a] -> [[a]]

思路

维护一个当前正在构建的子列表(current group)。遍历输入列表:

在 Haskell 中可以用两种思路实现:

  1. 显式递归:手动维护当前组和已完成的组列表
  2. 高阶函数:用 spangroupBy 抽象分组逻辑

实现

方法一:递归 + 累加器

1pack :: Eq a => [a] -> [[a]]
2pack []     = []
3pack (x:xs) = let (group, rest) = span (== x) xs
4              in (x : group) : pack rest

span (== x) xsxs 头部开始,提取所有等于 x 的连续元素(返回两个部分:匹配的列表和剩余部分)。然后把 x 放在匹配列表前面,构成一组,再递归处理剩余部分。

方法二:使用 group(标准库)

1import Data.List (group)
2
3pack :: Eq a => [a] -> [[a]]
4pack = group

标准库 Data.List.group 做的就是这个事。但如果自己实现,用方法一或方法三。

方法三:显式递归(逐元素推进)

1pack :: Eq a => [a] -> [[a]]
2pack [] = []
3pack (x:xs) = go [x] xs
4  where
5    go current []     = [current]
6    go current@(c:_) (y:ys)
7      | c == y    = go (y:current) ys
8      | otherwise = current : go [y] ys

用累加器 current 手动构建当前组。如果下一元素和当前组元素相同,加入组;否则结束当前组,开始新组。

注意:因为用 : 前插,组内元素是逆序的,但 group 不要求组内元素有序,所以不影响。

如果需要保持组内顺序,可以用 go (current ++ [y]) ys 但效率低。更好的做法是最后 reverse 每个组。

理解 span

span :: (a -> Bool) -> [a] -> ([a], [a]) 在谓词首次不满足时切分列表:

1span (== 'a') "aaaabccaadeeee"  ("aaaa", "bccaadeeee")

这正是分组需要的操作:从当前元素开始,把所有连续相等的元素切出来。

示例过程

输入 "aaaabccaadeeee"

1pack "aaaabccaadeeee"
2→ span (== 'a') "aaaabccaadeeee" = ("aaaa","bccaadeeee")
3→ "a" : "aaaa" = "aaaa"  →  ["aaaa"] ++ pack "bccaadeeee"
4
5pack "bccaadeeee"
6→ span (== 'b') "bccaadeeee" = ("b","ccaadeeee")
7→ ["b"] ++ pack "ccaadeeee"
8
9... 最终 → ["aaaa","b","cc","aa","d","eeee"]

测试

 1>>> pack "aaaabccaadeeee"
 2["aaaa","b","cc","aa","d","eeee"]
 3
 4>>> pack [1,1,2,2,2,3]
 5[[1,1],[2,2,2],[3]]
 6
 7>>> pack []
 8[]
 9
10>>> pack "a"
11["a"]

参考


← P08 去除连续重复元素 | P10 游程编码 →