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 !
3 Answers
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.
2 Comments
Chuck
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.Marc-André Lafortune
Hash.new{[]} would only work with assignment operators like +=, but with << it won't. I edited the answer accordingly.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
3 Comments
hurikhan77
+1 for a very elegant solution, but I'd like to mention that group_by may not available with older ruby APIs.
Matt S
I believe
.group_by is ruby 1.9 though there may be a rails option.Chuck
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.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]]}
2 Comments
Chuck
This seems more complicated than Matt's solution for no benefit. Why do you prefer this way of doing it?
hurikhan77
@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.