dict provides a new data structure specialized for representing dictionaries with string keys.
Install the package using devtools:
devtools::install_github('stefano-meschiari/dict')and import the library with library:
library(dict)A proper dictionary class
The new class dict can be used to represent a heterogeneous dictionary with character keys. dicts have a few advantages over named lists:
- Every value is uniquely associated with a key (no holes).
- Keys are unique and cannot be repeated.
- Printing of dicts is more compact.
- Keys are never partially matched (a key named
testwill not match withtorte). - A dict can have default values for non-existing keys.
- Dicts can contain NULL values.
This library also provides some useful functions for manipulating dictionary-like objects.
Creating a dictionary
The dict function can be used to create a new dictionary:
d <- dict(color='blue', pattern='solid', width=3)You can create a dictionary out of a list of keys and values using make_dict:
d <- make_dict(keys=c('color', 'pattern', 'width'),
values=c('blue', 'solid', 3))You can convert a named list or vector to a dictionary using as_dict:
d <- as_dict(list(color='blue', pattern='solid', width=3))
d <- as_dict(c("a" = 1, "b" = 2))Printing looks nice:
print(d)## $ color : [1] "blue"
## $ pattern : [1] "solid"
## $ width : [1] 3You can use a shorthand and leave keys implicit (this is similar to the ES6 object literal shorthand). dict will try to create implicit keys based on the arguments:
operating_system <- 'Amiga OS'
version <- '3.1'
cpu <- '68040'
machine <- dict(operating_system, version, cpu)
print(machine)## $ operating_system : [1] "Amiga OS"
## $ version : [1] "3.1"
## $ cpu : [1] "68040"Accessing entries
You can access values using the familiar R syntax for lists:
d <- dict(color='blue', pattern='solid', width=3)
d$color # blue
d[['pattern']] # solid
d['color', 'pattern'] # a sub-dictionary with keys color and patternYou can get keys and values using the keys and values functions:
keys(d) # c('color', 'pattern', 'width')
values(d) # list('blue', 'solid', 3)You can get a list of "entries" (each element being a list with key and value). This is useful for iteration:
for (entry in entries(d)) {
cat(entry$key, ' = ', entry$value)
}You can use the map_dict function to map over keys and values, and the keep_dict and discard_dict to filter entries. These functions are specializations of the map, keep and discard family of functions in purrr to the dictionary class.
# Returns a dict with the same keys and squared values
d <- dict(a=1, b=2, c=3)
map_dict(d, function(k, v) v^2)Setting entries
Entries can be set just like regular lists:
d <- dict(first='Harrison', last='Solo') # first=Harrison, last=Solo
d$last <- 'Ford' # first=Harrison, last=Ford
d[['first']] <- 'Leia' # first=Leia, last=Ford
d[c('last', 'title')] <- c('Organa', 'Princess') # first=Leia, last=Organa, title=PrincessSetting an entry to NULL does not delete the entry, but instead sets the entry to NULL. To delete one or more entries, use the omit function:
d <- dict(a=1, c=3)
d$b <- NULL # a=1, b=NULL, c=3
d <- omit(a, 'a') # b=NULL, c=3Utility functions
A few utility functions inspired by the Underscore library:
invert(dict)returns a dictionary where keys and values are swapped.has(dict, key)returns TRUE ifdictcontainskey.omit(dict, key1, key2, ...)returns a new dictionary omitting all the specified keys.extend(dict, dict1, ...)copies all entries indict1intodict, overriding any existing entries and returns a new dictionary.defaults(dict, defaults)fill in entries fromdefaultsintodictfor any keys missing indict.
The following functions specialize functions from the purrr library to dictionary objects:
map_dict(dict, fun, ...)calls a function on each (key, value) pair and builds a dictionary from the transformed input.keep_dict(dict, fun)anddiscard_dict(dict, fun)keep or discard entries based on the function or predicate.compact_dict(dict)removes any entries with NULL values.
Default dictionaries
You can create a dictionary with default values using default_dict. Any time a non-existing key is accessed, the default value is returned.
salaries <- default_dict(employee_a = 50000,
employee_b = 100000,
default = 65000)You can provide a default value for an existing dictionary by setting its default attribute:
attr(salaries, 'default') <- 70000Strict dictionaries
You can create a strict dictionary using strict_dict. Any time a non-existing key is accessed, an exception is thrown using stop().
# Associating each letter with a number
letters_to_numbers <- strict_dict(a=1, b=2, c=3, d=4) # etc.
# Accessing an existing key, that's OK!
print(letters_to_numbers$a)## [1] 1# Accessing a non-letter will throw!
tryCatch(letters_to_numbers$notaletter, error=function(e) print(e))## <simpleError in `$.dict`(letters_to_numbers, notaletter): Attempted access of non-existing keynotaletter>Immutable collections
You can turn any collection (lists, vectors, and dicts) into an immutable collection using the immutable function. Such a collection cannot be modified using the [, [[ and $ operators; otherwise, it will behave the same as the original collection. The immutable_dict function creates an immutable dictionary.
const_letters <- immutable(letters)
# Will throw an error
const_letters[1] <- '1' ## Error in `[<-.immutable`(`*tmp*`, 1, value = "1"): Attempting to mutate an immutable collection.physical_constants <- immutable_dict(
astronomical_unit = 1.5e11,
speed_of_light = 3e8,
gravitational_constant = 6.7e-11
)
# Will throw an error
physical_constants$speed_of_light = 1## Error in `$<-.immutable`(`*tmp*`, "speed_of_light", value = 1): Attempting to mutate an immutable collection.