对于两个变量的问题,outer
最有可能是此问题的最佳解决方案,如果要循环的空间足够小,则可以让expand.grid
正常工作。但是,如果我们有两个以上的变量并且有较大的循环空间,则将这些排除在外。 outer
不能处理两个以上的变量,并且expand.grid
占用的内存比我见过的机器能容纳的更多。
我最近发现自己正在编写如下代码:
n<-1000
for(c in 1:n){
for(b in 1:c){
for(a in 1:b){
if(foo(a,b,c))
{
bar(a,b,c)
}
}
}
}
在这些情况下,似乎嵌套循环是自然的解决方案(例如mapply
不会这样做,并且tapply
没有很好的使用条件),但是有更好的方法吗?看来这是通往不良代码的途径。
我怀疑combn
能够以某种方式做到这一点,但是根据我的经验,很快它就会落入与expand.grid
相同的内存陷阱中。如果有内存可用,我也知道它会采取不明智的措施,告诉我更改递归限制的全局设置。
答案 0 :(得分:6)
这是重复的组合。 rcppalgos可能是您开箱即用的最佳选择,但是在n = 1000L
上,要进行的组合超过5亿种,将占用约2GB的内存。
library(RcppAlgos)
n = 1000L
mat <- comboGeneral(n, 3L, repetition = TRUE)
现在有两条路线可以走。如果您有RAM并且可以对功能进行矢量化,则可以非常快速地完成上述操作。假设如果组合的总和大于1000,则需要组合的平均值,否则,则需要组合的总和。
res <- if (rowSums(mat) > 1000L)
rowMeans(mat)
else
rowSums(mat)
## Error: cannot allocate vector of size 1.2 Gb
哦,不!我得到了可怕的分配向量错误。 rcppalgos允许您返回函数的结果。但是请注意,它返回一个列表并且速度较慢,因为它将不得不评估R函数,而不是停留在c ++中。正因为如此,我改了n = 100L
,因为我没有整天的时间...
comboGeneral(100L, 3L, repetition = TRUE,
FUN = function(x) {
if (sum(x) > 100L)
mean(x)
else
sum(x)
}
)
如果我有一个静态集合,而我总是从n
中选择3种组合,那么根据Rcpp
和foo(a,b,c)
是什么,我可能会直接使用bar(a,b,c)
代码但首先,我想进一步了解这些功能。
答案 1 :(得分:5)
我以前的功能lazyExpandGrid
并不是一个完美的选择,但我认为它可以解决您对内存耗尽的担忧。其他语言可能会出现延迟迭代器。 R放在iterators
包中,并且由于我不精通,所以不久前我写了this gist来解决这个问题。
lazyExpandGrid
的一个问题是它希望因素是预先定义的。这可以通过快速的条件来处理,因此尽管不会节省空间,但会节省内存。我认为这不是在该方法中实现条件的快速解决方案,因为它懒惰地处理扩展的机制是知道数学上哪个索引与哪个 combination 因素...条件将破坏这一点。
以下是该功能在此处的工作方式:
n <- 3
it <- lazyExpandGrid(aa = 1:n, bb = 1:n, cc = 1:n)
while (length(thistask <- it$nextItem())) {
if (with(thistask, bb > aa || cc > bb)) next
print(jsonlite::toJSON(thistask))
}
# [{"aa":1,"bb":1,"cc":1}]
# [{"aa":2,"bb":1,"cc":1}]
# [{"aa":3,"bb":1,"cc":1}]
# [{"aa":2,"bb":2,"cc":1}]
# [{"aa":3,"bb":2,"cc":1}]
# [{"aa":3,"bb":3,"cc":1}]
# [{"aa":2,"bb":2,"cc":2}]
# [{"aa":3,"bb":2,"cc":2}]
# [{"aa":3,"bb":3,"cc":2}]
# [{"aa":3,"bb":3,"cc":3}]
### to demonstrate what an exhausted lazy-expansion looks like
it$nextItem()
# NULL
it$nextItem()
# NULL
(请注意,带有next
的条件语句如何跳过这些组合。)
这将转化为您的流程:
n <- 1000
it <- lazyExpandGrid(aa = 1:n, bb = 1:n, cc = 1:n)
it
# lazyExpandGrid: 4 factors, 1e+09 rows
# $ index : 0
while (length(thistask <- it$nextItem())) {
if (with(thistask, bb > aa || cc > bb)) next
with(thistask, {
if (foo(aa, bb, cc)) bar(aa, bb, cc)
})
}
(或不使用with
,使用thistask$aa
等)
注意:我不会撒谎,尽管这简化了流程,但并没有使流程变快。在这种情况下,1e+09
次操作将花费 的时间,除了并行操作和友好的R主机群集之外,我不知道有什么方法可以帮助完成此任务。 (如上所述,我开始运行一个空的no-op while
循环,并花了268秒的时间才能解决其中的822K。我希望您有很多处理能力。)
答案 2 :(得分:4)
重要的是要指出为什么使用rcpp既容易又推荐这样做。
当我们提到r时,在c下编译的是一堆代码。到目前为止,R开发人员无需开发已编译的代码即可允许将任意函数foo()
和bar()
与重复结合使用。因此,作为用户,我们可以使用r循环来获得r的灵活性,或者,当我们有很多迭代要遍历时,请考虑一些替代方法。
将R循环简化为Rcpp循环。我已经包含了任意函数,以便我们可以返回一些内容(如果OP帖子也包含要返回的内容,那将是很好的……):
#include <Rcpp.h>
using namespace Rcpp;
bool foo(int x, int y, int z) {
return((x + y + z) > 50);
}
int bar(int x, int y, int z) {
return(x - y + z);
}
// [[Rcpp::export]]
double manual_combos_w_reps(const int n) {
double ans = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
for (int k = 1; k <= j; k++) {
if (foo(i, j, k)) {
ans += bar(i, j, k);
}
}
}
}
return(ans);
}
这是R中的对应部分,它只是添加了foo(...)
和bar(...)
的代码。
r_foo = function(x, y, z) {
return((x + y + z) > 50L)
}
r_bar = function (x, y, z) {
return(x - y + z)
}
r_loop = function (n) {
ans = 0;
for (i in 1:n) {
for (j in 1:i) {
for (k in 1:j) {
if (r_foo(i, j, k)) {
ans = ans + r_bar(i, j, k)
}
}
}
}
return(ans)
}
现在这是神奇的部分。 Rcpp经历了这些迭代。对于n = 1000L
,R代码需要360秒才能运行。 Rcpp只需运行0.5秒。
n = 10L
bench::mark(manual_combos_w_reps(n)
, r_loop(n)
)
### A tibble: 2 x 13
## expression min median `itr/sec` mem_alloc
## <bch:expr> <bch:> <bch:> <dbl> <bch:byt>
##1 manual_combos_w_reps(n) 4.7us 5us 178048. 2.48KB
##2 r_loop(n) 1.63ms 1.68ms 505. 0B
n = 100L
### A tibble: 2 x 13
## expression min median `itr/sec` mem_alloc
## <bch:expr> <bch> <bch:> <dbl> <bch:byt>
##1 manual_combos_w_reps(n) 467us 469us 2064. 2.48KB
##2 r_loop(n) 627ms 627ms 1.60 0B
n = 1000L
### A tibble: 2 x 13
## expression min median `itr/sec`
## <bch:expr> <bch:tm> <bch:tm> <dbl>
##1 manual_combos_w_reps(n) 459.29ms 459.39ms 2.18
##2 r_loop(n) 6.04m 6.04m 0.00276
为此,您应该绝对考虑rcpp -在基R中没有真正的规范答案可以为您正在执行的任务提供高性能。唯一需要考虑的是您实际的foo()
和bar()
函数的功能,因为它们可能难以在rcpp中实现。
答案 3 :(得分:1)
purrr
的 .filter
解决方案也可以:
library(purrr)
n <- 10L
levels <- 3L
# keep only elements below diagonal
isdesc<- function(...){all(diff(unlist(list(...)))<=0)}
# some extra filtering
foo <- function(...) { sum(unlist(list(...)))==27}
filter <- function(...) {!isdesc(...)|!foo(...)}
cross_list <- cross(rep(list(1L:n),levels),.filter = filter)
bar <- function(...) ( unlist(list(...)))
cross_list %>% map(bar)
不幸的是,与grid.expand
一样,它的缩放效果也不理想,因为cross
在过滤之前先分配了完整的笛卡尔积。