Comment supprimer une clé de Hash et obtenir le hachage restant dans Ruby / Rails?

560

Pour ajouter une nouvelle paire à Hash, je fais:

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

Existe-t-il un moyen similaire de supprimer une clé de Hash?

Cela marche:

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

mais je m'attendrais à avoir quelque chose comme:

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

Il est important que la valeur de retour soit le hachage restant, donc je pourrais faire des choses comme:

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

en une seule ligne.

Misha Moroshko
la source
1
Vous pouvez toujours étendre (ouvrir au moment de l'exécution) le hachage intégré pour ajouter cette méthode personnalisée si vous en avez vraiment besoin.
dbryson

Réponses:

750

Rails a un sauf / sauf! méthode qui renvoie le hachage avec ces clés supprimées. Si vous utilisez déjà Rails, il est inutile de créer votre propre version de cela.

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
la source
51
Vous n'avez pas besoin d'utiliser la pile complète de Rails. Vous pouvez inclure inclure ActiveSupport dans n'importe quelle application Ruby.
Fryie
10
Pour ajouter à la réponse de Fryie, vous n'avez même pas besoin de charger tout ActiveSupport; vous pouvez simplement les inclure ensuiterequire "active_support/core_ext/hash/except"
GMA
trop tard pour éditer: je voulais dire "inclure la gemme" pas "les inclure"
GMA
@GMA: lorsque vos cinq minutes de montage sont terminées, vous pouvez toujours copier, supprimer, modifier et republier un commentaire.
iconoclaste
212

Oneliner plain ruby, il ne fonctionne qu'avec un rubis> 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} 

La méthode Tap renvoie toujours l'objet sur lequel est invoqué ...

Sinon, si vous en avez besoin active_support/core_ext/hash(ce qui est automatiquement requis dans chaque application Rails), vous pouvez utiliser l'une des méthodes suivantes en fonction de vos besoins:

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

sauf qu'il utilise une approche de liste noire, il supprime donc toutes les clés répertoriées comme arguments, tandis que slice utilise une approche de liste blanche, il supprime donc toutes les clés qui ne sont pas répertoriées comme arguments. Il existe également la version bang de ces méthodes ( except!et slice!) qui modifient le hachage donné mais leur valeur de retour est différente les deux retournent un hachage. Il représente les clés supprimées slice!et les clés conservées pour 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
la source
18
+1 Il convient de mentionner que cette méthode est destructrice h. Hash#exceptne modifiera pas le hachage d'origine.
Merci
3
Utilisez h.dup.tap { |hs| hs.delete(:a) }pour éviter de modifier le hachage d'origine.
Magicode
182

Pourquoi ne pas simplement utiliser:

hash.delete(key)
dbryson
la source
2
@dbryson: Je suis d'accord que parfois ça n'en vaut pas la peine. Je me demande pourquoi il y a merge, merge!, deletemais pas detele!...
Misha Moroshko
1
si vous en avez vraiment besoin comme un seul paquebot:foo(hash.delete(key) || hash)
Bert Goethals
13
Il serait plus cohérent avec les conventions Ruby s'il deletene modifiait pas son paramètre et s'il delete!existait et modifiait son paramètre.
David J.
60
Cela ne renvoie pas le hachage restant comme mentionné dans la question, il renvoie la valeur associée à la clé supprimée.
MhdSyrwan
1
delete renvoie la clé mais elle modifie également le hachage. Quant à savoir pourquoi il n'y a pas de suppression !, je suppose que cela n'a pas de sens sur le plan sémantique d'appeler supprimer sur quelque chose et non de le supprimer. appeler hash.delete () par opposition à hash.delete! () serait un no-op.
eggmatters
85

Il existe de nombreuses façons de supprimer une clé d'un hachage et d'obtenir le hachage restant dans Ruby.

  1. .slice=> Il renverra les clés sélectionnées et ne les supprimera pas du hachage d'origine. Utilisez slice!si vous souhaitez supprimer définitivement les clés, sinon utilisez 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 => Il supprimera les clés sélectionnées du hachage d'origine (il ne peut accepter qu'une seule clé et pas plus d'une).

    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=> Il renverra les clés restantes mais ne supprimera rien du hachage d'origine. Utilisez except!si vous souhaitez supprimer définitivement les clés, sinon utilisez 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=> Dans le cas où vous devez supprimer une clé basée sur une valeur. Cela supprimera évidemment les clés correspondantes du hachage d'origine.

    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=> Il est utilisé pour supprimer toutes les nilvaleurs du hachage. Utilisez compact!si vous souhaitez supprimer nildéfinitivement les valeurs, sinon utilisez 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}

Résultats basés sur Ruby 2.2.2.

techdreams
la source
16
sliceet exceptsont ajoutés à l'aide de ActiveSupport::CoreExtensions::Hash. Ils ne font pas partie du noyau Ruby. Ils peuvent être utilisés parrequire 'active_support/core_ext/hash'
Madis Nõmme
3
Depuis Ruby 2.5 Hash#sliceest dans la bibliothèque standard. ruby-doc.org/core-2.5.0/Hash.html#method-i-slice Yay!
Madis Nõmme
38

Si vous voulez utiliser du Ruby pur (pas de Rails), ne voulez pas créer de méthodes d'extension (peut-être en avez-vous besoin seulement à un ou deux endroits et ne voulez pas polluer l'espace de noms avec des tonnes de méthodes) et ne voulez pas modifier le hachage en place (c'est-à-dire que vous êtes fan de la programmation fonctionnelle comme moi), vous pouvez «sélectionner»:

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

J'ai configuré cela de sorte que .remove renvoie une copie du hachage avec les clés supprimées, tout en supprimant! modifie le hachage lui-même. Ceci est conforme aux conventions rubis. par exemple, à partir de la 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)
=> {}
Max Williams
la source
26

Vous pouvez utiliser à except!partir de la facetsgemme:

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

Le hachage d'origine ne change pas.

EDIT: comme le dit Russel, les facettes ont des problèmes cachés et ne sont pas complètement compatibles avec l'API avec ActiveSupport. D'un autre côté, ActiveSupport n'est pas aussi complet que les facettes. À la fin, j'utiliserais AS et laisserais les cas de bord dans votre code.

réécrit
la source
Juste require 'facets/hash/except'et ce ne sont pas des "problèmes" (je ne sais pas quels problèmes ils seraient de toute façon autres que 100% AS API). Si vous faites un projet Rails en utilisant AS est logique, sinon Facets a une empreinte beaucoup plus petite.
trans
@trans ActiveSupport a de nos jours aussi une empreinte assez petite, et vous ne pouvez en exiger qu'une partie. Tout comme les facettes, mais avec beaucoup plus d'yeux (donc je suppose que cela obtient de meilleures critiques).
réécrit le
19

Au lieu de patcher des singes ou d'inclure inutilement de grandes bibliothèques, vous pouvez utiliser des améliorations si vous utilisez 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

Vous pouvez utiliser cette fonctionnalité sans affecter d'autres parties de votre programme, ni avoir à inclure de grandes bibliothèques externes.

class FabulousCode
  using HashExtensions

  def incredible_stuff
    delightful_hash.except(:not_fabulous_key)
  end
end
Mohamad
la source
17

en Rubis pur:

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

Voir Ruby on Rails: Supprimer plusieurs clés de hachage

hash.delete_if{ |k,| keys_to_delete.include? k }
Nakilon
la source
keys_to_delete.each {| k | hash.delete (k)} est beaucoup plus rapide pour les grands ensembles de données. corrigez-moi si je me trompe.
Vignesh Jayavel
@VigneshJayavel, vous avez raison mais OP voulait que le hachage soit retourné. eachretournerait le tableau.
Nakilon
3

C'était génial si supprimer renvoyait la paire de suppression du hachage. Je fais ça:

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

Il s'agit d'une méthode en ligne, mais elle n'est pas très lisible. Recommande d'utiliser deux lignes à la place.

use_remaining_hash_for_something(Proc.new { hash.delete(:key); hash }.call)
the_minted
la source
1
Hash#exceptet Hash#except!ont déjà été suffisamment mentionnés. La Proc.newversion n'est pas très lisible comme vous le mentionnez et aussi plus compliquée que use_remaining_hash_for_something(begin hash.delete(:key); hash end). Peut-être supprimez-vous simplement cette réponse.
Michael Kohl
1
J'ai raccourci ma réponse et supprimé ce qui avait déjà été dit. Garder ma réponse avec votre commentaire car ils répondent à la question et font de bonnes recommandations d'utilisation.
the_minted du
0

Plusieurs façons de supprimer Key in Hash. vous pouvez utiliser n'importe quelle méthode d'en bas

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

Il y a tellement de façons, vous pouvez consulter le document Ruby de Hash ici .

Je vous remercie

Ketan Mangukiya
la source
-12

Cela fonctionnerait également: hash[hey] = nil

fdghdfg
la source
3
h = {: a => 1,: b => 2,: c => 3}; h [: a] = zéro; h.chaque {| k, v | met k} n'est pas la même chose que: h = {: a => 1,: b => 2,: c => 3}; h. supprimer (: a); h.chaque {| k, v | met k}
obaqueiro
1
Supprimer une clé d'un hachage n'est pas la même chose que de supprimer la valeur d'une clé d'un hachage. Comme cela pourrait amener les gens à confondre, il serait préférable de supprimer cette réponse.
Sebastian Palma