我编写了一个程序,该程序适用于3n + 1问题(又名“奇妙数字”和其他各种事物)。但它有一个双循环。我怎么能把它矢量化呢?
代码是
count <- vector("numeric", 100000)
L <- length(count)
for (i in 1:L)
{
x <- i
while (x > 1)
{
if (round(x/2) == x/2)
{
x <- x/2
count[i] <- count[i] + 1
} else
{
x <- 3*x + 1
count[i] <- count[i] + 1
}
}
}
谢谢!
答案 0 :(得分:9)
我通过创建向量x将其转换为“由内向外”,其中第i个元素是算法每次迭代后的值。结果相对容易理解为
f1 <- function(L) {
x <- seq_len(L)
count <- integer(L)
while (any(i <- x > 1)) {
count[i] <- count[i] + 1L
x <- ifelse(round(x/2) == x/2, x / 2, 3 * x + 1) * i
}
count
}
这可以优化为(a)仅跟踪仍在播放的那些值(通过idx)和(b)避免不必要的操作,例如,ifelse为x的所有值评估两个参数,x / 2评估两次。
f2 <- function(L) {
idx <- x <- seq_len(L)
count <- integer(L)
while (length(x)) {
ix <- x > 1
x <- x[ix]
idx <- idx[ix]
count[idx] <- count[idx] + 1L
i <- as.logical(x %% 2)
x[i] <- 3 * x[i] + 1
i <- !i
x[i] <- x[i] / 2
}
count
}
用f0原始函数,我有
> L <- 10000
> system.time(ans0 <- f0(L))
user system elapsed
7.785 0.000 7.812
> system.time(ans1 <- f1(L))
user system elapsed
1.738 0.000 1.741
> identical(ans0, ans1)
[1] TRUE
> system.time(ans2 <- f2(L))
user system elapsed
0.301 0.000 0.301
> identical(ans1, ans2)
[1] TRUE
调整是将奇数值更新为3 * x [i] + 1,然后无条件地将除数除以
x[i] <- 3 * x[i] + 1
count[idx[i]] <- count[idx[i]] + 1L
x <- x / 2
count[idx] <- count[idx] + 1
这是f3(不知道为什么f2今天早上比较慢!)我得到了
> system.time(ans2 <- f2(L))
user system elapsed
0.36 0.00 0.36
> system.time(ans3 <- f3(L))
user system elapsed
0.201 0.003 0.206
> identical(ans2, ans3)
[1] TRUE
似乎可以在二分之二阶段采取更大的步骤,例如,8是2 ^ 3,所以我们可以采取3步(加3计数)并完成,20是2 ^ 2 * 5所以我们可以采取两个步骤并进入下一次迭代5.实现?
答案 1 :(得分:4)
因为您需要迭代x
的值,所以您无法真正对此进行矢量化。在某些时候,R必须分别依次处理x的每个值。您可以在单独的CPU内核上运行计算以加快速度,可能使用同名包中的foreach
。
否则,(这只是隐藏你的循环),将循环的主体包裹为一个函数,例如:
wonderous <- function(n) {
count <- 0
while(n > 1) {
if(isTRUE(all.equal(n %% 2, 0))) {
n <- n / 2
} else {
n <- (3*n) + 1
}
count <- count + 1
}
return(count)
}
然后您可以使用sapply()
在一组数字上运行该函数:
> sapply(1:50, wonderous)
[1] 0 1 7 2 5 8 16 3 19 6 14 9 9 17 17
[16] 4 12 20 20 7 7 15 15 10 23 10 111 18 18 18
[31] 106 5 26 13 13 21 21 21 34 8 109 8 29 16 16
[46] 16 104 11 24 24
或者您可以使用Vectorize
返回wonderous
的矢量化版本,这个版本本身就是一个隐藏更多内容的函数:
> wonderousV <- Vectorize(wonderous)
> wonderousV(1:50)
[1] 0 1 7 2 5 8 16 3 19 6 14 9 9 17 17
[16] 4 12 20 20 7 7 15 15 10 23 10 111 18 18 18
[31] 106 5 26 13 13 21 21 21 34 8 109 8 29 16 16
[46] 16 104 11 24 24
我认为目前你可以使用标准的R工具。 @Martin Morgan表明你可以做得比这更好,巧妙地解决了这个问题。 确实使用了R的矢量化技能。
答案 2 :(得分:2)
一种不同的方法可以识别出一个经常重新访问低数字的方法,那么为什么不记住它们并节省重新计算成本呢?
memo_f <- function() {
e <- new.env(parent=emptyenv())
e[["1"]] <- 0L
f <- function(x) {
k <- as.character(x)
if (!exists(k, envir=e))
e[[k]] <- 1L + if (x %% 2) f(3L * x + 1L) else f(x / 2L)
e[[k]]
}
f
}
给出了
> L <- 100
> vals <- seq_len(L)
> system.time({ f <- memo_f(); memo1 <- sapply(vals, f) })
user system elapsed
0.018 0.000 0.019
> system.time(won <- sapply(vals, wonderous))
user system elapsed
0.921 0.005 0.930
> all.equal(memo1, won) ## integer vs. numeric
[1] TRUE
这可能不会很好地并行化,但那么50倍加速可能没有必要吗?递归也可能过于深入,但递归可以写成循环(无论如何都可能更快)。