Come rimuovere una chiave da Hash e ottenere l'hash rimanente in Ruby / Rails?


560

Per aggiungere una nuova coppia a Hash, faccio:

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

Esiste un modo simile per eliminare una chiave da Hash?

Questo funziona:

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

ma mi aspetterei di avere qualcosa di simile:

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

È importante che il valore di ritorno sarà l'hash rimanente, quindi potrei fare cose come:

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

in una riga.


1
Puoi sempre estendere (apri in fase di esecuzione) l'hash incorporato per aggiungere questo metodo personalizzato se ne hai davvero bisogno.
Dbryson,

Risposte:


750

Rails ha un tranne / tranne! metodo che restituisce l'hash con quelle chiavi rimosse. Se stai già utilizzando Rails, non ha senso creare la tua versione di questo.

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
Non è necessario utilizzare l'intero stack di Rails. Puoi includere ActiveSupport in qualsiasi applicazione Ruby.
Fryie,

10
Per aggiungere alla risposta di Fryie, non è nemmeno necessario caricare tutto ActiveSupport; puoi semplicemente includerli allorarequire "active_support/core_ext/hash/except"
GMA

troppo tardi per modificarlo: intendevo "includere la gemma" non "includerli"
GMA

@GMA: quando i tuoi cinque minuti di modifica sono scaduti, puoi sempre copiare, eliminare, modificare e ripubblicare un commento.
iconoclast

212

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

Il metodo Tap restituisce sempre l'oggetto su cui viene invocato ...

Altrimenti, se richiesto active_support/core_ext/hash(che è automaticamente richiesto in ogni applicazione Rails) è possibile utilizzare uno dei seguenti metodi a seconda delle esigenze:

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

tranne usa un approccio di blacklist, quindi rimuove tutte le chiavi elencate come args, mentre slice usa un approccio di whitelist, quindi rimuove tutte le chiavi che non sono elencate come argomenti. Esiste anche la versione bang di quel metodo ( except!e slice!) che modifica l'hash dato ma il loro valore di ritorno è diverso entrambi restituiscono un hash. Rappresenta le chiavi rimosse per slice!e le chiavi conservate per 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 la pena ricordare che questo metodo è distruttivo h. Hash#exceptnon modificherà l'hash originale.
Grazie

3
Utilizzare h.dup.tap { |hs| hs.delete(:a) }per evitare di modificare l'hash originale.
Magicode,

182

Perché non usare solo:

hash.delete(key)

2
@dbryson: sono d'accordo che a volte non ne vale la pena. Mi chiedo solo perché ci sono merge, merge!, delete, ma non detele!...
Misha Moroshko

1
se ne hai davvero bisogno come una sola fodera fai:foo(hash.delete(key) || hash)
Bert Goethals

13
Sarebbe più coerente con le convenzioni di Ruby se deleteha non modificare il suo parametro e se delete!esistesse e ha fatto modificare il suo parametro.
David J.

60
Questo non restituisce l'hash rimanente come indicato nella domanda, restituirà il valore associato alla chiave eliminata.
MhdSyrwan,

1
delete restituisce la chiave ma modifica anche l'hash. Per quanto riguarda il motivo per cui non esiste l'eliminazione !, la mia ipotesi è che semanticamente non ha senso chiamare Elimina su qualcosa e non effettivamente eliminarlo. chiamare hash.delete () invece di hash.delete! () sarebbe un no-op.
Eggmatters

85

Esistono molti modi per rimuovere una chiave da un hash e ottenere l'hash rimanente in Ruby.

  1. .slice=> Restituirà le chiavi selezionate e non le cancellerà dall'hash originale. Utilizzare slice!se si desidera rimuovere le chiavi in ​​modo permanente, altrimenti utilizzare semplice 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 => Elimina le chiavi selezionate dall'hash originale (può accettare solo una chiave e non più di 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=> Restituirà le chiavi rimanenti ma non cancellerà nulla dall'hash originale. Utilizzare except!se si desidera rimuovere le chiavi in ​​modo permanente, altrimenti utilizzare semplice 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=> Nel caso in cui sia necessario rimuovere una chiave in base a un valore. Rimuoverà ovviamente le chiavi corrispondenti dall'hash originale.

    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=> Viene utilizzato per rimuovere tutti i nilvalori dall'hash. Utilizzare compact!se si desidera rimuovere nilpermanentemente i valori, altrimenti utilizzare 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}

Risultati basati su Ruby 2.2.2.


16
slicee exceptvengono aggiunti utilizzando ActiveSupport::CoreExtensions::Hash. Non fanno parte del nucleo di Ruby. Possono essere usati darequire 'active_support/core_ext/hash'
Madis Nõmme il

3
Dal momento che Ruby 2.5 Hash#sliceè nella libreria standard. ruby-doc.org/core-2.5.0/Hash.html#method-i-slice Yay!
Madis Nõmme,

38

Se vuoi usare Ruby puro (no Rails), non vuoi creare metodi di estensione (forse ne hai bisogno solo in uno o due posti e non vuoi inquinare lo spazio dei nomi con tonnellate di metodi) e non vuoi modifica l'hash sul posto (ovvero, sei un fan della programmazione funzionale come me), puoi "selezionare":

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

Ho impostato questo in modo che .remove restituisca una copia dell'hash con le chiavi rimosse, mentre rimuovi! modifica l'hash stesso. Questo è in linea con le convenzioni sul rubino. ad es. dalla 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

Puoi usare except!dalla facetsgemma:

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

L'hash originale non cambia.

EDIT: come dice Russel, le sfaccettature hanno alcuni problemi nascosti e non sono completamente compatibili con le API con ActiveSupport. Dall'altro lato, ActiveSupport non è completo come le sfaccettature. Alla fine, userei AS e lascerei i casi limite nel tuo codice.


Giusto require 'facets/hash/except'e loro non sono "problemi" (non sono sicuro di quali problemi sarebbero comunque diversi dal 100% AS API). Se stai facendo un progetto Rails usando AS ha senso, altrimenti Facets ha un footprint molto più piccolo.
Trans

Oggi @trans ActiveSupport ha anche un ingombro piuttosto ridotto e puoi richiederne solo una parte. Proprio come le sfaccettature, ma con molti più occhi su di esso (quindi suppongo che ottenga recensioni migliori).
riscritto il

19

Invece di rattoppare le scimmie o includere inutilmente grandi librerie, puoi usare i perfezionamenti se stai usando 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

È possibile utilizzare questa funzione senza influire su altre parti del programma o senza dover includere librerie esterne di grandi dimensioni.

class FabulousCode
  using HashExtensions

  def incredible_stuff
    delightful_hash.except(:not_fabulous_key)
  end
end

17

in puro rubino:

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


3

È stato fantastico se delete restituisce la coppia di eliminazione dell'hash. Sto facendo questo:

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

1

Questo è un modo in una riga per farlo, ma non è molto leggibile. Consiglia invece di utilizzare due righe.

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

1
Hash#excepte Hash#except!sono già stati menzionati abbastanza. La Proc.newversione non è molto leggibile come dici tu e anche più complicata di use_remaining_hash_for_something(begin hash.delete(:key); hash end). Forse basta cancellare questa risposta.
Michael Kohl,

1
Abbreviato la mia risposta e rimosso ciò che era già stato detto. Mantenere la mia risposta insieme al tuo commento perché rispondono alla domanda e danno buoni consigli per l'uso.
the_minted

0

Diversi modi per eliminare la chiave in hash. puoi usare qualsiasi metodo dal basso

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

Tanti modi è lì, si può guardare su Ruby doc di Hash qui .

Grazie


-12

Questo funzionerebbe anche: hash[hey] = nil


3
h = {: a => 1,: b => 2,: c => 3}; h [: a] = nil; h.each {| k, v | put k} Non è lo stesso di: h = {: a => 1,: b => 2,: c => 3}; h.delete (a); h.each {| k, v | mette k}
obaqueiro

1
Rimuovere una chiave da un hash non equivale a rimuovere il valore di una chiave da un hash. Poiché ciò potrebbe indurre le persone a confondersi, sarebbe meglio rimuovere questa risposta.
Sebastian Palma,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.