views:

86

answers:

8

I have an array that looks something like this:

[[100, "one"],
 [101, "one"],
 [102, "one"],
 [103, "two"],
 [104, "three"],
 [105, "three"]]

What I would like to do is create an array of hashes that looks like this

[{"one" => [100,101,102]},
 {"two" => [103]},
 {"three" => [104,105]}]

The number portion will always be unique, the string portion will have duplicates. Every way I think about doing this I get some long function, I would like to know the "rails way" of going about this, I'm sure there's some obscure function I am missing.

A: 

There are no really function to do that. You need use inject and create yourself you hash.

shingara
+5  A: 

Not a Rails helper, but a common Ruby idiom can get you there. A little

arr.inject({}) { |h,(v,k)| h[k] ||= []; h[k] << v; h }

will do the trick.

Joe
exactly what I was looking for. thanks!
Russ Bradberry
This is a little above and beyond, but can you explain what the hell the line above does to those of us who arent ruby inclined?
Dested
Well, very quickly: inject processes each element of the array through the block, accumulating the results. The first block parameter is the hash we're building; the second one is the n-th element of the array. We're just processing the array and building up a hash incrementally. The first step is to create an empty array for each key. The next step appends the next value to the array. Finally, we return the updated hash for further processing.
Joe
Instead of manually checking whether the key exists or not and conditionally initializing it, I would just let Ruby do the work for you and simply use a `Hash` with a default value: `arr.inject(Hash.new {|h, k| h[k] = [] }) {|h, (v, k)| h[k] << v; h }` Also, I am a big of using the K combinator (i.e. `Object#tap`) instead of an explicit separate return at the end: `arr.inject(Hash.new {|h, k| h[k] = [] }) {|h, (v, k)| h.tap { h[k] << v }}`
Jörg W Mittag
+2  A: 
array = [[100, "one"], [101, "one"], [102, "one"], [103, "two"], [104, "three"], [105, "three"]]
h = Hash.new { |h,k| h[k] = []}
array.each { |a| h[a[1]] << a[0] }
Sławosz
I was just about to post the same thing, except with with `each` block I would use 2 parameters to unpack the input instead of using array indexes i.e. `array.each { |value, key| h[key] << value }` Also, I would link to the [Hash documentation](http://ruby-doc.org/core/classes/Hash.html#M002840) that explains how passing a block to `new` works.
mikej
Nice! Totally forgot about default values for Ruby hashes.
Jonas Elfström
A: 

As shingara points out, this is pretty specific to the format of the array(s). You can do what you need like so:

a = [...your data...]
r = a.inject( {} ) do |h, el|
  h[el.last] ||= []
  h[el.last] << el.first
  h
end

That gives a result like: {'one' => [101, 102], ... } which is better than your request for an array of one-key hashes, IMO.

thenduks
A: 

Here's one implementation

your_array.inject(Hash.new{|h,k| h[k] = []}) do |result, (a, b)|
  result[b] << a
  result
end
Chubas
+1  A: 

If what you need is only group stuff then you can use the Rails group_by function :

[[100, "one"],
 [101, "one"],
 [102, "one"], 
 [103, "two"],
 [104, "three"],
 [105, "three"]].group_by { |a| a[1] }

 => #<OrderedHash {"three"=>[[104, "three"], [105, "three"]],
                   "two"=>[[103, "two"]], 
                   "one"=>[[100, "one"], [101, "one"], [102, "one"]]}

Not to far from what you need. So if you can use it as it stand, I guess that's fine, but if you need exaclty the format you said. I think it easier to do it yourself rather than usign this and converting.

mb14
`group_by` is also included in Ruby 1.8.7+
Jonas Elfström
A: 

Is there a specific reason why you need that specific format, i.e. an array of single-element hashes instead of just a bog-standard hash? Because in that case, it would literally be just

arr.group_by(&:last)
Jörg W Mittag
Jonas Elfström
A: 

The shortest way to get exactly what you ask for that I can come up with is:

a = [[100, "one"],
     [101, "one"],
     [102, "one"], 
     [103, "two"],
     [104, "three"],
     [105, "three"]]

b = a.group_by(&:pop)
#=> {"three"=>[[104], [105]], "two"=>[[103]], "one"=>[[100], [101], [102]]}

which is probably what you want.

please note that a gets ruined by this

a
#=> [[100], [101], [102], [103], [104], [105]]

if that bothers you, you can write

b = a.map(&:dup).group_by &:pop

instead.

And if you really want that format you wrote then you can add another map:

b.map{|h,k| [h => k]}
#=> [{"one" => [100,101,102]}, {"two" => [103]}, {"three" => [104,105]}]

So to sum up:

[[100, "one"],
 [101, "one"],
 [102, "one"], 
 [103, "two"],
 [104, "three"],
 [105, "three"]].group_by(&:pop).map{ |h,k| [h => k] }
ormuriauga