Como remover uma chave do Hash e obter o hash restante no Ruby / Rails?

560

Para adicionar um novo par ao Hash, eu faço:

{:a => 1, :b => 2}.merge!({:c => 3})   #=> {:a => 1, :b => 2, :c => 3}

Existe uma maneira semelhante de excluir uma chave do Hash?

Isso funciona:

{:a => 1, :b => 2}.reject! { |k| k == :a }   #=> {:b => 2}

mas eu esperaria ter algo como:

{:a => 1, :b => 2}.delete!(:a)   #=> {:b => 2}

É importante que o valor retornado seja o hash restante, para que eu possa fazer coisas como:

foo(my_hash.reject! { |k| k == my_key })

em uma linha.

Misha Moroshko
fonte
1
Você sempre pode estender (abrir em tempo de execução) o Hash interno para adicionar esse método personalizado, se você realmente precisar.
dbryson

Respostas:

750

O Rails possui um exceto / exceto! método que retorna o hash com essas chaves removidas. Se você já está usando o Rails, não faz sentido criar sua própria versão disso.

class Hash
  # Returns a hash that includes everything but the given keys.
  #   hash = { a: true, b: false, c: nil}
  #   hash.except(:c) # => { a: true, b: false}
  #   hash # => { a: true, b: false, c: nil}
  #
  # This is useful for limiting a set of parameters to everything but a few known toggles:
  #   @person.update(params[:person].except(:admin))
  def except(*keys)
    dup.except!(*keys)
  end

  # Replaces the hash without the given keys.
  #   hash = { a: true, b: false, c: nil}
  #   hash.except!(:c) # => { a: true, b: false}
  #   hash # => { a: true, b: false }
  def except!(*keys)
    keys.each { |key| delete(key) }
    self
  end
end
Peter Brown
fonte
51
Você não precisa usar a pilha completa do Rails. Você pode incluir o ActiveSupport em qualquer aplicativo Ruby.
Fryie 27/09/2013
10
Para adicionar à resposta de Fryie, você nem precisa carregar todo o ActiveSupport; você pode apenas incluí-los entãorequire "active_support/core_ext/hash/except"
GMA
tarde demais para edit: eu quis dizer "incluem a jóia" não "incluí-los"
GMA
@ GMA: quando seus cinco minutos de edição terminam, você sempre pode copiar, excluir, modificar e repassar um comentário.
iconoclast
212

Oneliner plain ruby, funciona apenas com ruby> 1.9.x:

1.9.3p0 :002 > h = {:a => 1, :b => 2}
 => {:a=>1, :b=>2} 
1.9.3p0 :003 > h.tap { |hs| hs.delete(:a) }
 => {:b=>2} 

Toque método sempre retorna o objeto no qual é chamado ...

Caso contrário, se você solicitou active_support/core_ext/hash(o que é automaticamente necessário em todos os aplicativos Rails), você pode usar um dos seguintes métodos, dependendo de suas necessidades:

  ~  irb
1.9.3p125 :001 > require 'active_support/core_ext/hash' => true 
1.9.3p125 :002 > h = {:a => 1, :b => 2, :c => 3}
 => {:a=>1, :b=>2, :c=>3} 
1.9.3p125 :003 > h.except(:a)
 => {:b=>2, :c=>3} 
1.9.3p125 :004 > h.slice(:a)
 => {:a=>1} 

exceto utiliza uma abordagem de lista negra, por isso remove todas as chaves listadas como args, enquanto a fatia usa uma abordagem de lista de permissões, por isso remove todas as chaves que não estão listadas como argumentos. Também existe a versão bang desses métodos ( except!e slice!) que modificam o hash especificado, mas seu valor de retorno é diferente, ambos retornam um hash. Representa as chaves removidas slice!e as chaves mantidas para except!:

1.9.3p125 :011 > {:a => 1, :b => 2, :c => 3}.except!(:a)
 => {:b=>2, :c=>3} 
1.9.3p125 :012 > {:a => 1, :b => 2, :c => 3}.slice!(:a)
 => {:b=>2, :c=>3} 
Fabio
fonte
18
+1 Vale ressaltar que esse método é destrutivo h. Hash#exceptnão modificará o hash original.
Obrigado
3
Use h.dup.tap { |hs| hs.delete(:a) }para evitar modificar o hash original.
Magicode 13/11/2018
181

Por que não usar:

hash.delete(key)
dbryson
fonte
2
@ dbryson: Concordo que às vezes não vale a pena. Eu só quero saber por que há merge, merge!, delete, mas não detele!...
Misha Moroshko
1
se você realmente precisa dele como um liner:foo(hash.delete(key) || hash)
Bert Goethals
13
Seria mais consistente com as convenções de Ruby se deletese não modificar seu parâmetro e se delete!existiu e fez modificar seu parâmetro.
David J.
60
Isso não retorna o hash restante, conforme mencionado na pergunta, retornará o valor associado à chave excluída.
MhdSyrwan 9/07
1
delete retorna a chave, mas também altera o hash. Quanto ao motivo pelo qual não há exclusão !, meu palpite é que, semanticamente, não faz sentido chamar delete em algo e na verdade não o exclui. chamar hash.delete () em vez de hash.delete! () seria um no-op.
eggmatters 7/08/2015
85

Existem várias maneiras de remover uma chave de um hash e obter o restante no Ruby.

  1. .slice=> Ele retornará as teclas selecionadas e não as excluirá do hash original. Use slice!se você deseja remover as chaves permanentemente ou use simple slice.

    2.2.2 :074 > hash = {"one"=>1, "two"=>2, "three"=>3}
     => {"one"=>1, "two"=>2, "three"=>3} 
    2.2.2 :075 > hash.slice("one","two")
     => {"one"=>1, "two"=>2} 
    2.2.2 :076 > hash
     => {"one"=>1, "two"=>2, "three"=>3} 
  2. .delete => Ele excluirá as chaves selecionadas do hash original (ele pode aceitar apenas uma chave e não mais de uma).

    2.2.2 :094 > hash = {"one"=>1, "two"=>2, "three"=>3}
     => {"one"=>1, "two"=>2, "three"=>3} 
    2.2.2 :095 > hash.delete("one")
     => 1 
    2.2.2 :096 > hash
     => {"two"=>2, "three"=>3} 
  3. .except=> Ele retornará as chaves restantes, mas não excluirá nada do hash original. Use except!se você deseja remover as chaves permanentemente ou use simple except.

    2.2.2 :097 > hash = {"one"=>1, "two"=>2, "three"=>3}
     => {"one"=>1, "two"=>2, "three"=>3} 
    2.2.2 :098 > hash.except("one","two")
     => {"three"=>3} 
    2.2.2 :099 > hash
     => {"one"=>1, "two"=>2, "three"=>3}         
  4. .delete_if=> Caso você precise remover uma chave com base em um valor. Obviamente, ele removerá as chaves correspondentes do hash original.

    2.2.2 :115 > hash = {"one"=>1, "two"=>2, "three"=>3, "one_again"=>1}
     => {"one"=>1, "two"=>2, "three"=>3, "one_again"=>1} 
    2.2.2 :116 > value = 1
     => 1 
    2.2.2 :117 > hash.delete_if { |k,v| v == value }
     => {"two"=>2, "three"=>3} 
    2.2.2 :118 > hash
     => {"two"=>2, "three"=>3} 
  5. .compact=> É usado para remover todos os nilvalores do hash. Use compact!se você deseja remover os nilvalores permanentemente ou use simple compact.

    2.2.2 :119 > hash = {"one"=>1, "two"=>2, "three"=>3, "nothing"=>nil, "no_value"=>nil}
     => {"one"=>1, "two"=>2, "three"=>3, "nothing"=>nil, "no_value"=>nil} 
    2.2.2 :120 > hash.compact
     => {"one"=>1, "two"=>2, "three"=>3}

Resultados baseados em Ruby 2.2.2.

techdreams
fonte
16
slicee exceptsão adicionados usando ActiveSupport::CoreExtensions::Hash. Eles não fazem parte do núcleo do Ruby. Eles podem ser usados ​​porrequire 'active_support/core_ext/hash'
Madis Nõmme 9/10
3
Como o Ruby 2.5 Hash#sliceestá na biblioteca padrão. ruby-doc.org/core-2.5.0/Hash.html#method-i-slice Yay!
Madis Nõmme
38

Se você deseja usar Ruby puro (sem Rails), não deseja criar métodos de extensão (talvez você precise disso apenas em um ou dois lugares e não queira poluir o espaço de nome com vários métodos) e não deseja editar o hash no lugar (ou seja, você é fã de programação funcional como eu), pode 'selecionar':

>> x = {:a => 1, :b => 2, :c => 3}
=> {:a=>1, :b=>2, :c=>3}
>> x.select{|x| x != :a}
=> {:b=>2, :c=>3}
>> x.select{|x| ![:a, :b].include?(x)}
=> {:c=>3}
>> x
=> {:a=>1, :b=>2, :c=>3}
Yura Taras
fonte
30
#in lib/core_extensions.rb
class Hash
  #pass single or array of keys, which will be removed, returning the remaining hash
  def remove!(*keys)
    keys.each{|key| self.delete(key) }
    self
  end

  #non-destructive version
  def remove(*keys)
    self.dup.remove!(*keys)
  end
end

#in config/initializers/app_environment.rb (or anywhere in config/initializers)
require 'core_extensions'

Eu configurei isso para que .remove retorne uma cópia do hash com as chaves removidas enquanto remove! modifica o próprio hash. Isso está de acordo com as convenções ruby. por exemplo, do console

>> hash = {:a => 1, :b => 2}
=> {:b=>2, :a=>1}
>> hash.remove(:a)
=> {:b=>2}
>> hash
=> {:b=>2, :a=>1}
>> hash.remove!(:a)
=> {:b=>2}
>> hash
=> {:b=>2}
>> hash.remove!(:a, :b)
=> {}
Max Williams
fonte
26

Você pode usar a except!partir da facetsgema:

>> require 'facets' # or require 'facets/hash/except'
=> true
>> {:a => 1, :b => 2}.except(:a)
=> {:b=>2}

O hash original não muda.

EDIT: como diz Russel, as facetas têm alguns problemas ocultos e não são completamente compatíveis com a API do ActiveSupport. Por outro lado, o ActiveSupport não é tão completo quanto as facetas. No final, eu usaria o AS e deixaria os casos extremos no seu código.

reescrito
fonte
Apenas require 'facets/hash/except'e os seus não são "problemas" (não tenho certeza de quais problemas eles seriam, a não ser 100% AS API). Se você estiver executando um projeto Rails usando o AS, faz sentido; caso contrário, o Facets terá uma área de cobertura muito menor.
trans
Atualmente, o @trans ActiveSupport também possui uma área muito pequena, e você pode exigir apenas partes dela. Assim como facetas, mas com muito mais olhos nele (então suponho que receba melhores críticas).
reescrito
19

Em vez de aplicar patches em macacos ou incluir desnecessariamente grandes bibliotecas, você pode usar refinamentos se estiver usando o Ruby 2 :

module HashExtensions
  refine Hash do
    def except!(*candidates)
      candidates.each { |candidate| delete(candidate) }
      self
    end

    def except(*candidates)
      dup.remove!(candidates)
    end
  end
end

Você pode usar esse recurso sem afetar outras partes do seu programa ou precisar incluir grandes bibliotecas externas.

class FabulousCode
  using HashExtensions

  def incredible_stuff
    delightful_hash.except(:not_fabulous_key)
  end
end
Mohamad
fonte
17

em Ruby puro:

{:a => 1, :b => 2}.tap{|x| x.delete(:a)}   # => {:b=>2}
Gamov
fonte
13

Veja Ruby on Rails: Exclua várias chaves de hash

hash.delete_if{ |k,| keys_to_delete.include? k }
Nakilon
fonte
keys_to_delete.each {| k | hash.delete (k)} é muito mais rápido para grandes conjuntos de dados. me corrija se estiver errado.
Vignesh Jayavel
@VigneshJayavel, você está certo, mas o OP queria que o hash fosse retornado. eachretornaria a matriz.
Nakilon
3

Foi ótimo se delete retornar o par de exclusão do hash. Eu estou fazendo isto:

hash = {a: 1, b: 2, c: 3}
{b: hash.delete(:b)} # => {:b=>2}
hash  # => {:a=>1, :c=>3} 
frenesim
fonte
1

Essa é uma maneira de fazer uma linha, mas não é muito legível. Recomenda o uso de duas linhas.

use_remaining_hash_for_something(Proc.new { hash.delete(:key); hash }.call)
the_minted
fonte
1
Hash#excepte Hash#except!já foram mencionados o suficiente. A Proc.newversão não é muito legível como você mencionou e também é mais complicada do que use_remaining_hash_for_something(begin hash.delete(:key); hash end). Talvez apenas exclua esta resposta.
Michael Kohl
1
Encurtou minha resposta e removeu o que já havia sido dito. Mantendo minha resposta junto com seu comentário, porque eles respondem à pergunta e fazem boas recomendações para uso.
#
0

Várias maneiras de excluir Key in Hash. você pode usar qualquer método abaixo

hash = {a: 1, b: 2, c: 3}
hash.except!(:a) # Will remove *a* and return HASH
hash # Output :- {b: 2, c: 3}

hash = {a: 1, b: 2, c: 3}
hash.delete(:a) # will remove *a* and return 1 if *a* not present than return nil

Há tantas maneiras, você pode consultar o documento Ruby do Hash aqui .

Obrigado

Ketan Mangukiya
fonte
-12

Isso também funcionaria: hash[hey] = nil

fdghdfg
fonte
3
h = {: a => 1,: b => 2,: c => 3}; h [: a] = nulo; h.each {| k, v | coloca k} Não é o mesmo que: h = {: a => 1,: b => 2,: c => 3}; h.delete (: a); h.each {| k, v | puts k}
obaqueiro
1
Remover uma chave de um hash não é o mesmo que remover o valor de uma chave de um hash. Como isso pode levar as pessoas a confundir, seria melhor remover esta resposta.
9138 Sebastian Palma