1

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

2
  • I think the reason you have received no upvotes is that all of the elements of some_array are 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.) Commented Apr 13, 2018 at 23:40
  • I think also no attempt was made to find a solution Commented Jan 16, 2020 at 17:13

5 Answers 5

7

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]] 
Sign up to request clarification or add additional context in comments.

5 Comments

Thank you so much. This works perfect for me. I have accepted the answer aswell.
Welcome, glad it helps :)
Though it is not clear from the question, I would expect the desired return value for your example would be [["FANUC CORP", "100048", [9, 26, 23, 111]], ["FANUC CORP", "100049", [19, 126, 123, 1111]]].
I agree, in that case we don't need to use flatten i.e. some_array.group_by{|a| [a[0], a[1]]}.map{|key, value| key + [value.map(&:last)]}
0
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]]

1 Comment

adding .pop.pop will bring the expected result of him p [[["FANUC CORP", "100048", [9, 26, 23, 111]]]].pop.pop
0

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.

Comments

0

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

Comments

0

Using transpose and yield_self:

some_array.transpose.yield_self { |*a, b| a.flatten.uniq << b }
 #=> ["FANUC CORP", "100048", [9, 26, 23, 111]]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.