The Container
class serves as the base class for Deque
, Set
and Dict
, which inherit all methods from Container
, except those that are overwritten (see below). In addition, the Container
and all its subclasses are iterable, that is, they provide a method returning an Iterator
to iterate through the elements of the container object.
The following table shows member methods divided by class. The top half contains all Container
methods, each derived by the subclasses to the right unless there is a new entry in a sub-class column, meaning the method is overwritten by the subclass. The bottom half contains methods unique to each subclass.
Iterable | Container | Deque | Set | Dict |
---|---|---|---|---|
Container$new() |
Deque$new() |
s <- Set$new() |
Dict$new() |
|
iter() |
||||
add(elem) |
add(elem) |
add(key, value) |
||
apply(f) |
||||
clear() |
||||
discard(elem, right=F) |
discard(key) |
|||
empty() |
||||
has(elem) |
has(key) |
|||
print(list.len=10) |
||||
remove(elem, right=F) |
remove(key) |
|||
size() |
||||
type() |
||||
values() |
||||
addleft(elem) |
union(s) |
get(key) |
||
count(elem) |
intersect(s) |
keys() |
||
peek() |
diff(s) |
peek(key, default=NULL) |
||
peekleft() |
is.equal(s) |
pop(key) |
||
pop() |
is.subset(s) |
popitem() |
||
popleft() |
is.superset(s) |
set(key, value, add=FALSE) |
||
reverse() |
sort(decr=FALSE) |
|||
rotate(n=1L) |
update(other) |
More details about the methods are found in the respective online helps (see ?Container
, ?Deque
, ?Set
, and ?Dict
).
The base Container
is ready to be used by itself. Examples of Deque
, Set
, and Dict
, follow below.
library(container)
collection <- Container$new()
collection$empty()
#> [1] TRUE
By default, elements internally are stored in a basic list
and therefore can be of any type.
collection$add(1)
collection$add("A")
collection$add(data.frame(B=1, C=2))
collection$type()
#> [1] "list"
The internal representation can always be retrieved directly using the values
function.
collection$values()
#> [[1]]
#> [1] 1
#>
#> [[2]]
#> [1] "A"
#>
#> [[3]]
#> B C
#> 1 1 2
The container's print method presents the content more compact similar to utils::str
collection$print()
#> <Container> of 3 elements: List of 3
#> $ : num 1
#> $ : chr "A"
#> $ :'data.frame': 1 obs. of 2 variables:
#> ..$ B: num 1
#> ..$ C: num 2
If initialized with an R object, the type of the object is adopted to allow for efficient internal representations, if required.
ints <- Container$new(integer())
ints$type()
#> [1] "integer"
The add
method supports chaining.
ints$add(1)$add(2)$add(3.7)$print()
#> <Container> of 3 elements: int [1:3] 1 2 3
Initialization also works with vectors.
ints <- Container$new(1:10)$print()
#> <Container> of 10 elements: int [1:10] 1 2 3 4 5 6 7 8 9 10
ints$values()
#> [1] 1 2 3 4 5 6 7 8 9 10
ints$size()
#> [1] 10
ints$has(11)
#> [1] FALSE
ints$has(7)
#> [1] TRUE
ints$discard(7)$has(7)
#> [1] FALSE
ints$remove(8)$values()
#> [1] 1 2 3 4 5 6 9 10
Using remove
on non-existent elements throws an error,
ints$remove(8)
#> Error in ints$remove(8): 8 not in Container
but discard does not.
ints$discard(8) # ok
Discard and remove work also from the right.
ints$add(1:3)$values()
#> [1] 1 2 3 4 5 6 9 10 1 2 3
ints$discard(1)$values()
#> [1] 2 3 4 5 6 9 10 1 2 3
ints$discard(2, right=TRUE)$values()
#> [1] 2 3 4 5 6 9 10 1 3
There is also an apply method, which applies a function to all elements and returns a copy of the result.
unlist(ints$apply(f = function(x) x^2))
#> [1] 4 9 16 25 36 81 100 1 9
ints$clear()$empty()
#> [1] TRUE
More details and examples are found in the online help (see ?Container
).
Being based on R6 classes, any Container
object provides reference semantics.
members <- Container$new(c("Lisa", "Bob", "Joe"))$print()
#> <Container> of 3 elements: chr [1:3] "Lisa" "Bob" "Joe"
remove_Joe <- function(cont) cont$discard("Joe")
remove_Joe(members)
members
#> <Container> of 2 elements: chr [1:2] "Lisa" "Bob"
it <- members$iter()
print(it)
#> <Iterator> at position 0
while(it$has_next()) {
print(it$get_next())
print(it)
}
#> [1] "Lisa"
#> <Iterator> at position 1
#> [1] "Bob"
#> <Iterator> at position 2
Once iterated to the last element, trying to iterate further leads to an error.
it$get_next()
#> Error in private$`i++`(): Iterator has no more elements.
d <- Deque$new(0L)
d$type()
#> [1] "integer"
d
#> <Deque> of 1 elements: int 0
d$add(1)$add(2)$addleft(1)$addleft(2)$values()
#> [1] 2 1 0 1 2
d$count(0) # count number of 0s
#> [1] 1
d$count(1) # count number of 1s
#> [1] 2
A peek
shows the last value, while pop
shows and removes it afterwards.
d$peek()
#> [1] 2
d$pop()
#> [1] 2
d$pop()
#> [1] 1
d$values()
#> [1] 2 1 0
Being a double-ended queue, both methods are also defined for the left side.
d$peekleft()
#> [1] 2
d$popleft()
#> [1] 2
d$values()
#> [1] 1 0
d$count(2)
#> [1] 0
Invoking peek
on an empty Deque
gives NULL
while pop
stops with an error.
Deque$new()$peek()
#> NULL
Deque$new()$pop()
#> Error in Deque$new()$pop(): pop at empty Deque
d$add(rep(0, 3))$values()
#> [1] 1 0 0 0 0
d$rotate()$values() # rotate 1 to the right
#> [1] 0 1 0 0 0
d$rotate(2)$values() # rotate 2 to the right
#> [1] 0 0 0 1 0
d$rotate(-3)$values() # rotate 3 to the left
#> [1] 1 0 0 0 0
d$addleft(4:2)$values()
#> [1] 4 3 2 1 0 0 0 0
d$reverse()$values()
#> [1] 0 0 0 0 1 2 3 4
As a silly example, define a reverse perfect shuffler.
reverse_ps <- function(x)
{
it <- Iterator$new(seq_along(x))
d <- Deque$new(integer())
while(it$has_next()) {
it$.next()
d$add(it$get())
if (it$has_next()) d$addleft(it$get_next())
}
x[d$values()]
}
(zz <- rep(c(0, 1), 10))
#> [1] 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1
reverse_ps(zz)
#> [1] 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
s1 <- Set$new(1:3)$print()
#> <Set> of 3 elements: int [1:3] 1 2 3
s1$add(1) # does not change the set
s1
#> <Set> of 3 elements: int [1:3] 1 2 3
s1 <- Set$new(c(1, 2, 4, 5))
s2 <- Set$new(c( 2, 3, 5, 6))
s1$union(s2)$print()
#> <Set> of 6 elements: num [1:6] 1 2 4 5 3 6
s1$intersect(s2)$print()
#> <Set> of 2 elements: num [1:2] 2 5
s1$diff(s2)$print()
#> <Set> of 2 elements: num [1:2] 1 4
s1$is.subset(s2)
#> [1] FALSE
s1$is.subset(s1$union(s2))
#> [1] TRUE
s1$intersect(s2)$is.subset(s1)
#> [1] TRUE
s1$is.equal(s2)
#> [1] FALSE
s1$is.equal(s1)
#> [1] TRUE
s1$is.superset(s2)
#> [1] FALSE
s1$union(s2)$is.superset(s2)
#> [1] TRUE
ages <- Dict$new(c(Peter=24, Lisa=23, Bob=32))$print()
#> <Dict> of 3 elements: Named num [1:3] 24 23 32
#> - attr(*, "names")= chr [1:3] "Peter" "Lisa" "Bob"
ages$keys()
#> [1] "Peter" "Lisa" "Bob"
ages$peek("Lisa")
#> [1] 23
ages$peek("Anna")
#> NULL
Due to the key-value semantic, several Container
methods are modified/extended to take the key argument.
ages$add("Albert", 139)$values()
#> Peter Lisa Bob Albert
#> 24 23 32 139
ages$add("Bob", 40)
#> Error in ages$add("Bob", 40): key 'Bob' already in Dict
ages$has("Peter")
#> [1] TRUE
ages$discard("Albert")$values()
#> Peter Lisa Bob
#> 24 23 32
# Trying to discard a non-existing key has no effect
ages$discard("Albert")$values()
#> Peter Lisa Bob
#> 24 23 32
# Trying to remove a non-existing key throws an error
ages$remove("Albert")
#> Error in ages$remove("Albert"): key 'Albert' not in Dict
Trying to set
a value at a non-existing key throws an error unless the set
method is explicitly told to add it to the Dict
.
ages$set("Anna", 23)
#> Error in ages$set("Anna", 23): key 'Anna' not in Dict
ages$set("Anna", 23, add=TRUE) # alternatively ages$add("Anna", 23)
ages
#> <Dict> of 4 elements: Named num [1:4] 24 23 32 23
#> - attr(*, "names")= chr [1:4] "Peter" "Lisa" "Bob" "Anna"
This allows fine control over the insert-behaviour of the Dict
. If already existing, the value is overwritten.
ages$set("Lisa", 11)$values()
#> Peter Lisa Bob Anna
#> 24 11 32 23
A similar control is provided via the different methods to retrieve elements.
ages$pop("Lisa")
#> [1] 11
ages$values()
#> Peter Bob Anna
#> 24 32 23
ages$pop("Lisa")
#> Error in self$remove(key): key 'Lisa' not in Dict
ages$get("Lisa")
#> Error in ages$get("Lisa"): key 'Lisa' not in Dict
ages$peek("Lisa")
#> NULL
The Dict
can also be used as a sampler (without replacement).
set.seed(123)
while(!ages$empty()) print(ages$popitem())
#> Peter
#> 24
#> Anna
#> 23
#> Bob
#> 32
shoplist <- Dict$new(list(eggs=10, potatoes=10, bananas=5, apples=4))
shoplist2 <- Dict$new(list(eggs=6, broccoli=4))
shoplist$update(shoplist2)$values()
#> $eggs
#> [1] 6
#>
#> $potatoes
#> [1] 10
#>
#> $bananas
#> [1] 5
#>
#> $apples
#> [1] 4
#>
#> $broccoli
#> [1] 4