如何测试列表元素是否存在?

问题

我想测试一下列表的元素是否存在,这里有一个例子

foo <- list(a=1)
exists('foo')
TRUE   #foo does exist
exists('foo$a')
FALSE  #suggests that foo$a does not exist
foo$a
[1] 1  #but it does exist

在这个例子中,我知道 foo$a存在,但是测试返回 FALSE

我查看了 ?exists,发现 with(foo, exists('a')返回 TRUE,但不明白为什么 exists('foo$a')返回 FALSE

问题

  • 为什么 exists('foo$a')返回 FALSE
  • 使用 with(...)是首选的方法吗?
127079 次浏览

这其实比你想象的要棘手。由于列表实际上(经过一些努力)可以包含 NULL 元素,因此可能不足以检查 is.null(foo$a)。更严格的测试可能是检查名称是否在列表中实际定义:

foo <- list(a=42, b=NULL)
foo


is.null(foo[["a"]]) # FALSE
is.null(foo[["b"]]) # TRUE, but the element "exists"...
is.null(foo[["c"]]) # TRUE


"a" %in% names(foo) # TRUE
"b" %in% names(foo) # TRUE
"c" %in% names(foo) # FALSE

... 而且 foo[["a"]]foo$a更安全,因为后者使用部分匹配,因此也可能匹配一个更长的名称:

x <- list(abc=4)
x$a  # 4, since it partially matches abc
x[["a"]] # NULL, no match

[更新]那么,回到为什么 exists('foo$a')不工作的问题。exists函数只检查环境中是否存在变量,而不检查对象的某些部分是否存在。字符串 "foo$a"的解释是文学性的: 是否有一个称为“ foo $a”的变量?答案是 FALSE

foo <- list(a=42, b=NULL) # variable "foo" with element "a"
"bar$a" <- 42   # A variable actually called "bar$a"...
ls() # will include "foo" and "bar$a"
exists("foo$a") # FALSE
exists("bar$a") # TRUE

检查命名元素的最佳方法是使用 exist(),但是上面的答案没有正确地使用函数。您需要使用 where参数来检查变量 内心的列表。

foo <- list(a=42, b=NULL)


exists('a', where=foo) #TRUE
exists('b', where=foo) #TRUE
exists('c', where=foo) #FALSE

下面是其他答案中提出的方法的性能比较。

> foo <- sapply(letters, function(x){runif(5)}, simplify = FALSE)
> microbenchmark::microbenchmark('k' %in% names(foo),
is.null(foo[['k']]),
exists('k', where = foo))
Unit: nanoseconds
expr  min   lq    mean median   uq   max neval cld
"k" %in% names(foo)  467  933 1064.31    934  934 10730   100  a
is.null(foo[["k"]])    0    0  168.50      1  467  3266   100  a
exists("k", where = foo) 6532 6998 7940.78   7232 7465 56917   100   b

如果您计划使用这个列表作为快速字典访问多次,那么 is.null方法可能是唯一可行的选择。我假设它是 O (1) ,而 %in%方法是 O (n) ?

一个尚未出现的解决方案是使用 length,它成功地处理了 NULL。据我所知,除 NULL 之外的所有值的长度都大于0。

x <- list(4, -1, NULL, NA, Inf, -Inf, NaN, T, x = 0, y = "", z = c(1,2,3))
lapply(x, function(el) print(length(el)))
[1] 1
[1] 1
[1] 0
[1] 1
[1] 1
[1] 1
[1] 1
[1] 1
[1] 1
[1] 1
[1] 3

因此,我们可以创建一个同时使用命名索引和编号索引的简单函数:

element.exists <- function(var, element)
{
tryCatch({
if(length(var[[element]]) > -1)
return(T)
}, error = function(e) {
return(F)
})
}

如果该元素不存在,它会导致 tryCatch 块捕获到一个出界条件。

一个稍微修改过的@salamander 版本,如果想检查完整路径,可以使用它。

Element_Exists_Check = function( full_index_path ){
tryCatch({
len_element = length(full_index_path)
exists_indicator = ifelse(len_element > 0, T, F)
return(exists_indicator)
}, error = function(e) {
return(F)
})
}

使用 purrr::has_element检查列表元素的 价值:

> x <- list(c(1, 2), c(3, 4))
> purrr::has_element(x, c(3, 4))
[1] TRUE
> purrr::has_element(x, c(3, 5))
[1] FALSE

rlang::has_name()也可以做到这一点:

foo = list(a = 1, bb = NULL)
rlang::has_name(foo, "a")  # TRUE
rlang::has_name(foo, "b")  # FALSE. No partial matching
rlang::has_name(foo, "bb")  # TRUE. Handles NULL correctly
rlang::has_name(foo, "c")  # FALSE

正如您所看到的,它本质上处理了@Tommy 展示的如何使用基础 R 处理的所有情况,并且适用于具有未命名项的列表。我仍然推荐 exists("bb", where = foo),因为在另一个可读性的答案中提出,但是如果您有未命名的项目,has_name是一个替代品。