How do I extract a sub-hash from a hash?

I have a hash:

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

What is the best way to extract a sub-hash like this?

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

ActiveSupport, at least since 2.3.8, provides four convenient methods: #slice, #except and their destructive counterparts: #slice! and #except!. They were mentioned in other answers, but to sum them in one place:

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}

Note the return values of the bang methods. They will not only tailor existing hash but also return removed (not kept) entries. The Hash#except! suits best the example given in the 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 does not require whole Rails, is pretty lightweight. In fact, a lot of non-rails gems depend on it, so most probably you already have it in Gemfile.lock. No need to extend Hash class on your own.


If you specifically want the method to return the extracted elements but h1 to remain the same:

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} 

And if you want to patch that into the Hash class:

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

If you just want to remove the specified elements from the hash, that is much easier using 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} 

Ruby 2.5 added Hash#slice:

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

If you use rails, Hash#slice is the way to go.

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

If you don't use rails, Hash#values_at will return the values in the same order as you asked them so you can do this:

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

Explanation:

Out of {:a => 1, :b => 2, :c => 3} we want {: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}

If you feels like monkey patching is the way to go, following is what you want:

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

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}