P28 - 按子列表长度排序

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

P28 - 按子列表长度排序

Sorting a list of lists according to length of sublists

官方模块:Problems.P28 核心函数:lsort, lfsort


← P27 互斥分组 | P29 斐波那契数 →


题目描述

实现两个排序函数:

函数签名

1lsort  :: [[a]] -> [[a]]
2lfsort :: [[a]] -> [[a]]

实现

lsort 方法一:sortBy + comparing

1import Data.List (sortBy)
2import Data.Ord (comparing)
3
4lsort :: [[a]] -> [[a]]
5lsort = sortBy (comparing length)

comparing length 生成比较器,按 length 排序。等价于 sortOn length

lsort 方法二:sortOn

1import Data.List (sortOn)
2
3lsort :: [[a]] -> [[a]]
4lsort = sortOn length

标准库推荐的写法,GHC 的 sortOn 用装饰-排序-去装饰(Schwartzian transform)优化,比 sortBy 少做重复计算。

lsort 方法三:手动插入排序

1lsort :: [[a]] -> [[a]]
2lsort [] = []
3lsort (x:xs) = insertByLen x (lsort xs)
4  where
5    insertByLen y [] = [y]
6    insertByLen y (z:zs)
7      | length y <= length z = y : z : zs
8      | otherwise            = z : insertByLen y zs

纯递归,不依赖标准库排序函数。适合教学演示排序原理,但效率远不如 sortOn

lfsort 方法一:Map 计数

1import Data.List (sortOn)
2import qualified Data.Map.Strict as Map
3
4lfsort :: [[a]] -> [[a]]
5lfsort xs = sortOn frequency xs
6  where
7    counts = Map.fromListWith (+) [(length ys, 1 :: Int) | ys <- xs]
8    frequency ys = Map.findWithDefault 0 (length ys) counts

先统计每种长度出现的次数(一个 Map),然后按这个次数排序。

注意:这里引入 Int 类型注解 1 :: Int,避免 Num 默认类型歧义。

lfsort 方法二:group + sortOn 组合

1import Data.List (sortOn, groupBy)
2import Data.Function (on)
3
4lfsort :: [[a]] -> [[a]]
5lfsort = concat . sortOn length . groupBy ((==) `on` length) . sortOn length

先按长度排序,然后把相同长度的子列表分组,再按组的长度(即该长度出现的次数)排序这些组,最后 concat 展平。

这种方法巧妙利用了 groupBy 本身的分组能力,不依赖 Map。

lsort 方法对比

方法代码量效率特点
sortBy + comparing1 行O(n log n)显式比较器
sortOn1 行O(n log n)Schwartzian 变换,推荐
手写插入排序8 行O(n²)教学用途

测试

1>>> lsort ["xxx","xx","xxx","xx","xxxx","xx","x"]
2["x","xx","xx","xx","xxx","xxx","xxxx"]
3>>> lfsort ["xxx","xx","xxx","xx","xxxx","xx"]
4["xxxx","xxx","xxx","xx","xx","xx"]
5>>> lsort []
6[]

参考