ruby-on-rails - value - select hash ruby




Como remover uma chave do hash e obter o hash restante em Ruby/Rails? (9)

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 de retorno seja o hash restante, para que eu possa fazer coisas como:

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

em uma linha.


Em vez de corrigir os 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 programa ou ter que incluir grandes bibliotecas externas.

class FabulousCode
  using HashExtensions

  def incredible_stuff
    delightful_hash.except(:not_fabulous_key)
  end
end

Esta é uma maneira de fazer uma linha, mas não é muito legível. Recomende usar duas linhas em vez disso.

use_remaining_hash_for_something(Proc.new { hash.delete(:key); hash }.call)

Há muitas maneiras de remover uma chave de um hash e obter o hash restante em Ruby.

  1. .slice => Ele retornará as chaves selecionadas e não as excluirá do hash original

    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 irá apagar 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

    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, 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} 

Resultados baseados em Ruby 2.2.2.


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


Por que não usar apenas:

hash.delete(key)

Se você quiser usar puro Ruby (não Rails), não queira criar métodos de extensão (talvez você precise disso apenas em um ou dois lugares e não queira poluir namespace com toneladas de métodos) e não quer editar hash no lugar (ou seja, você é fã de programação funcional como eu), você 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}

Você pode usar except! da gema das facets :

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

O hash original não muda.

EDIT: como Russel diz, facetas tem alguns problemas ocultos e não é completamente compatível com API com o ActiveSupport. Por outro lado, o ActiveSupport não é tão completo quanto as facetas. No final, eu usaria o AS e deixaria os casos de borda no seu código.


em puro Ruby:

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

#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 de rubi. 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)
=> {}






ruby-hash