├── .gitignore ├── Gemfile ├── LICENSE ├── README.rdoc ├── Rakefile ├── lib ├── range_operators.rb └── range_operators │ ├── array_operator_definitions.rb │ └── range_operator_definitions.rb ├── range_operators.gemspec └── spec ├── array_operator_definitions_spec.rb ├── range_operators_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = RangeOperators 2 | 3 | This gem will mixin range operations into Ruby's Range and Array classes. 4 | 5 | Range Methods:: Addition and subtraction 6 | 7 | Array Methods:: Combining elements into ranges, determining intersection of elements, 8 | and determining missing elements 9 | 10 | It assumes inclusive ranges (ie. 1..4) and range.first <= range.last. 11 | Also, the objects making up the ranges must have #succ and #<=> methods. 12 | 13 | = Usage 14 | 15 | The methods added are Range#- (alias Range#minus), Range#+ (alias Range#plus), Array#rangify, Array#intersection, and Array#missing. 16 | 17 | 18 | Examples: 19 | 20 | irb > require 'range_operators' 21 | 22 | irb > (1..10) - (4..6) => [1..3, 7..10] 23 | 24 | irb > (1..10).minus(9..12) => [1..8] 25 | 26 | Note: #- requires that the second operand have a #- (integer) method defined (ie. Fixnum, Bignum, and Date). 27 | (This allows the determination of a previous/predecessor value of an object.) 28 | ---- 29 | 30 | irb > (1..10) + (9..12) => [1..12] 31 | 32 | irb > (1..10).plus(15..20) => [1..10, 15..20] 33 | ---- 34 | 35 | irb > [1,2,3,6,7,8].rangify => [1..3, 6..8] 36 | 37 | irb > [10..15, 16..20, 21, 22].rangify => [10..22] 38 | ---- 39 | 40 | irb > [1, 2].intersection => nil 41 | 42 | irb > [1..10, 1].intersection => 1 43 | 44 | irb > [5..10, 1..10, 4..8 ].intersection => 5..8 45 | 46 | Note: #intersection will determine the values in common to all of the elements. 47 | ---- 48 | 49 | irb > [100, 9..11, 14, 1..5, 16, 10..12, 17..17].missing => [6..8, 13, 15, 18..99] 50 | 51 | Note: Like #-, #missing requires that the second operand have a #- (integer) method. 52 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rake' 3 | require 'echoe' 4 | 5 | Echoe.new('range_operators', '0.1.1') do |p| 6 | p.description = "A gem that adds range methods to Ruby's Range and Array classes. (Range#+, Range#-, Array#rangify, Array#intersection, Array#missing)" 7 | p.url = "http://github.com/monocle/range_operators" 8 | p.author = "monocle" 9 | p.email = "frappez_2000@yahoo.com" 10 | p.ignore_pattern = ["tmp/*", "script/*"] 11 | p.development_dependencies = [] 12 | end 13 | -------------------------------------------------------------------------------- /lib/range_operators.rb: -------------------------------------------------------------------------------- 1 | require 'range_operators/array_operator_definitions' 2 | require 'range_operators/range_operator_definitions' 3 | -------------------------------------------------------------------------------- /lib/range_operators/array_operator_definitions.rb: -------------------------------------------------------------------------------- 1 | module RangeOperators 2 | #= Rangify 3 | # 4 | #This module will add the rangify method to Ruby's Array class. 5 | #The rangify method will produce appropriate ranges from the objects in the array. 6 | # 7 | #= Usage 8 | # 9 | #Examples: 10 | # 11 | #[1,2,3,6,7,8].rangify = [1..3, 6..8] 12 | # 13 | #[10..15, 16..20, 21, 22].rangify = [10..22] 14 | # 15 | # 16 | #Assumes inclusive ranges (ie. 1..4) and range.first <= range.last. 17 | # 18 | #Works with integers, dates and strings. However, all the objects in the array must be of the same class. 19 | module ArrayOperatorDefinitions 20 | def rangify 21 | array = self.sort_elements 22 | array.inject([]) do |collection, value| 23 | unless collection.empty? 24 | last = collection.last 25 | last_value = comparison_value(last, :last) 26 | current_value = comparison_value(value, :first) 27 | if (last_value + 1 <=> current_value) == -1 28 | collection << value 29 | else 30 | first = comparison_value(last, :first) 31 | second = [comparison_value(last, :last), comparison_value(value, :last)].max 32 | collection[-1] = [first..second] 33 | collection.flatten! 34 | end 35 | else 36 | collection << value 37 | end 38 | end 39 | end 40 | 41 | # Returns the values in common for an array set (nil, singe value/object, or range). 42 | def intersection 43 | array = self.sort_elements 44 | array.inject() do |common, element| 45 | value_first = comparison_value(common, :last) 46 | value_element = comparison_value(element, :first) 47 | case value_first <=> value_element 48 | when -1 then return nil 49 | when 0 then value_first 50 | else 51 | if element.class == Range 52 | value_element..([value_first, comparison_value(element, :last)].min) 53 | else 54 | value_element 55 | end 56 | end 57 | end 58 | end 59 | 60 | #Returns the missing elements in an array set 61 | def missing 62 | missing, array = [], self.rangify 63 | i, length = 0, array.size - 1 64 | 65 | while i < length 66 | current = comparison_value(array[i], :last) 67 | nextt = comparison_value(array[i+1], :first) 68 | missing << (current + 2 == nextt ? current + 1 : (current + 1)..(nextt - 1)) 69 | i += 1 70 | end 71 | missing 72 | end 73 | 74 | protected 75 | 76 | def sort_elements 77 | self.uniq.sort_by { |element| comparison_value(element, :first) } 78 | end 79 | 80 | private 81 | 82 | # For a Range, will return value.first or value.last. A non-Range will return itself. 83 | def comparison_value(value, position) 84 | return value if value.class != Range 85 | position == :first ? value.first : value.last 86 | end 87 | end 88 | end 89 | 90 | class Array 91 | include RangeOperators::ArrayOperatorDefinitions 92 | end -------------------------------------------------------------------------------- /lib/range_operators/range_operator_definitions.rb: -------------------------------------------------------------------------------- 1 | module RangeOperators 2 | #Will add range operations ('+' and '-') to Ruby's Range class. 3 | #Requires that the second operand have a '- (integer)' method defined (ie. Fixnum, Bignum, and Date). 4 | #Assumes inclusive ranges (ie. 1..4) and range.first <= range.last. 5 | #Values returned from the operations are in an array. 6 | module RangeOperatorDefinitions 7 | 8 | def +(value) 9 | [self, value].rangify 10 | end 11 | 12 | alias plus + 13 | 14 | def -(value) 15 | if value.class == self.first.class 16 | self.minus_obj(value) 17 | else 18 | [self.minus_obj(value.first)[0], self.minus_obj(value.last)[1]].compact 19 | end 20 | end 21 | 22 | alias minus - 23 | 24 | protected 25 | 26 | def minus_obj(value) 27 | first = case value <=> self.first + 1 28 | when -1 then nil 29 | when 0 then self.first 30 | else 31 | value < self.last + 1 ? self.first..(value - 1) : self 32 | end 33 | 34 | second = case self.last <=> value + 1 35 | when -1 then nil 36 | when 0 then self.last 37 | else 38 | value + 1 > self.first ? value + 1..self.last : self 39 | end 40 | 41 | [first, second] 42 | end 43 | end 44 | end 45 | 46 | class Range 47 | include RangeOperators::RangeOperatorDefinitions 48 | end -------------------------------------------------------------------------------- /range_operators.gemspec: -------------------------------------------------------------------------------- 1 | $LOAD_PATH << File.expand_path("../lib", __FILE__) 2 | 3 | Gem::Specification.new do |s| 4 | s.name = %q{range_operators} 5 | s.version = '0.1.1' 6 | s.summary = %q{A gem that adds range methods to Ruby's Range and Array classes. 7 | (Range#+, Range#-, Array#rangify, Array#intersection, Array#missing)} 8 | s.description = %q{A gem that adds range methods to Ruby's Range and Array classes. 9 | (Range#+, Range#-, Array#rangify, Array#intersection, Array#missing)} 10 | 11 | s.files = `git ls-files`.split("\n") 12 | s.test_files = `git ls-files -- Appraisals {spec}/*`.split("\n") 13 | 14 | s.require_paths = ['lib'] 15 | s.required_ruby_version = Gem::Requirement.new(">= 1.9.2") 16 | 17 | s.authors = ['monocle'] 18 | s.email = ['frappez_2000@yahoo.com'] 19 | 20 | s.homepage = 'http://github.com/monocle/range_operators' 21 | 22 | s.add_development_dependency('rspec', '~> 2.0') 23 | end -------------------------------------------------------------------------------- /spec/array_operator_definitions_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Array do 4 | describe '#missing' do 5 | it { [1,3..3].missing.should == [2] } 6 | it { [1..5,10..12].missing.should == [6..9] } 7 | it { [100, 9..11, 14, 1..5, 16, 10..12, 17..17].missing.should == [6..8, 13, 15, 18..99] } 8 | end 9 | 10 | describe '#intersection' do 11 | it { [1, 2].intersection.should == nil } 12 | it { [1..10, 11..20].intersection.should == nil } 13 | it { [1..10, 5..15, 11..20].intersection.should == nil } 14 | it { [8..8, 1..10, 10 ].intersection.should == nil } 15 | it { [10, 1..10].intersection.should == 10 } 16 | it { [1..10, 1].intersection.should == 1 } 17 | it { [1, 1].intersection.should == 1 } 18 | it { [1..10, 5, 5..8, 5..10 ].intersection.should == 5 } 19 | it { [1..10, 5..8, 5..10 ].intersection.should == (5..8) } 20 | it { [5..10, 1..10, 4..8 ].intersection.should == (5..8) } 21 | it { [5..10, 1..10, 4..20, 8].intersection.should == 8 } 22 | 23 | end 24 | 25 | describe '#rangify' do 26 | 27 | it 'An array of consecutive integers should return an array made up of a single range.' do 28 | [1,2,3,4,5].rangify.should == [1..5] 29 | end 30 | 31 | it 'An array of non-consecutive integers should return the original array.' do 32 | [1,3,5,7].rangify.should == [1,3,5,7] 33 | end 34 | 35 | describe 'An array of consecutive and non-consecutive integers' do 36 | before :each do 37 | @result = [1..3, 6..8, 10, 15] 38 | end 39 | 40 | it 'should return the correct ranges.' do 41 | [1,2,3,6,7,8,10,15].rangify.should == @result 42 | end 43 | 44 | it 'Array element order should not affect the result.' do 45 | [8, 1, 15, 2, 6, 3, 7, 10].rangify.should == @result 46 | end 47 | 48 | it 'Duplicate elements should not affect the result.' do 49 | [8, 1, 15, 2, 6, 3, 7, 10, 8, 15, 2, 3, 1, 2].rangify.should == @result 50 | end 51 | end 52 | 53 | it 'An array of ranges should return the correct ranges.' do 54 | arr = [40..45, 1..3, 4..10, 20..30, 24..28, 42..50, 1..6, 1..3, 1..1] 55 | arr.rangify.should == [1..10, 20..30, 40..50] 56 | end 57 | 58 | it 'An array of ranges and integers should return the correct ranges.' do 59 | arr = [99, 100, 1..3, 101, 4..5, 103, 10..19, 99, 20..20, 31, 32..33, 98, 97] 60 | arr.rangify.should == [1..5, 10..20, 31..33, 97..101, 103] 61 | end 62 | end 63 | end -------------------------------------------------------------------------------- /spec/range_operators_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Range do 4 | describe '#+' do 5 | describe 'when given a range of integers' do 6 | 7 | it { lambda{ (1..10) + 'a' }.should raise_error } 8 | 9 | it { ((1..10) + (11..11)).should == [1..11] } 10 | 11 | it { ((1..10) + 12).should == [1..10, 12] } 12 | 13 | it { ((1..10) + (10..12)).should == [1..12] } 14 | 15 | it { ((1..10) + (11..12)).should == [1..12] } 16 | 17 | it { ((1..10) + (9..12)).should == [1..12] } 18 | 19 | it { ((1..10) + (1..12)).should == [1..12] } 20 | 21 | it { ((1..10) + (12..20)).should == [1..10, 12..20] } 22 | 23 | it { ((1..10) + 0).should == [0..10] } 24 | 25 | it { ((1..10) + -1).should == [-1, 1..10] } 26 | 27 | it { ((1..10) + (-10..-1)).should == [-10..-1, 1..10] } 28 | 29 | it { ((1..10) + (-10..0)).should == [-10..10] } 30 | 31 | it { ((1..10) + (-10..1)).should == [-10..10] } 32 | 33 | it { ((1..10) + (-10..2)).should == [-10..10] } 34 | 35 | it { ((1..10) + (-10..20)).should == [-10..20]} 36 | end 37 | end 38 | 39 | describe '#-' do 40 | describe 'when given a range of integers' do 41 | 42 | it { lambda{ (1..10) - 'a' }.should raise_error } 43 | 44 | describe 'and subtracting an integer' do 45 | 46 | it { ((1..10) - (5..5)).should == [1..4, 6..10] } 47 | 48 | it { ((1..10) - 2).should == [1, 3..10] } 49 | 50 | it { ((1..10) - 9).should == [1..8, 10] } 51 | 52 | it { ((1..10) - 10).should == [1..9, nil] } 53 | 54 | it { ((1..10) - 1).should == [nil, 2..10] } 55 | 56 | it { ((1..10) - 11).should == [1..10, nil] } 57 | 58 | it { ((1..10) - 12).should == [1..10, nil] } 59 | 60 | it { ((1..10) - 0).should == [nil, 1..10] } 61 | 62 | it { ((1..10) - -1).should == [nil, 1..10] } 63 | end 64 | 65 | describe 'and subtracting another range' do 66 | 67 | it { ((1..10) - (4..6)).should == [1..3, 7..10] } 68 | 69 | it { ((1..10) - (2..6)).should == [1, 7..10] } 70 | 71 | it { ((1..10) - (4..9)).should == [1..3, 10] } 72 | 73 | it { ((1..10) - (2..9)).should == [1, 10] } 74 | 75 | it { ((1..10) - (2..11)).should == [1] } 76 | 77 | it { ((1..10) - (0..9)).should == [10] } 78 | 79 | it { ((1..10) - (4..10)).should == [1..3] } 80 | 81 | it { ((1..10) - (4..12)).should == [1..3] } 82 | 83 | it { ((1..10) - (1..6)).should == [7..10] } 84 | 85 | it { ((1..10) - (-2..6)).should == [7..10] } 86 | 87 | it { ((1..10) - (11..20)).should == [1..10] } 88 | 89 | it { ((1..10) - (-10..0)).should == [1..10] } 90 | 91 | it { ((1..10) - (-10..20)).should == [] } 92 | end 93 | end 94 | end 95 | 96 | it '#minus is an alias of #-' do 97 | Range.instance_method(:minus).should == Range.instance_method(:-) 98 | end 99 | 100 | it '#plus is an alias of #+' do 101 | Range.instance_method(:plus).should == Range.instance_method(:+) 102 | end 103 | end -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) 2 | $LOAD_PATH.unshift(File.dirname(__FILE__)) 3 | require 'rspec' 4 | require 'range_operators' 5 | 6 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} 7 | 8 | RSpec.configure do |config| 9 | 10 | end --------------------------------------------------------------------------------