Overview

The Container class serves as the base class for Deque, Set and Dict, which inherit all methods from Container, except those that are overwritten. 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 S3 interface provides constructor methods for the Container class as well as all its derived classes Deque, Set and Dict, which return objects of these respective classes. Then for each object, S3 methods and operators are provided to emulate the corresponding member methods.

The following table shows member methods divided by class. The top half contains all Container-related 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.

Container Deque Set Dict
cont <- container() deq <- deque() s <- set() d <- dict()
add(cont, elem) add(s, elem) add(d, key, val), d[key] <- val
clear(cont)
discard(cont, elem, right=F) discard(d, key)
empty(cont)
has(cont, elem) has(d, key)
print(cont, list.len=10)
remove(cont, elem, right=F) remove(d, key)
size(cont)
type(cont)
values(cont)
addleft(deq, elem) s1 + s2 getval(d, key), d[[key]]
count(deq, elem) s1 / s2 keys(d)
peek(deq) s1 - s2 peek(d, key, default=NULL), d[key]
peekleft(deq) s1 == s2 pop(d, key)
pop(deq) s1 < s2 popitem(d)
popleft(deq) s1 > s2 setval(d, key, val, add=F), d[[key, add=F]] <- val
reverse(deq) sortkey(decr=FALSE)
rotate(deq, n=1L) update(d, other)

Method descriptions are found in the respective online helps (see ?container, ?deque, ?set, and ?dict).

Container

Objects created using the base container function are ready to be used. Examples of specialized objects using deque, set, and dict, follow below.

library(container)
#> 
#> Attaching package: 'container'
#> The following object is masked from 'package:base':
#> 
#>     remove
collection <- container()
empty(collection)
#> [1] TRUE

Since the created objects are still the same as when created via the R6 interface, they always work both ways:

size(collection)
#> [1] 0

collection$size()
#> [1] 0

add

By default, elements internally are stored in a basic list and therefore can be of any type.

add(collection, 1)
add(collection, "A")
add(collection, data.frame(B=1, C=2))
type(collection)
#> [1] "list"

The internal representation can always be retrieved directly using the values function.

values(collection)
#> [[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

print(collection)
#> <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(integer())
type(ints)
#> [1] "integer"

Initialization also works with vectors.

ints <- container(1:10)
print(ints)
#> <Container> of 10 elements:  int [1:10] 1 2 3 4 5 6 7 8 9 10

values(ints)
#>  [1]  1  2  3  4  5  6  7  8  9 10

size(ints)
#> [1] 10

discard, remove

has(ints, 11)
#> [1] FALSE

has(ints, 7)
#> [1] TRUE

discard(ints, 7)
has(ints, 7)
#> [1] FALSE

remove(ints, 8)
values(ints)
#> [1]  1  2  3  4  5  6  9 10

Using remove on non-existent elements throws an error,

remove(ints, 8)
#> Error in x$remove(elem, right): 8 not in Container

but discard does not.

discard(ints, 8) # ok

Discard and remove work also from the right.

values(add(ints, 1:3))
#>  [1]  1  2  3  4  5  6  9 10  1  2  3

values(discard(ints, 1))
#>  [1]  2  3  4  5  6  9 10  1  2  3

values(discard(ints, 2, right=TRUE))
#> [1]  2  3  4  5  6  9 10  1  3

More details and examples are found in the online help (see ?Container).

Reference semantics and iterator

Being based on R6 classes, any Container object provides reference semantics.

members <- print(container(c("Lisa", "Bob", "Joe")))
#> <Container> of 3 elements:  chr [1:3] "Lisa" "Bob" "Joe"

remove_Joe <- function(cont) discard(cont, "Joe")
remove_Joe(members)
members
#> <Container> of 2 elements:  chr [1:2] "Lisa" "Bob"
it <- iter(members)
print(it)
#> <Iterator> at position 0

while(ithas_next(it)) {
    print(itget_next(it))
    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.

itget_next(it)
#> Error in private$`i++`(): Iterator has no more elements.

Deque

d <- deque(0L)
type(d)
#> [1] "integer"
d
#> <Deque> of 1 elements:  int 0

addleft, count

add(d, 1)
add(d, 2)
addleft(d, 1)
addleft(d, 2)
values(d)
#> [1] 2 1 0 1 2

count(d, 0)  # count number of 0s
#> [1] 1

count(d, 1)  # count number of 1s
#> [1] 2

peek, pop

A peek shows the last value, while pop shows and removes it afterwards.

peek(d)
#> [1] 2

pop(d)
#> [1] 2

pop(d)
#> [1] 1

d
#> <Deque> of 3 elements:  int [1:3] 2 1 0

Being a double-ended queue, both methods are also defined for the left side.

peekleft(d)
#> [1] 2

popleft(d)
#> [1] 2

d
#> <Deque> of 2 elements:  int [1:2] 1 0

Invoking peek on an empty Deque gives NULL while pop stops with an error.

peek(deque())
#> NULL

pop(deque())
#> Error in x$pop(): pop at empty Deque

rotate, reverse

values(add(d, rep(0, 3)))
#> [1] 1 0 0 0 0

values(rotate(d))    # rotate 1 to the right
#> [1] 0 1 0 0 0

values(rotate(d, 2))   # rotate 2 to the right
#> [1] 0 0 0 1 0

values(rotate(d, -3))  # rotate 3 to the left
#> [1] 1 0 0 0 0

values(addleft(d, 4:2))
#> [1] 4 3 2 1 0 0 0 0

values(reverse(d))
#> [1] 0 0 0 0 1 2 3 4

There is also a +-operator available, which does not work by reference but returns a fresh copy.

d
#> <Deque> of 8 elements:  int [1:8] 0 0 0 0 1 2 3 4

5:6 + d
#> <Deque> of 10 elements:  int [1:10] 5 6 0 0 0 0 1 2 3 4

d + 5:6
#> <Deque> of 10 elements:  int [1:10] 0 0 0 0 1 2 3 4 5 6

Deque iterator

As a silly example, define a reverse perfect shuffler.

reverse_ps <- function(x)
{
    it <- iter(seq_along(x))
    d <- deque(integer())

    while(ithas_next(it)) {
        itnext(it)
        d$add(itget(it))
        if (ithas_next(it)) d$addleft(itget_next(it))
    }
    x[values(d)]
}

(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

Set

s1 <- set(1:3)
s1
#> <Set> of 3 elements:  int [1:3] 1 2 3

add(s1, 1)  # does not change the set 
s1
#> <Set> of 3 elements:  int [1:3] 1 2 3

set operators

s1 <- set(c(1, 2,    4, 5))
s2 <- set(c(   2, 3,    5, 6))

s1 + s2
#> <Set> of 6 elements:  num [1:6] 1 2 4 5 3 6

s1 / s2
#> <Set> of 2 elements:  num [1:2] 2 5

s1 - s2
#> <Set> of 2 elements:  num [1:2] 1 4

s1 < s2
#> [1] FALSE

s1 < (s1 + s2)
#> [1] TRUE

(s1 / s2) < s1
#> [1] TRUE

s1 == s2
#> [1] FALSE

s1 == s1
#> [1] TRUE

s1 > s2
#> [1] FALSE

(s1 + s2) > s2
#> [1] TRUE

Dict

(ages <- dict(c(Peter=24, Lisa=23, Bob=32)))
#> <Dict> of 3 elements:  Named num [1:3] 24 23 32
#>  - attr(*, "names")= chr [1:3] "Peter" "Lisa" "Bob"

keys(ages)
#> [1] "Peter" "Lisa"  "Bob"

peek(ages, "Lisa")
#> [1] 23

peek(ages, "Anna")
#> NULL

add, discard, remove

Due to the key-value semantic, several Container methods are modified/extended to take the key argument.

values(add(ages, "Albert", 139))
#>  Peter   Lisa    Bob Albert 
#>     24     23     32    139

add(ages, "Bob", 40)
#> Error in x$add(key, value): key 'Bob' already in Dict

has(ages, "Peter")
#> [1] TRUE

values(discard(ages, "Albert"))
#> Peter  Lisa   Bob 
#>    24    23    32

# Trying to discard a non-existing key has no effect ...
discard(ages, "Albert")

# ... but trying to remove a non-existing key throws an error
remove(ages, "Albert")
#> Error in x$remove(key): key 'Albert' not in Dict

add vs setval

Trying to set a value at a non-existing key throws an error unless the method is explicitly told to add it to the Dict.

setval(ages, "Anna", 23)
#> Error in x$set(key, value, add): key 'Anna' not in Dict
#ages[["Anna"]] <- 23             

setval(ages, "Anna", 23, add=TRUE)  # alternatively: add(ages, "Anna", 23)
#ages[["Anna", add=TRUE]] <- 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.

setval(ages, "Lisa", 11)
#ages[["Lisa"]] <- 11

values(ages)
#> Peter  Lisa   Bob  Anna 
#>    24    11    32    23

peek, pop, and popitem

A similar control is provided via the different methods to retrieve elements.

pop(ages, "Lisa")
#> [1] 11

values(ages)
#> Peter   Bob  Anna 
#>    24    32    23

pop(ages, "Lisa")
#> Error in self$remove(key): key 'Lisa' not in Dict

getval(ages, "Lisa")
#> Error in x$get(key): key 'Lisa' not in Dict

peek(ages, "Lisa")
#> NULL

Finally, the Dict could also be used as a sampler (without replacement).

set.seed(123)
while(!ages$empty()) print(ages$popitem())
#> Peter 
#>    24 
#> Anna 
#>   23 
#> Bob 
#>  32

update

shoplist <- dict(list(eggs=10, potatoes=10, bananas=5, apples=4))

shoplist2 <- dict(list(eggs=6, broccoli=4))

unlist(values(update(shoplist, shoplist2)))
#>     eggs potatoes  bananas   apples broccoli 
#>        6       10        5        4        4

dict operators

To enhance interactive use, various operators are provided for existing methods (see also overview table above).

d <- dict()
d["A"] <- 1             # add(d, "A", 1)

d
#> <Dict> of 1 elements: List of 1
#>  $ A: num 1

d["A"] <- 2             # add(d, "A", 2)
#> Error in dic$add(key, value): key 'A' already in Dict

d[["A"]] <- 2           # setval(d, "A", 2)

d[["B"]] <- 3           # setval(d, "A", 3)
#> Error in dic$set(key, value, add): key 'B' not in Dict

d[["B", add=T]] <- 3    # setval(d, "A", 3, add=T)

d
#> <Dict> of 2 elements: List of 2
#>  $ A: num 2
#>  $ B: num 3

d["A"]                  # peek(d, "A")
#> [1] 2

d[["C"]]                # getval(d, "C")
#> Error in dic$get(key): key 'C' not in Dict

d["C"]                  # peek(d, "C")
#> NULL
other <- dict(list(B=7, C=10))
d + other
#> <Dict> of 3 elements: List of 3
#>  $ A: num 2
#>  $ B: num 7
#>  $ C: num 10

d - other
#> <Dict> of 1 elements: List of 1
#>  $ A: num 2

Conversion from and to base R types

(co <- as.container(1:3))
#> <Container> of 3 elements:  int [1:3] 1 2 3

as.vector(co)
#> [1] 1 2 3

as.list(co)
#> [[1]]
#> [1] 1
#> 
#> [[2]]
#> [1] 2
#> 
#> [[3]]
#> [1] 3

df <- data.frame(A=1:3, B=3:1)

d <- as.dict(df)
d
#> <Dict> of 2 elements: List of 2
#>  $ A: int [1:3] 1 2 3
#>  $ B: int [1:3] 3 2 1

as.data.frame(d)
#>   A B
#> 1 1 3
#> 2 2 2
#> 3 3 1