Ruby - splitting an array into auxiliary arrays when changing a value and ignoring / deleting that value
There are many ways to accomplish this. One way is to turn the array into a string, split the groups, and reassign 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]]
source to share
I liked the different answers! So I took his time to check some of them.
This is how I would do it:
new_array = a.each_with_object([ [] ]) {|i, n| i == 1 ? ( n.last << i) : (n.last.empty? ? true : (n << []))}
The method #each_with_object
allows me to iterate over the array while using an object to store whatever data I collect along the way (the object assigns a variable n
that stands for ' n ew_array').
In this approach, I am collecting data into an array of nested arrays [ [] ]
, adding 1 to the last nested array n.last << i
when recognized, and adding a new empty nested array n << []
if the data isn't what I want to collect (and the existing nested array is not empty).
I am using two built-in operators if:else
using short hand:
condition ? do_if_true : do_if_false
Comparative analysis of some answers
Checking some of the answers on my MacBook Pro, it seems like my approach has been the fastest so far ... but maybe I'm biased.
Note on reports: Results are displayed in seconds. Less is faster.
Two best results: bold .
Report for an array with 10 elements, 100,000 iterations:
user system total real
Approach @tykowale 0.210000 0.000000 0.210000 ( 0.209799 )
@infused approach 1.300000 0.010000 1.310000 (1.304084)
Approach @CarySwoveland 0.830000 0.000000 0.830000 (0.839012)
@ Mystical Approach 0.170000 0.000000 0.170000 ( 0.169915 )
Approach @Sid 0.590000 0.000000 0.590000 (0.595671)
Report for an array with 100 elements, 10,000 iterations:
user system total real
@Tykowale approach 0.160000 0.000000 0.160000 ( 0.155997 )
@infused approach 1.030000 0.000000 1.030000 (1.030392)
Approach @CarySwoveland 0.420000 0.010000 0.430000 (0.424801)
@ Mystical Approach 0.150000 0.000000 0.150000 ( 0.143403 )
@Sid Approach 0.260000 0.000000 0.260000 (0.255548)
Report for an array with 1000 elements, 1,000 iterations:
user system total real
Approach @tykowale 0.150000 0.000000 0.150000 ( 0.160459 )
@infused approach 1.030000 0.000000 1.030000 (1.033616)
Approach @CarySwoveland 0.310000 0.000000 0.310000 (0.312325)
@ Mystical Approach 0.130000 0.000000 0.130000 ( 0.133339 )
Approach @Sid 0.210000 0.000000 0.210000 (0.217960)
Report for an array with 10,000 items, 100 iterations:
user system total real
Approach @tykowale 0.250000 0.000000 0.250000 (0.252399)
@infused approach 1.020000 0.000000 1.020000 (1.017766)
Approach @CarySwoveland 0.320000 0.000000 0.320000 (0.321452)
@ Mystical Approach 0.130000 0.000000 0.130000 ( 0.128247 )
Approach @Sid 0.210000 0.000000 0.210000 ( 0.212489 )
Benchmarking code
Below 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 approach") {cycles.times { a.split_by {|x| x == 0} } }
bm.report("@infused approach") {cycles.times { a.join.split(/0/).map {|group| group.split(//).map(&:to_i) unless group == ''}.compact } }
bm.report("@CarySwoveland approach") { cycles.times { a.chunk(&:itself).select { |a| a.first==1 }.map(&:last) } }
bm.report("@Myst approach") { cycles.times { a.each_with_object([[]]) {|i, n| i == 1 ? ( n.last << i) : (n.last.empty? ? true : (n << [])) } } }
bm.report("@Sid approach") { cycles.times { a.chunk {|x| x==1 || nil}.map{|y,ys| ys} } }
end
end
source to share
Here's an enumerated # chunk way :
a.chunk { |n| n==1 }.select(&:first).map(&:last)
#=> [[1, 1], [1], [1, 1, 1]]
And more, using Enumerated # slice_when , which was introduced in version 2.2:
a.slice_when { |bef,aft| bef!=aft }.reject { |e| e.first != 1 }
#=> [[1, 1], [1], [1, 1, 1]]
source to share
You can defuse this with an enum and pass a block to it so that it 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]]
Discovered (most) of Split array into subarrays based on value
EDIT: Changed the way to remove empty sets result.pop if a.empty?
and removed the unnecessary result row from the end
source to share