Ordered sequence basics
ordered_sequence is a persistent structure that
associates elements with keys, typically numeric or character (string).
They store elements sorted by key, with stable first-in-first-out (FIFO)
behavior for duplicate keys, and provide fast key-based lookup, range
queries over keys, and insertion.
All operations are persistent, and return modified copies. Peeking by
key returns the stored element (which may be any type), popping returns
a list with $value containing the stored element,
$key the key, and $remaining the rest of the
sequence without that element.
xs <- ordered_sequence("a1", "b1", "b2", "c1", keys = c(1, 2, 2, 3))
xs
#> Unnamed ordered_sequence with 4 elements.
#>
#> Elements (by key order):
#>
#> [[1]] (key 1)
#> [1] "a1"
#>
#> [[2]] (key 2)
#> [1] "b1"
#>
#> [[3]] (key 2)
#> [1] "b2"
#>
#> [[4]] (key 3)
#> [1] "c1"The as_ordered_sequence() variant builds a sequence from
a vector or list of elements paired with a key vector, useful when keys
are already in a separate vector.
xs2 <- as_ordered_sequence(c(3, 1, 2, 1), keys = letters[1:4])
xs2
#> Unnamed ordered_sequence with 4 elements.
#>
#> Elements (by key order):
#>
#> [[1]] (key a)
#> [1] 3
#>
#> [[2]] (key b)
#> [1] 1
#>
#> [[3]] (key c)
#> [1] 2
#>
#> [[4]] (key d)
#> [1] 1as_flexseq(xs) returns a payload-only
flexseq; keys and any custom monoids are dropped (see
as.list to convert and preservekey metadata).
Peek, pop, insert, and range query by key
Insertion is by key, and keys may be duplicated.
seq <- as_ordered_sequence(1:3, keys = letters[1:3])
seq2 <- insert(seq, 10, key = "b")
seq2
#> Unnamed ordered_sequence with 4 elements.
#>
#> Elements (by key order):
#>
#> [[1]] (key a)
#> [1] 1
#>
#> [[2]] (key b)
#> [1] 2
#>
#> [[3]] (key b)
#> [1] 10
#>
#> [[4]] (key c)
#> [1] 3pop_key() removes and returns the first match for a key
as $value/$key/$remaining. Its
“all” counterpart pop_all_key() removes the entire tie run
for that key, returning $elements (an ordered sequence of
removed matches) and $remaining.
one <- pop_key(seq, key = "b")
one$value
#> [1] 2
one$key
#> [1] "b"
one$remaining
#> Unnamed ordered_sequence with 2 elements.
#>
#> Elements (by key order):
#>
#> [[1]] (key a)
#> [1] 1
#>
#> [[2]] (key c)
#> [1] 3
all_two <- pop_all_key(seq, "b")
all_two$elements
#> Unnamed ordered_sequence with 1 element.
#>
#> Elements (by key order):
#>
#> [[1]] (key b)
#> [1] 2
all_two$remaining
#> Unnamed ordered_sequence with 2 elements.
#>
#> Elements (by key order):
#>
#> [[1]] (key a)
#> [1] 1
#>
#> [[2]] (key c)
#> [1] 3Keys may be counted, and elements accessed; peek_key()
returns one element, the first in insertion order.
peek_all_key() returns an ordered sequence with all
matching keys.
count_key(seq, key = "b")
#> [1] 1
peek_key(seq, key = "b")
#> [1] 2
peek_all_key(seq, key = "b")
#> Unnamed ordered_sequence with 1 element.
#>
#> Elements (by key order):
#>
#> [[1]] (key b)
#> [1] 2Counting and accessing elements over query ranges support
inclusive/exclusive on both sides (defaulting to TRUE).
elements_between(seq, from_key = "b", to_key ="c", include_from = TRUE, include_to = TRUE)
#> Unnamed ordered_sequence with 2 elements.
#>
#> Elements (by key order):
#>
#> [[1]] (key b)
#> [1] 2
#>
#> [[2]] (key c)
#> [1] 3
count_between(seq, from_key = "b", to_key = "c", include_from = TRUE, include_to = TRUE)
#> [1] 2
# exclude "b" keys
elements_between(seq, from_key = "b", to_key ="c", include_from = FALSE, include_to = TRUE)
#> Unnamed ordered_sequence with 1 element.
#>
#> Elements (by key order):
#>
#> [[1]] (key c)
#> [1] 3Key boundaries, and extrema
lower_bound() finds the first element with key
>= a query key; upper_bound() finds the
first with key strictly >. Both return a list with
$found, $index, $value, and
$key (NULL fields when no match exists).
Together they support successor queries (“find the nearest entry at or
above this key”) and duplicate counting via index arithmetic.
seq <- as_ordered_sequence(1:4, keys = c("b", "d", "d", "f"))
lower_bound(seq, key = "d") |> str()
#> List of 4
#> $ found: logi TRUE
#> $ index: int 2
#> $ value: int 2
#> $ key : chr "d"
upper_bound(seq, key = "d") |> str()
#> List of 4
#> $ found: logi TRUE
#> $ index: int 4
#> $ value: int 4
#> $ key : chr "f"When the query key falls between or outside existing keys,
lower_bound() returns the next entry at or above, useful
for nearest-match lookups. Both return found = FALSE when
no keys satisfy the condition.
lower_bound(seq, key = "a") |> str()
#> List of 4
#> $ found: logi TRUE
#> $ index: int 1
#> $ value: int 1
#> $ key : chr "b"
upper_bound(seq, key = "g") |> str()
#> List of 4
#> $ found: logi FALSE
#> $ index: NULL
#> $ value: NULL
#> $ key : NULLThe difference
upper_bound(x, k)$index - lower_bound(x, k)$index gives the
count of entries with key k (this is what
count_key() does internally).
upper_bound(seq, key = "d")$index - lower_bound(seq, key = "d")$index
#> [1] 2
count_key(seq, key = "d")
#> [1] 2min_key() and max_key() return the current
minimum and maximum keys (not the stored elements).
min_key(xs)
#> [1] 1
max_key(xs)
#> [1] 3
min_key(ordered_sequence()) # NULL when empty
#> NULLEmpty sequences
Key and range helpers are non-throwing on empty sequences, and
length() allows for empty checking.
empty_os <- ordered_sequence()
length(empty_os)
#> [1] 0
peek_key(empty_os, 1)
#> NULL
count_between(empty_os, 1, 5)
#> [1] 0Named ordered sequences
Ordered sequences can carry names, set either at construction or
through as_ordered_sequence() on a named list. Names and
integer positions both support read-only indexing via [,
[[, and $. All replacement forms
([<-, [[<-, $<-) error,
because index-based assignment may break the ordering invariant. Ordered
sequences may be cast down with as_flexseq() or
as.list(). Named and unnamed elements cannot be mixed
within one sequence.
xs_named <- as_ordered_sequence(
setNames(list("alice", "bob", "carol"), c("a", "b", "c")),
keys = c(3, 1, 2)
)
xs_named
#> Named ordered_sequence with 3 elements.
#>
#> Elements (by key order):
#>
#> $b (key 1)
#> [1] "bob"
#>
#> $c (key 2)
#> [1] "carol"
#>
#> $a (key 3)
#> [1] "alice"
xs_named[["b"]]
#> [1] "bob"
xs_named[c("a", "c")]
#> Warning: Ordered subsetting canonicalizes selector order; pre-sort and unique
#> selectors to silence this warning.
#> Named ordered_sequence with 2 elements.
#>
#> Elements (by key order):
#>
#> $c (key 2)
#> [1] "carol"
#>
#> $a (key 3)
#> [1] "alice"
xs_named[1] # positional read also works
#> Named ordered_sequence with 1 element.
#>
#> Elements (by key order):
#>
#> $b (key 1)
#> [1] "bob"
try(xs_named$a <- "!!") # replacement blocked
#> Error in .ft_stop_ordered_like(x, "$<-", "Replacement indexing is not supported. Consider converting with as_flexseq().") :
#> `$<-()` is not supported for ordered_sequence. Replacement indexing is not supported. Consider converting with as_flexseq().Transforming, aterating, merging
fapply() maps a function over elements while preserving
keys and order. The function receives (value, key), or
(value, key, name) if it accepts a third argument. Keys and
names are passed in read-only, and the return value replaces the stored
element at the associated key (and name if named).
xs_t <- ordered_sequence("alice", "bob", "carol", keys = c(3, 1, 2))
fapply(xs_t, function(value, key) toupper(value))
#> Unnamed ordered_sequence with 3 elements.
#>
#> Elements (by key order):
#>
#> [[1]] (key 1)
#> [1] "BOB"
#>
#> [[2]] (key 2)
#> [1] "CAROL"
#>
#> [[3]] (key 3)
#> [1] "ALICE"loop() (re-exported from the coro
package) walks the sequence in key-ascending order, yielding bare
values. Keys are dropped from each yield; use fapply() if
your callback needs the key alongside the value, or iterate over
as.list() when you want a list keyed by name.
Plain for (v in xs_t) (without loop()) does
not dispatch to the iteration protocol, it walks the underlying
internal structure and yields those rather than sequence elements.
Always wrap with loop().
merge(x, y) combines two ordered sequences into a new
one in key order, preserving left-biased FIFO on duplicate keys (all of
x’s entries at a tied key precede y’s). The
general case runs in O(m + n); when the key ranges are disjoint it
collapses to O(log(min(m, n))) via concat.
a <- as_ordered_sequence(c("a1", "a2", "a3"), keys = c(1, 3, 5))
b <- as_ordered_sequence(c("b1", "b2", "b3"), keys = c(2, 3, 6))
merge(a, b)
#> Unnamed ordered_sequence with 6 elements.
#>
#> Elements (by key order):
#>
#> [[1]] (key 1)
#> [1] "a1"
#>
#> [[2]] (key 2)
#> [1] "b1"
#>
#> ... (skipping 2 elements)
#>
#> [[5]] (key 5)
#> [1] "a3"
#>
#> [[6]] (key 6)
#> [1] "b3"Both sequences must share the same key type and monoid set; mismatches error. Both inputs are left unmodified.
