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

560

Para agregar un nuevo par a Hash lo hago:

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

¿Hay una forma similar de eliminar una clave de Hash?

Esto funciona:

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

pero esperaría tener algo como:

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

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

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

en una linea

Misha Moroshko
fuente
1
Siempre puede extender (abrir en tiempo de ejecución) el hash integrado para agregar este método personalizado si realmente lo necesita.
dbryson

Respuestas:

750

Rails tiene una excepción / excepto! método que devuelve el hash con esas claves eliminadas. Si ya está utilizando Rails, no tiene sentido crear su 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
Peter Brown
fuente
51
No tiene que usar la pila completa de Rails. Puede incluir incluir ActiveSupport en cualquier aplicación de Ruby.
Fryie
10
Para agregar a la respuesta de Fryie, ni siquiera necesita cargar todo ActiveSupport; puedes incluirlos entoncesrequire "active_support/core_ext/hash/except"
GMA
demasiado tarde para editar: quise decir "incluir la gema" no "incluirlos"
GMA
@GMA: cuando terminen los cinco minutos de edición, siempre puede copiar, eliminar, modificar y volver a publicar un comentario.
iconoclasta
212

Oneliner liso rubí, solo funciona con rubí> 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 lo ha requerido active_support/core_ext/hash(que se requiere automáticamente en todas las aplicaciones de 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 utiliza un enfoque de lista negra, por lo que elimina todas las claves enumeradas como argumentos, mientras que slice utiliza un enfoque de lista blanca, por lo que elimina todas las claves que no figuran como argumentos. También existe la versión de explosión de esos métodos ( except!y slice!) que modifican el hash dado pero su valor de retorno es diferente, ambos devuelven un hash. Representa las claves eliminadas slice!y las claves que se guardan 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
fuente
18
+1 Vale la pena mencionar que este método es destructivo h. Hash#exceptno modificará el hash original.
Gracias
3
Use h.dup.tap { |hs| hs.delete(:a) }para evitar modificar el hash original.
Magicode
182

¿Por qué no solo usar:

hash.delete(key)
dbryson
fuente
2
@dbryson: estoy de acuerdo en que a veces no vale la pena. Me pregunto por qué hay merge, merge!, delete, pero sin detele!...
Misha Moroshko
1
si realmente lo necesita como una línea:foo(hash.delete(key) || hash)
Bert Goethals
13
Sería más coherente con las convenciones de Ruby si deletehizo no modificar su parámetro y si delete!existió y modificó su parámetro.
David J.
6060
Esto no devuelve el hash restante como se menciona en la pregunta, devolverá el valor asociado con la clave eliminada.
MhdSyrwan
1
delete devuelve la clave pero también altera el hash. En cuanto a por qué no hay eliminar, supongo que semánticamente no tiene sentido llamar a eliminar en algo y no eliminarlo. llamar a hash.delete () en lugar de hash.delete! () sería un no-op.
Eggmatters
85

Hay muchas formas 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. Úselo slice!si desea eliminar las claves permanentemente; de ​​lo contrario, 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 => Eliminará las claves seleccionadas del hash original (puede aceptar solo 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 eliminará nada del hash original. Úselo except!si desea eliminar las claves permanentemente; de ​​lo contrario, 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=> 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} 
  5. .compact=> Se utiliza para eliminar todos los nilvalores del hash. Úselo compact!si desea eliminar los nilvalores permanentemente; de ​​lo contrario, 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 basados ​​en Ruby 2.2.2.

sueños tecnológicos
fuente
16
slicey exceptse agregan usando ActiveSupport::CoreExtensions::Hash. No son parte del núcleo de Ruby. Pueden ser utilizados porrequire 'active_support/core_ext/hash'
Madis Nõmme
3
Dado que Ruby 2.5 Hash#sliceestá en la biblioteca estándar. ruby-doc.org/core-2.5.0/Hash.html#method-i-slice ¡Yay!
Madis Nõmme
38

Si desea usar Ruby puro (sin Rails), no desea crear métodos de extensión (tal vez solo necesite esto en uno o dos lugares y no quiera contaminar el espacio de nombres con toneladas de métodos) y no quiera edite el hash en su lugar (es decir, usted es fanático de la programación funcional como yo), puede '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}
Yura Taras
fuente
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'

He configurado esto para que .remove devuelva una copia del hash con las claves eliminadas, ¡mientras que eliminar! modifica el hash mismo. Esto está de acuerdo con las convenciones de rubí. 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)
=> {}
Max Williams
fuente
26

Puedes usar except!desde la facetsgema:

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

El hash original no cambia.

EDITAR: como dice Russel, las facetas tienen algunos problemas ocultos y no son completamente compatibles con API con ActiveSupport. Por otro lado, ActiveSupport no es tan completo como las facetas. Al final, usaría AS y dejaría los casos extremos en su código.

reescrito
fuente
Justo require 'facets/hash/except'y no hay "problemas" (no estoy seguro de qué problemas serían de todos modos que no sean 100% como API). Si está haciendo un proyecto Rails con AS tiene sentido, si no, Facets tiene una huella mucho menor.
trans
@trans ActiveSupport hoy en día también tiene una huella bastante pequeña, y solo puede requerir partes de ella. Al igual que las facetas, pero con muchos más ojos en él (así que supongo que recibe mejores críticas).
reescrito el
19

En lugar de parchear mono o incluir innecesariamente grandes bibliotecas, puede usar mejoras 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 función 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
Mohamad
fuente
17

en puro rubí:

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

Ver Ruby on Rails: eliminar varias claves hash

hash.delete_if{ |k,| keys_to_delete.include? k }
Nakilon
fuente
keys_to_delete.each {| k | hash.delete (k)} es mucho más rápido para grandes conjuntos de datos. corrígeme si está equivocado.
Vignesh Jayavel
@VigneshJayavel, tienes razón, pero OP quería que se devolviera el hash. eachdevolvería la matriz.
Nakilon
3

Fue genial si eliminar devuelve el par de eliminación del hash. Estoy haciendo esto:

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

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

use_remaining_hash_for_something(Proc.new { hash.delete(:key); hash }.call)
the_minted
fuente
1
Hash#excepty ya Hash#except!se han mencionado lo suficiente. La Proc.newversión no es muy legible como mencionas y también es más complicada que use_remaining_hash_for_something(begin hash.delete(:key); hash end). Tal vez solo borre esta respuesta.
Michael Kohl
1
Acorté mi respuesta y eliminé lo que ya se había dicho. Mantengo mi respuesta junto con su comentario porque responden la pregunta y hacen buenas recomendaciones de uso.
the_minted
0

Múltiples formas de eliminar Key in Hash. puedes usar cualquier método desde abajo

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

Hay tantas maneras, puedes mirar Ruby doc de Hash aquí .

Gracias

Ketan Mangukiya
fuente
-12

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

fdghdfg
fuente
3
h = {: a => 1,: b => 2,: c => 3}; h [: a] = nulo; h.each {| k, v | pone k} No es lo mismo que: h = {: a => 1,: b => 2,: c => 3}; h.delete (: a); h.each {| k, v | pone k}
obaqueiro
1
Eliminar una clave de un hash no es lo mismo que eliminar el valor de una clave de un hash. Como esto podría llevar a las personas a confundirse, sería mejor eliminar esta respuesta.
Sebastian Palma el