110 votes

Comment extraire un sous-hachage d'un hachage ?

J'ai un hash :

h1 = {:a => :A, :b => :B, :c => :C, :d => :D}

Quelle est la meilleure façon d'extraire un sous-réseau comme celui-ci ?

h1.extract_subhash(:b, :d, :e, :f) # => {:b => :B, :d => :D}
h1 #=> {:a => :A, :c => :C}

5 votes

Note complémentaire : apidock.com/rails/Hash/slice%21

1 votes

@JanDvorak Cette question ne concerne pas seulement le retour d'un subhash mais aussi la modification d'un subhash existant. Ce sont des choses très similaires mais ActiveSupport a des moyens différents pour les traiter.

156voto

skalee Points 3227

ActiveSupport au moins depuis la version 2.3.8, fournit quatre méthodes pratiques : #slice , #except et leurs homologues destructeurs : #slice! et #except! . Ils ont été mentionnés dans d'autres réponses, mais pour les résumer en un seul endroit :

x = {a: 1, b: 2, c: 3, d: 4}
# => {:a=>1, :b=>2, :c=>3, :d=>4}

x.slice(:a, :b)
# => {:a=>1, :b=>2}

x
# => {:a=>1, :b=>2, :c=>3, :d=>4}

x.except(:a, :b)
# => {:c=>3, :d=>4}

x
# => {:a=>1, :b=>2, :c=>3, :d=>4}

Notez les valeurs de retour des méthodes bang. Elles ne vont pas seulement adapter le hachage existant mais aussi retourner les entrées supprimées (non conservées). Le site Hash#except! convient le mieux à l'exemple donné dans la question :

x = {a: 1, b: 2, c: 3, d: 4}
# => {:a=>1, :b=>2, :c=>3, :d=>4}

x.except!(:c, :d)
# => {:a=>1, :b=>2}

x
# => {:a=>1, :b=>2}

ActiveSupport ne nécessite pas l'ensemble de Rails, est assez léger. En fait, beaucoup de gemmes non-rails en dépendent, donc il est fort probable que vous l'ayez déjà dans Gemfile.lock. Il n'est pas nécessaire d'étendre la classe Hash par vous-même.

59voto

Gazler Points 23588

Si vous voulez spécifiquement que la méthode renvoie les éléments extraits mais que h1 reste le même :

h1 = {:a => :A, :b => :B, :c => :C, :d => :D}
h2 = h1.select {|key, value| [:b, :d, :e, :f].include?(key) } # => {:b=>:B, :d=>:D} 
h1 = Hash[h1.to_a - h2.to_a] # => {:a=>:A, :c=>:C} 

Et si vous voulez patcher ça dans la classe Hash :

class Hash
  def extract_subhash(*extract)
    h2 = self.select{|key, value| extract.include?(key) }
    self.delete_if {|key, value| extract.include?(key) }
    h2
  end
end

Si vous voulez simplement supprimer les éléments spécifiés du hachage, c'est beaucoup plus facile en utilisant la méthode suivante supprimer_if .

h1 = {:a => :A, :b => :B, :c => :C, :d => :D}
h1.delete_if {|key, value| [:b, :d, :e, :f].include?(key) } # => {:a=>:A, :c=>:C} 
h1  # => {:a=>:A, :c=>:C}

2 votes

C'est O(n2) - vous aurez une boucle sur le select, une autre boucle sur l'include qui sera appelée h1.size plusieurs fois.

3 votes

Bien que cette réponse soit décente pour ruby pur, si vous utilisez rails, la réponse ci-dessous (utilisant l'intégré slice ou except en fonction de vos besoins) est beaucoup plus propre

0 votes

.slice & .except sont les bonnes réponses, voir ci-dessous.

43voto

dhulihan Points 3321

Ruby 2.5 ajouté Hash#slice :

h = { a: 100, b: 200, c: 300 }
h.slice(:a)           #=> {:a=>100}
h.slice(:b, :c, :d)   #=> {:b=>200, :c=>300}

30voto

metakungfu Points 820

Si vous utilisez des rails , Hash#slice est la voie à suivre.

{:a => :A, :b => :B, :c => :C, :d => :D}.slice(:a, :c)
# =>  {:a => :A, :c => :C}

Si vous n'utilisez pas de rails , Hash#values_at retournera les valeurs dans l'ordre dans lequel vous les avez demandées. pour que vous puissiez faire ça :

def slice(hash, *keys)
  Hash[ [keys, hash.values_at(*keys)].transpose]
end

def except(hash, *keys)
  desired_keys = hash.keys - keys
  Hash[ [desired_keys, hash.values_at(*desired_keys)].transpose]
end

ex :

slice({foo: 'bar', 'bar' => 'foo', 2 => 'two'}, 'bar', 2) 
# => {'bar' => 'foo', 2 => 'two'}

except({foo: 'bar', 'bar' => 'foo', 2 => 'two'}, 'bar', 2) 
# => {:foo => 'bar'}

Explication :

Hors de {:a => 1, :b => 2, :c => 3} nous voulons {:a => 1, :b => 2}

hash = {:a => 1, :b => 2, :c => 3}
keys = [:a, :b]
values = hash.values_at(*keys) #=> [1, 2]
transposed_matrix =[keys, values].transpose #=> [[:a, 1], [:b, 2]]
Hash[transposed_matrix] #=> {:a => 1, :b => 2}

Si vous avez l'impression que le singe Parcheando est la voie à suivre, voici ce que vous voulez :

module MyExtension
  module Hash 
    def slice(*keys)
      ::Hash[[keys, self.values_at(*keys)].transpose]
    end
    def except(*keys)
      desired_keys = self.keys - keys
      ::Hash[[desired_keys, self.values_at(*desired_keys)].transpose]
    end
  end
end
Hash.include MyExtension::Hash

6voto

Vijay Points 113

Vous pouvez utiliser slice !(*keys) qui est disponible dans les extensions de base d'ActiveSupport.

initial_hash = {:a => 1, :b => 2, :c => 3, :d => 4}

extracted_slice = initial_hash.slice!(:a, :c)

initial_hash serait maintenant

{:b => 2, :d =>4}

La diapositive extraite serait maintenant

{:a => 1, :c =>3}

Vous pouvez consulter slice.rb in ActiveSupport 3.1.3

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X