☰
TIL:在 Clojure 中用 clojure.math.combinatorics 从序列生成组合
2026/10/5 10:01:25 网站建设 项目流程
  • 文档
  • 教程
  • 知识库

【免费下载链接】til

:memo: Today I Learned

项目地址:https://gitcode.com/gh_mirrors/ti/til
点击查看免费下载

本篇 TIL 笔记源自 clojure/combinations-of-items-from-a-sequence.md,讲解如何在 Clojure 中借助clojure.math.combinatorics库的combinations函数,从一个序列中枚举出所有"指定大小"的组合。读完本文你将掌握:组合与排列的区别、combinations的完整用法与返回结构、如何验证组合总数(C(n, k)),以及在大输入下的惰性处理技巧,可直接迁移到分组配对、测试用例生成等实战场景。

场景:从 5 个人中唯一配对 2 人

有时我们想从一个列表中取出所有"组合"。例如有 5 个人,希望知道从这 5 人中唯一配对出 2 人的全部方式——这正是组合数学中的"从 5 个元素中取 2 个的组合"问题,记作 C(5, 2),其结果为:

C(5, 2) = 5! / (2! × (5-2)!) = 10

10 种配对方式里,(A, B)与(B, A)被视为同一种组合,因为组合不关心元素的排列顺序——这正是它区别于排列(permutation)的核心。若你恰好需要"顺序有意义的排列",可以对比仓库中的另一篇笔记 math/generate-permutations-of-all-valid-9-ball-racks.md,那篇以 9 球开球排列为例,介绍了用Array#permutation枚举 7! = 5040 种排列的思路。

引入 math.combinatorics 库

Clojure 官方组织的 clojure/math.combinatorics 中的 Clojure 分类索引),因此在任意 Clojure 项目中按以下方式引入即可:

  • Leiningen(在project.clj的:dependencies中添加):
[org.clojure/math.combinatorics "0.2.0"]
  • tools.deps(在deps.edn的:deps中添加):
org.clojure/math.combinatorics {:mvn/version "0.2.0"}

以上坐标以当前公开发布的版本为准,实际使用时可替换为项目锁定的最新版本号。

引入后在 REPL 中加载命名空间即可使用。原笔记中使用的是use形式:

(use '[clojure.math.combinatorics :as combo])

在现代 Clojure 代码中更推荐使用require,语义更清晰且不会污染当前命名空间:

(require '[clojure.math.combinatorics :as combo])

如果不记得库中到底提供了哪些函数,可以参考仓库笔记 clojure/list-functions-for-a-namespace.md 中的方法,用(dir clojure.math.combinatorics)或(keys (ns-publics 'clojure.math.combinatorics))列出全部公开函数。

combinations 的用法与完整示例

combinations函数接收两个参数:一个元素集合(列表/向量/集合等序列)以及一个整数,表示每个组合的大小。原笔记给出的示例是:从 5 位角色名中两两配对:

(use '[clojure.math.combinatorics :as combo]) (combo/combinations ["Liz", "Tracy", "Kenneth", "Jack", "Jenna"] 2) ; (("Liz" "Tracy") ("Liz" "Kenneth") ("Liz" "Jack") ; ("Liz" "Jenna") ("Tracy" "Kenneth") ("Tracy" "Jack") ; ("Tracy" "Jenna") ("Kenneth" "Jack") ("Kenneth" "Jenna") ; ("Jack" "Jenna"))

观察输出可以验证两点:

  1. 组合数正确:5 个元素取 2 个共 10 种组合,输出恰好 10 个元素,与 C(5, 2) = 10 一致,无重复、无遗漏。
  2. 顺序有规律:组合按输入序列的顺序以"字典序"生成——以"Liz"打头的组合排在最前,然后是"Tracy"、"Kenneth"、"Jack"依次打头。这使结果可预测,便于调试与断言。

函数行为:参数语义与惰性序列

从combinations的签名与行为可以推断其核心语义:

  • 第一个参数:任意可遍历的集合(vector、list、set 均可)。示例中使用的是字符串向量。
  • 第二个参数:组合大小k,即每个结果分组中元素的个数。
  • 返回值:一个序列,其每个元素都是一个"大小为 k 的组合"(以有序集合形式呈现)。由于组合枚举可以按索引递增的方式逐步产出,该序列是以惰性(lazy)方式生成的——这意味着面对大集合时,我们不需要一次性物化全部结果,可以按需取出前几个。

配合 Clojure 的take即可实现"只枚举前 N 个组合"的按需消费模式:

(take 3 (combo/combinations (range 1 11) 3)) ; ((1 2 3) (1 2 4) (1 2 5))

边界情况与实用建议

组合是组合爆炸(combinatorial explosion)的高发地带,使用时需注意以下边界:

场景行为说明
k = 0返回一个仅含空组合的序列((())),即"什么都不选"这一种方式
k = 1返回与输入元素一一对应的单元素组合,共 n 个
k > n元素数量不足,无法构成任何大小为 k 的组合,结果为空的惰性序列
大集合大 kC(n, k) 增长迅速,如 C(100, 50) 数量级极大,务必配合take或count评估后再全量消费

实战扩展:让组合落到业务代码中

combinations不仅适用于字符串列表,也适用于任何 Clojure 数据。几个典型用法:

1. 锦标赛对阵表——把队员两两配对生成全部对阵:

(combo/combinations [:alice :bob :carol :dave] 2) ; ((:alice :bob) (:alice :carol) (:alice :dave) ; (:bob :carol) (:bob :dave) (:carol :dave))

2. 测试用例组合——枚举多个配置项的取值组合进行矩阵测试:

(def envs ["staging" "production"]) (def browsers ["chrome" "firefox"]) (combo/combinations envs 1) ; 单元素组合 (combo/combinations browsers 2) ; 双元素组合

3. 与序列式处理函数组合——由于返回的是普通惰性序列,可以无缝接入map、filter、reduce等 Clojure 核心函数。关于惰性序列的中间态构建,可参考仓库笔记 clojure/reductions.md 了解reductions如何以惰性方式累积中间结果。

小结

clojure.math.combinatorics的combinations函数让"从序列中枚举全部大小为 k 的组合"变成一行代码:传入集合与组合大小,即可拿到按字典序生成、无重复的完整组合序列。理解其"组合不关心顺序"的语义、惰性生成的行为以及 C(n, k) 的增长规律,就能在配对、抽样、测试矩阵等场景中安全高效地使用它。

  • 文档
  • 教程
  • 知识库

【免费下载链接】til

:memo: Today I Learned

项目地址:https://gitcode.com/gh_mirrors/ti/til
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询