ruby - into - ¿Cómo extraigo un sub hash de un hash?
hash.map ruby (12)
Aquí hay una comparación de rendimiento rápido de los métodos sugeridos, #select
parece ser el más rápido
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)
El refinamiento se verá así:
module CoreExtensions
module Extractable
refine Hash do
def extract(*keys)
select { |k, _v| keys.include?(k) }
end
end
end
end
Y para usarlo:
using ::CoreExtensions::Extractable
{ a: 1, b: 2, c: 3 }.extract(:a, :b)
Tengo un hash:
h1 = {:a => :A, :b => :B, :c => :C, :d => :D}
¿Cuál es la mejor manera de extraer un sub hash como este?
h1.extract_subhash(:b, :d, :e, :f) # => {:b => :B, :d => :D}
h1 #=> {:a => :A, :c => :C}
Aquí hay una solución funcional que puede ser útil si no está utilizando Ruby 2.5 y en el caso de que no quiera contaminar su clase Hash agregando un nuevo método:
slice_hash = -> keys, hash { hash.select { |k, _v| keys.include?(k) } }.curry
Luego puede aplicarlo incluso en hashes anidados:
my_hash = [{name: "Joe", age: 34}, {name: "Amy", age: 55}]
my_hash.map(&slice_hash.([:name]))
# => [{:name=>"Joe"}, {:name=>"Amy"}]
Este código inyecta la funcionalidad que está solicitando en la clase 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
y produce los resultados que proporcionó:
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}
Nota: este método realmente devuelve las claves / valores extraídos.
Puede usar slice! (* Keys) que está disponible en las extensiones principales de ActiveSupport
initial_hash = {:a => 1, :b => 2, :c => 3, :d => 4}
extracted_slice = initial_hash.slice!(:a, :c)
initial_hash ahora sería
{:b => 2, :d =>4}
extraído_slide sería ahora
{:a => 1, :c =>3}
Puede ver slice.rb in ActiveSupport 3.1.3
Si desea específicamente que el método devuelva los elementos extraídos, h1 permanecerá igual:
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}
Y si quieres aplicar un parche a la clase 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 solo desea eliminar los elementos especificados del hash, es mucho más fácil utilizar delete_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}
si usa rieles, puede ser conveniente usar Hash.except
h = {a:1, b:2}
h1 = h.except(:a) # {b:2}
Ruby 2.5 añadió Hash#slice :
h = { a: 100, b: 200, c: 300 }
h.slice(:a) #=> {:a=>100}
h.slice(:b, :c, :d) #=> {:b=>200, :c=>300}
ActiveSupport
, al menos desde 2.3.8, proporciona cuatro métodos convenientes: #slice
, #except
y sus contrapartes destructivas: #slice!
y #except!
. Se mencionaron en otras respuestas, pero para resumirlas en un solo lugar:
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}
Tenga en cuenta los valores de retorno de los métodos de explosión. No solo adaptarán el hash existente, sino que también devolverán las entradas eliminadas (no guardadas). The Hash#except!
se adapta mejor al ejemplo dado en la pregunta:
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
no requiere raíles completos, es bastante liviano. De hecho, muchas gemas que no son de rieles dependen de ello, así que lo más probable es que ya lo tengas en Gemfile.lock. No es necesario extender la clase Hash por su cuenta.
Si usa rieles , Hash#slice es el camino a seguir.
{:a => :A, :b => :B, :c => :C, :d => :D}.slice(:a, :c)
# => {:a => :A, :c => :C}
Si no utiliza los rieles , Hash # values_at devolverá los valores en el mismo orden en que los solicitó para que pueda hacer esto:
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''}
Explicación:
Fuera de {:a => 1, :b => 2, :c => 3}
queremos {: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 siente que el parche de monos es el camino a seguir, lo siguiente es lo que quiere:
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
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)
h1 = {:a => :A, :b => :B, :c => :C, :d => :D}
keys = [:b, :d, :e, :f]
h2 = (h1.keys & keys).each_with_object({}) { |k,h| h.update(k=>h1.delete(k)) }
#=> {:b => :B, :d => :D}
h1
#=> {:a => :A, :c => :C}
module HashExtensions
def subhash(*keys)
keys = keys.select { |k| key?(k) }
Hash[keys.zip(values_at(*keys))]
end
end
Hash.send(:include, HashExtensions)
{:a => :A, :b => :B, :c => :C, :d => :D}.subhash(:a) # => {:a => :A}