如何在R中快速生成无重复分组的组合网格?
R中高效生成无重复分组的嵌套列表/按需组合方案
核心问题分析
你需要将20个数字无重复分为1组10个、2组3个、2组2个,总组合数达46亿之多。原代码的嵌套循环逐行处理方式在数据量放大后完全不可行,既耗时又会耗尽内存——仅存储全部组合的扁平data.table就需要约368GB内存,远超常规机器的承载能力。
可行解决方案
针对这种超大规模组合场景,需调整思路,放弃一次性生成全部扁平data.table的想法,改用以下两种方案:
方案1:递归生成嵌套列表(适合小/中等数据量)
利用递归+gtools::combinations生成嵌套列表结构,每个元素对应一组完整的分组结果,避免中间合并操作带来的性能损耗。
library(gtools) library(data.table) # 递归生成嵌套分组列表 generate_nested_groups <- function(values, group_sizes) { # 终止条件:分组完成 if (length(group_sizes) == 0) return(list()) current_size <- group_sizes[1] # 生成当前分组的所有可能组合 current_combos <- combinations(n = length(values), r = current_size, v = values) # 若为最后一组,直接返回单元素列表 if (length(group_sizes) == 1) { return(lapply(1:nrow(current_combos), function(i) list(current_combos[i,]))) } # 递归处理剩余分组 lapply(1:nrow(current_combos), function(i) { current_group <- current_combos[i,] remaining_vals <- setdiff(values, current_group) rest_groups <- generate_nested_groups(remaining_vals, group_sizes[-1]) # 拼接当前分组与剩余分组结果 lapply(rest_groups, function(rest) c(list(current_group), rest)) }) %>% unlist(recursive = FALSE) } # 测试小数据场景(10个数字分5/3/2) n <- 10 group_sizes <- c(5,3,2) nested_result <- generate_nested_groups(1:n, group_sizes) # 按需转换为data.table(仅适合小数据) small_dt <- rbindlist(lapply(nested_result, function(x) as.data.table(t(unlist(x)))))
方案2:迭代器按需生成组合(适合超大规模场景)
通过迭代器逐个生成组合,无需一次性加载全部结果到内存,适合需要逐个评估分组的场景。
library(iterators) library(gtools) # 创建分组迭代器 group_iterator <- function(values, group_sizes) { iter_func <- function(v, gs) { if (length(gs) == 0) { yield(list()) return() } current_size <- gs[1] current_combos <- combinations(n = length(v), r = current_size, v = v) # 遍历当前分组的每个组合,递归生成剩余分组 for (i in 1:nrow(current_combos)) { current_group <- current_combos[i,] remaining_vals <- setdiff(v, current_group) sub_iter <- iter_func(remaining_vals, gs[-1]) while (hasNext(sub_iter)) { rest_groups <- nextElem(sub_iter) yield(c(list(current_group), rest_groups)) } } } # 返回迭代器对象 iteror(function() iter_func(values, group_sizes)) } # 使用迭代器逐个获取组合 it <- group_iterator(1:10, c(5,3,2)) # 示例:获取前5个分组结果 for (i in 1:5) { cat("组合", i, ":\n") print(nextElem(it)) }
关键提示
对于20个数字的超大规模分组场景:
- 绝对不要尝试生成完整的扁平data.table,内存完全无法承载
- 优先使用迭代器方案,逐个处理组合,避免内存过载
- 若需并行加速,可按第一个分组的组合拆分任务,用
parallel包多进程生成子组合
内容的提问来源于stack exchange,提问作者Daycent
相关产品推荐
相关产品推荐

