¿Cómo eliminar una clave de Hash y obtener el hash restante en Ruby/Rails?


Para añadir un nuevo par al Hash lo hago:

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

¿Existe una forma similar de eliminar una clave del Hash ?

Esto funciona:

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

Pero yo esperaría tener algo como:

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

Es importante que el valor devuelto sea el hash restante, por lo que podría hacer cosas como:

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

En una línea.

Author: Scott Schupbach, 2011-06-03

13 answers

Rails tiene un except / except! método que devuelve el hash con esas claves eliminadas. Si ya estás usando Rails, no tiene sentido crear tu propia versión de esto.

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
 626
Author: Peter Brown,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-03-20 01:27:49

Oneliner plain ruby, solo funciona con 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} 

El método Tap siempre devuelve el objeto sobre el que se invoca...

De lo contrario, si ha requerido active_support/core_ext/hash (que se requiere automáticamente en cada aplicación Rails), puede usar uno de los siguientes métodos según sus necesidades:

➜  ~  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} 

Excepto que usa un enfoque de lista negra, por lo que elimina todas las claves listadas como args, mientras que slice usa un enfoque de lista blanca, por lo que elimina todas las claves que no aparecen como argumentos. También existe la versión bang de esos métodos (except! y slice!) que modifican el hash dado, pero su valor de retorno es diferente. Representa las claves eliminadas para slice! y las claves que se mantienen 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} 
 174
Author: Fabio,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-04 10:24:57

¿Por qué no usar:

hash.delete(key)
 140
Author: dbryson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-15 14:51:28

Hay muchas maneras de eliminar una clave de un hash y obtener el hash restante en Ruby.

  1. .slice => Devolverá las claves seleccionadas y no las eliminará del 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 => Eliminará las claves seleccionadas del hash original (solo puede aceptar una clave y no más de una)

    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 => Devolverá las claves restantes pero no borrará nada del original hash

    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 => En caso de que necesite eliminar una clave basada en un valor. Obviamente, eliminará las claves coincidentes del 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 basados en Ruby 2.2.2.

 46
Author: techdreams,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-30 15:05:36

Si quieres usar pure Ruby (sin Rails), no quieres crear métodos de extensión (tal vez necesitas esto solo en uno o dos lugares y no quieres contaminar el espacio de nombres con toneladas de métodos) y no quieres editar hash en su lugar (es decir, eres fan de la programación funcional como yo), puedes 'seleccionar':

>> 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}
 33
Author: Yura Taras,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-19 13:55:03
#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'

He configurado esto para que .remove devuelve una copia del hash con las claves eliminadas, mientras que remove! modifica el hash en sí. Esto está en consonancia con las convenciones de ruby. por ejemplo, desde la consola

>> 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)
=> {}
 30
Author: Max Williams,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-06 11:13:50

Puedes usar except! desde la gema facets:

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

El hash original no cambia.

EDIT: como dice Russel, facets tiene algunos problemas ocultos y no es completamente compatible con la API de ActiveSupport. Por otro lado, ActiveSupport no es tan completo como facets. Al final, usaría COMO y dejaría que los casos extremos en su código.

 27
Author: rewritten,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-12-19 10:30:02

En lugar de aplicar parches monkey o incluir innecesariamente bibliotecas grandes, puede usar refinamientos si está utilizando 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

Puede usar esta característica sin afectar otras partes de su programa, o tener que incluir grandes bibliotecas externas.

class FabulousCode
  using HashExtensions

  def incredible_stuff
    delightful_hash.except(:not_fabulous_key)
  end
end
 18
Author: Mohamad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-12 19:53:44

En Rubí puro:

{:a => 1, :b => 2}.tap{|x| x.delete(:a)}   # => {:b=>2}
 15
Author: gamov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-07 04:25:13

Ver Ruby on Rails: Eliminar múltiples claves hash

hash.delete_if{ |k,| keys_to_delete.include? k }
 11
Author: Nakilon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:54

Esta es una forma de una línea para hacerlo, pero no es muy legible. Recomiendo usar dos líneas en su lugar.

use_remaining_hash_for_something(Proc.new { hash.delete(:key); hash }.call)
 0
Author: the_minted,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-14 12:00:52

Fue genial si delete devuelve el par delete del hash. Estoy haciendo esto:

hash = {a: 1, b: 2, c: 3}
{b: hash.delete(:b)} # => {:b=>2}
hash  # => {:a=>1, :c=>3} 
 0
Author: frenesim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-02 09:48:38

Esto también funcionaría: hash[hey] = nil

 -7
Author: fdghdfg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-04-12 18:43:13