3

Let us say I have an array of arrays

aoa = [1, 2, [3, 4], 5, [6, 7, 8], 9]

I want to extract arrays and individual elements as array like below

[[1,2,5,9],[3,4],[6,7,8]] #=>order is not important

I tried this but not sure how to deal with individual elements

aoa.map{|i|  i if i.kind_of?(Array)}.compact #=> [[3, 4], [6, 7, 8]] 

5 Answers 5

7

You can use partition (and the splat operator, as pointed by @CarySwoveland)

a, i = aoa.partition{ |i| i.is_a? Array }
# => [[[3, 4], [6, 7, 8]], [1, 2, 5, 9]] 
[*a, i]
# => [[3, 4], [6, 7, 8], [1, 2, 5, 9]]
Sign up to request clarification or add additional context in comments.

3 Comments

I prefer this solution. It is more direct, as it doesn't rely on the side effects of grouping by size.
Regarding your edit, what if aoa contains elements other than integers and arrays? Better to write !i.is_a?(Array). I think partition is the way to go here, and I think that's what readers liked about your answer. Your "edit" is a distraction. I'd delete it and add the extra step to modify the array returned by partition.
@CarySwoveland Agree, however I left the array check because the example in the question uses only integers. Thank you.
4

Enumerable#group_by returns a Hash, whose values are what you want:

aoa.group_by(&:size).values.map(&:flatten)
# => [[1, 2, 5, 9], [3, 4], [6, 7, 8]]

@Cary Swoveland pointed out that using size to group is a bad idea, because subarrays with the same size as Fixnum#size would cause unexpected result. group_by(&:class) should be used instead.

7 Comments

I would add .map(&:flatten) to meet required form from question.
Almost the same, only group_by(&:class)
@YevgeniyAnfilofyev Your solution follows OP's words, while mine follows the example in the question. I guess you are right, as that's what the accepted answer does. I'll keep it as it is for now though.
Yu, I think you need to go with @YevgeniyAnfilofyev's suggestion, as [1, [1,2,3,4,5,6,7,8], 2].group_by(&:size).values.map(&:flatten) #=> [[1, 1, 2, 3, 4, 5, 6, 7, 8, 2]] (because the array has 8 elements).
@CarySwoveland Isn't the result [[1, 2], [1, 2, 3, 4, 5, 6, 7, 8]]?
|
2

Here are three other ways, for:

aoa = [1, 'cat', [3, 4], 5, [6, 7, 8], 9]

1

is_array = ->(e) { Array===e } 
[aoa.reject(&is_array)].concat(aoa.select(&is_array))
  #=> [[1, "cat", 5, 9], [3, 4], [6, 7, 8]]

2

Adding a step to @Doguita's use of Enumerable#partition:

a, e = aoa.partition { |e| Array===e }
[e,*a]
  #=> [[1, "cat", 5, 9], [3, 4], [6, 7, 8]]

3

sorted = aoa.sort_by { |e| (Array===e) ? 1 : 0 }
[sorted.shift(aoa.count { |e| !(Array===e) })].concat(sorted)
  #=> [[1, "cat", 9, 5], [6, 7, 8], [3, 4]]

What if aoa contains only arrays?

All of these methods will return an array containing an empty array if all the elements of aoa are arrays. If an empty array is not desired, tack .reject(&:empty?) to the end. For example:

aoa = [[3, 4], [6, 7, 8]]
[aoa.reject(&is_array)].concat(aoa.select(&is_array))
  #=> [[], [3, 4], [6, 7, 8]]
[aoa.reject(&is_array)].concat(aoa.select(&is_array)).reject(&:empty?)
  #=> [[3, 4], [6, 7, 8]] 

aoa = [1, 'cat', 5, 9]
[aoa.reject(&is_array)].concat(aoa.select(&is_array))
  #=> [[1, "cat", 5, 9]]
[aoa.reject(&is_array)].concat(aoa.select(&is_array)).reject(&:empty?)
  #=> [[1, "cat", 5, 9]]

You can instead use reject!, but if you do, avoid the pitfall!

You can replace reject(&:empty?) with reject!(&:empty?), which is a bit more efficient, but if you do, remember than reject! returns nil if no change is made, so you need to write:

 aoa = [1, 'cat', 5, 9]
 arr = [aoa.reject(&is_array)].concat(aoa.select(&is_array))
   #=> [[1, "cat", 5, 9]]
 arr.reject!(&:empty?)
   #=> nil
 arr
   #=> [[1, "cat", 5, 9]]

Comments

2
nested_a = [[]]
aoa.each {|e| e.is_a?(Array) ? nested_a << e : nested_a[0] << e  }
#remove 1st nested array if empty(Occurs if there were no individual elements)
nested_a.shift if nested_a[0].empty?
nested_a # => [[1, 2, 5, 9], [3, 4], [6, 7, 8]]

1 Comment

Interesting! You might consider nested_a = aoa.each_with_object([[]]) { |e,arr|.... Also, I suggest you remove # in your last (comment) line to remind readers that nested_a is needed there.
1
 >   aoa.inject([[]]) {|temp, x| x.is_a?(Array) ? temp << x : (temp.first << x); temp }
 #=> [[1, 2, 5, 9], [3, 4], [6, 7, 8]]

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.