Ruby - splitting an array into auxiliary arrays when changing a value and ignoring / deleting that value

I want to split the following array into sub arrays so that the sub-arrays start and end on first start and end ...

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

      

so I am getting this as a new array ...

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

      

Does anyone have any idea...?

+3


source to share


6 answers


The simplest and most readable way would probably be:

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

      



If you're fine with Ruby on Rails, you can use an even simpler solution:

a.split(0).reject(&:empty?)
  #=> [[1, 1], [1], [1, 1, 1]]

      

+4


source


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

      

+2


source


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

      

+1


source


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

      

+1


source


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

+1


source


a.join.split('0').select {|b| b if not b.empty?}.map {|c| c.split(//).map{|d| d.to_i}}

      

0


source







All Articles