Я превратил это «наизнанку», создав вектор 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
}
. Это можно оптимизировать, чтобы (а) отслеживать только те значения, которые все еще находятся в игре (через idx), и (б) избегать ненужных операций, например, 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. Реализации?