tags:

views:

97

answers:

3

Hey ,Guys ,I have a question. I want to transform the array.[[1, [-1, 1]], [1, [20, 8]], [1, [30, 4]], [1, [40, 2]], [1, [41, 6]], [1, [70, 243]]] into this style [1,[[-1,1],[20,8],[30,4]...] or a hash [1=>...] How can i do this trick? thank you !

+6  A: 

Dict method:

array = [your array]
dict = {}
array.each{ |a| (dict[a[0]] ||= []) << a[1] }

For clarity Chuck's suggestion would bring this to:

array = [your array]
dict = Hash.new {|h,k| h[k] = []}
array.each{ |a| dict[a[0]] << a[1] }

You can then get an array from this in the style you want by doing:

new_arr = dict.select{|k,v| [k, v]}

Which will give you:

[[1, [[-1, 1], [20, 8], [30, 4], [40, 2], [41, 6], [70, 243]]]]

Notice the extra array, because if you had arrays begining with 2 you'd have another set at the end. So new_arr[0] will give you the array you were originally looking for.

Matt S
You could also easily derive the array equivalent this way. Create the hash, then do `hash.map {|key, values| [key, values]}`. Also, you can greatly simplify your `each` by creating the hash as `Hash.new {[]}` — then you don't need to to the `||= []` on each iteration, since nonexistent keys will return an empty array.
Chuck
`Hash.new{[]}` would only work with assignment operators like `+=`, but with `<<` it won't. I edited the answer accordingly.
Marc-André Lafortune
A: 

You can do this:

array = [[1, [-1, 1]], [1, [20, 8]], [1, [30, 4]],
         [1, [40, 2]], [1, [41, 6]], [1, [70, 243]]]

# map into hashes that can be merged together
hashes = array.map do |key,value|
  { key => [value] }
end

# fold all hashes into one hash by merging the values by key
merged_hash = hashes.inject({}) do |accu,value|
  accu.merge!(value) {|_,o,n| o|n }
end

This can be written as a not so easy one-liner:

array.map{|k,v|{k=>[v]}}.inject({}){|a,v|a.merge!(v){|_,o,n|o|n}}
#==> {1=>[[-1, 1], [20, 8], [30, 4], [40, 2], [41, 6], [70, 243]]}
hurikhan77
This seems more complicated than Matt's solution for no benefit. Why do you prefer this way of doing it?
Chuck
@Chuck It solves the problem without polluting the local variable namespace by using `inject`. And I prefer the `merge` method over ||=[]. Of course this is a matter of taste. But I think Matt's solution could be rewritten using `inject`, too, thus preventing to pollute the namespace with temp vars.
hurikhan77
+2  A: 

If you want it as a Hash, it's simply

h = Hash[ary.group_by(&:first).map {|k, v| [k, v.map(&:last)] }]

And if you want it as an Array, you just convert the Hash to an Array:

a = *h
Jörg W Mittag
+1 for a very elegant solution, but I'd like to mention that group_by may not available with older ruby APIs.
hurikhan77
I believe `.group_by` is ruby 1.9 though there may be a rails option.
Matt S
The code runs unmodified in Ruby 1.8.7 and newer. To use it in Ruby 1.8.6, you'll have to require backports for `Enumerable#group_by` and `Symbol#to_proc`.
Chuck