Best way to split arrays into multiple small arrays in ruby
Asked Answered
W

4

20

What is the simplest way to split arrays into multiple arrays based on some conditions? In my scenario, I need to move the integer and the string values to different arrays. I have tried split method, but does not work as expected.

x=[1,2,3,"a","b",4]
x.split {|item| item.kind_of? Fixnum}

In C#, there is a group by option in Linq, which helps you group the objects based on the conditions. Is there a similar method on Object (not using activerecord) ?

Is there a simple way?

Wallaroo answered 16/4, 2011 at 12:33 Comment(0)
L
42

You're looking for Enumerable#partition:

x = [1, 2, 3, "a", "b", 4]
numbers, not_numbers = x.partition{|item| item.kind_of?(Fixnum)}
# => [[1, 2, 3, 4], ["a", "b"]]
Looksee answered 16/4, 2011 at 12:42 Comment(2)
+1 It's a solid win for speed too! See the benchmarks I added.Jason
Answers below use group_by, which allows splitting into more than two groups.Mcfarland
J
9

Just to throw some more solutions into the pool:

x = [1,2,3,"a","b",4]

numbers = x.select{ |e| e.is_a?(Fixnum) } # => [1, 2, 3, 4]
letters = x - numbers # => ["a", "b"]

numbers = x.select{ |e| e.kind_of?(Fixnum) } # => [1, 2, 3, 4]
letters = x - numbers # => ["a", "b"]

or

(numbers, letters) = x.group_by {|a| a.class}.values_at(Fixnum, String)
numbers # => [1, 2, 3, 4]
letters # => ["a", "b"]

Along with some benchmarks showing how a subtle change effects speed:

require 'benchmark'

x = [1,2,3,"a","b",4] * 100
n = 10_000
Benchmark.bm do |bench|
  bench.report { n.times {
    numbers = x.select{ |e| e.is_a?(Fixnum) }
    letters = x - numbers
  }}

  bench.report { n.times {
    numbers = x.select{ |e| e.kind_of?(Fixnum) }
    letters = x - numbers
  }}

  bench.report { n.times {
    (numbers, letters) = x.group_by {|a| a.class}.values_at(Fixnum, String)
  }}

  bench.report { n.times {
    numbers, not_numbers = x.partition{|item| item.kind_of? Fixnum}
  }}
end
# >>       user     system      total        real
# >>   4.270000   0.010000   4.280000 (  4.282922)
# >>   4.290000   0.000000   4.290000 (  4.288720)
# >>   5.160000   0.010000   5.170000 (  5.163695)
# >>   3.720000   0.000000   3.720000 (  3.721459)
Jason answered 17/4, 2011 at 3:43 Comment(2)
Strangely, I find .partition faster then even creating 2 arrays, and doing a .each and if e.kind_of(Fixnum). I guess they're doing some optimizations for .partition in C.Looksee
I haven't looked at the source but I have the same suspicion.Jason
B
5

Try:

x.group_by {|x| x.class}

You can get an array back by then calling to_a on the result which in the example you've given will return:

[[Fixnum, [1, 2, 3, 4]], [String, ["a", "b"]]]
Brilliantine answered 16/4, 2011 at 12:42 Comment(0)
S
3

Here is my solution:

hash = x.group_by { |t| t.kind_of? Fixnum }
# hash  => {true=>[1, 2, 3, 4], false=>["a", "b"]} 
array1 = hash[true] # The array of integers
array2 = hash[false] # The array of strings
Shagbark answered 16/4, 2011 at 12:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.