Як перевірити, чи існує елемент списку?


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вказували в @ Джима відповіді ?
David LeBauer

"bar$a" <- 42Я дуже хочу, щоб це був недійсний синтаксис, і існування ("foo $ a") працювало в наївному сенсі.
Енді 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

Одне рішення, яке ще не з'явилося, - це використання довжини, яка успішно обробляє 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

Чи працює він, якщо елемент вкладений / на будь-якому рівні вкладання? Я перевірив документи, і це було незрозуміло
David LeBauer

@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.