A Range
represents an interval—a set of values with a start
and an end. Ranges may be constructed using the
s..
e and
s...
e literals, or with
Range::new
. Ranges constructed using ..
run from
the start to the end inclusively. Those created using ...
exclude the end value. When used as an iterator, ranges return each value
in the sequence.
(-1..-5).to_a #=> [] (-5..-1).to_a #=> [-5, -4, -3, -2, -1] ('a'..'e').to_a #=> ["a", "b", "c", "d", "e"] ('a'...'e').to_a #=> ["a", "b", "c", "d"]
Ranges can be constructed using objects of any type, as long as the objects
can be compared using their <=>
operator and they
support the succ
method to return the next object in sequence.
class Xs # represent a string of 'x's include Comparable attr :length def initialize(n) @length = n end def succ Xs.new(@length + 1) end def <=>(other) @length <=> other.length end def to_s sprintf "%2d #{inspect}", @length end def inspect 'x' * @length end end r = Xs.new(3)..Xs.new(6) #=> xxx..xxxxxx r.to_a #=> [xxx, xxxx, xxxxx, xxxxxx] r.member?(Xs.new(5)) #=> true
In the previous code example, class Xs
includes the
Comparable
module. This is because
Enumerable#member?
checks for equality using ==
.
Including Comparable
ensures that the ==
method
is defined in terms of the <=>
method implemented in
Xs
.
Constructs a range using the given start and end. If the
third parameter is omitted or is false
, the range
will include the end object; otherwise, it will be excluded.
static VALUE range_initialize(int argc, VALUE *argv, VALUE range) { VALUE beg, end, flags; rb_scan_args(argc, argv, "21", &beg, &end, &flags); /* Ranges are immutable, so that they should be initialized only once. */ if (RANGE_EXCL(range) != Qnil) { rb_name_error(rb_intern("initialize"), "`initialize' called twice"); } range_init(range, beg, end, RTEST(flags)); return Qnil; }
Returns true
only if obj is a Range, has equivalent beginning and end items (by
comparing them with ==
), and has the same
exclude_end?
setting as rng.
(0..2) == (0..2) #=> true (0..2) == Range.new(0,2) #=> true (0..2) == (0...2) #=> false
static VALUE range_eq(VALUE range, VALUE obj) { if (range == obj) return Qtrue; if (!rb_obj_is_kind_of(obj, rb_cRange)) return Qfalse; return rb_exec_recursive_paired(recursive_equal, range, obj, obj); }
Returns true
if obj is an element of rng,
false
otherwise. Conveniently, ===
is the
comparison operator used by case
statements.
case 79 when 1..50 then print "low\n" when 51..75 then print "medium\n" when 76..100 then print "high\n" end
produces:
high
static VALUE range_eqq(VALUE range, VALUE val) { return rb_funcall(range, rb_intern("include?"), 1, val); }
Returns the first object in rng.
static VALUE range_begin(VALUE range) { return RANGE_BEG(range); }
Returns true
if obj is between beg and end, i.e
beg <= obj <= end
(or end exclusive when
exclude_end?
is true).
("a".."z").cover?("c") #=> true ("a".."z").cover?("5") #=> false
static VALUE range_cover(VALUE range, VALUE val) { VALUE beg, end; beg = RANGE_BEG(range); end = RANGE_END(range); if (r_le(beg, val)) { if (EXCL(range)) { if (r_lt(val, end)) return Qtrue; } else { if (r_le(val, end)) return Qtrue; } } return Qfalse; }
Iterates over the elements rng, passing each in turn to the block.
You can only iterate if the start object of the range supports the
succ
method (which means that you can't iterate over
ranges of Float
objects).
(10..15).each do |n| print n, ' ' end
produces:
10 11 12 13 14 15
static VALUE range_each(VALUE range) { VALUE beg, end; RETURN_ENUMERATOR(range, 0, 0); beg = RANGE_BEG(range); end = RANGE_END(range); if (!rb_respond_to(beg, id_succ)) { rb_raise(rb_eTypeError, "can't iterate from %s", rb_obj_classname(beg)); } if (FIXNUM_P(beg) && FIXNUM_P(end)) { /* fixnums are special */ long lim = FIX2LONG(end); long i; if (!EXCL(range)) lim += 1; for (i = FIX2LONG(beg); i < lim; i++) { rb_yield(LONG2FIX(i)); } } else if (TYPE(beg) == T_STRING) { VALUE args[2]; args[0] = end; args[1] = EXCL(range) ? Qtrue : Qfalse; rb_block_call(beg, rb_intern("upto"), 2, args, rb_yield, 0); } else { range_each_func(range, each_i, NULL); } return range; }
Returns the object that defines the end of rng.
(1..10).end #=> 10 (1...10).end #=> 10
static VALUE range_end(VALUE range) { return RANGE_END(range); }
Returns true
only if obj is a Range, has equivalent beginning and end items (by
comparing them with eql?), and has
the same exclude_end?
setting as rng.
(0..2) == (0..2) #=> true (0..2) == Range.new(0,2) #=> true (0..2) == (0...2) #=> false
static VALUE range_eql(VALUE range, VALUE obj) { if (range == obj) return Qtrue; if (!rb_obj_is_kind_of(obj, rb_cRange)) return Qfalse; return rb_exec_recursive_paired(recursive_eql, range, obj, obj); }
Returns true
if rng excludes its end value.
static VALUE range_exclude_end_p(VALUE range) { return EXCL(range) ? Qtrue : Qfalse; }
Returns the first object in rng, or the first n
elements.
static VALUE range_first(int argc, VALUE *argv, VALUE range) { VALUE n, ary[2]; if (argc == 0) return RANGE_BEG(range); rb_scan_args(argc, argv, "1", &n); ary[0] = n; ary[1] = rb_ary_new2(NUM2LONG(n)); rb_block_call(range, rb_intern("each"), 0, 0, first_i, (VALUE)ary); return ary[1]; }
Generate a hash value such that two ranges with the same start and end points, and the same value for the “exclude end” flag, generate the same hash value.
static VALUE range_hash(VALUE range) { long hash = EXCL(range); VALUE v; v = rb_hash(RANGE_BEG(range)); hash ^= v << 1; v = rb_hash(RANGE_END(range)); hash ^= v << 9; hash ^= EXCL(range) << 24; return LONG2FIX(hash); }
Returns true
if obj is an element of rng,
false
otherwise. If beg and end are numeric, comparison is
done according magnitude of values.
("a".."z").include?("g") # => true ("a".."z").include?("A") # => false
static VALUE range_include(VALUE range, VALUE val) { VALUE beg = RANGE_BEG(range); VALUE end = RANGE_END(range); int nv = FIXNUM_P(beg) || FIXNUM_P(end) || rb_obj_is_kind_of(beg, rb_cNumeric) || rb_obj_is_kind_of(end, rb_cNumeric); if (nv || !NIL_P(rb_check_to_integer(beg, "to_int")) || !NIL_P(rb_check_to_integer(end, "to_int"))) { if (r_le(beg, val)) { if (EXCL(range)) { if (r_lt(val, end)) return Qtrue; } else { if (r_le(val, end)) return Qtrue; } } return Qfalse; } else if (TYPE(beg) == T_STRING && TYPE(end) == T_STRING && RSTRING_LEN(beg) == 1 && RSTRING_LEN(end) == 1) { if (NIL_P(val)) return Qfalse; if (TYPE(val) == T_STRING) { if (RSTRING_LEN(val) == 0 || RSTRING_LEN(val) > 1) return Qfalse; else { char b = RSTRING_PTR(beg)[0]; char e = RSTRING_PTR(end)[0]; char v = RSTRING_PTR(val)[0]; if (ISASCII(b) && ISASCII(e) && ISASCII(v)) { if (b <= v && v < e) return Qtrue; if (!EXCL(range) && v == e) return Qtrue; return Qfalse; } } } } /* TODO: ruby_frame->this_func = rb_intern("include?"); */ return rb_call_super(1, &val); }
Convert this range object to a printable form (using inspect
to convert the start and end objects).
static VALUE range_inspect(VALUE range) { return rb_exec_recursive(inspect_range, range, 0); }
Returns the last object in rng, or the last n
elements.
static VALUE range_last(int argc, VALUE *argv, VALUE range) { VALUE rb_ary_last(int, VALUE *, VALUE); if (argc == 0) return RANGE_END(range); return rb_ary_last(argc, argv, rb_Array(range)); }
Returns the maximum value in rng. The second uses the block to compare values. Returns nil if the first value in range is larger than the last value.
static VALUE range_max(VALUE range) { VALUE e = RANGE_END(range); int ip = FIXNUM_P(e) || rb_obj_is_kind_of(e, rb_cInteger); if (rb_block_given_p() || (EXCL(range) && !ip)) { return rb_call_super(0, 0); } else { VALUE b = RANGE_BEG(range); int c = rb_cmpint(rb_funcall(b, id_cmp, 1, e), b, e); if (c > 0) return Qnil; if (EXCL(range)) { if (c == 0) return Qnil; if (FIXNUM_P(e)) { return LONG2NUM(FIX2LONG(e) - 1); } return rb_funcall(e, '-', 1, INT2FIX(1)); } return e; } }
Returns true
if obj is an element of rng,
false
otherwise. If beg and end are numeric, comparison is
done according magnitude of values.
("a".."z").include?("g") # => true ("a".."z").include?("A") # => false
static VALUE range_include(VALUE range, VALUE val) { VALUE beg = RANGE_BEG(range); VALUE end = RANGE_END(range); int nv = FIXNUM_P(beg) || FIXNUM_P(end) || rb_obj_is_kind_of(beg, rb_cNumeric) || rb_obj_is_kind_of(end, rb_cNumeric); if (nv || !NIL_P(rb_check_to_integer(beg, "to_int")) || !NIL_P(rb_check_to_integer(end, "to_int"))) { if (r_le(beg, val)) { if (EXCL(range)) { if (r_lt(val, end)) return Qtrue; } else { if (r_le(val, end)) return Qtrue; } } return Qfalse; } else if (TYPE(beg) == T_STRING && TYPE(end) == T_STRING && RSTRING_LEN(beg) == 1 && RSTRING_LEN(end) == 1) { if (NIL_P(val)) return Qfalse; if (TYPE(val) == T_STRING) { if (RSTRING_LEN(val) == 0 || RSTRING_LEN(val) > 1) return Qfalse; else { char b = RSTRING_PTR(beg)[0]; char e = RSTRING_PTR(end)[0]; char v = RSTRING_PTR(val)[0]; if (ISASCII(b) && ISASCII(e) && ISASCII(v)) { if (b <= v && v < e) return Qtrue; if (!EXCL(range) && v == e) return Qtrue; return Qfalse; } } } } /* TODO: ruby_frame->this_func = rb_intern("include?"); */ return rb_call_super(1, &val); }
Returns the minimum value in rng. The second uses the block to compare values. Returns nil if the first value in range is larger than the last value.
static VALUE range_min(VALUE range) { if (rb_block_given_p()) { return rb_call_super(0, 0); } else { VALUE b = RANGE_BEG(range); VALUE e = RANGE_END(range); int c = rb_cmpint(rb_funcall(b, id_cmp, 1, e), b, e); if (c > 0 || (c == 0 && EXCL(range))) return Qnil; return b; } }
Iterates over rng, passing each nth element to the block.
If the range contains numbers, n is added for each iteration.
Otherwise step
invokes succ
to iterate through
range elements. The following code uses class Xs
, which is
defined in the class-level documentation.
range = Xs.new(1)..Xs.new(10) range.step(2) {|x| puts x} range.step(3) {|x| puts x}
produces:
1 x 3 xxx 5 xxxxx 7 xxxxxxx 9 xxxxxxxxx 1 x 4 xxxx 7 xxxxxxx 10 xxxxxxxxxx
static VALUE range_step(int argc, VALUE *argv, VALUE range) { VALUE b, e, step, tmp; RETURN_ENUMERATOR(range, argc, argv); b = RANGE_BEG(range); e = RANGE_END(range); if (argc == 0) { step = INT2FIX(1); } else { rb_scan_args(argc, argv, "01", &step); if (!rb_obj_is_kind_of(step, rb_cNumeric)) { step = rb_to_int(step); } if (rb_funcall(step, '<', 1, INT2FIX(0))) { rb_raise(rb_eArgError, "step can't be negative"); } else if (!rb_funcall(step, '>', 1, INT2FIX(0))) { rb_raise(rb_eArgError, "step can't be 0"); } } if (FIXNUM_P(b) && FIXNUM_P(e) && FIXNUM_P(step)) { /* fixnums are special */ long end = FIX2LONG(e); long i, unit = FIX2LONG(step); if (!EXCL(range)) end += 1; i = FIX2LONG(b); while (i < end) { rb_yield(LONG2NUM(i)); if (i + unit < i) break; i += unit; } } else if (ruby_float_step(b, e, step, EXCL(range))) { /* done */ } else if (rb_obj_is_kind_of(b, rb_cNumeric) || !NIL_P(rb_check_to_integer(b, "to_int")) || !NIL_P(rb_check_to_integer(e, "to_int"))) { ID op = EXCL(range) ? '<' : rb_intern("<="); while (RTEST(rb_funcall(b, op, 1, e))) { rb_yield(b); b = rb_funcall(b, '+', 1, step); } } else { tmp = rb_check_string_type(b); if (!NIL_P(tmp)) { VALUE args[2], iter[2]; b = tmp; args[0] = e; args[1] = EXCL(range) ? Qtrue : Qfalse; iter[0] = INT2FIX(1); iter[1] = step; rb_block_call(b, rb_intern("upto"), 2, args, step_i, (VALUE)iter); } else { VALUE args[2]; if (!rb_respond_to(b, id_succ)) { rb_raise(rb_eTypeError, "can't iterate from %s", rb_obj_classname(b)); } args[0] = INT2FIX(1); args[1] = step; range_each_func(range, step_i, args); } } return range; }
Convert this range object to a printable form.
static VALUE range_to_s(VALUE range) { VALUE str, str2; str = rb_obj_as_string(RANGE_BEG(range)); str2 = rb_obj_as_string(RANGE_END(range)); str = rb_str_dup(str); rb_str_cat(str, "...", EXCL(range) ? 3 : 2); rb_str_append(str, str2); OBJ_INFECT(str, str2); return str; }