Como testar se o elemento da lista existe?


113

Problema

Eu gostaria de testar se existe um elemento de uma lista, aqui está um exemplo

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

Neste exemplo, sei que foo$aexiste, mas o teste retorna FALSE.

Procurei ?existse descobri que with(foo, exists('a')retorna TRUE, mas não entendo por que exists('foo$a')retorna FALSE.

Questões

  • Por que exists('foo$a')retorna FALSE?
  • É o uso da with(...)abordagem preferida?

1
talvez !is.null(foo$a)(ou !is.null(foo[["a"]])para estar no lado seguro)? (ou exists("a",where=foo))
Ben Bolker,

1
@BenBolker obrigado - seria uma boa resposta; por que a última opção é preferida?
David LeBauer,

3
@David correspondência parcial ... tente o acima comfoo <- list(a1=1)
baptiste

Respostas:


151

Na verdade, isso é um pouco mais complicado do que você pensa. Como uma lista pode realmente (com algum esforço) conter elementos NULL, pode não ser o suficiente para verificar is.null(foo$a). Um teste mais rigoroso pode ser verificar se o nome está realmente definido na lista:

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

... e foo[["a"]]é mais seguro do que foo$a, uma vez que o último usa correspondência parcial e, portanto, também pode corresponder a um nome mais longo:

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

[ATUALIZAÇÃO] Então, de volta à pergunta por exists('foo$a')que não funciona. A existsfunção verifica apenas se uma variável existe em um ambiente, não se existem partes de um objeto. A string "foo$a"é interpretada literariamente: existe uma variável chamada "foo $ a"? ... e a resposta é 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
ainda não está claro - há uma razão para isso exists('foo$a') == FALSE?
David LeBauer,

Isso sugere que geralmente não há uma boa solução para isso no R! Alguém pode querer coisas mais complexas (como testar se $mylist[[12]]$out$mcerrorestá definido) que atualmente seriam complicadas como o inferno.
TMS

Você estava ciente do whereargumento existsapontado na resposta de @Jim ?
David LeBauer

"bar$a" <- 42Eu realmente gostaria que essa sintaxe fosse inválida e existisse ("foo $ a") funcionasse no sentido ingênuo.
Andy V

44

A melhor maneira de verificar os elementos nomeados é usar exist(), no entanto, as respostas acima não estão usando a função corretamente. Você precisa usar o whereargumento para verificar a variável na lista.

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

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

8
Usar exists()em uma lista funciona, mas acredito que R coage internamente para um ambiente antes de verificar um objeto com esse nome, o que é ineficiente e pode resultar em erros se houver algum elemento sem nome. Por exemplo, se você executar exists('a', list(a=1, 2)), ele irá dar um erro: Error in list2env(list(a = 1, 2), NULL, <environment>) : attempt to use zero-length variable name. A conversão acontece aqui: github.com/wch/r-source/blob/...
wch

5

Aqui está uma comparação de desempenho dos métodos propostos em outras respostas.

> 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

Se você planeja usar a lista como um dicionário rápido acessado muitas vezes, então a is.nullabordagem pode ser a única opção viável. Presumo que seja O (1), enquanto a %in%abordagem é O (n)?


4

Uma versão ligeiramente modificada de @ saliente.salamander, se alguém quiser verificar o caminho completo, pode ser usada.

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

Uma solução que ainda não surgiu é usar length, que lida com NULL com sucesso. Pelo que eu posso dizer, todos os valores, exceto NULL, têm um comprimento maior que 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

Assim, poderíamos fazer uma função simples que funcione com índices nomeados e numerados:

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

Se o elemento não existir, ele causará uma condição fora dos limites capturada pelo bloco tryCatch.


3

rlang::has_name() pode fazer isso também:

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

Como você pode ver, ele trata de maneira inerente todos os casos que @Tommy mostrou como tratar usando a base R e funciona para listas com itens não nomeados. Eu ainda recomendaria exists("bb", where = foo)como proposto em outra resposta para facilitar a leitura, mas has_nameé uma alternativa se você tiver itens sem nome.


0

Use purrr::has_elementpara verificar o valor de um elemento da lista:

> 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

Funciona se o elemento estiver aninhado / em qualquer nível de aninhamento? Eu verifiquei os documentos e não estava claro
David LeBauer

@DavidLeBauer, no. Nesse caso, eu usaria rapply(algo como any(rapply(x, function(v) identical(v, c(3, 4)), how = 'unlist')))
Dmitry Zotikov
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.