How to run function on the deepest level only in a nested list?
We can recursively descend lst
to find the maximum depth and then use that to recursively descend again applying unique
only at the maximum depth. No packages are used.
maxDepth <- function(x, depth = 0) {
if (is.list(x)) max(sapply(x, maxDepth, depth+1))
else depth
}
lstUnique <- function(x, depth = maxDepth(x)) {
if (depth == 0) unique(x)
else if (is.list(x)) lapply(x, lstUnique, depth-1)
else x
}
lstUnique(lst)
Variation using rapply
A variation of the above is to recursively add a class to each leaf equal to its depth. Then we can use rapply
three times. First use rapply
to extract the classes and take the maximum to find the maximum depth. second use rapply
to apply unique
on just the nodes having the maximum depth class. Third, remove any remaining classes that were not removed by unique
because the node was not at maximum depth. (The third rapply
, i.e. the last line of code below, could be omitted if it is ok to leave some leaves with the classes we added.)
addDepth <- function(x, depth = 0) {
if (is.list(x)) lapply(x, addDepth, depth+1)
else structure(x, class = format(depth))
}
lst2 <- addDepth(lst)
mx <- max(as.numeric(rapply(lst2, class))) # max depth
lst3 <- rapply(lst2, unique, classes = format(mx), how = "replace")
rapply(lst3, as.vector, how = "replace")
Note on rapply
Note that if you alternately wanted to run unique on all leaves rather than just on the maximum depth leaves then rapply
in base R would work.
rapply(lst, unique, how = "replace")
data.tree
This alternative does require the use of a package. First we create a data.tree dt
and then traverse it applying unique to the nodes that satisfy the filterFun.
library(data.tree)
dt <- as.Node(lst)
dt$Do(function(x) x$"1" <- unique(x$"1"),
filterFun = function(x) x$level == dt$height)
print(dt, "1")
rrapply
The rrapply package provides an enhancement to rapply which can also pass a position vector whose length equals the depth so we can use it first to calculate the maximum depth mx and then again to apply unique only at that depth. (Have updated rrapply
call to use how = "unlist" as opposed to applying unlist afterwards as per suggestion in comments.)
library(rrapply)
mx <- max(rrapply(lst, f = function(x, .xpos) length(.xpos), how = "unlist"))
uniq_mx <- function(x, .xpos) if (length(.xpos) == mx) unique(x) else x
rrapply(lst, is.numeric, uniq_mx)