
本文介绍如何在 go 中高效实现任意数量字符串切片的笛卡尔积(cartesian product),支持动态长度输入,无需编译期确定维度,精准复现 ruby 的 `array#product` 行为。
Go 标准库未内置笛卡尔积函数,但可通过索引迭代法优雅实现——其核心思想是将多维遍历抽象为「字典序递增的索引数组」,类似多进制计数器。以下是一个通用、内存友好且无依赖的实现:
package main
import "fmt"
// NextIndex 将索引数组 ix 更新为字典序下一个有效状态:
// 每个 ix[i] 满足 0 ≤ ix[i] < lens(i),lens(i) 表示第 i 维的长度。
func NextIndex(ix []int, lens func(i int) int) {
for j := len(ix) - 1; j >= 0; j-- {
ix[j]++
if j == 0 || ix[j] < lens(j) {
return // 成功进位,退出
}
ix[j] = 0 // 归零并继续向高位进位
}
}
// CartesianProduct 计算任意数量字符串切片的笛卡尔积,返回 [][]string
func CartesianProduct(sets [][]string) [][]string {
if len(sets) == 0 {
return [][]string{{}}
}
for _, s := range sets {
if len(s) == 0 {
return [][]string{} // 任一集合为空 → 结果为空
}
}
lens := func(i int) int { return len(sets[i]) }
result := make([][]string, 0)
ix := make([]int, len(sets))
// 主循环:从全0索引开始,直到第一维越界
for ix[0] < lens(0) {
// 构造当前组合
combo := make([]string, len(sets))
for j, k := range ix {
combo[j] = sets[j][k]
}
result = append(result, combo)
// 生成下一组索引
NextIndex(ix, lens)
}
return result
}
func main() {
a := []string{"a1"}
b := []string{"b1", "b2"}
c := []string{"c1", "c2", "c3"}
d := []string{"d1"}
// 等价于 Ruby 的 a.product(*e),其中 e = [b, c, d]
sets := [][]string{a, b, c, d}
result := CartesianProduct(sets)
for _, r := range result {
fmt.Println(r)
}
}✅ 关键特性说明:
- 动态维度支持:sets 是 [][]string 类型,长度可在运行时任意变化;
- 边界健壮性:自动处理空集合(返回空结果)和单元素集合;
- 零分配优化:NextIndex 复用同一索引数组,避免频繁内存分配;
- 可扩展性强:只需修改 lens 函数和组合构造逻辑,即可适配任意类型(如 [][]int)。
⚠️ 注意事项:
- 笛卡尔积结果大小为各集合长度乘积(∏|set_i|),对大数据集需警惕内存爆炸;建议流式处理(如传入回调函数替代构建完整 [][]string);
- 若需兼容 nil 切片,应在 CartesianProduct 开头增加 if sets == nil 判断;
- Ruby 的 product 支持链式调用与块参数(如 a.product(b) { |x,y| ... }),Go 中可封装为带 func([]string) 参数的版本以实现类似流式消费。
该方案不依赖第三方包,符合 Go 的简洁哲学,是生产环境中实现笛卡尔积的推荐实践。










