Tom Rossi
Tom Rossi

Reputation: 12066

How to break a range down into smaller non-overlapping ranges

What is the most beautiful way to break a larger range into smaller non overlapping ranges?

range = 1..375

Desired Output:

1..100
101..200
201..300
301..375

Upvotes: 5

Views: 419

Answers (3)

Tom Rossi
Tom Rossi

Reputation: 12066

Currently, I'm using the step method, but I don't like having to check the top of the range and do calculations to avoid overlapping:

For example:

range = 1..375
interval = 100
range.step(interval).each do |start|
  stop = [range.last, start + (interval - 1)].min
  puts "#{start}..#{stop}"
end

I've taken this code and extended Range as well:

class Range
  def in_sub_ranges(interval)
    step(interval).each do |start|
      stop = [range.last, start + (interval - 1)].min
      yield(start..stop)
    end
  end
end

This allows me to do

range.in_sub_ranges(100) { |sub| puts sub }

Upvotes: 0

Cary Swoveland
Cary Swoveland

Reputation: 110675

The following may not be the most elegant solution but it is designed to be relatively efficient, by avoiding the creation of temporary arrays.

def divide_range(range, sz)
  start = range.begin
  (range.size/sz).times.with_object([]) do |_,arr|
    arr << (start..start+sz-1)
    start += sz
  end.tap { |arr| (arr << (start..range.end)) if start < range.end }
end

divide_range(1..375, 100)
  #=> [1..100, 101..200, 201..300, 301..375] 
divide_range(1..400, 100)
  #=> [1..100, 101..200, 201..300, 301..400] 
divide_range(50..420, 50)
  #=> [50..99, 100..149, 150..199, 200..249, 250..299, 300..349,
  #    350..399, 400..420]
n = 1_000_000_000_000
divide_range(1..n, n/2)
  #=> [1..500000000000, 500000000001..1000000000000] 

Upvotes: 2

Viktor
Viktor

Reputation: 2773

You can use #each_slice in combination with #map:

(1..375).each_slice(100).map { |a,*,b| (a..b) }     

#=> [1..100, 101..200, 201..300, 301..375]

Upvotes: 7

Related Questions