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

Dav*_*uer 97 r

问题

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

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
Run Code Online (Sandbox Code Playgroud)

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

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

问题

  • 为什么要exists('foo$a')回来FALSE
  • 是使用with(...)首选方法吗?

Tom*_*mmy 132

这实际上比你想象的要复杂一些.由于列表实际上(通过一些努力)可以包含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
Run Code Online (Sandbox Code Playgroud)

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

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

[更新]所以,回到问题为什么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
Run Code Online (Sandbox Code Playgroud)

  • 现在还不清楚 - 是否存在"存在('foo $ a')== FALSE`的原因? (2认同)

Jim*_*Jim 38

检查命名元素的最佳方法是使用exist(),但上述答案未正确使用该函数.您需要使用的where参数检查变量的列表.

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

exists('a', where=foo) #TRUE
exists('b', where=foo) #TRUE
exists('c', where=foo) #FALSE
Run Code Online (Sandbox Code Playgroud)

  • 在列表上使用`exists()`确实有效,但我相信R在检查该名称的对象之前会在内部强制它到环境,这是低效的,如果有任何未命名的元素,可能会导致错误.例如,如果你运行`exists('a',list(a = 1,2))`,它会给出一个错误:`list2env中的错误(list(a = 1,2),NULL,<environment>):尝试使用零长度变量名称.转换发生在这里:https://github.com/wch/r-source/blob/af7f52f70101960861e5d995d3a4bec010bc89e6/src/main/envir.c#L1943 (6认同)

Dav*_*vic 5

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

> 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
Run Code Online (Sandbox Code Playgroud)

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


sal*_*der 5

一种尚未提出的解决方案是使用长度,它成功地处理了 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
Run Code Online (Sandbox Code Playgroud)

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

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

如果该元素不存在,则会导致 tryCatch 块捕获到越界条件。