จะทดสอบได้อย่างไรว่ามีองค์ประกอบรายการอยู่หรือไม่


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))
Ben Bolker

1
@ BenBolker ขอบคุณ - จะให้คำตอบที่ดี; ทำไมตัวเลือกหลังจึงเป็นที่ต้องการ?
David LeBauer

3
@David จับคู่บางส่วน ... ลองข้างบนด้วยfoo <- list(a1=1)
baptiste

คำตอบ:


151

นี่เป็นเรื่องยากกว่าที่คุณคิด ตั้งแต่รายการสามารถจริง (ที่มีความพยายามบางอย่าง) 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

[UPDATE] กลับไปที่คำถามว่าทำไม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หรือไม่?
David LeBauer

สิ่งนี้ชี้ให้เห็นว่าโดยทั่วไปไม่มีทางออกที่ดีสำหรับสิ่งนี้ใน R! คนหนึ่งอาจต้องการสิ่งที่ซับซ้อนมากขึ้น (เช่นการทดสอบว่า$mylist[[12]]$out$mcerrorมีการกำหนดไว้หรือไม่) ซึ่งปัจจุบันจะซับซ้อนราวกับนรก
TMS

คุณตระหนักถึงwhereข้อโต้แย้งที่existsชี้ให้เห็นในคำตอบของ @ Jim หรือไม่?
David LeBauer

"bar$a" <- 42ฉันหวังเป็นอย่างยิ่งว่านี่เป็นไวยากรณ์ที่ไม่ถูกต้องและมีอยู่ ("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

วิธีแก้ปัญหาหนึ่งที่ยังไม่เกิดขึ้นคือการใช้ความยาวซึ่งจัดการกับ 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')))
Dmitry Zotikov
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.