在列表中高效地查找唯一的向量元素

9

我有一些数值向量的列表,需要创建一个只包含每个向量一个副本的列表。没有一个相同函数的列表方法,所以我编写了一个函数来检查每个向量是否与其他向量相同。

F1 <- function(x){

    to_remove <- c()
    for(i in 1:length(x)){
        for(j in 1:length(x)){
            if(i!=j && identical(x[[i]], x[[j]]) to_remove <- c(to_remove,j)
        }
    }
    if(is.null(to_remove)) x else x[-c(to_remove)] 
} 

问题是随着输入列表x的大小增加,该函数变得非常缓慢,部分原因是由于for循环分配了两个大向量。我希望找到一种方法,在长度为150万且向量长度为15的列表上运行时间不超过1分钟,但这可能是乐观的。
有人知道比较列表中每个向量与其他向量更有效的方法吗?这些向量本身保证具有相等的长度。
下面显示了示例输出。
x = list(1:4, 1:4, 2:5, 3:6)
F1(x)
> list(1:4, 2:5, 3:6)

我们只讨论数值向量还是任何类型的向量? - Ernest A
2
Ryan -- 为了日后的搜索者着想,您能否将“接受”从我的答案改为 @RicardoSaporta 的答案呢?谢谢! - Josh O'Brien
2个回答

14
根据@JoshuaUlrich和@thelatemail的说法,ll[!duplicated(ll)]可以很好地工作。
因此,unique(ll)也应该如此。 我之前提出了一种使用sapply的方法,想法是不检查列表中的每个元素(我删除了那个答案,因为我认为使用unique更有意义)。
由于效率是一个目标,我们应该对这些进行基准测试。
# Let's create some sample data
xx <- lapply(rep(100,15), sample)
ll <- as.list(sample(xx, 1000, T))
ll

与一些基准进行比较

fun1 <- function(ll) {
  ll[c(TRUE, !sapply(2:length(ll), function(i) ll[i] %in% ll[1:(i-1)]))]
}

fun2 <- function(ll) {
  ll[!duplicated(sapply(ll, digest))]
}

fun3 <- function(ll)  {
  ll[!duplicated(ll)]
}

fun4 <- function(ll)  {
  unique(ll)
}

#Make sure all the same
all(identical(fun1(ll), fun2(ll)), identical(fun2(ll), fun3(ll)), 
    identical(fun3(ll), fun4(ll)), identical(fun4(ll), fun1(ll)))
# [1] TRUE


library(rbenchmark)

benchmark(digest=fun2(ll), duplicated=fun3(ll), unique=fun4(ll), replications=100, order="relative")[, c(1, 3:6)]

        test elapsed relative user.self sys.self
3     unique   0.048    1.000     0.049    0.000
2 duplicated   0.050    1.042     0.050    0.000
1     digest   8.427  175.563     8.415    0.038
# I took out fun1, since when ll is large, it ran extremely slow

最快的选项:

unique(ll)

11

您可以对每个向量进行哈希,然后使用 !duplicated() 来识别结果字符向量的唯一元素:

library(digest)  

## Some example data
x <- 1:44
y <- 2:10
z <- rnorm(10)
ll <- list(x,y,x,x,x,z,y)

ll[!duplicated(sapply(ll, digest))]
# [[1]]
#  [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
# [26] 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
# 
# [[2]]
# [1]  2  3  4  5  6  7  8  9 10
# 
# [[3]]
#  [1]  1.24573610 -0.48894189 -0.18799758 -1.30696395 -0.05052373  0.94088670
#  [7] -0.20254574 -1.08275938 -0.32937153  0.49454570
为了一目了然地看到为什么这有效,以下是哈希值的样子:
sapply(ll, digest)
[1] "efe1bc7b6eca82ad78ac732d6f1507e7" "fd61b0fff79f76586ad840c9c0f497d1"
[3] "efe1bc7b6eca82ad78ac732d6f1507e7" "efe1bc7b6eca82ad78ac732d6f1507e7"
[5] "efe1bc7b6eca82ad78ac732d6f1507e7" "592e2e533582b2bbaf0bb460e558d0a5"
[7] "fd61b0fff79f76586ad840c9c0f497d1"

非常感谢,我的代码的这一部分不再受速率限制。Digest包似乎非常强大 :) - Róisín Grannell
@JoshO'Brien - 我无法在当前设置上安装软件包,但是这是否比仅对list()对象执行x[!duplicated(x)]更快? - thelatemail
1
@JoshuaUlrich -- 很好的发现。谢谢。我只能用这个知识来安慰自己,这仍然是一个非常好的答案,可以高效地找到列表中的唯一元素。 - Josh O'Brien
@thelatemail 你是不是想说"jinx"呢? :-) - Carl Witthoft
@Josh O'Brien - 你的回答对我仍然非常有用;我最终在我的代码中使用了digest包。 - Róisín Grannell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接