计算R中集合的幂集(所有可能子集)的算法

计算R中集合的幂集(所有可能子集)的算法,r,set,powerset,R,Set,Powerset,我在任何地方都找不到答案,所以这里是我的解决方案 问题是:如何计算R中的功率集 可以使用库“set”来实现这一点,使用命令2^as.set(c(1,2,3,4)),该命令生成输出{{}、{1}、{2}、{3}、{4}、{1,2}、{1,3}、{1,4}、{2,3}、{2, 4}, {3, 4}, {1, 2, 3}, {1, 2, 4}, {1, 3, 4}, {2, 3, 4}, {1, 2,3,4}。然而,这使用了一个递归算法,这是相当缓慢的 这是我提出的算法 它是非递归的,因此比其他解决

我在任何地方都找不到答案,所以这里是我的解决方案

问题是:如何计算R中的功率集

可以使用库“set”来实现这一点,使用命令
2^as.set(c(1,2,3,4))
,该命令生成输出
{{}、{1}、{2}、{3}、{4}、{1,2}、{1,3}、{1,4}、{2,3}、{2,
4}, {3, 4}, {1, 2, 3}, {1, 2, 4}, {1, 3, 4}, {2, 3, 4}, {1,
2,3,4}
。然而,这使用了一个递归算法,这是相当缓慢的


这是我提出的算法

它是非递归的,因此比其他解决方案快得多(在我的机器上比“集合”包中的算法快100倍)。速度仍然是O(2^n)

该算法的概念基础如下:

for each element in the set:
    for each subset constructed so far:
        new subset = (subset + element)
这是R代码:

编辑:这里有一个相同概念的更快版本;我最初的算法在这篇文章的第三条评论中。在我的机器上,对于长度为19的一组,这一个要快30%

powerset = function(s){
    len = length(s)
    l = vector(mode="list",length=2^len) ; l[[1]]=numeric()
    counter = 1L
    for(x in 1L:length(s)){
        for(subset in 1L:counter){
            counter=counter+1L
            l[[counter]] = c(l[[subset]],s[x])
        }
    }
    return(l)
}

此版本通过在开始时以其最终长度启动向量并跟踪保存新子集位置的“计数器”变量来节省时间。也可以通过分析计算位置,但速度稍慢。

可以将子集视为布尔向量,指示元素是否在非的子集中。 这些布尔向量可以看作是用二进制写的数字。 枚举
1:n的所有子集
因此,相当于枚举从
0
2^n-1
的数字

f <- function(set) { 
  n <- length(set)
  masks <- 2^(1:n-1)
  lapply( 1:2^n-1, function(u) set[ bitwAnd(u, masks) != 0 ] )
}
f(LETTERS[1:4])

f在包
HapEstXXR
中有一个函数
powerset
,它似乎比您的函数和另一个答案中的函数更快。请参见下文(您的函数名为.powerset


由于
powerset
似乎速度非常快,因此您可能需要查看
HapEstXXR
包中函数的代码。

下面是另一种简单的方法,它似乎对小集合的性能相当好。随着数据基数的增加,这种方法存在明显的内存问题

getPowSet <- function(set) {     
  n <- length(set)
  keepBool <- sapply(2^(1:n - 1), function(k) 
    rep(c(FALSE, TRUE), each=k, times=(2^n / (2*k))))
  lapply(1:2^n, function(j) set[keepBool[j, ]])
}
但是对于
n=15
,原始函数的性能似乎更好:

microbenchmark(powerset(LETTERS[1:15]), f(LETTERS[1:15]), getPowSet(LETTERS[1:15]))

Unit: milliseconds
                     expr       min        lq      mean    median       uq      max neval
  powerset(LETTERS[1:15])  81.48276  88.50272  94.88927  91.61366  94.8262 174.0636   100
         f(LETTERS[1:15]) 110.86208 118.08736 124.38501 122.35872 126.7830 189.3131   100
 getPowSet(LETTERS[1:15])  86.16286  93.32314  98.14936  96.85443 100.6075 159.1030   100

下面应该创建一个功率集,减去空集元素

powerset <- function(x) {
  sets <- lapply(1:(length(x)), function(i) combn(x, i, simplify = F))
  unlist(sets, recursive = F)
}

powerset Now,我不知道为什么,但是这个解决方案比我发布的要慢。它只涉及一个列表创建,但我想必须做算术会减慢它的速度。它的速度比O(n^2)大
powerset1=function(set){ps=vector(mode=“list”,length=length(set)^2);ps[[1]]=numeric();for(1中的e:length(set)){f=2^(e-1);for(1:f中的子集){ps[[f+子集]]=c(ps[[subset]],set[e];};return(ps)}
(set){ps=list();ps[[1]]=numeric();for(set中的元素){temp=vector(mode=“list”,length=length(ps));for(1中的子集:length(ps)){temp[[subset]]]=c(ps[[subset]],element)};ps=c(ps,temp)};return(ps)};powerset(1:4)列表
temp
是做算术和创建新列表的速度成本之间的折衷。库中的
set_power()
也应该可以工作。是的,它非常快…因为它使用C/C++代码。对于较大的功率集,速度差异并没有那么大,但在我的机器上,它仍然快2-3倍,并且可以扩展到0(2^n)。缺点是,对于大于15的集,它只能将功率集输出到一个文件,而不能输出到R。对于15的集,我的函数需要约0.15秒。这确实是一个比我更优雅的解决方案。但是,我对它进行了基准测试,小集和大集都需要50-70%的时间。我很惊讶它需要更长的时间。但是,就st好吧,这个答案教会了我们很多东西。它是一种开箱即用的好方法。
microbenchmark(powerset(LETTERS[1:15]), f(LETTERS[1:15]), getPowSet(LETTERS[1:15]))

Unit: milliseconds
                     expr       min        lq      mean    median       uq      max neval
  powerset(LETTERS[1:15])  81.48276  88.50272  94.88927  91.61366  94.8262 174.0636   100
         f(LETTERS[1:15]) 110.86208 118.08736 124.38501 122.35872 126.7830 189.3131   100
 getPowSet(LETTERS[1:15])  86.16286  93.32314  98.14936  96.85443 100.6075 159.1030   100
powerset <- function(x) {
  sets <- lapply(1:(length(x)), function(i) combn(x, i, simplify = F))
  unlist(sets, recursive = F)
}