Code Monkey home page Code Monkey logo

listenv's Introduction

CRAN check status R CMD check status Coverage Status

listenv: Environments Behaving (Almost) as Lists

Summary

List environments are environments that have list-like properties. They are implemented by the listenv package. The main features of a list environment are summarized in the below table:

Property list environments lists environments
Number of elements, e.g. length() yes yes yes
Named elements, e.g. names(), x$a and x[["a"]] yes yes yes
Duplicated names yes yes
Element names are optional yes yes
Indexed elements, e.g. x[[4]] yes yes
Dimensions, e.g. dim(x), t(x), and aperm(x, c(3,1,2)) yes yes
Names of dimensions, e.g. dimnames(x) yes yes
Indexing by dimensions, e.g. x[[2, 4]] and x[[2, "D"]] yes yes
Multi-element subsetting, e.g. x[c("a", "c")], x[-1] and x[2:1, , 3] yes yes
Multi-element subsetting preserves element names yes
Resizing, e.g. length(x) <- 6 yes yes
Removing elements by assigning NULL, e.g. x$c <- NULL and x[1:3] <- NULL yes yes
Removing parts of dimensions by assigning NULL, e.g. x[,2] <- NULL yes
Mutable, e.g. y <- x; y$a <- 3; identical(y, x) yes yes
Compatible* with assign(), delayedAssign(), get() and exists() yes yes

For example,

> x <- listenv(a = 1, b = 2, c = "hello")
> x
Alistenvvector with 3 elements (‘a’, ‘b’, ‘c’).
> length(x)
[1] 3
> names(x)
[1] "a" "b" "c"
> x$a
[1] 1
> x[[3]] <- toupper(x[[3]])
> x$c
[1] "HELLO"
> y <- x
> y$d <- y$a + y[["b"]]
> names(y)[2] <- "a"
> y$a
[1] 1
> y
Alistenvvector with 4 elements (‘a’, ‘a’, ‘c’, ‘d’).
> identical(y, x)
[1] TRUE
> for (ii in seq_along(x)) {
+     cat(sprintf("Element %d (%s): %s\n", ii, sQuote(names(x)[ii]), 
+         x[[ii]]))
+ }
Element 1 (‘a’): 1
Element 2 (‘a’): 2
Element 3 (‘c’): HELLO
Element 4 (‘d’): 3
> x[c(1, 3)] <- list(2, "Hello world!")
> x
Alistenvvector with 4 elements (‘a’, ‘a’, ‘c’, ‘d’).
> y <- as.list(x)
> str(y)
List of 4
 $ a: num 2
 $ a: num 2
 $ c: chr "Hello world!"
 $ d: num 3
> z <- as.listenv(y)
> z
Alistenvvector with 4 elements (‘a’, ‘a’, ‘c’, ‘d’).
> identical(z, x)
[1] FALSE
> all.equal(z, x)
[1] TRUE

Creating list environments

List environments are created similarly to lists but also similarly to environments. To create an empty list environment, use

> x <- listenv()
> x
Alistenvvector with 0 elements (unnamed).

This can later can be populated using named assignments,

> x$a <- 1
> x
Alistenvvector with 1 element (‘a’).

comparable to how both lists and environments work. Similarly to lists, they can also be populated using indices, e.g.

> x[[2]] <- 2
> x$c <- 3
> x
Alistenvvector with 3 elements (‘a’, ‘’, ‘c’).

Just as for lists, a list environment is expanded with NULL elements whenever a new element is added that is beyond the current length plus one, e.g.

> x[[5]] <- 5
> x
Alistenvvector with 5 elements (‘a’, ‘’, ‘c’, ‘’, ‘’).
> x[[4]]
NULL

As with lists, the above list environment can also be created from the start, e.g.

> x <- listenv(a = 1, 3, c = 4, NULL, 5)
> x
Alistenvvector with 5 elements (‘a’, ‘’, ‘c’, ‘’, ‘’).

As for lists, the length of a list environment can at any time be increased or decreased by assigning it a new length. If decreased, elements are dropped, e.g.

> x
Alistenvvector with 5 elements (‘a’, ‘’, ‘c’, ‘’, ‘’).
> length(x) <- 2
> x
Alistenvvector with 2 elements (‘a’, ‘’).
> x[[1]]
[1] 1
> x[[2]]
[1] 3

If increased, new elements are populated with unnamed elements of NULL, e.g.

> length(x) <- 4
> x
Alistenvvector with 4 elements (‘a’, ‘’, ‘’, ‘’).
> x[[3]]
NULL
> x[[4]]
NULL

To allocate an "empty" list environment (with all NULL:s) of a given length, do

> x <- listenv()
> length(x) <- 4
> x
Alistenvvector with 4 elements (unnamed).

Note: Unfortunately, it is not possible to use x <- vector("listenv", length = 4); that construct is only supported for the basic data types.

Elements can be dropped by assigning NULL, e.g. to drop the first and third element of a list environment, do:

> x[c(1, 3)] <- NULL
> x
Alistenvvector with 2 elements (unnamed).

Iterating over elements

Iterating over elements by names

Analogously to lists and plain environments, it is possible to iterate over elements of list environments by the element names. For example,

> x <- listenv(a = 1, b = 2, c = 3)
> for (name in names(x)) {
+     cat(sprintf("Element %s: %s\n", sQuote(name), x[[name]]))
+ }
Elementa: 1
Elementb: 2
Elementc: 3

Iterating over elements by indices

Analogously to lists, but contrary to plain environments, it is also possible to iterate over elements by their indices. For example,

> x <- listenv(a = 1, b = 2, c = 3)
> for (ii in seq_along(x)) {
+     cat(sprintf("Element %d: %s\n", ii, x[[ii]]))
+ }
Element 1: 1
Element 2: 2
Element 3: 3

Coercion to and from list environments

Coercing to lists and vectors

Coercing a list environment to a list:

> x <- listenv(a = 2, b = 3, c = "hello")
> x
Alistenvvector with 3 elements (‘a’, ‘b’, ‘c’).
> y <- as.list(x)
> str(y)
List of 3
 $ a: num 2
 $ b: num 3
 $ c: chr "hello"

Coercing a list to a list environment:

> z <- as.listenv(y)
> z
Alistenvvector with 3 elements (‘a’, ‘b’, ‘c’).
> identical(z, x)
[1] FALSE
> all.equal(z, x)
[1] TRUE

Unlisting:

> unlist(x)
      a       b       c 
    "2"     "3" "hello" 
> unlist(x[-3])
a b 
2 3 
> unlist(x[1:2], use.names = FALSE)
[1] 2 3

Multi-dimensional list environments

Analogously to lists, and contrary to plain environments, list environments can have dimensions with corresponding names. For example,

> x <- as.listenv(1:6)
> dim(x) <- c(2, 3)
> dimnames(x) <- list(c("a", "b"), c("A", "B", "C"))
> x
Alistenvmatrix with 6 elements (unnamed) arranged in 2x3 rows (‘a’, ‘b’) and columns (‘A’, ‘B’, ‘C’).

An easy way to quickly get an overview is to coerce to a list, e.g.

> as.list(x)
  A B C
a 1 3 5
b 2 4 6

Individual elements of a list environment can be accessed using standard subsetting syntax, e.g.

> x[["a", "B"]]
[1] 3
> x[[1, 2]]
[1] 3
> x[[1, "B"]]
[1] 3

We can assign individual elements similarly, e.g.

> x[["b", "B"]] <- -x[["b", "B"]]
> as.list(x)
  A B  C
a 1 3  5
b 2 -4 6

We can also assign multiple elements through dimensional subsetting, e.g.

> x[2, -1] <- 98:99
> as.list(x)
  A B  C 
a 1 3  5 
b 2 98 99
> x["a", c(1, 3)] <- list(97, "foo")
> as.list(x)
  A  B  C    
a 97 3  "foo"
b 2  98 99   
> x[] <- 1:6
> as.list(x)
  A B C
a 1 3 5
b 2 4 6

Concurrently with dimensional names it is possible to have names of the individual elements just as for list environments without dimensions. For example,

> names(x) <- letters[seq_along(x)]
> x
Alistenvmatrix with 6 elements (‘a’, ‘b’, ‘c’, ..., ‘f’) arranged in 2x3 rows (‘a’, ‘b’) and columns (‘A’, ‘B’, ‘C’).
> x[["a"]]
[1] 1
> x[["f"]]
[1] 6
> x[c("a", "f")]
Alistenvvector with 2 elements (‘a’, ‘f’).
> unlist(x)
a b c d e f 
1 2 3 4 5 6 
> as.list(x)
  A B C
a 1 3 5
b 2 4 6
attr(,"names")
[1] "a" "b" "c" "d" "e" "f"

Contrary to lists, element names are preserved also with multi-dimensional subsetting, e.g.

> x[1, 2]
Alistenvvector with 1 element (‘c’).
> x[1, 2, drop = FALSE]
Alistenvmatrix with 1 element (‘c’) arranged in 1x1 rows (‘a’) and columns (‘B’).
> x[1:2, 2:1]
Alistenvmatrix with 4 elements (‘c’, ‘d’, ‘a’, ‘b’) arranged in 2x2 rows (‘a’, ‘b’) and columns (‘B’, ‘A’).
> x[2, ]
Alistenvvector with 3 elements (‘b’, ‘d’, ‘f’).
> x[2, , drop = FALSE]
Alistenvmatrix with 3 elements (‘b’, ‘d’, ‘f’) arranged in 1x3 rows (‘b’) and columns (‘A’, ‘B’, ‘C’).
> x["b", -2, drop = FALSE]
Alistenvmatrix with 2 elements (‘b’, ‘f’) arranged in 1x2 rows (‘b’) and columns (‘A’, ‘C’).

Note, whenever dimensions are set using dim(x) <- dims both the dimensional names and the element names are removed, e.g.

> dim(x) <- NULL
> names(x)
NULL

This behavior is by design, cf. help("dim", package="base").

To allocate an "empty" list environment array (with all NULL:s) of a given dimension, do

> x <- listenv()
> dim(x) <- c(2, 3)
> dimnames(x) <- list(c("a", "b"), c("A", "B", "C"))
> x
Alistenvmatrix with 6 elements (unnamed) arranged in 2x3 rows (‘a’, ‘b’) and columns (‘A’, ‘B’, ‘C’).

Rows and columns can be dropped by assigning NULL, e.g. to drop the first and third column of a list-environment matrix, do:

> x[, c(1, 3)] <- NULL
> x
Alistenvmatrix with 2 elements (unnamed) arranged in 2x1 rows (‘a’, ‘b’) and columns (‘B’).

Important about environments

List environments are as their name suggests environments. Whenever working with environments in R, it is important to understand that environments are mutable whereas all other of the basic data types in R are immutable. For example, consider the following function that assigns zero to element a of object x:

> setA <- function(x) {
+     x$a <- 0
+     x
+ }

If we pass a regular list to this function,

> x <- list(a = 1)
> y <- setA(x)
> x$a
[1] 1
> y$a
[1] 0

we see that x is unaffected by the assignment. This is because lists are immutable in R. However, if we pass an environment instead,

> x <- new.env()
> x$a <- 1
> y <- setA(x)
> x$a
[1] 0
> y$a
[1] 0

we find that x was affected by the assignment. This is because environments are mutable in R. Since list environments inherits from environments, this also goes for them, e.g.

> x <- listenv(a = 1)
> y <- setA(x)
> x$a
[1] 0
> y$a
[1] 0

What is also important to understand is that it is not just the content of an environment that is mutable but also its attributes. For example,

> x <- listenv(a = 1)
> y <- x
> attr(y, "foo") <- "Hello!"
> attr(x, "foo")
[1] "Hello!"

More importantly, since dimensions and their names are also attributes, this also means they are mutable. For example,

> x <- as.listenv(1:6)
> dim(x) <- c(2, 3)
> x
Alistenvmatrix with 6 elements (unnamed) arranged in 2x3 unnamed rows and columns.
> y <- x
> dim(y) <- c(3, 2)
> x
Alistenvmatrix with 6 elements (unnamed) arranged in 3x2 unnamed rows and columns.

Installation

R package listenv is available on CRAN and can be installed in R as:

install.packages("listenv")

Pre-release version

To install the pre-release version that is available in Git branch develop on GitHub, use:

remotes::install_github("HenrikBengtsson/listenv", ref="develop")

This will install the package from source.

Contributing

To contribute to this package, please see CONTRIBUTING.md.

listenv's People

Contributors

henrikbengtsson avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

Forkers

edwardbetts

listenv's Issues

IDEA: Add dfenv()?

Would it make sense to also have a data frame environment that is an environment but emulates how a data frame works? For example,

df <- dfenv(a=1:3, b=c("x", "y", "z"))
df <- cbind(df, c=c(TRUE, FALSE, TRUE))
df$d <- 5:7

Support `append`, `insert`, `extend`, `pop` methods

Hi Henrik,

Do you think it a good idea to support append, insert, extend, pop and similar methods like python's list?

'listenv' data structure in R comes closest to python's list with reference semantics.

Error: Not a valid variable name: '!b'

Should the following work?

library(listenv)
env <- listenv("a" = 1, "!b" = 2)
p1 <- parse_env_subset("a", envir = env, substitute = FALSE)
p2 <- parse_env_subset("!b", envir = env, substitute = FALSE)
## Error: Not a valid variable name: '!b'

Add vignettes

Add vignettes:

  • Basic vignette
  • Multi-dimension list environment vignette. Should warn about the fact that attributes are used on environments, e.g. changing the dimension changes it everywhere.

Removing element from list environment with dimensions should drop dimensions

Removing element from list environment with dimensions should drop dimensions, cf. lists. For example:

> x <- as.list(1:6)
> dim(x) <- c(2:3)
> x[[3]] <- NULL
> x
[[1]]
[1] 1

[[2]]
[1] 2

[[3]]
[1] 4

[[4]]
[1] 5

[[5]]
[1] 6

whereas list environments currently gives:

> library("listenv")
> x <- as.listenv(1:6)
> dim(x) <- c(2:3)
> x
A 'listenv' matrix with 6 unnamed elements arranged in 2x3 unnamed rows and colu
mns.
> x[[3]] <- NULL
> x
A 'listenv' matrix with 5 unnamed elements arranged in 2x3 unnamed rows and colu
mns.
> as.list(x)
Error in dim(res) <- dim :
  dims [product 6] do not match the length of object [5]
> dim(x) <- NULL
> str(as.list(x))
List of 5
 $ : int 1
 $ : int 2
 $ : int 4
 $ : int 5
 $ : int 6

Add x[subset] to effectively be short for as.list(x)[subset]?

Question

To better emulate list:s, should subsetting via [ also be supported?

Example

Currently, we get:

> x <- listenv()
> x$a <- 1
> x$b <- 2
> x$c <- 3
> as.list(x)[2:3]
$b
[1] 2

$c
[1] 3

> x[2:3]
Error in x[2:3] : object of type 'environment' is not subsettable

After adding support for [, we should get a list:

> x[2:3]
$b
[1] 2

$c
[1] 3

...or a list environment? (see below).

Requirements

  • To minimize overhead, and to avoid blocking scenarios (e.g. via futures), the subsetting should implemented such that only the elements requested are queried/touched/retrieved and all other are ignored.
  • Should it return a list or a listenv? Note that it is not possible to return a listenv while not copying the elements. In other words, doing y <- x[1:2]; y[1] <- 4 will not change x[1]. Because of this, does it make more sense to return a list? Also, note that environment cannot be subetted; if they would then listenv would also be subsettable automatically.

Dimensions: unlist(x) when x has dimensions looses names attribute

Call unlist(x) on a list environment x with dimensions looses the names attribute, e.g.

> x <- as.listenv(1:6)
> dim(x) <- c(2,3)
> names(x) <- letters[1:6]
> x
`listenv` with 6 (2x3) elements: 'a', 'b', 'c', 'd', 'e', 'f'.
> unlist(x)
[1] 1 2 3 4 5 6

However, if we remove dimensions first, then the names are there:

> x <- undim(x)
> unlist(x)
a b c d e f
1 2 3 4 5 6

CONCERN: Is it safe to have attributes on environments?

Note to self: Is it safe to have attributes on environments? It might not be, cf. r-lib/R6#64 and https://bugs.r-project.org/bugzilla3/show_bug.cgi?id=16441.

UPDATE: I'm pretty sure I went through this discussion back in ~2001/2 for the R.oo package. The Object:s in R.oo basically uses:

x <- NA
attr(x, "envir") <- new.env()
class(x) <- c("Object")

such that attributes are not attached to the environment. However, I'm not sure if this is related.

Support dimensions for list environments?

With lists one can do:

> x <- as.list(1:6)
> dim(x) <- c(2,3)
> x
     [,1] [,2] [,3]
[1,] 1    3    5
[2,] 2    4    6
> str(x)
List of 6
 $ : int 1
 $ : int 2
 $ : int 3
 $ : int 4
 $ : int 5
 $ : int 6
 - attr(*, "dim")= int [1:2] 2 3

> x[1,3]
[[1]]
[1] 5

> x[[1,3]]
[1] 5

> x[1,]
[[1]]
[1] 1

[[2]]
[1] 3

[[3]]
[1] 5

> x[,2]
[[1]]
[1] 3

[[2]]
[1] 4

Can we add something similar to list environments?

Dimensions: Add aperm() for list environments?

Currently,

> x <- as.list(1:6); dim(x) <- c(2,3)
> aperm(x, 2:1)
     [,1] [,2]
[1,] 1    2
[2,] 3    4
[3,] 5    6
> y <- as.listenv(x)
> aperm(y, 2:1)
Error in aperm.default(y, 2:1) : invalid first argument, must be an array
> is.array(y)
[1] TRUE

aperm() is a generic function, so should be possible to write one for list environments.

CONSISTENCY: Assigning NULL to element does not drop element as for list():s - should it?

Question

Should assignment of NULL to an element drop the element as it does for regular lists? Currently it does not, but it assigns the value NULL.

Example

Compare:

> x <- list()
> x$a <- NULL
> str(as.list(x))
 list()

with:

> library(listenv)
> y <- listenv()
> y$a <- NULL
> str(as.list(y))
List of 1
 $ a: NULL

Suggestion

We could add an argument that controls the behavior, e.g.

y <- listenv(dropIfNULL=TRUE)

If so, it should also support assigning NULL by

y["a"] <- list(NULL)

Maybe the above should be the default?

parse_env_subset() does not distinguish between x[[1,4]] and x[[c(1,4)]]

We need to make parse_env_subset() to generate different output for x[[1,4]] and x[[c(1,4)]], cf. lists.

> library("listenv")

> x <- listenv(a=1, b=2)
> dim(x) <- c(1,2)

> target <- parse_env_subset(x[[1,2]], substitute=TRUE)
> str(target)
List of 6
 $ envir :Classes 'listenv', 'environment' <environment: 0x000000000aac1ff8>
  ..- attr(*, "dim.")= int [1:2] 1 2
 $ name  : chr [1:2] "a" "b"
 $ subset:List of 2
  ..$ : num 1
  ..$ : num 2
 $ idx   : num [1:2] 1 2
 $ exists: Named logi TRUE
  ..- attr(*, "names")= chr "b"
 $ code  : chr "x[[1, 2]]"

> target2 <- parse_env_subset(x[[c(1,2)]], substitute=TRUE)
> str(target2)
List of 6
 $ envir :Classes 'listenv', 'environment' <environment: 0x000000000aac1ff8>
  ..- attr(*, "dim.")= int [1:2] 1 2
 $ name  : chr [1:2] "a" "b"
 $ subset:List of 2
  ..$ : num 1
  ..$ : num 2
 $ idx   : num [1:2] 1 2
 $ exists: Named logi TRUE
  ..- attr(*, "names")= chr "b"
 $ code  : chr "x[[c(1, 2)]]"

> target$code <- target2$code <- NULL

> > all.equal(target2, target)
[1] TRUE

Best practice to delete entire rows and columns

Dear Henrik,

To delete a row (or a column), I am currently doing this.

library("listenv")
x           <- as.listenv(1:6)
dim(x)      <- c(2, 3)
dimnames(x) <- list(c("a", "b"), c("A", "B", "C"))
x           <- x[1:2, c(1,3)]                      # remove second column
x
  • Is this the optimized way to do it?
  • Does this result in not copying the entire matrix?
  • After dropping a row/col, do I need to a call gc to return memory?

-- I require to maintain large matrices on a shoe-string budget. New rows and columns keep getting added and deleted. Please suggest an idea, if possible!

Regards,
Srikanth

WISH: listenv(a=1, b=TRUE, c="Hello world!") ?

Question: Is it possible to also support the following? Should we?

x <- listenv(a=1, b=TRUE, c="Hello world!")

If so, what about the following ambiguous cases?

x <- listenv(length=3)
x <- listenv(names=c('a', 'b', 'c'))
x <- listenv(length=2, names=c('a', 'b', 'c'))
x <- listenv(a=1, length=2, names=c('a', 'b', 'c'))

Should we drop preallocation of listenv() in place of the above to make listenv() become even more similar to list()?

Add support for length(x) <- n

Ah, lists supports setting the length using length<-(), e.g.

> x <- list()
> length(x)
[1] 0

> length(x) <- 2
> x
[[1]]
NULL
[[2]]
NULL

> length(x) <- 1
> x
[[1]]
NULL

> length(x) <- 0
> x
list()

List environment should support the same, i.e. add a length<-.listenv method.

Add clone()?

Add clone() method, e.g.

clone.listenv <- function(x, ...) {
  as.listenv(as.list(x, ...))
}

This can be useful when changing dimensions, e.g. y <- as.matrix(clone(x)).

Subsetting a non-named list environment introduces NA names

Subsetting a non-named list environment introduces NA names, e.g.

> x <- as.listenv(as.list(1:6))
> x
`listenv` with 6 elements that are not named.
> y <- x[1:4]
> y
`listenv` with 4 elements: 'NA', 'NA', 'NA', 'NA'
> names(y)
[1] NA NA NA NA

With names, it's correct:

> x <- as.listenv(as.list(1:6))
> names(x) <- letters[1:6]
> x
`listenv` with 6 elements: 'a', 'b', 'c', 'd', 'e', 'f'
> y <- x[1:4]
> y
`listenv` with 4 elements: 'a', 'b', 'c', 'd'
> names(y)
[1] "a" "b" "c" "d"

WISH: Add support for logical subsetting

Add support for logical subsetting, e.g.

> x <- list()
> x[c('a', 'b', 'c')] <- 1:3
> str(x[c(TRUE, FALSE, TRUE)])
List of 2
 $ a: int 1
 $ c: int 3

but currently

> x <- listenv()
> x[c('a', 'b', 'c')] <- 1:3
> str(x[c(TRUE, FALSE, TRUE)])
Error in `[.default`(x, c(TRUE, FALSE, TRUE)) :
  object of type 'environment' is not subsettable

Add dim(x) <- dim for x of length(x) = 0

Analogously to length<-.listenv, add dim<-.listenv. For similarly to,

> x <- listenv()
> length(x) <- 6
> x
A 'listenv' vector with 6 unnamed elements.
> dim(x) <- c(2, 3)
> x
A 'listenv' matrix with 6 unnamed elements arranged in 2x3 unnamed rows and columns.

one should be able to just:

> x <- listenv()
> dim(x) <- c(2, 3)
> x
A 'listenv' matrix with 6 unnamed elements arranged in 2x3 unnamed rows and columns.

R CMD check NOTE: Documented arguments not in \usage in documentation ...

Version: 0.9.0
Check: Rd \usage sections
Result: NOTE
    Documented arguments not in \usage in documentation object ‘get_variable’:
     ‘x’ ‘name’ ‘mustExist’ ‘create’
    
    Functions with \usage entries need to have the appropriate \alias
    entries, and all their arguments documented.
    The \usage entries must correspond to syntactically valid R code.
    See chapter ‘Writing R documentation files’ in the ‘Writing R
    Extensions’ manual.
Flavor: [r-devel-linux-x86_64-debian-gcc](https://www.r-project.org/nosvn/R.check/r-devel-linux-x86_64-debian-gcc/listenv-00check.html)

Improve presentation of list environment done by print()

Improve presentation of list environment done by print() to something like:

Empty list environment:

A `listenv` with 0 elements.

List environment without names:

A `listenv` vector with 1 non-named element.
A `listenv` vector with 24 non-named elements.
A `listenv` matrix with 1 non-named element arranged in 1x1 non-named rows and columns.
A `listenv` array with 1 non-named element arranged in 1x1x1 non-named dimensions.
A `listenv` matrix with 24 non-named elements arranged in 2x12 non-named rows and columns.
A `listenv` array with 24 non-named elements arranged in 2x3x4 non-named dimensions.

List environment with named elements:

A `listenv` vector with 1 named element ('a').
A `listenv` vector with 24 named elements ('a', 'b', 'c', ..., 'x').
A `listenv` matrix with 24 named elements ('a', 'b', 'c', ..., 'x') arranged in 2x12 non-named rows and columns.
A `listenv` array with 24 named elements ('a', 'b', 'c', ..., 'x') arranged in 2x3x4 non-named dimensions.

List environment with named dimensions:

A `listenv` vector with 24 non-named elements.
A `listenv` matrix with 24 non-named elements arranged in 2x12 named rows ('A', 'B') and columns ('A', 'B', 'C', ..., 'L').
A `listenv` matrix with 24 non-named elements arranged in 2x12 named rows ('A', 'B') and unnamed columns.
A `listenv` array with 24 non-named elements arranged in 2x3x4 named dimensions ('A', 'B'; 'A', 'B', 'C'; 'A', 'B', 'C', 'D').
columns.
A `listenv` array with 24 non-named elements arranged in 2x3x4 partially-named dimensions ('A', 'B'; NULL; 'A', 'B', 'C', 'D').

List environment with named elements and named dimensions:

A `listenv` vector with 24 named elements ('a', 'b', 'c', ..., 'x').
A `listenv` matrix with 24 named elements ('a', 'b', 'c', ..., 'x') arranged in 2x12 named rows ('A', 'B') and columns ('A', 'B', 'C', ..., 'L').
A `listenv` array with 24 named elements ('a', 'b', 'c', ..., 'x') arranged in 2x3x4 named dimensions ('A', 'B'; 'A', 'B', 'C'; 'A', 'B', 'C', 'D').

map() -> mapping()

To avoid clashes with purrr::map(), let's simply rename map() to mapping().

Add lengths()

Add lengths() for list environments, cf. base::lengths() available in R (>= 3.2.0).

feature request: a method to modify listenv using another list/listenv

Hi @HenrikBengtsson,

Possible enhancement in having a function to modify existing listenv with another list/ listenv, similar to how the function utils::modifyList works.

Here is an example of what it could be like:

UPDATED Code Chunk: ----

library(listenv)

modifyListenv <- function (x, val, keep.null = FALSE) {
  stopifnot(
    inherits(x, "listenv") || is.list(x),
    inherits(val, "listenv") || is.list(val)
  )
  xnames <- names(x)
  vnames <- names(val)[nzchar(names(val))]
  if (keep.null) {
    for (v in vnames) {
      if (v %in% xnames
          && is.list(x[[v]])
          && (is.list(val[[v]]) || inherits(val[[v]], "listenv"))) {
        x[v] <- list(modifyListenv(x[[v]], val[[v]], keep.null = keep.null))
      } else if (v %in% xnames
          && inherits(x[[v]], "listenv")
          && (is.list(val[[v]]) || inherits(val[[v]], "listenv"))) {
        modifyListenv(x[[v]], val[[v]], keep.null = keep.null)
      } else {
        x[v] <- list(val[[v]])
      }
    }
  } else {
    for (v in vnames) {
      if (v %in% xnames
          && is.list(x[[v]])
          && (is.list(val[[v]]) || inherits(val[[v]], "listenv"))) {
        x[[v]] <- modifyListenv(x[[v]], val[[v]], keep.null = keep.null)
      } else if (v %in% xnames
          && inherits(x[[v]], "listenv")
          && (is.list(val[[v]]) || inherits(val[[v]], "listenv"))) {
        modifyListenv(x[[v]], val[[v]], keep.null = keep.null)
      } else {
        x[[v]] <- val[[v]]
      }
    }
  }
  x
}

  
X = listenv(a=7, c="dummy")
Y = listenv(a=6,b=10)
Z = listenv(d = NULL)

modifyListenv(X,Y)
#> A 'listenv' vector with 3 elements ('a', 'c', 'b').
as.list(X)
#> $a
#> [1] 6
#> 
#> $c
#> [1] "dummy"
#> 
#> $b
#> [1] 10

modifyListenv(X,Z, keep.null = T)
#> A 'listenv' vector with 4 elements ('a', 'c', 'b', 'd').
as.list(X)
#> $a
#> [1] 6
#> 
#> $c
#> [1] "dummy"
#> 
#> $b
#> [1] 10
#> 
#> $d
#> NULL

X = listenv(a=7, c="dummy")
Y = listenv(b=10, x = listenv(a=2))
Z = listenv(d = NULL, x= X)

modifyListenv(Z,Y)
#> A 'listenv' vector with 3 elements ('d', 'x', 'b').
as.list(Z$x)
#> $a
#> [1] 2
#> 
#> $c
#> [1] "dummy"


X = listenv(a=7, c="dummy", d = list(e=1,f=2,g=3))
Y = listenv(b=10, d = list(f=20))
modifyListenv(X,Y)
#> A 'listenv' vector with 4 elements ('a', 'c', 'd', 'b').
as.list(X$d)
#> $e
#> [1] 1
#> 
#> $f
#> [1] 20
#> 
#> $g
#> [1] 3

X = listenv(a=7, c="dummy", d = list(e=1,f=2,g=listenv(h = 1, i = 2)))
Y = listenv(b=10, d = list(f=20, g=listenv(h = 10)))
modifyListenv(X,Y)
#> A 'listenv' vector with 4 elements ('a', 'c', 'd', 'b').
as.list(X$d$g)
#> $h
#> [1] 10
#> 
#> $i
#> [1] 2

Created on 2022-05-27 by the reprex package (v2.0.1)

Alittle bit messy at the moment :S But seems to do what is required :)

Add all.equal() for list environments

Add all.equal() for list environments. Poor-mans version:

all.equal.listenv <- function(target, current, ...) {
  if (identical(target, current)) return(TRUE)
  target <- as.list(target)
  current <- as.list(target)
  all.equal(target=target, current=current, ...)
}

R-devel: sprintf() coding mistakes

> sprintf_scan_tests()
...
> message("* List environment and multiple dimensions ...")
* List environment and multiple dimensions ...

> x <- listenv()

> dim(x) <- c(0, 0)

> print(x)
SPRINTF WARNING: 'argument not used by format'
- call: sprintf("%s unnamed rows and columns", s, dimstr)
Press ENTER to continue: 

> printf("x = %s.\n", hpaste(x), max_head = 3) # Default
SPRINTF WARNING: 'argument not used by format'
- call: sprintf(...)
Press ENTER to continue: 

naive performance benchmarks

Thanks so much for listenv. It is a great idea, and I have enjoyed using already. I am in over my head, but I thought some naive performance benchmarks would be interesting, since environment usually offers a big performance boost (see http://jeffreyhorner.tumblr.com/post/117059271933/hash-table-performance-in-r-part-iv)

library(microbenchmark)
library(listenv)

n <- 2e4

env_env <- function(){
  env_env <- new.env()
  for(x in seq.int(1,n)){
    env_env[[as.character(x)]] <- runif(1)
  }  
  env_env
}

list_env <- function(){
  list_env <- listenv()
  for(x in seq.int(1,n)){
    list_env[[x]] <- runif(1)
  }
  list_env
}
list_list <- function(){
  list_list <- list()
  for(x in seq.int(1,n)){
    list_list[[x]] <- runif(1)
  }
  list_list
}

mb <- microbenchmark(
  env_env()
  ,list_env()
  ,list_list()
  ,times=10
)

autoplot(mb)

image

I18N: potools issues

> potools::translate_package()
Found 21 R messaging calls that might be better suited for gettextf for ease of translation:

Problematic call:
stop("Length of argument 'perm' does not match the dimension of 'a': ", length(perm), " != ", ndim)
< File:aperm.R, Line:41 >
Potential replacement:
stop(domain=NA, gettextf("Length of argument 'perm' does not match the dimension of 'a': %s != %s", length(perm), ndim))

Problematic call:
stop("Subscript must be a name or an index: ", mode(name), call. = FALSE)
< File:get_variable.R, Line:24 >
Potential replacement:
stop(domain=NA, gettextf("Subscript must be a name or an index: %s%s", mode(name), FALSE))

Problematic call:
stop("Subscript must be a scalar: ", length(name), call. = FALSE)
< File:get_variable.R, Line:30 >
Potential replacement:
stop(domain=NA, gettextf("Subscript must be a scalar: %s%s", length(name), FALSE))

Problematic call:
stop("Argument 'value' may only have one NA: ", sprintf("c(%s)", paste(value, collapse = ", ")))
< File:listenv,dims.R, Line:56 >
Potential replacement:
stop(domain=NA, gettextf("Argument 'value' may only have one NA: %s", sprintf("c(%s)", paste(value, collapse = ", "))))

Problematic call:
stop("Subsetting of more than one element at the time is not allowed for listenv's: ", length(i))
< File:listenv.R, Line:459 >
Potential replacement:
stop(domain=NA, gettextf("Subsetting of more than one element at the time is not allowed for listenv's: %s", length(i)))

Problematic call:
stop("Cannot assign value. More than one name specified: ", hpaste(sQuote(name)), call. = FALSE)
< File:listenv.R, Line:629 >
Potential replacement:
stop(domain=NA, gettextf("Cannot assign value. More than one name specified: %s%s", hpaste(sQuote(name)), FALSE))

Problematic call:
stop("Cannot assign value. Empty name specific: ", sQuote(name), call. = FALSE)
< File:listenv.R, Line:632 >
Potential replacement:
stop(domain=NA, gettextf("Cannot assign value. Empty name specific: %s%s", sQuote(name), FALSE))

Problematic call:
stop("Cannot assign value. More than one index specified: ", hpaste(i), call. = FALSE)
< File:listenv.R, Line:671 >
Potential replacement:
stop(domain=NA, gettextf("Cannot assign value. More than one index specified: %s%s", hpaste(i), FALSE))

Problematic call:
stop("Cannot assign value. Non-finite index: ", i, call. = FALSE)
< File:listenv.R, Line:674 >
Potential replacement:
stop(domain=NA, gettextf("Cannot assign value. Non-finite index: %s%s", i, FALSE))

Problematic call:
stop("Cannot assign value. Non-positive index: ", i, call. = FALSE)
< File:listenv.R, Line:676 >
Potential replacement:
stop(domain=NA, gettextf("Cannot assign value. Non-positive index: %s%s", i, FALSE))

Problematic call:
stop("Cannot remove element. More than one name specified: ", hpaste(sQuote(name)), call. = FALSE)
< File:listenv.R, Line:711 >
Potential replacement:
stop(domain=NA, gettextf("Cannot remove element. More than one name specified: %s%s", hpaste(sQuote(name)), FALSE))

Problematic call:
stop("Cannot remove element. Empty name specific: ", sQuote(name), call. = FALSE)
< File:listenv.R, Line:714 >
Potential replacement:
stop(domain=NA, gettextf("Cannot remove element. Empty name specific: %s%s", sQuote(name), FALSE))

Problematic call:
stop("Cannot remove element. More than one index specified: ", hpaste(i), call. = FALSE)
< File:listenv.R, Line:747 >
Potential replacement:
stop(domain=NA, gettextf("Cannot remove element. More than one index specified: %s%s", hpaste(i), FALSE))

Problematic call:
stop("Cannot remove element. Non-finite index: ", i, call. = FALSE)
< File:listenv.R, Line:750 >
Potential replacement:
stop(domain=NA, gettextf("Cannot remove element. Non-finite index: %s%s", i, FALSE))

Problematic call:
stop("Cannot remove element. Non-positive index: ", i, call. = FALSE)
< File:listenv.R, Line:752 >
Potential replacement:
stop(domain=NA, gettextf("Cannot remove element. Non-positive index: %s%s", i, FALSE))

Problematic call:
stop("Invalid syntax: ", sQuote(code), call. = FALSE)
< File:parse_env_subset.R, Line:61 >
Potential replacement:
stop(domain=NA, gettextf("Invalid syntax: %s%s", sQuote(code), FALSE))

Problematic call:
stop("Not a valid variable name: ", sQuote(name), call. = FALSE)
< File:parse_env_subset.R, Line:122 >
Potential replacement:
stop(domain=NA, gettextf("Not a valid variable name: %s%s", sQuote(name), FALSE))

Problematic call:
stop("Multi-dimensional subsetting on list environment without dimensions: ", sQuote(code), call. = TRUE)
< File:parse_env_subset.R, Line:172 >
Potential replacement:
stop(domain=NA, gettextf("Multi-dimensional subsetting on list environment without dimensions: %s%s", sQuote(code), TRUE))

Problematic call:
stop("Invalid (negative) indices: ", hpaste(i))
< File:parse_env_subset.R, Line:244 >
Potential replacement:
stop(domain=NA, gettextf("Invalid (negative) indices: %s", hpaste(i)))

Problematic call:
stop("Invalid (zero) indices: ", hpaste(i))
< File:parse_env_subset.R, Line:254 >
Potential replacement:
stop(domain=NA, gettextf("Invalid (zero) indices: %s", hpaste(i)))

Problematic call:
stop("Invalid subset: ", sQuote(code), call. = TRUE)
< File:parse_env_subset.R, Line:270 >
Potential replacement:
stop(domain=NA, gettextf("Invalid subset: %s%s", sQuote(code), TRUE))
Exit now to repair any of these? [y/N]
y

parse_env_subset(x[[c("a", "b")]]): Should give an error when 'x' is an environment

> x <- new.env()
> target <- parse_env_subset(x[[c("a", "a")]])
> str(target)
List of 7
 $ envir :<environment: 0x55a9d045bda8> 
 $ name  : chr [1:2] "a" "a"
 $ op    : chr "[["
 $ subset:List of 1
  ..$ : chr [1:2] "a" "a"
 $ idx   : int NA
 $ exists: logi FALSE
 $ code  : chr "x[[c(\"a\", \"a\")]]

but if we do:

> x[[c("a", "a")]]
Error in x[[c("a", "a")]] : wrong arguments for subsetting an environment

print(x) for names, empty listenv() is empty

> x <- listenv()
> names(x) <- character(0L)
> print(x)
> capture.output(print(x))
character(0)

Instead, update to give something like:

> print(x)
A named 'listenv' vector with 0 elements.

WISH: Add as.listenv()

Add as.listenv() so one can do:

x <- as.listenv(list(a=1, b=TRUE, c="Hello world!"))

but also

e <- new.env()
e$a <- 1
e$b <- TRUE
e$c <- "Hello world!"
x <- as.listenv(e)

Add unlist()

Add unlist(x) for listenv x to effectively be equivalent to unlist(as.list(x)), e.g.

> unlist.listenv <- function(x, ...) unlist(as.list(x), ...)

> x <- as.listenv(1:3)
> x
`listenv` with 3 elements that are not named.
> unlist(x)
[1] 1 2 3

However, it could be implemented more efficiently that the above.

x[[kk]] <- NULL on non-existing element gives warning

> library("listenv")
> x <- listenv(a=1, b=2)
> x[[4]] <- 4
> x[[3]] <- NULL
Warning message:
In remove(list = var, envir = x, inherits = FALSE) : object 'NA' not found

This is because the "mapped" element is NA_character;

> x <- listenv(a=1, b=2)
> x[[4]] <- 4
> map(x)
                      a                       b
       ".listenv_var_1"        ".listenv_var_2"                      NA

".listenv_var_3.000000"

WISH: Add argument 'names' to listenv()

Add argument names to listenv(), i.e.

listenv <- function(length=length(names), names=NULL) {
   ...
}

This would allow us to allocate named list environments as:

x <- listenv(names=c('a', 'b', 'c'))

instead of as now:

x <- listenv(length=3L)
names(x) <- c('a', 'b', 'c')

Add support for x[elements] <- list of values

Add support for assigning to multiple elements at once, e.g.

x["a"] <- value
x[2] <- value
x[3:4] <- values

This is needed in order to support x["a"] <- NULL, which should drop an element, cf. Issue #1.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.