Check if two lists are equal up to a permutation

Viewed 89

In R, how can one check if two lists are equal, up to a permutation?

For example,

l1 <- list(diag(3), diag(c(-1,-1,1)))
l2 <- list(diag(c(-1,-1,1)), diag(3))

I would like l1 and l2 to be considered equal.

At present, what I have is

library(purrr)
map_lgl(l1, function(x) any(map_lgl(l2, function(y) identical(x,y))))

It works for this example, but it is not very elegant.

What could be a better way to achieve this?

4 Answers

can there be such an option?

l1 <- list(diag(3), diag(c(-1,-1,1)))
l2 <- list(diag(c(-1,-1,1)), diag(3))
dplyr::setequal(l1, l2)

Though it has been marked as answered by an elegant answer, but still if you want to understand the working of purrr you may write

all(map_lgl(l1, \(x) any(map_lgl(l2, ~ all(x == .x)))))

[1] TRUE

Basically the same like in the question but using base functions:

all(sapply(l1, function(x) any(sapply(l2, function(y) identical(x,y)))))
#[1] TRUE

Packed in a function and testing with other list:

L1 <- list(diag(3), matrix(0, 2, 3))
L2 <- list(matrix(0, 2, 3), diag(3))
L3 <- list(diag(3), matrix(0, 3, 2))

f <- function(a, b) {
  all(sapply(a, function(x) any(sapply(b, function(y) identical(x, y)))))
}

f(L1, L2)
#[1] TRUE
f(L1, L3)
#[1] FALSE
f(L2, L3)
#[1] FALSE

In case names should not be considered, use in addition unname.

f <- function(a, b) {
  all(sapply(a, function(x) any(sapply(b, function(y)
    identical(unname(x), unname(y))))))
}

User Yuriy Saraykin suggested to use setequal(). It works for most cases, but does not consider the dimensions of the matrix. Nevertheless, it led me to a solution:

Inspecting the code for setequal(), we see that it checks for match(x,y) and match(y,x). The function match() returns us the corresponding indices for which x is equal to y, and vice versa. Thus, if there are no NA values, then x is a subset of y, and vice versa. This makes sense, as two sets are equal iff they are subsets of each other.

However, a quick test shows that list(matrix(0, 2, 3)) matches list(matrix(0, 3, 2)), though these are clearly not identical. Thus match() does not respect matrix dimensions. To my understanding, this is why -- as a few users pointed out -- setequal() does not respect matrix dimensions.

But match() gives us a possible permutation p so that l1[p] could be equal to l2. Although matrix dimensions are not respected, this can be checked with identical(). Thus we can use

identical(l1[match(l1,l2)], l2)
Related