1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
|
#' Create a stack
#'
#' A `faststack` is backed by a list. The backing list will grow or shrink as
#' the stack changes in size.
#'
#' `faststack` objects have the following methods:
#'
#' \describe{
#' \item{\code{push(x)}}{
#' Push an object onto the stack.
#' }
#' \item{\code{mpush(..., .list = NULL)}}{
#' Push objects onto the stack. `.list` can be a list of objects to add.
#' }
#' \item{\code{pop(missing = missing_default)}}{
#' Remove and return the top object on the stack. If the stack is empty,
#' it will return `missing`, which defaults to the value of
#' `missing_default` that `stack()` was created with (typically, `NULL`).
#' }
#' \item{\code{mpop(n, missing = missing_default)}}{
#' Remove and return the top `n` objects on the stack, in a list. The first
#' element of the list is the top object in the stack. If `n` is greater
#' than the number of objects in the stack, any requested items beyond
#' those in the stack will be replaced with `missing` (typically, `NULL`).
#' }
#' \item{\code{peek(missing = missing_default)}}{
#' Return the top object on the stack, but do not remove it from the stack.
#' If the stack is empty, this will return `missing`.
#' }
#' \item{\code{reset()}}{
#' Reset the stack, clearing all items.
#' }
#' \item{\code{size()}}{
#' Returns the number of items in the stack.
#' }
#' \item{\code{as_list()}}{
#' Return a list containing the objects in the stack, where the first
#' element in the list is the object at the bottom of the stack, and the
#' last element in the list is the object at the top of the stack.
#' }
#' }
#'
#'
#' @param init Initial size of the list that backs the stack. This is also used
#' as the minimum size of the list; it will not shrink any smaller.
#' @param missing_default The value to return when `pop()` or `peek()` are
#' called when the stack is empty. Default is `NULL`.
#' @export
faststack <- function(init = 20, missing_default = NULL) {
force(missing_default)
# A list that represents the stack
s <- vector("list", init)
# Current size of the stack
count <- 0L
push <- function(x) {
new_size <- count + 1L
# R 3.4.0 and up will automatically grow vectors in place, if possible, so
# we don't need to explicitly grow the list here.
if (is.null(x)) {
# Special case for NULL (in the normal case, we'll avoid creating a new
# list() and then unwrapping it.)
s[new_size] <<- list(NULL)
} else {
s[[new_size]] <<- x
}
count <<- new_size
invisible()
}
mpush <- function(..., .list = NULL) {
if (is.null(.list)) {
# Fast path for common case
args <- list(...)
} else {
args <- c(list(...), .list)
}
if (length(args) == 0) {
stop("`mpush`: No items provided to push on stack.")
}
new_size <- count + length(args)
# R 3.4.0 and up will automatically grow vectors in place, if possible, so
# we don't need to explicitly grow the list here.
s[count + seq_along(args)] <<- args
count <<- new_size
invisible()
}
pop <- function(missing = missing_default) {
if (count == 0L) {
return(missing)
}
value <- s[[count]]
s[count] <<- list(NULL)
count <<- count - 1L
# Shrink list if < 1/2 of the list is used, down to a minimum size of `init`
len <- length(s)
if (len > init && count < len/2) {
new_len <- max(init, count)
s[seq.int(new_len + 1L, len)] <<- list(NULL)
}
value
}
mpop <- function(n, missing = missing_default) {
n <- as.integer(n)
if (n < 1) {
stop("`n` must be at least 1.")
}
if (n > count) {
n_pop <- count
n_extra <- n - count
} else {
n_pop <- n
n_extra <- 0L
}
idx <- seq.int(count, count - n_pop + 1L)
if (n_extra != 0) {
values <- vector("list", n)
values[seq_len(n_pop)] <- s[idx]
if (!is.null(missing)) {
values[seq.int(n_pop + 1, n)] <- missing
}
} else {
values <- s[idx]
}
s[idx] <<- list(NULL)
count <<- count - n_pop
# Shrink list if < 1/2 of the list is used, down to a minimum size of `init`
len <- length(s)
if (len > init && count < len/2) {
new_len <- max(init, count)
# Assign in place; avoids making copies
s[seq.int(new_len + 1L, len)] <<- NULL
}
values
}
peek <- function(missing = missing_default) {
if (count == 0L) {
return(missing)
}
s[[count]]
}
reset <- function() {
s <<- vector("list", init)
count <<- 0L
invisible()
}
size <- function() {
count
}
# Return the entire stack as a list, where the first item in the list is the
# oldest item in the stack, and the last item is the most recently added.
as_list <- function() {
s[seq_len(count)]
}
list(
push = push,
mpush = mpush,
pop = pop,
mpop = mpop,
peek = peek,
reset = reset,
size = size,
as_list = as_list
)
}
|