Title: | A Tidy API for Sequence Iteration and Set Comprehension |
---|---|
Description: | A friendly API for sequence iteration and set comprehension. |
Authors: | Jacob Goldsmith [aut, cre] |
Maintainer: | Jacob Goldsmith <[email protected]> |
License: | GPL-2 |
Version: | 0.3.1 |
Built: | 2025-03-05 04:17:19 UTC |
Source: | https://github.com/jacgoldsm/peruse |
Clone an Iterator, making an exact copy that can then be modified separately.
This is a simple wrapper around rlang::env_clone()
. Optionally,
override old initial parameters.
clone(iter, ...)
clone(iter, ...)
iter |
an |
... |
optionally override the |
a copy of the Iterator
passed as a parameter
it <- Iterator({m <- m + n}, list(m = 0, n = 1), m) other <- clone(it) yield_next(it) current(other) == current(it) # false it2 <- clone(other, n = 5) yield_next(it2) it2$initial$n # 5
it <- Iterator({m <- m + n}, list(m = 0, n = 1), m) other <- clone(it) yield_next(it) current(other) == current(it) # false it2 <- clone(other, n = 5) yield_next(it2) it2$initial$n # 5
An Iterator
yield
s a variable every time yield_next()
is called.
Get the current value of that variable without changing the state of the Iterator.
current(iter)
current(iter)
iter |
An |
The current value of iter
Test if an object is an Iterator
is_Iterator(list)
is_Iterator(list)
list |
Object to test |
Create an Iterator object, where the user defines a sequence and a
set of initial values, and then calls yield_next()
to generate the
next element of the sequence. Iterator
s are R environments, which means
they are modified in place, even when passed as arguments to functions.
To make a copy of an Iterator that can be modified separately, see clone()
.
Iterator(result, initial, yield)
Iterator(result, initial, yield)
result |
R expression to run each time 'yield_next' is called |
initial |
named list or vector; declare and initialize every variable that appears in 'result' |
yield |
variable to yield when 'yield_next()' is called |
An environment object of S3 type Iterator
The expression to be evaluated can include constant values not defined in
$initial
as long as they are defined in the enclosure of where yield_next() is called,
not where the Iterator is created.
These values will not vary from iteration to
iteration (unless you do something strange in the code, like including <<- in $result
.)
yield_next()
, yield_while()
, current()
rlang::qq_show()
#Create the Collatz sequence starting with 50 and print out the first 30 elements collatz <- Iterator({ if (n %% 2 == 0) n <- n / 2 else n <- n*3 + 1 }, initial = c(n = 50), yield = n) seq <- yield_more(collatz, 30) # If you want to define the expression outside the Iterator, use [quote()] and `!!`: expr <- quote(if (n %% 2 == 0) n <- n / 2 else n <- n*3 + 1) collatz <- Iterator(!!expr, c(n = 50), n) # using objects defined outside `$initial`: # Note that `n` in `$initial` overrides the global `n` m <- 100 n <- 10 it <- Iterator({out <- n + m}, initial = c(n = -10), yield = out) yield_next(it) # environments are modified in place, so be aware: it <- Iterator({m <- m + 1}, c(m = 0), m) other <- it yield_next(it) current(other)
#Create the Collatz sequence starting with 50 and print out the first 30 elements collatz <- Iterator({ if (n %% 2 == 0) n <- n / 2 else n <- n*3 + 1 }, initial = c(n = 50), yield = n) seq <- yield_more(collatz, 30) # If you want to define the expression outside the Iterator, use [quote()] and `!!`: expr <- quote(if (n %% 2 == 0) n <- n / 2 else n <- n*3 + 1) collatz <- Iterator(!!expr, c(n = 50), n) # using objects defined outside `$initial`: # Note that `n` in `$initial` overrides the global `n` m <- 100 n <- 10 it <- Iterator({out <- n + m}, initial = c(n = -10), yield = out) yield_next(it) # environments are modified in place, so be aware: it <- Iterator({m <- m + 1}, c(m = 0), m) other <- it yield_next(it) current(other)
Increments the Iterator without returning anything.
move_more()
repeats move_next()
a specified number of times. move_while()
repeats move_next()
until a condition is met. Refer to the number of the current
iteration with .iter
.
move_next(iter) move_more(iter, more = 1L) move_while(iter, cond)
move_next(iter) move_more(iter, more = 1L) move_while(iter, cond)
iter |
An Iterator object object |
more |
How many times to iterate |
cond |
A quoted logical expression involving some variable(s) in |
primes <- 2:10000 %>% that_for_all(range(2, .x)) %>% we_have(~.x %% .y != 0, "Iterator") current(primes) move_more(primes, 100) current(primes)
primes <- 2:10000 %>% that_for_all(range(2, .x)) %>% we_have(~.x %% .y != 0, "Iterator") current(primes) move_more(primes, 100) current(primes)
Wrapper around base::seq()
that replaces the maximal end value with the supremum
and returns an empty vector if b <= a, in the style of Python's range()
.
Note that peruse::range
views end as a supremum, not a maximum, thus range(a,b)
is equivalent to the set [
a,b) when a < b or {}
when b >= a.
range(a, b, ...)
range(a, b, ...)
a |
minimum |
b |
supremum |
... |
other params passed to |
range(1,5) range(9,10) range(1,6, by = 2)
range(1,5) range(9,10) range(1,6, by = 2)
Set comprehension with the magrittr Pipe. Always use the basic syntax:
.x %>% that_for_all(.y) %>% we_have_*(f(.x, .y))
,
but see the examples for more detail.
that_for_all(.x, .y) that_for_any(.x, .y) we_have(that_for, formula, result = "vector")
that_for_all(.x, .y) that_for_any(.x, .y) we_have(that_for, formula, result = "vector")
.x |
A set, represented as either an atomic vector or a list |
.y |
A set to compare to |
that_for |
A list passed to |
formula |
A function, lambda, or formula. Must be understood by
|
result |
Should the expression return a |
formula
can be anything that is recognized as a function by rlang::as_function()
.
See the examples for how to specify the end of a sequence when used with an Iterator
.
Handling missing values in these expressions is possible and sometimes desirable but
potentially painful because NA
values can't be compared with normal operators.
See the README for a detailed example.
Note that .x %>% that_for_all(.y)
is vacuously true if .y
is empty, while
.x %>% that_for_any(.y)
is vacuously false if .y
is empty.
For that_for_all()
and that_for_any()
, an object of S3 class that_for_all or that_for_any.
For we_have()
, a vector of the same type as .x
if return == 'vector'
and an Iterator object if return == 'Iterator'
.
if .y
is an numeric vector, you probably want a value obtained from
range(start, end)
rather than start:end
or seq.int(start,end)
,
as when start is greater than end you want an empty vector rather than counting backwards.
Note that range()
views end as a supremum, not a maximum, thus range(a,b)
is equivalent to the set [
a,b) when a < b or the empty set when b >= a.
Also note that there is some indirection in the way that .x
and .y
are referenced
in the formula. In the function we_have()
, the actual name of the two sets is .x
and .y
. That is what makes the function interface work,
e.g. function(.x, .y) .x - .y
. On the other hand, purrr
-style lambda expressions,
e.g. ~.x - .y
, use positional arguments, where .x
is the first argument and .y
is the second argument, no matter their names. Because those are actually their names,
this difference should never matter.
The implementation of these functions involves code adapted from purrr::every()
and purrr::some()
, by Lionel Henry, Hadley Wickham, and RStudio, available under the
MIT license.
2:100 %>% that_for_all(range(2, .x)) %>% we_have(function(.x, .y) .x %% .y != 0) #is the same as 2:100 %>% that_for_all(range(2, .x)) %>% we_have(~.x %% .y) # 0 = F, (not 0) = T #c.f. primes <- 2:100 %>% that_for_all(range(2, .x)) %>% we_have(~.x %% .y, "Iterator") yield_next(primes) primes2 <- clone(primes) # Refer to the vector .x with `.x_vector` and the current index of that vector with `.i` # For example, to yield to the end of the sequence: yield_while(primes, .x_vector[.i] <= length(.x_vector)) # `.finished` is an alias for `.x_vector[.i] > length(.x_vector)` # Equivalent to previous expression: yield_while(primes2, !.finished) {c("I", "Don't", "wan't", "chicken") %>% that_for_all("\'") %>% we_have(~grepl(.y, .x))} #Twin primes 1 through 100 primes <- 2:100 %>% that_for_all(range(2, .x)) %>% we_have(~.x %% .y) primes %>% that_for_any(primes) %>% we_have(~abs(.x - .y) == 2) #Prime numbers 1 through 100 that are two away from a square number (2:100 %>% that_for_all(range(2, .x)) %>% we_have(~.x %% .y)) %>% that_for_any(range(2, .x)) %>% we_have(~sqrt(.x + 2) == .y | sqrt(.x - 2) == .y)
2:100 %>% that_for_all(range(2, .x)) %>% we_have(function(.x, .y) .x %% .y != 0) #is the same as 2:100 %>% that_for_all(range(2, .x)) %>% we_have(~.x %% .y) # 0 = F, (not 0) = T #c.f. primes <- 2:100 %>% that_for_all(range(2, .x)) %>% we_have(~.x %% .y, "Iterator") yield_next(primes) primes2 <- clone(primes) # Refer to the vector .x with `.x_vector` and the current index of that vector with `.i` # For example, to yield to the end of the sequence: yield_while(primes, .x_vector[.i] <= length(.x_vector)) # `.finished` is an alias for `.x_vector[.i] > length(.x_vector)` # Equivalent to previous expression: yield_while(primes2, !.finished) {c("I", "Don't", "wan't", "chicken") %>% that_for_all("\'") %>% we_have(~grepl(.y, .x))} #Twin primes 1 through 100 primes <- 2:100 %>% that_for_all(range(2, .x)) %>% we_have(~.x %% .y) primes %>% that_for_any(primes) %>% we_have(~abs(.x - .y) == 2) #Prime numbers 1 through 100 that are two away from a square number (2:100 %>% that_for_all(range(2, .x)) %>% we_have(~.x %% .y)) %>% that_for_any(range(2, .x)) %>% we_have(~sqrt(.x + 2) == .y | sqrt(.x - 2) == .y)
Finds the value of the next iteration(s) of an Iterator object
and increments the Iterator to the next value(s). yield_more()
repeats
yield_next()
a specified number of times.
Refer to the number of the current iteration in yield_more()
with .iter
.
yield_next(iter) yield_more(iter, more = 1L)
yield_next(iter) yield_more(iter, more = 1L)
iter |
An Iterator object |
more |
How many values to yield |
An object of whatever type result
evaluates to from the Iterator, or
a vector of that type in the case of yield_more(iter, more > 1L)
.
primes <- 2:10000 %>% that_for_all(range(2, .x)) %>% we_have(~.x %% .y != 0, "Iterator") sequence <- yield_more(primes, 100) # use `.iter` to reference the current iteration rwd <- Iterator({ set.seed(seeds[.iter]) n <- n + sample(c(-1L, 1L), size = 1L, prob = c(0.25, 0.75)) }, initial = list(n = 0, seeds = 1:100), yield = n) yield_more(rwd, 100)
primes <- 2:10000 %>% that_for_all(range(2, .x)) %>% we_have(~.x %% .y != 0, "Iterator") sequence <- yield_more(primes, 100) # use `.iter` to reference the current iteration rwd <- Iterator({ set.seed(seeds[.iter]) n <- n + sample(c(-1L, 1L), size = 1L, prob = c(0.25, 0.75)) }, initial = list(n = 0, seeds = 1:100), yield = n) yield_more(rwd, 100)
Keep yielding the next element of an Iterator
while a condition is met.
A condition is a logical expression involving variables in iter$initial
or variables
that are defined in the enclosure. Refer to the number of the current iteration with .iter
.
yield_while(iter, cond)
yield_while(iter, cond)
iter |
An |
cond |
A logical expression involving some variable(s) in |
collatz <- Iterator({ if (n %% 2 == 0) n <- n / 2 else n <- n*3 + 1 }, initial = list(n = 50), yield = n) yield_while(collatz, n != 1L) p_success <- 0.5 threshold <- 100 seeds <- 1000:1e6 iter <- Iterator({ set.seed(seeds[.iter]) n <- n + sample(c(1,-1), 1, prob = c(p_success, 1 - p_success)) }, list(n = 0), n) sequence <- yield_while(iter, n <= threshold)
collatz <- Iterator({ if (n %% 2 == 0) n <- n / 2 else n <- n*3 + 1 }, initial = list(n = 50), yield = n) yield_while(collatz, n != 1L) p_success <- 0.5 threshold <- 100 seeds <- 1000:1e6 iter <- Iterator({ set.seed(seeds[.iter]) n <- n + sample(c(1,-1), 1, prob = c(p_success, 1 - p_success)) }, list(n = 0), n) sequence <- yield_while(iter, n <= threshold)