Skip to content

accumulate_while #253

@dgrtwo

Description

@dgrtwo

There's a functional pattern that I think would be useful in purrr: repeat a function until some predicate is met, where the output would then be of an unpredictable size. This would be very useful in iterative algorithms like this EM that run until convergence.

For example, consider this implementation of accumulate_while, which (like accumulate) iteratively changes a value while keeping intermediate states, then stops when a predicate is not met:

accumulate_while <- function(.x, .f, .p, ..., .compare = FALSE, .max = 100000) {
  .f <- as_function(.f, ...)
  .p <- as_function(.p)

  ret <- vector("list", .max)
  ret[[1]] <- .x

  so_far <- .x
  for (i in seq(2, .max)) {
    result <- .f(so_far)
    ret[[i]] <- result

    if (.compare && !.p(so_far, result)) {
      break
    }
    if (!.compare && !.p(result)) {
      break
    }

    so_far <- result
  }
  if (i == .max) {
    message("Reached .max limit of ", .max)
  }

  head(ret, i)
}

As one minimal example:

# add one until not less than 10
as_vector(accumulate_while(1, ~ . + 1, ~ . < 10))
# [1]  1  2  3  4  5  6  7  8  9 10

There's also a .compare option where the predicate takes the last two values, which is very useful for "run until convergence"

as_vector(accumulate_while(1, ~ . / 2, ~ abs(.x - .y) > 1e-4, .compare = TRUE))
 [1] 1.000000e+00 5.000000e-01 2.500000e-01 1.250000e-01 6.250000e-02 3.125000e-02 1.562500e-02
 [8] 7.812500e-03 3.906250e-03 1.953125e-03 9.765625e-04 4.882812e-04 2.441406e-04 1.220703e-04
[15] 6.103516e-05

I can think of other variations as well:

  • reduce_while: same, but drop the intermediate states
  • rerun_while: performs each trial independently until the result satisfies a condition (like a geometric process), keeps the intermediate result. (Technically a special case of accumulate_while).

If you support this I can turn it into a pull request with docs and tests.

(Also note that the final version could easily support an option of .max being infinite, and successively doubling the size of ret when it is reached- I left it out for simplicity here).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions