lib/external/chunkable.rb in external-0.1.0 vs lib/external/chunkable.rb in external-0.3.0

- old
+ new

@@ -1,105 +1,131 @@ -module External - - # The Chunkable mixin provides methods for organizing a span or range - # into chunks no larger than a specified block size. - module Chunkable - attr_accessor :length, :default_blksize - - # Returns the default span: [0, length] - def default_span - [0, length] - end - - # Breaks the input range or span into chunks of blksize or less. - # The offset and length of each chunk will be provided to the - # block, if given. - # - # blksize # => 100 - # chunk(0..250) # => [[0,100],[100,100],[200,50]] - # - # results = [] - # chunk([10,190]) {|offset, length| results << [offset, length]} - # results # => [[10,100],[110,90]] - # - def chunk(range_or_span=default_span, blksize=default_blksize) - return collect_results(:chunk, range_or_span) unless block_given? - - rbegin, rend = range_begin_and_end(range_or_span) - - # chunk the final range to make sure that no chunks - # greater than blksize are returned - while rend - rbegin > blksize - yield(rbegin, blksize) - rbegin += blksize - end - yield(rbegin, rend - rbegin) if rend - rbegin > 0 - end - - # Breaks the input range or span into chunks of blksize or less, - # beginning from the end of the interval. The offset and length - # of each chunk will be provided to the block, if given. - # - # blksize # => 100 - # reverse_chunk(0..250) # => [[150,100],[50,100],[0,50]] - # - # results = [] - # reverse_chunk([10,190]) {|offset, length| results << [offset, length]} - # results # => [[100,100],[10,90]] - # - def reverse_chunk(range_or_span=default_span, blksize=default_blksize) - return collect_results(:reverse_chunk, range_or_span) unless block_given? - - rbegin, rend = range_begin_and_end(range_or_span) - - # chunk the final range to make sure that no chunks - # greater than blksize are returned - while rend - rbegin > blksize - rend -= blksize - yield(rend, blksize) - end - yield(rbegin, rend - rbegin) if rend - rbegin > 0 - end - - protected - - # Converts a range into an offset and length. Negative values are - # counted back from self.length - # - # length # => 10 - # split_range(0..9) # => [0,10] - # split_range(0...9) # => [0,9] - # - # split_range(-1..9) # => [9,1] - # split_range(0..-1) # => [0,10] - def split_range(range) - begin_range = range.begin + (range.begin < 0 ? self.length : 0) - end_range = range.end + (range.end < 0 ? self.length : 0) - length = end_range - begin_range - (range.exclude_end? ? 1 : 0) - - [begin_range, length] - end - - def split_span(span) - span[0] += self.length if span[0] < 0 - span - end - - def range_begin_and_end(range_or_span) - rbegin, rend = range_or_span.kind_of?(Range) ? split_range(range_or_span) : split_span(range_or_span) - raise ArgumentError.new("negative offset specified: #{PP.singleline_pp(range_or_span,'')}") if rbegin < 0 - rend += rbegin - - [rbegin, rend] - end - - private - - def collect_results(method, args) # :nodoc: - results = [] - send(method, args) do |*result| - results << result - end - results - end - end -end +module External + + # The Chunkable mixin provides methods for organizing a span or range + # into chunks no larger than a specified block size. For reference: + # + # span an array like: [start, length] + # range a Range like: start..end or start...(end - 1) + # + module Chunkable + + # The length of the chunkable object; + # length must be set by the object. + attr_accessor :length + + # The default block size for chunking a chunkable + # object; default_blksize must be set by the object. + attr_accessor :default_blksize + + # Returns the default span: [0, length] + def default_span + [0, length] + end + + # Breaks the input range or span into chunks of blksize or less. + # The offset and length of each chunk will be provided to the + # block, if given. + # + # blksize # => 100 + # chunk(0..250) # => [[0,100],[100,100],[200,50]] + # + # results = [] + # chunk([10,190]) {|offset, length| results << [offset, length]} + # results # => [[10,100],[110,90]] + # + def chunk(range_or_span=default_span, blksize=default_blksize) + return collect_results(:chunk, range_or_span) unless block_given? + + rbegin, rend = range_begin_and_end(range_or_span) + + # chunk the final range to make sure that no chunks + # greater than blksize are returned + while rend - rbegin > blksize + yield(rbegin, blksize) + rbegin += blksize + end + yield(rbegin, rend - rbegin) if rend - rbegin > 0 + end + + # Breaks the input range or span into chunks of blksize or less, + # beginning from the end of the interval. The offset and length + # of each chunk will be provided to the block, if given. + # + # blksize # => 100 + # reverse_chunk(0..250) # => [[150,100],[50,100],[0,50]] + # + # results = [] + # reverse_chunk([10,190]) {|offset, length| results << [offset, length]} + # results # => [[100,100],[10,90]] + # + def reverse_chunk(range_or_span=default_span, blksize=default_blksize) + return collect_results(:reverse_chunk, range_or_span) unless block_given? + + rbegin, rend = range_begin_and_end(range_or_span) + + # chunk the final range to make sure that no chunks + # greater than blksize are returned + while rend - rbegin > blksize + rend -= blksize + yield(rend, blksize) + end + yield(rbegin, rend - rbegin) if rend - rbegin > 0 + end + + module_function + + # Converts a range into an offset and length. Negative values are + # counted back from self.length + # + # length # => 10 + # split_range(0..9) # => [0,10] + # split_range(0...9) # => [0,9] + # + # split_range(-1..9) # => [9,1] + # split_range(0..-1) # => [0,10] + def split_range(range) + start, finish = range.begin, range.end + start += length if start < 0 + finish += length if finish < 0 + + [start, finish - start - (range.exclude_end? ? 1 : 0)] + end + + # The compliment to split_range; returns the span with a negative + # start index counted back from self.length. + # + # length # => 10 + # split_span([0, 10]) # => [0,10] + # split_span([-1, 1]) # => [9,1] + # + def split_span(span) + span[0] += self.length if span[0] < 0 + span + end + + # Returns the begining and end of a range or span. + # + # range_begin_and_end(0..10) # => [0, 10] + # range_begin_and_end(0...10) # => [0, 9] + # range_begin_and_end([0, 10]) # => [0, 10] + # + def range_begin_and_end(range_or_span) + rbegin, rend = range_or_span.kind_of?(Range) ? split_range(range_or_span) : split_span(range_or_span) + raise ArgumentError.new("negative offset specified: #{PP.singleline_pp(range_or_span,'')}") if rbegin < 0 + rend += rbegin + + [rbegin, rend] + end + + private + + # a utility method to collect the results of a method + # that requires a block. + def collect_results(method, args) # :nodoc: + results = [] + send(method, args) do |*result| + results << result + end + results + end + end +end