Je l'ai transformé en créant un vecteur x dont le ième élément est la valeur après chaque itération de l'algorithme. Le résultat est relativement intelligible car
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
}
Ceci peut être optimisé pour (a) suivre uniquement les valeurs encore en jeu (via idx) et (b) éviter les opérations inutiles, par exemple, ifelse évalue les deux arguments pour toutes les valeurs de x, x/2 évalué deux fois.
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
}
avec f0 la fonction originale, j'ai
> 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
Une astuce consiste à mettre à jour les valeurs impaires en 3 * x[i] + 1, puis à effectuer la division par deux sans condition.
x[i] <- 3 * x[i] + 1
count[idx[i]] <- count[idx[i]] + 1L
x <- x / 2
count[idx] <- count[idx] + 1
Avec ceci en f3 (je ne sais pas pourquoi f2 est plus lent ce matin !) j'obtiens
> 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
Il semble que de plus grandes étapes peuvent être prises à l'étape de division par deux, par exemple, 8 est 2^3 donc nous pourrions prendre 3 étapes (ajouter 3 pour compter) et avoir fini, 20 est 2^2 * 5 donc nous pourrions prendre deux étapes et entrer dans l'itération suivante à 5. Implémentations ?