Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

R - merge lists with overwrite and recursion

Tags:

list

r

recursion

Suppose I have two lists with names,

a = list( a=1, b=2, c=list( d=1, e=2 ), d=list( a=1, b=2 ) )
b = list( a=2, c=list( e=1, f=2 ), d=3, e=2 )

I'd like to recursively merge those lists, overwriting entries if the second argument contains conflicting values. I.e. the expected output would be

$a
[1] 2

$b
[1] 2

$c
$c$d
[1] 1

$c$e
[1] 1

$c$f
[1] 2

$d
[1] 3

$e
[1] 2

Any hint?

like image 460
zeeMonkeez Avatar asked Dec 11 '12 00:12

zeeMonkeez


2 Answers

I am not so sure if a custom function is necessary here. There is a function utils::modifyList() to perform this exact same operation! See modifyList for more info.

a <- list( a=1, b=2, c=list( d=1, e=2 ), d=list( a=1, b=2 ) )
b <- list( a=2, c=list( e=1, f=2 ), d=3, e=2 )

modifyList(a, b) # updates(modifies) 'a' with 'b'

Which gives the following

$a
[1] 2

$b
[1] 2

$c
$c$d
[1] 1

$c$e
[1] 1

$c$f
[1] 2

$d
[1] 3

$e
[1] 2 
like image 103
Arun Balakrishnan Avatar answered Nov 10 '22 11:11

Arun Balakrishnan


I think you'll have to write your own recursive function here.

A function that takes in two lists, list1 and list2. If:

  • list1[[name]] exists but not list2[[name]], use list1[[name]];
  • list1[[name]] exists as well as list2[[name]] and both are not lists, use list2[[name]];
  • otherwise, recurse with list1[[name]] and list2[[name]] as the new lists.

Something like:

myMerge <- function (list1, list2) {
    allNames <- unique(c(names(list1), names(list2)))
    merged <- list1 # we will copy over/replace values from list2 as necessary
    for (x in allNames) {
        # convenience
        a <- list1[[x]]
        b <- list2[[x]]
        if (is.null(a)) {
            # only exists in list2, copy over
            merged[[x]] <- b
        } else if (is.list(a) && is.list(b)) {
            # recurse
            merged[[x]] <- myMerge(a, b)
        } else if (!is.null(b)) {
            # replace the list1 value with the list2 value (if it exists)
            merged[[x]] <- b
        }
    }
    return(merged)
}

Caveats - if your lists to be merged are weird, you might get weird output. For example:

a <- list( a=list(a=1, b=2), b=3 )
b <- list( a=2 )

Then your merged list has a=2, b=3. This is because the value from b$a overrides the value from a$a, even though a$a is a list (you did not specify what would happen if this were the case). However it is simple enough to modify myMerge to handle these sorts of cases. Just remember - use is.list to test if it's a list, and is.null(myList$a) to see if entry a exists in list myList.


Here is the "vectorized" version using sapply:

merge.lists <- function(a, b) {
    a.names <- names(a)
    b.names <- names(b)
    m.names <- sort(unique(c(a.names, b.names)))
    sapply(m.names, function(i) {
        if (is.list(a[[i]]) & is.list(b[[i]])) merge.lists(a[[i]], b[[i]])
        else if (i %in% b.names) b[[i]]
        else a[[i]]
    }, simplify = FALSE)
}
like image 20
mathematical.coffee Avatar answered Nov 10 '22 11:11

mathematical.coffee