Как проверить, существует ли элемент списка?


113

проблема

Я хотел бы проверить, существует ли элемент списка, вот пример

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(...)ли предпочтительный подход?

1
может быть !is.null(foo$a)(или !is.null(foo[["a"]])на всякий случай)? (или exists("a",where=foo))
Бен Болкер

1
@BenBolker спасибо - ответит неплохо; почему предпочтение отдается последнему варианту?
Дэвид ЛеБауэр

3
@David частичное совпадение ... попробуйте описанное выше сfoo <- list(a1=1)
baptiste

Ответы:


151

На самом деле это немного сложнее, чем вы думаете. Поскольку список может (с некоторыми усилиями) содержать элементы 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

2
до сих пор непонятно - зачем exists('foo$a') == FALSE?
Дэвид ЛеБауэр

Это говорит о том, что в R нет хорошего решения для этого! Кто-то может захотеть более сложные вещи (например, тестирование, если $mylist[[12]]$out$mcerrorопределено), которые в настоящее время были бы чертовски сложными.
TMS

Вы знали об whereаргументе, existsуказанном в ответе @ Jim ?
Дэвид ЛеБауэр

"bar$a" <- 42Я действительно хотел бы, чтобы это был недопустимый синтаксис, а exists ("foo $ a") работал в наивном смысле.
Andy V

44

Лучший способ проверить именованные элементы - использовать exist(), однако приведенные выше ответы не используют эту функцию должным образом. Вам нужно использовать whereаргумент, чтобы проверить переменную в списке.

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

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

8
Использование exists()списка действительно работает, но я считаю, что R внутренне принуждает его к среде перед проверкой объекта с таким именем, что неэффективно и может привести к ошибкам, если есть какие-либо неименованные элементы. Например , если вы бежите exists('a', list(a=1, 2)), он выдаст сообщение об ошибке: Error in list2env(list(a = 1, 2), NULL, <environment>) : attempt to use zero-length variable name. Преобразование происходит здесь: github.com/wch/r-source/blob/…
wch

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

Если вы планируете использовать список в качестве быстрого словаря, доступ к которому осуществляется много раз, этот is.nullподход может быть единственным жизнеспособным вариантом. Я предполагаю, что это O (1), а %in%подход - O (n)?


4

Немного измененная версия @ salient.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)
  })
}

3

Одно из решений, которое еще не появилось, - это использование 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.


3

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это альтернатива, если у вас есть безымянные элементы.


0

Используется 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

Работает ли это, если элемент вложен / на любом уровне вложенности? Я проверил документы, и там ничего не было ясно
Дэвид ЛеБауэр

@DavidLeBauer, нет. В таком случае, я бы использовал rapply(что-то вроде any(rapply(x, function(v) identical(v, c(3, 4)), how = 'unlist')))
Дмитрий Зотиков
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.