Mrunal Pradeep Patekar
Mrunal Pradeep Patekar

Reputation: 157

Group by, array of arrays in ruby

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

Answers (5)

Sagar Pandya
Sagar Pandya

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

Cary Swoveland
Cary Swoveland

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]}

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]]]      

Upvotes: 0

3limin4t0r
3limin4t0r

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

Salil
Salil

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

Aleksei Matiushkin
Aleksei Matiushkin

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

Related Questions