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.

1voto

Victor Moroz Points 4689
class Hash
  def extract(*keys)
    key_index = Hash[keys.map{ |k| [k, true] }] # depends on the size of keys
    partition{ |k, v| key_index.has_key?(k) }.map{ |group| Hash[group] }  
  end
end

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

1voto

Vadym Tyemirov Points 918

Voici une comparaison rapide des performances des méthodes proposées, #select semble être le plus rapide

k = 1_000_000
Benchmark.bmbm do |x|
  x.report('select') { k.times { {a: 1, b: 2, c: 3}.select { |k, _v| [:a, :b].include?(k) } } }
  x.report('hash transpose') { k.times { Hash[ [[:a, :b], {a: 1, b: 2, c: 3}.fetch_values(:a, :b)].transpose ] } }
  x.report('slice') { k.times { {a: 1, b: 2, c: 3}.slice(:a, :b) } }
end

Rehearsal --------------------------------------------------
select           1.640000   0.010000   1.650000 (  1.651426)
hash transpose   1.720000   0.010000   1.730000 (  1.729950)
slice            1.740000   0.010000   1.750000 (  1.748204)
----------------------------------------- total: 5.130000sec

                     user     system      total        real
select           1.670000   0.010000   1.680000 (  1.683415)
hash transpose   1.680000   0.010000   1.690000 (  1.688110)
slice            1.800000   0.010000   1.810000 (  1.816215)

Le raffinement ressemblera à ceci :

module CoreExtensions
  module Extractable
    refine Hash do
      def extract(*keys)
        select { |k, _v| keys.include?(k) }
      end
    end
  end
end

Et de l'utiliser :

using ::CoreExtensions::Extractable
{ a: 1, b: 2, c: 3 }.extract(:a, :b)

0voto

Andy Points 2015

Ce code injecte la fonctionnalité que vous demandez dans la classe Hash :

class Hash
    def extract_subhash! *keys
      to_keep = self.keys.to_a - keys
      to_delete = Hash[self.select{|k,v| !to_keep.include? k}]
      self.delete_if {|k,v| !to_keep.include? k}
      to_delete
    end
end

et produit les résultats que vous avez fournis :

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

Remarque : cette méthode renvoie en fait les clés/valeurs extraites.

0voto

Martinos Points 187

Voici une solution fonctionnelle qui peut être utile si vous n'êtes pas sous Ruby 2.5 et dans le cas où vous ne voulez pas polluer votre classe Hash en ajoutant une nouvelle méthode :

slice_hash = -> keys, hash { hash.select { |k, _v| keys.include?(k) } }.curry

Ensuite, vous pouvez l'appliquer même sur des hachages imbriqués :

my_hash = [{name: "Joe", age: 34}, {name: "Amy", age: 55}]
my_hash.map(&slice_hash.([:name]))
# => [{:name=>"Joe"}, {:name=>"Amy"}]

0voto

YasirAzzu Points 48

Juste un ajout à la méthode slice, si les clés de sous-hachage que vous voulez séparer du hachage original vont être dynamiques, vous pouvez faire comme suit,

slice(*dynamic_keys) # dynamic_keys should be an array type

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