Bala
Bala

Reputation: 11244

How to extract sub arrays and rest of elements as Array from an Array?

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

Upvotes: 3

Views: 407

Answers (5)

Doguita
Doguita

Reputation: 15703

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

Upvotes: 7

Edgar Mkaka Joel
Edgar Mkaka Joel

Reputation: 96

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

Upvotes: 2

Yu Hao
Yu Hao

Reputation: 122383

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.

Upvotes: 4

Cary Swoveland
Cary Swoveland

Reputation: 110675

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

Upvotes: 2

Amit Thawait
Amit Thawait

Reputation: 5292

 >   aoa.inject([[]]) {|temp, x| x.is_a?(Array) ? temp << x : (temp.first << x); temp }
 #=> [[1, 2, 5, 9], [3, 4], [6, 7, 8]]

Upvotes: 1

Related Questions