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.


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

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} 

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)

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.


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}

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)
=> {}

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.


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

17

em Ruby puro:

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


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} 

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)

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


-12

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


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