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...?

0

6 Answers 6

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]]
Sign up to request clarification or add additional context in comments.

1 Comment

Clever! I learned something new. I see from the docs that you could also replace nil with :_separator. Note you could write map(&:last).
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]]

2 Comments

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 } }`
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.
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

5 Comments

Perhaps try the benchmark with a = Array.new(n) { rand 2 } for largish n.
You mean, "less is faster". :-) Please run the benchmark with increasing larger numbers of items. If and when my answer is fastest, stop there.
So, someone down-voted my answer... would anyone (especially that someone, but not necessarily) care to tell me how I can do better next time?
Very few downvoters fess up. Best to just move on. (I upvcoted.)
"Movin' on" is more idiomatic.
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]]

Comments

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

2 Comments

Interesting approach!
Good point, I had it in my mind that it would be returning everything that was deleting. Similar to how pop would function.
0
a.join.split('0').select {|b| b if not b.empty?}.map {|c| c.split(//).map{|d| d.to_i}}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.