1 # depends on: class.rb enumerable.rb
4 # A Range represents an interval, a set of values with a start and an end.
6 # Ranges may be constructed using the <tt>s..e</tt> and <tt>s...e</tt>
7 # literals, or with Range::new.
9 # Ranges constructed using <tt>..</tt> run from the start to the end
10 # inclusively. Those created using <tt>...</tt> exclude the end value. When
11 # used as an iterator, ranges return each value in the sequence.
13 # (-1..-5).to_a #=> []
14 # (-5..-1).to_a #=> [-5, -4, -3, -2, -1]
15 # ('a'..'e').to_a #=> ["a", "b", "c", "d", "e"]
16 # ('a'...'e').to_a #=> ["a", "b", "c", "d"]
18 # Ranges can be constructed using objects of any type, as long as the objects
19 # can be compared using their <tt><=></tt> operator and they support the
20 # <tt>succ</tt> method to return the next object in sequence.
22 # class Xs # represent a string of 'x's
32 # @length <=> other.length
35 # sprintf "%2d #{inspect}", @length
42 # r = Xs.new(3)..Xs.new(6) #=> xxx..xxxxxx
43 # r.to_a #=> [xxx, xxxx, xxxxx, xxxxxx]
44 # r.member?(Xs.new(5)) #=> true
46 # In the previous code example, class Xs includes the Comparable module. This
47 # is because Enumerable#member? checks for equality using ==. Including
48 # Comparable ensures that the == method is defined in terms of the <=> method
55 # Constructs a range using the given +start+ and +end+.
57 # If the third parameter is omitted or is false, the range will include the
58 # end object; otherwise, it will be excluded.
60 def initialize(first, last, exclude_end = false)
61 raise NameError, "`initialize' called twice" if @begin
63 unless first.is_a?(Fixnum) && last.is_a?(Fixnum)
65 raise ArgumentError, "bad value for range" unless first <=> last
67 raise ArgumentError, "bad value for range"
76 # Returns <tt>true</tt> only if <em>obj</em> is a Range, has
77 # equivalent beginning and end items (by comparing them with
78 # <tt>==</tt>), and has the same #exclude_end? setting as <i>rng</t>.
80 # (0..2) == (0..2) #=> true
81 # (0..2) == Range.new(0,2) #=> true
82 # (0..2) == (0...2) #=> false
85 (other.is_a?(Range) && self.first == other.first &&
86 self.last == other.last && self.exclude_end? == other.exclude_end?)
89 alias_method :eql?, :==
91 # Returns <tt>true</tt> if <em>obj</em> is an element of <em>rng</em>,
92 # <tt>false</tt> otherwise. Conveniently, <tt>===</tt> is the
93 # comparison operator used by <tt>case</tt> statements.
96 # when 1..50 then print "low\n"
97 # when 51..75 then print "medium\n"
98 # when 76..100 then print "high\n"
107 return true if value < @end
109 return true if value <= @end
116 alias_method :member?, :===
117 alias_method :include?, :===
121 # rng.each { |i| block } => rng
123 # Iterates over the elements +rng+, passing each in turn to the block. You
124 # can only iterate if the start object of the range supports the
125 # succ method (which means that you can't iterate over ranges of
128 # (10..15).each do |n|
137 first, last = @begin, @end # dup?
139 raise TypeError, "can't iterate from #{first.class}" unless first.respond_to? :succ
141 if first.is_a?(Fixnum) && last.is_a?(Fixnum)
142 last -= 1 if self.exclude_end?
143 first.upto(last, &block)
144 elsif first.is_a?(String)
145 first.upto(last) do |s|
146 block.call(s) unless @excl && s == last
151 while (current <=> last) < 0
153 current = current.succ
156 while (c = current <=> last) && c <= 0
159 current = current.succ
168 # rng.exclude_end? => true or false
170 # Returns true if +rng+ excludes its end value.
181 # Returns the first object in +rng+.
186 alias_method :begin, :first
188 # Generate a hash value such that two ranges with the same start and
189 # end points, and the same value for the "exclude end" flag, generate
190 # the same hash value.
194 hash ^= @begin.hash << 1
195 hash ^= @end.hash << 9
200 # Convert this range object to a printable form (using
201 # <tt>inspect</tt> to convert the start and end objects).
203 "#{@begin.inspect}#{@excl ? "..." : ".."}#{@end.inspect}"
206 # Returns the object that defines the end of <em>rng</em>.
209 # (1...10).end #=> 10
213 alias_method :end, :last
217 # rng.step(n = 1) { |obj| block } => rng
219 # Iterates over +rng+, passing each +n+th element to the block. If the range
220 # contains numbers or strings, natural ordering is used. Otherwise
221 # +step+ invokes +succ+ to iterate through range elements. The following
222 # code uses class Xs, which is defined in the class-level documentation.
224 # range = Xs.new(1)..Xs.new(10)
225 # range.step(2) { |x| puts x }
226 # range.step(3) { |x| puts x }
240 def step(step_size = 1, &block) # :yields: object
241 first, last = @begin, @end
242 step_size = (Float === first) ? Float(step_size) : Integer(step_size)
244 raise ArgumentError, "step can't be negative" if step_size < 0
245 raise ArgumentError, "step can't be 0" if step_size == 0
249 elsif first.kind_of?(Numeric)
250 cmp_method = self.exclude_end? ? :< : :<=
252 while first.__send__(cmp_method, last)
259 block.call(o) if counter % step_size == 0
268 # Convert this range object to a printable form.
271 "#{@begin}#{@excl ? "..." : ".."}#{@end}"