Matchu
Matchu

Reputation: 85802

Ruby: split one hash into two based on desired keys

I'm hoping this question has a very simple answer. I can think of ways to do with with boring, annoying looping, but I'm hoping there's a more elegant solution.

If I have the following two variables:

hash = {:a => 1, :b => 2, :c => 3, :d => 4}
keyset = [:a, :c]

How can I get the following two hashes in the simplest way possible?

hash1 = {:a => 1, :c => 3}
hash2 = {:b => 3, :d => 4}

If the example doesn't make my goal clear, in essence, what I want is a hybrid between #delete and #delete_if - #delete returns the deleted value, whereas #delete_if allows me to delete in bulk. I would prefer a way to delete in bulk, and have the deleted values returned - or something equivalent.

Thanks!

Upvotes: 28

Views: 20888

Answers (5)

D-Rock
D-Rock

Reputation: 2676

With Rails/Active Support you can use the extract! method:

hash = {:a => 1, :b => 2, :c => 3, :d => 4}
keyset = [:a, :c]

hash2 = hash.extract! *keyset
>> {:a=>1, :c=>3}

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

Upvotes: 11

Ryan McGeary
Ryan McGeary

Reputation: 239924

Try Active Support with Hash#slice and/or Hash#except. The bang methods also exist:

$ irb
>> require 'active_support/core_ext'
=> true

>> hash = {:a => 1, :b => 2, :c => 3, :d => 4}
=> {:a=>1, :d=>4, :b=>2, :c=>3}
>> keyset = [:a, :c]
=> [:a, :c]

>> remainders = hash.slice!(*keyset)
=> {:d=>4, :b=>2}

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

Upvotes: 36

Renra
Renra

Reputation: 5649

If you don't mind external dependencies you can use https://github.com/renra/split-off-ruby Then you can do:

hash2 = hash.split_off!(:b, :d)

hash will still contain the original values for keys :a and :c. The above methods are good enough, but sometimes it's better to be expressive about your intent with the right method name, I believe.

Upvotes: 0

Kim Toms
Kim Toms

Reputation: 151

hash = { a: 1, b: 2, c: 3, d: 4 }
keyset = [:a, :c]

left, right = hash.partition {|k,v| keyset.include? k }

This leaves left and right as arrays of arrays; turn back into hash:

left = Hash[left]
right = Hash[right]

puts "left=#{left.inspect}"
puts "right=#{right.inspect}"

Upvotes: 14

Mark W
Mark W

Reputation: 2552

new_hash = {}
keyset.each {|i| new_hash[i] = hash.delete(i)}

That seemed to do it for me, without pulling in extra requirements

Upvotes: 13

Related Questions