In Files

  • range.c

Range

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.

Public Class Methods

new(start, end, exclusive=false) => range click to toggle source

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;
}
            

Public Instance Methods

rng == obj => true or false click to toggle source

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);
}
            
rng === obj => true or false click to toggle source

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);
}
            
begin => obj click to toggle source

Returns the first object in rng.

 
               static VALUE
range_begin(VALUE range)
{
    return RANGE_BEG(range);
}
            
cover?(val) => true or false click to toggle source

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;
}
            
each {| i | block } => rng click to toggle source

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;
}
            
end => obj click to toggle source

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);
}
            
eql?(obj) => true or false click to toggle source

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);
}
            
exclude_end? => true or false click to toggle source

Returns true if rng excludes its end value.

 
               static VALUE
range_exclude_end_p(VALUE range)
{
    return EXCL(range) ? Qtrue : Qfalse;
}
            
first => obj click to toggle source
first(n) => an_array

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];
}
            
hash => fixnum click to toggle source

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);
}
            
include?(val) => true or false click to toggle source

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);
}
            
inspect => string click to toggle source

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);
}
            
last => obj click to toggle source
last(n) => an_array

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)); 
}
            
max => obj click to toggle source
max {| a,b | block } => obj

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;
    }
}
            
member?(val) => true or false click to toggle source

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);
}
            
min => obj click to toggle source
min {| a,b | block } => obj

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;
    }
}
            
step(n=1) {| obj | block } => rng click to toggle source

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;
}
            
to_s => string click to toggle source

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;
}