Как извлечь дополнительный хеш из хеша? - PullRequest
83 голосов
/ 27 января 2012

У меня есть хеш:

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

Каков наилучший способ извлечения такого подхеша, как этот?

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

Ответы [ 16 ]

124 голосов
/ 17 октября 2012

ActiveSupport, по крайней мере, начиная с 2.3.8, предоставляет четыре удобных метода: #slice, #except и их разрушительные аналоги: #slice! и #except!. Они упоминались в других ответах, но суммировали их в одном месте:

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}

Обратите внимание на возвращаемые значения методов взрыва. Они не только адаптируют существующий хеш, но и возвращают удаленные (не сохраненные) записи. Hash#except! лучше всего подходит для примера, приведенного в вопросе:

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 не требует целых Rails, довольно легкий. На самом деле, от него зависит множество драгоценных камней, не относящихся к рельсам, поэтому, скорее всего, они уже есть в Gemfile.lock. Нет необходимости самостоятельно расширять класс Hash.

55 голосов
/ 27 января 2012

Если вы хотите, чтобы метод возвращал извлеченные элементы, но h1 остался прежним:

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} 

А если вы хотите добавить это в класс 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

Если вы просто хотите удалить указанные элементы из хеша, это намного проще, используя 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} 
27 голосов
/ 31 мая 2015

Если вы используете рельсы , Hash # slice - это путь.

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

Если вы не используете rails , Hash # values_at вернет значения в том же порядке, в котором вы их задали , так что вы можете сделать это:

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

Объяснение:

Из {:a => 1, :b => 2, :c => 3} мы хотим {: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}

Если вы чувствуете, что патч обезьяны - это то, что вам нужно, вам нужно следующее:

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
18 голосов
/ 20 апреля 2018

Рубин 2,5 добавлено Хеш # ломтик :

h = { a: 100, b: 200, c: 300 }
h.slice(:a)           #=> {:a=>100}
h.slice(:b, :c, :d)   #=> {:b=>200, :c=>300}
5 голосов
/ 11 апреля 2012

Вы можете использовать слайс! (* Ключи), который доступен в основных расширениях ActiveSupport

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

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

initial_hash теперь будет

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

теперь извлеченный_скользящий будет

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

Вы можете посмотреть на slice.rb in ActiveSupport 3.1.3

4 голосов
/ 27 января 2012
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}
3 голосов
/ 28 июля 2015
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}
2 голосов
/ 18 июня 2012

если вы используете рельсы, может быть удобно использовать Hash.except

h = {a:1, b:2}
h1 = h.except(:a) # {b:2}
1 голос
/ 03 октября 2018

Оба delete_if и keep_if являются частью ядра Ruby. Здесь вы можете достичь того, что хотели бы, не исправляя тип Hash.

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

Для получения дополнительной информации, проверьте ссылки ниже из документации:

1 голос
/ 16 июля 2017

Вот быстрое сравнение производительности предлагаемых методов, #select кажется самым быстрым

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)

Уточнение будет выглядеть так:

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

И использовать его:

using ::CoreExtensions::Extractable
{ a: 1, b: 2, c: 3 }.extract(:a, :b)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...