lstrrr provides functional programming tools for working with deeply nested R lists — path-based access, recursive mapping/walking, conditional modification, and structure visualization. Motivation comes from purrr
purrr can handle with R list, but it converts R list in to a vector or atomic, which is not what we want. lstrrr is designed to work with R list and return the same list structure.
# install.packages("pak")
pak::pak("Exceret/lstrrr")Use it just like purrr
library(lstrrr)
x <- list(
a = 1,
b = list(x = 2, y = list(i = 3, j = 4))
)
# Path-based access
list_get(x, c("b", "y", "i"))
#> [1] 3
# Recursive mapping
list_map(x, ~ .x * 2) |> str()
#> List of 2
#> $ a: num 2
#> $ b:List of 2
#> ..$ x: num 4
#> ..$ y:List of 2
#> .. ..$ i: num 6
#> .. ..$ j: num 8
# Flatten with path names
list_flatten(x) |> str()
#> List of 4
#> $ a : num 1
#> $ b.x : num 2
#> $ b.y.i: num 3
#> $ b.y.j: num 4
# Conditional modification (only values > 2)
list_modify_if(x, ~ is.numeric(.x) && .x > 2, ~ .x * 10) |> str()
#> List of 2
#> $ a: num 1
#> $ b:List of 2
#> ..$ x: num 2
#> ..$ y:List of 2
#> .. ..$ i: num 30
#> .. ..$ j: num 40
# Tree visualization (requires ggplot2)
# viz_list(x)A similar project is rlist, but it is not for complex nested list.
Let’s see how they work differently. This is a code snippet from rlist:
nums <- list(a = c(1, 2, 3), b = c(2, 3, 4), c = c(3, 4, 5))
rlist::list.map(nums, c(min = min(.), max = max(.))) |> str()
#> List of 3
#> $ a: Named num [1:2] 1 3
#> ..- attr(*, "names")= chr [1:2] "min" "max"
#> $ b: Named num [1:2] 2 4
#> ..- attr(*, "names")= chr [1:2] "min" "max"
#> $ c: Named num [1:2] 3 5
#> ..- attr(*, "names")= chr [1:2] "min" "max"
rlist::list.filter(nums, x ~ mean(x) >= 3) |> str()
#> List of 2
#> $ b: num [1:3] 2 3 4
#> $ c: num [1:3] 3 4 5
rlist::list.map(nums, f(x, i) ~ sum(x, i)) |> str()
#> List of 3
#> $ a: num 7
#> $ b: num 11
#> $ c: num 15If nums becomes a complex nested list, rlist may not work as expected.
nums <- list(
a = c(1, 2, 3),
b = c(2, 3, 4),
c = c(3, 4, 5),
d = list(
e = c(4, 5, 6),
f = c(5, 6, 7)
)
)
try(rlist::list.map(nums, c(min = min(.), max = max(.))))
#> Error in min(.) : invalid 'type' (list) of argument
try(rlist::list.filter(nums, x ~ mean(x) >= 3))
#> Warning in mean.default(x): argument is not numeric or logical: returning NA
#> $b
#> [1] 2 3 4
#>
#> $c
#> [1] 3 4 5
try(rlist::list.map(nums, f(x, i) ~ sum(x, i)))
#> Error in sum(x, i) : invalid 'type' (list) of argumentRecursive mapping is the main difference between lstrrr and rlist. lstrrr views the list as a tree, and recursively applies the function to leaf nodes.
list_map(nums, \(v) c(min = min(v), max = max(v))) |> str()
#> List of 4
#> $ a: Named num [1:2] 1 3
#> ..- attr(*, "names")= chr [1:2] "min" "max"
#> $ b: Named num [1:2] 2 4
#> ..- attr(*, "names")= chr [1:2] "min" "max"
#> $ c: Named num [1:2] 3 5
#> ..- attr(*, "names")= chr [1:2] "min" "max"
#> $ d:List of 2
#> ..$ e: Named num [1:2] 4 6
#> .. ..- attr(*, "names")= chr [1:2] "min" "max"
#> ..$ f: Named num [1:2] 5 7
#> .. ..- attr(*, "names")= chr [1:2] "min" "max"
list_find(nums, ~ mean(.x) >= 3) |> str()
#> List of 4
#> $ b : num [1:3] 2 3 4
#> $ c : num [1:3] 3 4 5
#> $ d.e: num [1:3] 4 5 6
#> $ d.f: num [1:3] 5 6 7
list_map(nums, sum) |> str()
#> List of 4
#> $ a: num 6
#> $ b: num 9
#> $ c: num 12
#> $ d:List of 2
#> ..$ e: num 15
#> ..$ f: num 18