3

Take a simple nested list L:

L <- list(lev1 = list(lev2 = c("bit1","bit2")), other=list(yep=1))
L
#$lev1
#$lev1$lev2
#[1] "bit1" "bit2"
#
# 
#$other
#$other$yep
#[1] 1

And a vector giving a series of depths for each part I want to select from L:

sel <- c("lev1","lev2")

The result I want when indexing is:

L[["lev1"]][["lev2"]]
#[1] "bit1" "bit2"

Which I can generalise using Reduce like so:

Reduce(`[[`, sel, init=L)
#[1] "bit1" "bit2"

Now, I want to extend this logic to do a replacement, like so:

L[["lev1"]][["lev2"]] <- "new val"

, but I am genuinely stumped as to how to generate the recursive [[ selection in a way that will allow me to then assign to it as well.

thelatemail
  • 91,185
  • 12
  • 128
  • 188

3 Answers3

5

Why cant you just do

L[[sel]] <- "new val"

well if you want to do the long way then You could still use Reduce with modifyList or you could use [[<-. Here is an example with modifyList:

modifyList(L,Reduce(function(x,y)setNames(list(x),y),rev(sel),init = "new val"))
$lev1
$lev1$lev2
[1] "new val"


$other
$other$yep
[1] 1
Onyambu
  • 67,392
  • 3
  • 24
  • 53
  • I've clearly missed a trick and over-complicated matters. I didn't think `L[[sel]]` would work as intended. – thelatemail Nov 26 '19 at 02:26
  • From `?list` - "*‘[[’ can be applied recursively to lists, so that if the single index ‘i’ is a vector of length ‘p’, ‘alist[[i]]’ is equivalent to ‘alist[[i1]]...[[ip]]’ providing all but the final indexing results in a list.*" – thelatemail Nov 26 '19 at 02:30
  • The only difference seems to be that `L <- list(); L[[c("lev1","lev2")]] <- 1` will error out while `L <- list(); L[["lev1"]][["lev2"]] <- 1` works as intended. – thelatemail Nov 26 '19 at 02:36
  • @thelatemail when you have an empty list, you are not `REPLACING` but rather `CREATING` a new value, and thats why it will error out. But with replacement, you directly use it as stated in the documentation – Onyambu Nov 26 '19 at 03:50
  • 1
    True. Just thought it was worth noting the distinction. – thelatemail Nov 26 '19 at 04:21
  • @thelatemail also to extract, you do not need the `Reduce` function. Hope you tried it out – Onyambu Nov 26 '19 at 04:23
1

You could eval() and parse() by concatenating everything. I am not sure how generalized you could make it:

``` r
L <- list(lev1 = list(lev2 = c("bit1","bit2")), other=list(yep=1))
L
#> $lev1
#> $lev1$lev2
#> [1] "bit1" "bit2"
#> 
#> 
#> $other
#> $other$yep
#> [1] 1

sel <- c("lev1","lev2")

eval(parse(text = paste0('L', paste0('[["', sel, '"]]', collapse = ''), '<- "new val"')))

L
#> $lev1
#> $lev1$lev2
#> [1] "new val"
#> 
#> 
#> $other
#> $other$yep
#> [1] 1

Created on 2019-11-25 by the reprex package (v0.3.0)

Cole
  • 11,130
  • 1
  • 9
  • 24
0

Using a series of constructed calls and then evaluating:

Works for replacement:

sel <- c("lev1","lev2")
selexpr <- Reduce(\(x,y) call("[[", x, y), sel, init=quote(L))
## L[["lev1"]][["lev2"]]
eval(call("<-", selexpr, 2))
L
#$lev1
#$lev1$lev2
#[1] 2
#
#
#$other
#$other$yep
#[1] 1

Also works for generating new list entries:

sel <- c("new","chunk")
selexpr <- Reduce(\(x,y) call("[[", x, y), sel, init=quote(L))
eval(call("<-", selexpr, "new value"))
L
#$lev1
#$lev1$lev2
#[1] 2
#
#
#$other
#$other$yep
#[1] 1
#
#
#$new
#$new$chunk
#[1] "new value"
thelatemail
  • 91,185
  • 12
  • 128
  • 188