2

I need this Hash

{"peter" => ["apple", "orange", "mango"], "sandra" => ["flowers", "bike"]}

convert to this Array:

[["peter", "apple"], ["peter", "orange"], ["peter", "mango"], ["sandra", "flowers"], ["sandra", "bike"]]

Now I have got this solution

my_hash.inject([]){|ar, (k,v)| ar << v.map{|c| [k,c]}}.flatten(1)

But I believe here is more elegant solution with those zip or transpose magick :)

0

3 Answers 3

5

You are right to be suspicious about Enumerable#inject solutions. In Ruby, inject/reduce is somewhat abused, we must be careful and choose the right abstraction (map, select, zip, flatten...) if they fit the problem at hand. In this case:

h = {"peter" => ["apple", "orange", "mango"], "sandra" => ["flowers", "bike"]}
h.map { |k, vs| vs.map { |v| [k, v] } }.flatten(1)
#=> [["peter", "apple"], ["peter", "orange"], ["peter", "mango"], ["sandra", "flowers"], ["sandra", "bike"]]

But if you want to use Enumerable#zip don't let anyone stop you ;-)

h.map { |k, vs| [k].cycle(vs.size).zip(vs) }.flatten(1)

And as @steenslag says, also:

h.map { |k, vs| [k].product(vs) }.flatten(1)

So at the end we can write:

h.flat_map { |k, vs| [k].product(vs) }
Sign up to request clarification or add additional context in comments.

8 Comments

tokland, that's cheating! He asked for zipor transpose magick! EDIT: Sorry, you edited it 23 seconds before I wrote this comment.
@serabe, I think fl00r was just mentioning some functional constructions, not that he specifically wanted a zip-based solution. My second snippet is just for fun, the first is probably more clear.
@Serabe, tokland is right, I was asking for any more elegant solution :)
[k].product(vs) in stead of [k].cycle(vs.size).zip(vs) ?
@steenslag: no zip in there, but yes, good idea to use product, added.
|
2
h.inject([]){|a,(k,vs)| a+vs.map {|v| [k,v]}}

You could also use this version

h.inject([]){|a,(k,vs)| a+=vs.map {|v| [k,v]}}

Which is most efficient because it use the same list rather than creating a new one at each iteration. However it feels wrong (for me) to use inject and modify a variable in place. An each version would do the same.

a = []; h.each {|k,vs| a+=vs.map {|v| [k,v]}}

It's slightly shorter and as expressive.

Comments

1

With zip

hash.inject([]){ |ar, (k,v)| ar << ([k]*v.size).zip(v) }

A plausible solution using transpose too:

[
  hash.keys.map{|k| [k]*hash[k].size }.flatten,
  hash.keys.map{|k| hash[k] }.flatten
].transpose

Take into account that:

  1. hash.keys should return the keys in the same order in both cases, so don't use it in other language unless you are sure of this.
  2. I would go with the first option.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.