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]}
Use Enumerable#group_by
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]]]
some_arrayare the same except for their last (integer) values, so there is no grouping of elements. The examples given in the answers are better in that regard. (It is of course too late to change your example.)