3

I want to split the following array into sub arrays so that the sub-arrays start and finish when the 1's start and finish...

a=[1,1,0,0,1,0,1,1,1]

so I end up with this as a new array...

=>  [[1,1],[1],[1,1,1]]

anyone got any ideas...?

royhowie
  • 11,075
  • 14
  • 50
  • 67
treeseal7
  • 739
  • 8
  • 22

6 Answers6

5

Simplest and most readable way would probably be:

a.chunk {|x| x==1 || nil}.map(&:last)
  #=> [[1, 1], [1], [1, 1, 1]]

If you were okay with using Ruby on Rails, you can use an even simpler solution:

a.split(0).reject(&:empty?)
  #=> [[1, 1], [1], [1, 1, 1]]
Sid
  • 2,683
  • 18
  • 22
  • Clever! I learned something new. I see from the [docs](http://ruby-doc.org/core-2.2.0/Enumerable.html#method-i-chunk) that you could also replace `nil` with `:_separator`. Note you could write `map(&:last)`. – Cary Swoveland May 08 '15 at 22:07
2

There are many ways you can accomplish this. One way is to turn the array into a string, split out the groups and remap it as an array (ignoring any empty groups):

a=[1,1,0,0,1,0,1,1,1]
a.join.split(/0/).map {|group| group.split(//).map(&:to_i) unless group == ''}.compact
#=> [[1,1],[1],[1,1,1]]
infused
  • 24,000
  • 13
  • 68
  • 78
  • I loved the approach, but I got strings rather than numbers using your code. To get numbers, I used: ` a.join.split(/0/).map {|group| group.split(//) unless group == ''}.compact .each {|ia| ia.map! {|i| i.to_i } }` – Myst May 08 '15 at 19:20
  • Although the approach is beautiful, it can cost a lot in resources (see the benchmarks I ran in my answer)... I think this is due to allocating strings and the use of intermediate objects. – Myst May 08 '15 at 20:38
2

I loved the many different answers! So I took he time to test some of them out.

Here's how I would go about it:

new_array = a.each_with_object([ [] ]) {|i, n| i == 1 ? ( n.last << i) : (n.last.empty? ? true : (n << []))}

The #each_with_object method allows me to iterate the array while using an object to store any data I collect along the way (the object is assign the variable n, which stands for 'new_array').

In this approach I collect the data into a an array of nested arrays [ [] ], adding 1's to the last nested array n.last << i when they are recognized and adding a new empty nested array n << [] if the data isn't what I want to collect (and the existing nested array isn't empty).

I use two inline if:else statement's, using the short hand:

condition ? do_if_true : do_if_false

Benchmarking some of the answers

Testing out some of the answers on my MacBook Pro, it seems my approach was the fastest so far... but maybe I'm biased.

Note regarding reports: Results are in seconds. Less is faster.

Two best results are in bold.

Report for array with 10 items, 100,000 iterations:

                    user     system      total        real

@tykowale's approach 0.210000 0.000000 0.210000 ( 0.209799)

@infused's approach 1.300000 0.010000 1.310000 ( 1.304084)

@CarySwoveland's approach 0.830000 0.000000 0.830000 ( 0.839012)

@Myst's approach 0.170000 0.000000 0.170000 ( 0.169915)

@Sid's approach 0.590000 0.000000 0.590000 ( 0.595671)

Report for array with 100 items, 10,000 iterations:

                    user     system      total        real

@tykowale's approach 0.160000 0.000000 0.160000 ( 0.155997)

@infused's approach 1.030000 0.000000 1.030000 ( 1.030392)

@CarySwoveland's approach 0.420000 0.010000 0.430000 ( 0.424801)

@Myst's approach 0.150000 0.000000 0.150000 ( 0.143403)

@Sid's approach 0.260000 0.000000 0.260000 ( 0.255548)

Report for array with 1,000 items, 1,000 iterations:

                    user     system      total        real

@tykowale's approach 0.150000 0.000000 0.150000 ( 0.160459)

@infused's approach 1.030000 0.000000 1.030000 ( 1.033616)

@CarySwoveland's approach 0.310000 0.000000 0.310000 ( 0.312325)

@Myst's approach 0.130000 0.000000 0.130000 ( 0.133339)

@Sid's approach 0.210000 0.000000 0.210000 ( 0.217960)

Report for array with 10,000 items, 100 iterations:

                    user     system      total        real

@tykowale's approach 0.250000 0.000000 0.250000 ( 0.252399)

@infused's approach 1.020000 0.000000 1.020000 ( 1.017766)

@CarySwoveland's approach 0.320000 0.000000 0.320000 ( 0.321452)

@Myst's approach 0.130000 0.000000 0.130000 ( 0.128247)

@Sid's approach 0.210000 0.000000 0.210000 ( 0.212489)

Benchmarking code

The following is the script used for benchmarking:

module Enumerable
  def split_by
    result = [a=[]]
    each{ |o| yield(o) ? (result << a=[]) : (a << o) }
    result.pop if a.empty?
    result.delete_if { |x| x.empty? }
    result
  end
end

require 'benchmark'

[10, 100, 1000, 10000].each do |items|

  a = (Array.new(items) { rand 2 })
  cycles = 1_000_000 / items

  puts "report for array with #{items} items, #{cycles} iterations:"

  Benchmark.bm do |bm|
    bm.report("@tykowale's approach") {cycles.times { a.split_by {|x| x == 0} } }
    bm.report("@infused's approach") {cycles.times { a.join.split(/0/).map {|group| group.split(//).map(&:to_i) unless group == ''}.compact } }
    bm.report("@CarySwoveland's approach") { cycles.times { a.chunk(&:itself).select { |a| a.first==1 }.map(&:last) } }
    bm.report("@Myst's approach") { cycles.times { a.each_with_object([[]]) {|i, n| i == 1 ? ( n.last << i) : (n.last.empty? ? true : (n << [])) } } }
    bm.report("@Sid's approach") { cycles.times { a.chunk {|x| x==1 || nil}.map{|y,ys| ys} } }
  end
end
Myst
  • 18,516
  • 2
  • 45
  • 67
1

Here's a way using Enumerable#chunk:

a.chunk { |n| n==1 }.select(&:first).map(&:last)
  #=> [[1, 1], [1], [1, 1, 1]] 

And another, using Enumerable#slice_when, which was introduced in v2.2:

a.slice_when { |bef,aft| bef!=aft }.reject { |e| e.first != 1 }
  #=> [[1, 1], [1], [1, 1, 1]]
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
1

You can monkey patch this into enumerable and pass it a block so it is more can be used for any number or expression you want

module Enumerable
  def split_by
    result = [a=[]]
    each{ |o| yield(o) ? (result << a=[]) : (a << o) }
    result.delete_if { |a| a.empty? }
  end
end

a=[1,1,0,0,1,0,1,1,1]

p a.split_by {|x| x == 0}
#=> [[1,1],[1],[1,1,1]]

Found (most) of this from Split array into sub-arrays based on value

EDIT: Changed the way the deletion of empty sets work result.pop if a.empty? and removed unnecessary result line from end

Community
  • 1
  • 1
tykowale
  • 449
  • 3
  • 13
0
a.join.split('0').select {|b| b if not b.empty?}.map {|c| c.split(//).map{|d| d.to_i}}
Yeo
  • 11,416
  • 6
  • 63
  • 90