1

Say I have a hash like this

{"k1"=>["v1"], "k2"=>["v2"], "k3"=>["v3"], "k4"=>["v4"]}

And I want it to look like this:

{"k1"=>"v1", "k2"=>"v2", "k3"=>"v3", "k4"=>"v4"}

Is there a simpler way to do it than this ugly inject?

h1 = {"k1"=>["v1"], "k2"=>["v2"], "k3"=>["v3"], "k4"=>["v4"]}
h2 = h1.inject({}){|h,v| h[v.first]=v.last.first; h}

5 Answers 5

8

Somewhat less ugly than your "inject" solution:

h1 = {"k1"=>["v1"], "k2"=>["v2"], "k3"=>["v3"], "k4"=>["v4"]}
h2 = Hash[*h1.map.flatten]
h2 # => {"k1"=>"v1", "k2"=>"v2", "k3"=>"v3", "k4"=>"v4"}

As @the Tin Man points out in a comment, if your value arrays might have more than one element then you'll need to do something slightly different for it to work as expected:

h2 = Hash[*h1.map{|k,v|[k,v[0]]}.flatten]
h2 # => {"k1"=>"v1", "k2"=>"v2", "k3"=>"v3", "k4"=>"v4"}
Sign up to request clarification or add additional context in comments.

6 Comments

It won't work if the value arrays have more than one element.
Only works in 1.8, where map returns an Array. In 1.9, you need Hash[*h1.map.to_a.flatten], since map gives you an Enumerator. Otherwise, very cool.
Note that (fortunately) there is no need to flatten the array (that was necessary for old versions of Ruby). An array of pairs define a mapping: Hash[h1.map { |k,v| [k, v[0]] }]
@Amaden, @tokland: thanks for the tip! I've been doing Ruby stuff for a long time and yet have never spent any time using Ruby 1.9...
@tokland, yes and you could avoid the array index by doing Hash[h1.map{|k,(v)| [k, v]}]
|
3

You could modify it in-place with a simple each:

h = {"k1"=>["v1"], "k2"=>["v2"], "k3"=>["v3"], "k4"=>["v4"]}
h.each { |k,v| h[k] = v[0] }

Or, if you want to make a copy, you can use a cleaner inject thusly:

flatter_h = h.inject({ }) { |x, (k,v)| x[k] = v[0]; x }

Or, if you have each_with_object available (i.e. Rails or Ruby 1.9):

flatter_h = h.each_with_object({ }) { |(k,v), x| x[k] = v[0] }

3 Comments

@Ryan: You should have left your's up, it was reasonable solution if you wanted to leave the original alone.
Wait, we can mutate? Well damn :)
I like the (k,v) thing in your inject option and went one step further with h.inject({}){|h,(k,(v))| h[k]=v; h}
1

Perhaps a somewhat more attractive use of inject. Hash#merge is your friend:

hash = {"k1"=>["v1"], "k2"=>["v2"], "k3"=>["v3"], "k4"=>["v4"]}
hash.inject({}) {|r,a| r.merge(a.first=>a.last.first)}

3 Comments

Using |r,(k,v)| would be even more attractive, I added a couple options with this notation to my answer.
@mu: Nice, I didn't know you could get at the key/value arguments that way.
@the Tin Man: destructuring hashes like that has worked for a long time. The oldest Ruby I have lying around is 1.8.6 and it works there, but I'm quite sure this already worked in earlier versions too.
1
>> h = {"k1"=>["v1"], "k2"=>["v2"], "k3"=>["v3"], "k4"=>["v4"]}
>> Hash[h.map { |k, vs| [k, vs.first] }]
=> {"k1"=>"v1", "k2"=>"v2", "k3"=>"v3", "k4"=>"v4"}

Comments

1

Combining the Hash[h.map { |k, v| [k, v.first] }] syntax from @tokland's answer with the array destructuring syntax introduced in @mu's h.inject({ }) { |x, (k,v)| x[k] = v[0]; x }, I came up with what I think is the cleanest solution:

h1 = {"k1"=>["v1"], "k2"=>["v2"], "k3"=>["v3"], "k4"=>["v4"]}
h2 = Hash[h1.map{|k,(v)| [k, v]}]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.