I got stuck the other day on a project where I was trying to build a list step by step, passing the last item on the list to a function which would then return the next item to be added to the list. And so on until a break point.
It seemed like it should be simple, and I felt a bit dim for not immediately knowing how to do it. I built myself a simple bit of toy code to try to improve my understanding of how to make this work.
Here's a slightly fuller description of what I want to achieve:
- starting with a "seed"
x (could be a number, string, data frame), apply a function f(x) to return y.
- Now add
y to a list such that you get list(x, y)
- Now run
f() on y to return z and add that to the list - so the list is growing incrementally through the repeated application of f()
- Stop growing the list at some limit (list length, value of
x, whatever) and complete, returning the whole list.
If I knew the length of the list in advance, or could map along a vector of inputs, that would be easy for me. But in my example, you don't know what the next item on the list is going to be until you've run f().
Here's the code I wrote. It works, and gives me the exact output I want - but I still feel like I am missing something obvious or a much better way of doing this perhaps? I felt like purrr::accumulate should be the right tool for this, but it only works for functions that take two arguments. My function only takes one argument (the last item on the current list).
Your code critiques are welcome!
My toy code
library(dplyr, warn.conflicts = FALSE)
library(purrr)
# yes I want it to return a list not a vector (for my real project)
square_it <- function(x) {
y <- dplyr::last(x)^2
list(x, y) %>%
purrr::flatten()
}
build_squares <- function(x) {
while (dplyr::last(x) < 1e4) {
x <- square_it(x)
}
return(x)
}
squares <- build_squares(2)
squares
#> [[1]]
#> [1] 2
#>
#> [[2]]
#> [1] 4
#>
#> [[3]]
#> [1] 16
#>
#> [[4]]
#> [1] 256
#>
#> [[5]]
#> [1] 65536
Created on 2020-07-05 by the reprex package (v0.3.0)