Carpa
Carpa

Reputation: 468

How to compare two lists in R

I have two lists that I want to compare. I want to see if the values of each element of the list are equal or not.

> m1
[[1]]
integer(0)

[[2]]
[1] 3 4

[[3]]
integer(0)

[[4]]
[1] 1

[[5]]
[1] 2 3 4

> m3
[[1]]
[1] 3

[[2]]
[1] 1 4

[[3]]
[1] 2

[[4]]
[1] 3

[[5]]
[1] 1 4

And I expect a result like this:

> Result
[[1]]
[1]
FALSE

[[2]]
[1] 
FALSE TRUE

[[3]]
[1]
FALSE

[[4]]
[1]
FALSE

[[5]]
[1] 
FALSE FALSE TRUE

If I try to apply m1[1] == m3[1] or similar, I get messages as

Error in m1 == m3 : comparison of these types is not implemented.

I don't manage to do that simple thing! Thanks for your help in advance.

Data

m1 <- list(integer(0), 3:4, integer(0), 1, 2:4)
m3 <- list(3, c(1, 4), 2, 3, c(1, 4))

Upvotes: 8

Views: 8809

Answers (5)

Peter
Peter

Reputation: 2410

Expanding Darren's answer from above, one could also do:

any(unlist(Map(`%in%`, m1, m3)) == FALSE)

Upvotes: 0

Darren Tsai
Darren Tsai

Reputation: 35737

You can use Map(or mapply) with %in%.

Map(`%in%`, m1, m3)

# [[1]]
# logical(0)
# 
# [[2]]
# [1] FALSE  TRUE
# 
# [[3]]
# logical(0)
# 
# [[4]]
# [1] FALSE
# 
# [[5]]
# [1] FALSE FALSE  TRUE

However, m1 contains integer(0), which makes %in% return logical(0) instead of FALSE. So you need to convert logical(0) to FALSE afterward.

res <- Map(`%in%`, m1, m3)
res[lengths(res) == 0] <- FALSE
res

# [[1]]
# [1] FALSE
# 
# [[2]]
# [1] FALSE  TRUE
# 
# [[3]]
# [1] FALSE
# 
# [[4]]
# [1] FALSE
# 
# [[5]]
# [1] FALSE FALSE  TRUE

Update

Another one-liner solution:

Map(\(x, y) x[seq_len(max(1, length(x)))] %in% y, m1, m3)

Note: integer(0)[1] is NA and NA %in% y returns FALSE.

Upvotes: 5

Gorka
Gorka

Reputation: 4053

The compare() function in {waldo} makes it very easy to compare lists or data frames. In this case:

m1 = list(integer(), c(3,4), integer(), 1, c(2, 3, 4))
m3 = list(3, c(1,4), 2, 3, c(1, 4))

waldo::compare(m1, m3, x_arg = "m1", y_arg = "m3")
#> `m1[[1]]` is an integer vector ()
#> `m3[[1]]` is a double vector (3)
#> 
#> `m1[[2]]`: 3 4
#> `m3[[2]]`: 1 4
#> 
#> `m1[[3]]` is an integer vector ()
#> `m3[[3]]` is a double vector (2)
#> 
#> `m1[[4]]`: 1
#> `m3[[4]]`: 3
#> 
#> `m1[[5]]`: 2 3 4
#> `m3[[5]]`: 1   4

The console output is color coded:

enter image description here

Created on 2022-03-16 by the reprex package (v2.0.1)

Upvotes: 3

Oliver
Oliver

Reputation: 8602

Both Darren and EJJ decided to go along and use %*%

If we don't mind the default behaviour of NA in boolean comparison, we can use a simple for-loop or lapply

lapply(seq(la), function(i)a[[i]] == b[[i]])

If we want some security that the lists are 'matchable' we can compare their outer and inner lengths and wrap this in a function as below

compare_each_list_element <- function(a, b){
  la <- length(lla <- lengths(a))
  lb <- length(llb <- lengths(b))
  if(la != lb || any(lla != llb)){
    warning('Either length(a) != length(b) or some length within a is not equal to some length within b!')
    return(FALSE)
  }
  lapply(seq(la), function(i)a[[i]] == b[[i]])
}
compare_each_list_element(list(1:3, 2, c(3, 2)), list(1:3, 3, c(2, NA)))
[[1]]
[1] TRUE TRUE TRUE

[[2]]
[1] FALSE

[[3]]
[1] FALSE    NA

Upvotes: 2

EJJ
EJJ

Reputation: 1523

You can try using the purrr library to get your desired output, that examines each element. I used %in% instead of == if NAs exist. Also this assumes each list has the same structure to work.

library(purrr)
purrr::map2(m1, m3, function(x, y) x %in% y)

Upvotes: 4

Related Questions