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...?
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...?
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]]
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]]
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
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.
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)
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)
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)
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)
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
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]]
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
a.join.split('0').select {|b| b if not b.empty?}.map {|c| c.split(//).map{|d| d.to_i}}