Làm thế nào để kiểm tra xem phần tử danh sách có tồn tại không?


113

Vấn đề

Tôi muốn kiểm tra xem một phần tử của danh sách có tồn tại hay không, đây là một ví dụ

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

Trong ví dụ này, tôi biết điều đó foo$atồn tại, nhưng kiểm tra trả về FALSE.

Tôi đã xem xét ?existsvà thấy rằng with(foo, exists('a')trả lại TRUE, nhưng không hiểu tại sao exists('foo$a')trả về FALSE.

Câu hỏi

  • Tại sao exists('foo$a')trở lại FALSE?
  • Có sử dụng with(...)cách tiếp cận ưa thích không?

1
có thể !is.null(foo$a)(hoặc !is.null(foo[["a"]])ở bên an toàn)? (hoặc exists("a",where=foo))
Ben Bolker

1
@BenBolker cảm ơn - sẽ tạo ra một câu trả lời tốt; tại sao tùy chọn sau được ưu tiên?
David LeBauer

3
Hợp phần @ David ... thử trên vớifoo <- list(a1=1)
Baptiste

Câu trả lời:


151

Điều này thực sự phức tạp hơn bạn nghĩ một chút. Vì một danh sách thực sự có thể (với một số nỗ lực) chứa các phần tử NULL, nó có thể không đủ để kiểm tra is.null(foo$a). Một bài kiểm tra nghiêm ngặt hơn có thể là để kiểm tra xem tên có thực sự được xác định trong danh sách hay không:

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

... và foo[["a"]]an toàn hơn foo$a, vì sau này sử dụng đối sánh từng phần và do đó cũng có thể khớp với tên dài hơn:

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

[CẬP NHẬT] Vì vậy, quay lại câu hỏi tại sao exists('foo$a')không hoạt động. Các existschức năng chỉ kiểm tra xem một biến tồn tại trong một môi trường, không nếu các bộ phận của một đối tượng tồn tại. Chuỗi "foo$a"được hiểu theo nghĩa văn học: Có một biến được gọi là "foo $ a" không? ... và câu trả lời là 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
nó vẫn chưa rõ ràng - có lý do tại sao exists('foo$a') == FALSEkhông?
David LeBauer

Điều này cho thấy thường không có giải pháp tốt cho việc này trong R! Người ta có thể muốn những thứ phức tạp hơn (như thử nghiệm nếu $mylist[[12]]$out$mcerrorđược xác định) mà hiện tại sẽ phức tạp như địa ngục.
TMS

Bạn có biết về wherelập luận được existschỉ ra trong câu trả lời của @ Jim không?
David LeBauer

"bar$a" <- 42Tôi thực sự ước đây là cú pháp không hợp lệ và tồn tại ("foo $ a") hoạt động theo nghĩa ngây thơ.
Andy V

44

Cách tốt nhất để kiểm tra các phần tử được đặt tên là sử dụng exist(), tuy nhiên các câu trả lời trên không sử dụng đúng chức năng. Bạn cần sử dụng wheređối số để kiểm tra biến trong danh sách.

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

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

8
Việc sử dụng exists()trên một danh sách có hiệu quả, nhưng tôi tin rằng nội bộ R ép buộc nó vào một môi trường trước khi kiểm tra đối tượng có tên đó, điều này không hiệu quả và có thể dẫn đến lỗi nếu có bất kỳ phần tử nào chưa được đặt tên. Ví dụ, nếu bạn chạy exists('a', list(a=1, 2)), nó sẽ cho một lỗi: Error in list2env(list(a = 1, 2), NULL, <environment>) : attempt to use zero-length variable name. Chuyển đổi xảy ra ở đây: github.com/wch/r-source/blob/…
wch

5

Dưới đây là so sánh hiệu suất của các phương pháp được đề xuất trong các câu trả lời khác.

> 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

Nếu bạn định sử dụng danh sách này như một từ điển nhanh được truy cập nhiều lần, thì is.nullphương pháp này có thể là lựa chọn khả thi duy nhất. Tôi giả sử nó là O (1), trong khi %in%cách tiếp cận là O (n)?


4

Một phiên bản sửa đổi nhẹ của @ salient.salamander, nếu ai đó muốn kiểm tra đường dẫn đầy đủ, có thể sử dụng phiên bản này.

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

Một giải pháp vẫn chưa được đưa ra là sử dụng chiều dài, xử lý thành công NULL. Theo như tôi có thể nói, tất cả các giá trị ngoại trừ NULL đều có độ dài lớn hơn 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

Vì vậy, chúng ta có thể tạo một hàm đơn giản hoạt động với cả chỉ số được đặt tên và đánh số:

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

Nếu phần tử không tồn tại, nó gây ra tình trạng nằm ngoài giới hạn được khối tryCatch bắt.


3

rlang::has_name() cũng có thể làm điều này:

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

Như bạn có thể thấy, nó vốn xử lý tất cả các trường hợp mà @Tommy đã chỉ ra cách xử lý bằng cách sử dụng cơ sở R và hoạt động đối với danh sách có các mục chưa được đặt tên. Tôi vẫn sẽ giới thiệu exists("bb", where = foo)như được đề xuất trong một câu trả lời khác để dễ đọc, nhưng has_namelà một giải pháp thay thế nếu bạn có các mục chưa được đặt tên.


0

Sử dụng purrr::has_elementđể kiểm tra giá trị của một phần tử danh sách:

> 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

Nó có hoạt động nếu phần tử được lồng vào nhau / ở bất kỳ mức độ lồng nào không? Tôi đã kiểm tra tài liệu và nó không rõ ràng
David LeBauer

@DavidLeBauer, không. Trong trường hợp đó, tôi sẽ sử dụng rapply(một cái gì đó giống như any(rapply(x, function(v) identical(v, c(3, 4)), how = 'unlist')))
Dmitry Zotikov
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.