Reputation: 157
I have an array as follows
some_array = [["FANUC CORP", "100048", 9],
["FANUC CORP", "100048", 26],
["FANUC CORP", "100048", 23],
["FANUC CORP", "100048", 111]]
And I want to group by in the following way:
=> ["FANUC CORP", "100048", [9, 26,23,111]]
Can anyone suggest something, any help will be appreciated
Upvotes: 1
Views: 3438
Reputation: 9508
Using transpose
and yield_self
:
some_array.transpose.yield_self { |*a, b| a.flatten.uniq << b }
#=> ["FANUC CORP", "100048", [9, 26, 23, 111]]
Upvotes: 0
Reputation: 110755
Here are two ways to obtain the desired return value.
arr = [
[:a, "1", "c", 1],
[:b, "2", "e", 4],
[:a, "1", "c", 2],
[:b, "2", "e", 5],
[:a, "1", "d", 3]
]
Use the form of Hash#update that employs a block to determine the values of keys that are present in both hashes being merged.
arr.each_with_object({}) do |(*all_but_last, last), h|
h.update(all_but_last=>[last]) { |_k,o,n| o+n }
end.map { |k,v| [*k,v] }
#=> [[:a, "1", "c", [1, 2]], [:b, "2", "e", [4, 5]], [:a, "1", "d", [3]]]
See the doc for Hash#update
(aka merge!
) for an explanation of the block's three variables, _k
, o
and n
. (_k
holds the value of the common key. That the first character of the variable name is an underscore signifies that it is not used in the block calculation. Often it would just be written _
.)
Note that map
's receiver is the following.
arr.each_with_object({}) do |(*all_but_last, last), h|
h.update(all_but_last=>[last]) { |_k,o,n| o+n }
end
#=> {[:a, "1", "c"]=>[1, 2], [:b, "2", "e"]=>[4, 5], [:a, "1", "d"]=>[3]}
Here it is helpful to use the splat operator to advantage.
arr.group_by { |*all_but_last,_| all_but_last }.
map { |_,a| [*a.first[0..-2], a.map(&:last)] }
#=> [[:a, "1", "c", [1, 2]], [:b, "2", "e", [4, 5]], [:a, "1", "d", [3]]]
Upvotes: 0
Reputation: 21150
I came up with a combination between @mudasobwa and @salil answers.
# backslashes so you can copy to console, remove them in a script
# mutates the original arrays
some_array.group_by { |a| a.shift(2) } \
.flat_map { |k, v| k << v.flatten }
# doesn't mutate the original arrays
some_array.group_by { |a| a[0, 2] } \
.flat_map { |k, v| k << v.map(&:last) }
#=> ["FANUC CORP", "100048", [9, 26, 23, 111]]
You could also change a[0, 2]
with a[0..1]
, whatever suits your taste best.
Upvotes: 0
Reputation: 47532
Use following
some_array.group_by{|a| [a[0], a[1]]}
.map{|key, value| key + [value.map(&:last)]}
.flatten(1)
For multiple values in group by
2.3.1 :046 > some_array = [["FANUC CORP", "100048", 9], ["FANUC CORP", "100048", 26]
, ["FANUC CORP", "100048", 23], ["FANUC CORP", "100048", 111]
, ["FANUC CORP", "100049", 19],["FANUC CORP", "100049", 126],
["FANUC CORP", "100049", 123], ["FANUC CORP", "100049", 1111]]
=> [["FANUC CORP", "100048", 9], ["FANUC CORP", "100048", 26],
["FANUC CORP", "100048", 23], ["FANUC CORP", "100048", 111],
["FANUC CORP", "100049", 19], ["FANUC CORP", "100049", 126],
["FANUC CORP", "100049", 123], ["FANUC CORP", "100049", 1111]]
2.3.1 :047 > some_array.group_by{|a| [a[0], a[1]]}
.map{|key, value| key + [value.map(&:last)]}
.flatten(1)
=> ["FANUC CORP", "100048", [9, 26, 23, 111],
"FANUC CORP", "100049", [19, 126, 123, 1111]]
Upvotes: 7
Reputation: 121010
some_array.
map(&:dup).
group_by { |arr| arr.shift(2) }.
map { |k, v| [k + [v.flatten]] }
#⇒ [[["FANUC CORP", "100048", [9, 26, 23, 111]]]]
Call .first
if all the elements of the original array have the same first and second elements.
Or, for the exact case you posted, it’s even easier:
some_array.
each_with_object(["FANUC CORP", "100048", []]) do |(_, _, v), acc|
acc.last << v
end
#⇒ ["FANUC CORP", "100048", [9, 26, 23, 111]]
Upvotes: 0