Я вывернул это «наизнанку», создав вектор 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. Реализации?
person
Martin Morgan
schedule
18.12.2010