i 番目の要素がアルゴリズムの各反復後の値であるベクトル x を作成することで、この「裏返し」を変えました。結果は比較的わかりやすい
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 は 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 に更新してから、無条件に 2 で割ることです。
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
2 で割る段階で、より大きなステップを実行できるようです。たとえば、8 は 2^3 であるため、3 ステップ (カウントに 3 を追加) を実行して終了できます。20 は 2^2 * 5 なので、次のことができます。 2 つのステップを実行し、5. 実装で次の反復に入ります。