Spec-Zone.ru › Ruby 2.7

класс Range

Родитель:
Объект
Включенные модули:
Enumerable

Класс Range представляет интервал — множество значений с началом и концом. Диапазоны могут быть созданы с использованием литералов s..e и s...e или с помощью Range::new. Диапазоны, созданные с использованием .., охватывают значения от начала до конца включительно. Те, что созданы с использованием ..., не включают конечное значение. При использовании в качестве итератора диапазоны возвращают каждое значение в последовательности.

(-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"]

Диапазоны без начала/без конца

«Диапазон без начала» и «диапазон без конца» представляют полубесконечный диапазон. Литеральная запись для диапазона без начала:

(..1)
# or
(...1)

Литеральная запись для диапазона без конца:

(1..)
# or similarly
(1...)

Что эквивалентно

(1..nil)  # or similarly (1...nil)
Range.new(1, nil) # or Range.new(1, nil, true)

Диапазоны без начала/конца полезны, например, для идиоматического среза массивов:

[1, 2, 3, 4, 5][...2]   # => [1, 2]
[1, 2, 3, 4, 5][2...]   # => [3, 4, 5]

Некоторые детали реализации:

  • begin диапазона без начала и end диапазона без конца являются nil;

  • each диапазона без начала вызывает исключение;

  • each диапазона без конца перебирает бесконечную последовательность (может быть полезно в сочетании с Enumerable#take_while или аналогичными методами);

  • (1..) и (1...) не равны, хотя технически представляют одну и ту же последовательность.

Пользовательские объекты в диапазонах

Диапазоны могут быть созданы с использованием любых объектов, которые можно сравнивать с помощью оператора <=>. Методы, которые обрабатывают диапазон как последовательность (each и методы, унаследованные от Enumerable), ожидают, что объект начала будет реализовывать метод succ, чтобы возвращать следующий объект в последовательности. Методы step и include? требуют, чтобы начальный объект реализовывал succ или был числовым.

В классе Xs ниже реализованы как <=>, так и succ, чтобы Xs можно было использовать для создания диапазонов. Обратите внимание, что модуль Comparable включён, чтобы метод == был определён с точки зрения <=>.

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

Пример использования Xs для создания диапазона:

r = Xs.new(3)..Xs.new(6)   #=> xxx..xxxxxx
r.to_a                     #=> [xxx, xxxx, xxxxx, xxxxxx]
r.member?(Xs.new(5))       #=> true

Общедоступные методы класса

json_create(object) Показать исходный код
# File ext/json/lib/json/add/range.rb, line 10
def self.json_create(object)
  new(*object['a'])
end

Десериализует строку JSON, создав новый объект Range с аргументами a, сериализованными to_json.

new(begin, end, exclude_end=false) → rng Показать исходный код
static VALUE
range_initialize(int argc, VALUE *argv, VALUE range)
{
    VALUE beg, end, flags;

    rb_scan_args(argc, argv, "21", &beg, &end, &flags);
    range_modify(range);
    range_init(range, beg, end, RBOOL(RTEST(flags)));
    return Qnil;
}

Создаёт диапазон с заданным началом begin и концом end. Если параметр exclude_end опущен или равен false, диапазон будет включать конечный объект; в противном случае — исключать.

Методы публичного экземпляра

step(n=1) {| obj | блок } → rng Показать исходный код
step(n=1) → перечислитель
step(n=1) → арифметическая последовательность
rng % n → перечислитель
rng % n → арифметическая последовательность
static VALUE
range_percent_step(VALUE range, VALUE step)
{
    return range_step(1, &step, range);
}

Итерируется по диапазону, передавая каждый n-ый элемент в блок. Если begin и end являются числовыми, n добавляется на каждой итерации. В противном случае step вызывает succ для итерации по элементам диапазона.

Если блок не указан, вместо этого возвращается перечислитель. В частности, перечислитель является Enumerator::ArithmeticSequence, если begin и end диапазона являются числовыми.

range = Xs.new(1)..Xs.new(10)
range.step(2) {|x| puts x}
puts
range.step(3) {|x| puts x}

Результат:

 1 x
 3 xxx
 5 xxxxx
 7 xxxxxxx
 9 xxxxxxxxx

 1 x
 4 xxxx
 7 xxxxxxx
10 xxxxxxxxxx

См. Range для определения класса Xs.

rng == obj → true или 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);
}

Возвращает true только в том случае, если obj является Range, имеет эквивалентные begin и end элементы (сравнивая их с ==) и имеет то же значение exclude_end?, что и диапазон.

(0..2) == (0..2)            #=> true
(0..2) == Range.new(0,2)    #=> true
(0..2) == (0...2)           #=> false
rng === obj → true или false Показать исходный код
static VALUE
range_eqq(VALUE range, VALUE val)
{
    VALUE ret = range_include_internal(range, val, 1);
    if (ret != Qundef) return ret;
    return r_cover_p(range, RANGE_BEG(range), RANGE_END(range), val);
}

Возвращает true если obj находится между begin и end диапазона, false в противном случае (то же, что cover?). Удобно, что === - это оператор сравнения, используемый в case операторах.

case 79
when 1..50   then   puts "low"
when 51..75  then   puts "medium"
when 76..100 then   puts "high"
end
# Prints "high"

case "2.6.5"
when ..."2.4" then puts "EOL"
when "2.4"..."2.5" then puts "maintenance"
when "2.5"..."2.7" then puts "stable"
when "2.7".. then puts "upcoming"
end
# Prints "stable"
as_json(*) Показать исходный код
# File ext/json/lib/json/add/range.rb, line 16
def as_json(*)
  {
    JSON.create_id  => self.class.name,
    'a'             => [ first, last, exclude_end? ]
  }
end

Возвращает хеш, который будет преобразован в объект JSON и представлять этот объект.

begin → obj Показать исходный код
static VALUE
range_begin(VALUE range)
{
    return RANGE_BEG(range);
}

Возвращает объект, определяющий начало диапазона.

(1..10).begin   #=> 1
bsearch {|obj| блок } → значение Показать исходный код
static VALUE
range_bsearch(VALUE range)
{
    VALUE beg, end, satisfied = Qnil;
    int smaller;

    /* Implementation notes:
     * Floats are handled by mapping them to 64 bits integers.
     * Apart from sign issues, floats and their 64 bits integer have the
     * same order, assuming they are represented as exponent followed
     * by the mantissa. This is true with or without implicit bit.
     *
     * Finding the average of two ints needs to be careful about
     * potential overflow (since float to long can use 64 bits)
     * as well as the fact that -1/2 can be 0 or -1 in C89.
     *
     * Note that -0.0 is mapped to the same int as 0.0 as we don't want
     * (-1...0.0).bsearch to yield -0.0.
     */

#define BSEARCH(conv) \
    do { \
        RETURN_ENUMERATOR(range, 0, 0); \
        if (EXCL(range)) high--; \
        org_high = high; \
        while (low < high) { \
            mid = ((high < 0) == (low < 0)) ? low + ((high - low) / 2) \
                : (low < -high) ? -((-1 - low - high)/2 + 1) : (low + high) / 2; \
            BSEARCH_CHECK(conv(mid)); \
            if (smaller) { \
                high = mid; \
            } \
            else { \
                low = mid + 1; \
            } \
        } \
        if (low == org_high) { \
            BSEARCH_CHECK(conv(low)); \
            if (!smaller) return Qnil; \
        } \
        return satisfied; \
    } while (0)


    beg = RANGE_BEG(range);
    end = RANGE_END(range);

    if (FIXNUM_P(beg) && FIXNUM_P(end)) {
        long low = FIX2LONG(beg);
        long high = FIX2LONG(end);
        long mid, org_high;
        BSEARCH(INT2FIX);
    }
#if SIZEOF_DOUBLE == 8 && defined(HAVE_INT64_T)
    else if (RB_TYPE_P(beg, T_FLOAT) || RB_TYPE_P(end, T_FLOAT)) {
        int64_t low  = double_as_int64(NIL_P(beg) ? -HUGE_VAL : RFLOAT_VALUE(rb_Float(beg)));
        int64_t high = double_as_int64(NIL_P(end) ?  HUGE_VAL : RFLOAT_VALUE(rb_Float(end)));
        int64_t mid, org_high;
        BSEARCH(int64_as_double_to_num);
    }
#endif
    else if (is_integer_p(beg) && is_integer_p(end)) {
        RETURN_ENUMERATOR(range, 0, 0);
        return bsearch_integer_range(beg, end, EXCL(range));
    }
    else if (is_integer_p(beg) && NIL_P(end)) {
        VALUE diff = LONG2FIX(1);
        RETURN_ENUMERATOR(range, 0, 0);
        while (1) {
            VALUE mid = rb_funcall(beg, '+', 1, diff);
            BSEARCH_CHECK(mid);
            if (smaller) {
                return bsearch_integer_range(beg, mid, 0);
            }
            diff = rb_funcall(diff, '*', 1, LONG2FIX(2));
        }
    }
    else if (NIL_P(beg) && is_integer_p(end)) {
        VALUE diff = LONG2FIX(-1);
        RETURN_ENUMERATOR(range, 0, 0);
        while (1) {
            VALUE mid = rb_funcall(end, '+', 1, diff);
            BSEARCH_CHECK(mid);
            if (!smaller) {
                return bsearch_integer_range(mid, end, 0);
            }
            diff = rb_funcall(diff, '*', 1, LONG2FIX(2));
        }
    }
    else {
        rb_raise(rb_eTypeError, "can't do binary search for %s", rb_obj_classname(beg));
    }
    return range;
}

Используя двоичный поиск, находит значение в диапазоне, которое удовлетворяет заданному условию за O(log n), где n - размер диапазона.

Вы можете использовать этот метод в двух случаях: режим поиска минимума и режим поиска любого значения. В любом случае элементы диапазона должны быть монотонными (или отсортированными) относительно блока.

В режиме поиска минимума (это хороший выбор для типичного случая использования) блок должен возвращать true или false, и должно существовать значение x, такое что:

  • блок возвращает false для любого значения, меньшего x, и

  • блок возвращает true для любого значения, большего или равного x.

Если x находится в диапазоне, этот метод возвращает значение x. В противном случае он возвращает nil.

ary = [0, 4, 7, 10, 12]
(0...ary.size).bsearch {|i| ary[i] >= 4 } #=> 1
(0...ary.size).bsearch {|i| ary[i] >= 6 } #=> 2
(0...ary.size).bsearch {|i| ary[i] >= 8 } #=> 3
(0...ary.size).bsearch {|i| ary[i] >= 100 } #=> nil

(0.0...Float::INFINITY).bsearch {|x| Math.log(x) >= 0 } #=> 1.0

В режиме поиска любого значения (это поведение аналогично bsearch(3) libc) блок должен возвращать число, и должны существовать два значения x и y (x <= y), такие что:

  • блок возвращает положительное число для v, если v < x,

  • блок возвращает ноль для v, если x <= v < y, и

  • блок возвращает отрицательное число для v, если y <= v.

Этот метод возвращает любое значение, которое находится внутри пересечения заданного диапазона и x…y (если таковое есть). Если нет значения, удовлетворяющего условию, он возвращает nil.

ary = [0, 100, 100, 100, 200]
(0..4).bsearch {|i| 100 - ary[i] } #=> 1, 2 or 3
(0..4).bsearch {|i| 300 - ary[i] } #=> nil
(0..4).bsearch {|i|  50 - ary[i] } #=> nil

Вы не должны смешивать оба режима одновременно; блок всегда должен возвращать true/false или всегда возвращать число. Не определено, какое значение фактически выбирается на каждой итерации.

count → int Показать исходный код
count(item) → int
count { |obj| блок } → int
static VALUE
range_count(int argc, VALUE *argv, VALUE range)
{
    if (argc != 0) {
        /* It is odd for instance (1...).count(0) to return Infinity. Just let
         * it loop. */
        return rb_call_super(argc, argv);
    }
    else if (rb_block_given_p()) {
        /* Likewise it is odd for instance (1...).count {|x| x == 0 } to return
         * Infinity. Just let it loop. */
        return rb_call_super(argc, argv);
    }
    else if (NIL_P(RANGE_END(range))) {
        /* We are confident that the answer is Infinity. */
        return DBL2NUM(HUGE_VAL);
    }
    else if (NIL_P(RANGE_BEG(range))) {
        /* We are confident that the answer is Infinity. */
        return DBL2NUM(HUGE_VAL);
    }
    else {
        return rb_call_super(argc, argv);
    }
}

Идентично Enumerable#count, за исключением того, что он возвращает Infinity для бесконечных диапазонов.

cover?(obj) → true или false Показать исходный код
cover?(range) → true или false
static VALUE
range_cover(VALUE range, VALUE val)
{
    VALUE beg, end;

    beg = RANGE_BEG(range);
    end = RANGE_END(range);

    if (rb_obj_is_kind_of(val, rb_cRange)) {
        return RBOOL(r_cover_range_p(range, beg, end, val));
    }
    return r_cover_p(range, beg, end, val);
}

Возвращает true если obj находится между началом и концом диапазона.

Это проверяет begin <= obj <= end когда exclude_end? равно false и begin <= obj < end когда exclude_end? равно true.

Если вызывается с аргументом Range, возвращает true когда заданный диапазон покрыт получателем, сравнивая значения begin и end. Если аргумент может быть обработан как последовательность, этот метод обрабатывает его таким образом. В конкретном случае (a..b).cover?(c...d) с a <= c && b < d, конец последовательности должен быть вычислен, что может привести к плохой производительности, если c не является числовым. Возвращает false если начальное значение диапазона больше конечного значения. Также возвращает false если один из внутренних вызовов <=> возвращает nil (указывающее, что объекты не могут быть сравнены).

("a".."z").cover?("c")  #=> true
("a".."z").cover?("5")  #=> false
("a".."z").cover?("cc") #=> true
("a".."z").cover?(1)    #=> false
(1..5).cover?(2..3)     #=> true
(1..5).cover?(0..6)     #=> false
(1..5).cover?(1...6)    #=> true
each {| i | блок } → rng Показать исходный код
each → перечислитель
static VALUE
range_each(VALUE range)
{
    VALUE beg, end;
    long i, lim;

    RETURN_SIZED_ENUMERATOR(range, 0, 0, range_enum_size);

    beg = RANGE_BEG(range);
    end = RANGE_END(range);

    if (FIXNUM_P(beg) && NIL_P(end)) {
      fixnum_endless:
        i = FIX2LONG(beg);
        while (FIXABLE(i)) {
            rb_yield(LONG2FIX(i++));
        }
        beg = LONG2NUM(i);
      bignum_endless:
        for (;; beg = rb_big_plus(beg, INT2FIX(1)))
            rb_yield(beg);
    }
    else if (FIXNUM_P(beg) && FIXNUM_P(end)) { /* fixnums are special */
      fixnum_loop:
        lim = FIX2LONG(end);
        if (!EXCL(range))
            lim += 1;
        for (i = FIX2LONG(beg); i < lim; i++) {
            rb_yield(LONG2FIX(i));
        }
    }
    else if (RB_INTEGER_TYPE_P(beg) && (NIL_P(end) || RB_INTEGER_TYPE_P(end))) {
        if (SPECIAL_CONST_P(end) || RBIGNUM_POSITIVE_P(end)) { /* end >= FIXNUM_MIN */
            if (!FIXNUM_P(beg)) {
                if (RBIGNUM_NEGATIVE_P(beg)) {
                    do {
                        rb_yield(beg);
                    } while (!FIXNUM_P(beg = rb_big_plus(beg, INT2FIX(1))));
                    if (NIL_P(end)) goto fixnum_endless;
                    if (FIXNUM_P(end)) goto fixnum_loop;
                }
                else {
                    if (NIL_P(end)) goto bignum_endless;
                    if (FIXNUM_P(end)) return range;
                }
            }
            if (FIXNUM_P(beg)) {
                i = FIX2LONG(beg);
                do {
                    rb_yield(LONG2FIX(i));
                } while (POSFIXABLE(++i));
                beg = LONG2NUM(i);
            }
            ASSUME(!FIXNUM_P(beg));
            ASSUME(!SPECIAL_CONST_P(end));
        }
        if (!FIXNUM_P(beg) && RBIGNUM_SIGN(beg) == RBIGNUM_SIGN(end)) {
            if (EXCL(range)) {
                while (rb_big_cmp(beg, end) == INT2FIX(-1)) {
                    rb_yield(beg);
                    beg = rb_big_plus(beg, INT2FIX(1));
                }
            }
            else {
                VALUE c;
                while ((c = rb_big_cmp(beg, end)) != INT2FIX(1)) {
                    rb_yield(beg);
                    if (c == INT2FIX(0)) break;
                    beg = rb_big_plus(beg, INT2FIX(1));
                }
            }
        }
    }
    else if (SYMBOL_P(beg) && (NIL_P(end) || SYMBOL_P(end))) { /* symbols are special */
        beg = rb_sym2str(beg);
        if (NIL_P(end)) {
            rb_str_upto_endless_each(beg, sym_each_i, 0);
        }
        else {
            rb_str_upto_each(beg, rb_sym2str(end), EXCL(range), sym_each_i, 0);
        }
    }
    else {
        VALUE tmp = rb_check_string_type(beg);

        if (!NIL_P(tmp)) {
            if (!NIL_P(end)) {
                rb_str_upto_each(tmp, end, EXCL(range), each_i, 0);
            }
            else {
                rb_str_upto_endless_each(tmp, each_i, 0);
            }
        }
        else {
            if (!discrete_object_p(beg)) {
                rb_raise(rb_eTypeError, "can't iterate from %s",
                         rb_obj_classname(beg));
            }
            if (!NIL_P(end))
                range_each_func(range, each_i, 0);
            else
                for (;; beg = rb_funcallv(beg, id_succ, 0, 0))
                    rb_yield(beg);
        }
    }
    return range;
}

Итерируется по элементам диапазона, передавая каждый из них в блок.

Метод each может быть использован только в том случае, если начальный объект диапазона поддерживает метод succ. TypeError генерируется, если у объекта нет определенного метода succ (например, Float).

Если блок не задан, возвращается перечислитель.

(10..15).each {|n| print n, ' ' }
# prints: 10 11 12 13 14 15

(2.5..5).each {|n| print n, ' ' }
# raises: TypeError: can't iterate from Float
end → obj Показать исходный код
static VALUE
range_end(VALUE range)
{
    return RANGE_END(range);
}

Возвращает объект, определяющий конец диапазона.

(1..10).end    #=> 10
(1...10).end   #=> 10
entries → массив Показать исходный код
static VALUE
range_to_a(VALUE range)
{
    if (NIL_P(RANGE_END(range))) {
        rb_raise(rb_eRangeError, "cannot convert endless range to an array");
    }
    return rb_call_super(0, 0);
}

Возвращает массив, содержащий элементы в диапазоне.

(1..7).to_a  #=> [1, 2, 3, 4, 5, 6, 7]
(1..).to_a   #=> RangeError: cannot convert endless range to an array
eql?(obj) → true или 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);
}

Возвращает true только в том случае, если obj является Range, имеет эквивалентные begin и end элементы (сравнивая их с eql?) и имеет то же значение exclude_end?, что и диапазон.

(0..2).eql?(0..2)            #=> true
(0..2).eql?(Range.new(0,2))  #=> true
(0..2).eql?(0...2)           #=> false
exclude_end? → true или false Показать исходный код
static VALUE
range_exclude_end_p(VALUE range)
{
    return EXCL(range) ? Qtrue : Qfalse;
}

Возвращает true если диапазон исключает своё конечное значение.

(1..5).exclude_end?     #=> false
(1...5).exclude_end?    #=> true
first → obj Показать исходный код
first(n) → массив
static VALUE
range_first(int argc, VALUE *argv, VALUE range)
{
    VALUE n, ary[2];

    if (NIL_P(RANGE_BEG(range))) {
        rb_raise(rb_eRangeError, "cannot get the first element of beginless range");
    }
    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, idEach, 0, 0, first_i, (VALUE)ary);

    return ary[1];
}

Возвращает первый объект в диапазоне или массив из первых n элементов.

(10..20).first     #=> 10
(10..20).first(3)  #=> [10, 11, 12]
hash → целое Показать исходный код
static VALUE
range_hash(VALUE range)
{
    st_index_t hash = EXCL(range);
    VALUE v;

    hash = rb_hash_start(hash);
    v = rb_hash(RANGE_BEG(range));
    hash = rb_hash_uint(hash, NUM2LONG(v));
    v = rb_hash(RANGE_END(range));
    hash = rb_hash_uint(hash, NUM2LONG(v));
    hash = rb_hash_uint(hash, EXCL(range) << 24);
    hash = rb_hash_end(hash);

    return ST2FIX(hash);
}

Вычисляет код хэша для этого диапазона. Два диапазона с одинаковыми начальными и конечными точками (используя eql?), и одинаковым значением exclude_end? будут генерировать одинаковый код хэша.

См. также Object#hash.

include?(obj) → true или false Показать исходный код
static VALUE
range_include(VALUE range, VALUE val)
{
    VALUE ret = range_include_internal(range, val, 0);
    if (ret != Qundef) return ret;
    return rb_call_super(1, &val);
}

Возвращает true если obj является элементом диапазона, false в противном случае.

("a".."z").include?("g")   #=> true
("a".."z").include?("A")   #=> false
("a".."z").include?("cc")  #=> false

Если вам нужно убедиться, что obj находится между begin и end, используйте cover?

("a".."z").cover?("cc")  #=> true

Если begin и end являются числовыми, include? ведет себя как cover?

(1..3).include?(1.5) # => true
inspect → строка Показать исходный код
static VALUE
range_inspect(VALUE range)
{
    return rb_exec_recursive(inspect_range, range, 0);
}

Преобразует этот объект диапазона в удобочитаемую форму (используя inspect для преобразования объектов begin и end).

last → obj Показать исходный код
last(n) → массив
static VALUE
range_last(int argc, VALUE *argv, VALUE range)
{
    VALUE b, e;

    if (NIL_P(RANGE_END(range))) {
        rb_raise(rb_eRangeError, "cannot get the last element of endless range");
    }
    if (argc == 0) return RANGE_END(range);

    b = RANGE_BEG(range);
    e = RANGE_END(range);
    if (RB_INTEGER_TYPE_P(b) && RB_INTEGER_TYPE_P(e) &&
        RB_LIKELY(rb_method_basic_definition_p(rb_cRange, idEach))) {
        return rb_int_range_last(argc, argv, range);
    }
    return rb_ary_last(argc, argv, rb_Array(range));
}

Возвращает последний объект в диапазоне или массив последних n элементов.

Обратите внимание, что без аргументов last вернёт объект, определяющий конец диапазона, даже если exclude_end? имеет значение true.

(10..20).last      #=> 20
(10...20).last     #=> 20
(10..20).last(3)   #=> [18, 19, 20]
(10...20).last(3)  #=> [17, 18, 19]
max → obj Показать исходный код
max {| a,b | блок } → obj
max(n) → obj
max(n) {| a,b | блок } → obj
static VALUE
range_max(int argc, VALUE *argv, VALUE range)
{
    VALUE e = RANGE_END(range);
    int nm = FIXNUM_P(e) || rb_obj_is_kind_of(e, rb_cNumeric);

    if (NIL_P(RANGE_END(range))) {
        rb_raise(rb_eRangeError, "cannot get the maximum of endless range");
    }

    if (rb_block_given_p() || (EXCL(range) && !nm) || argc) {
        if (NIL_P(RANGE_BEG(range))) {
            rb_raise(rb_eRangeError, "cannot get the maximum of beginless range with custom comparison method");
        }
        return rb_call_super(argc, argv);
    }
    else {
        struct cmp_opt_data cmp_opt = { 0, 0 };
        VALUE b = RANGE_BEG(range);
        int c = OPTIMIZED_CMP(b, e, cmp_opt);

        if (c > 0)
            return Qnil;
        if (EXCL(range)) {
            if (!RB_INTEGER_TYPE_P(e)) {
                rb_raise(rb_eTypeError, "cannot exclude non Integer end value");
            }
            if (c == 0) return Qnil;
            if (!RB_INTEGER_TYPE_P(b)) {
                rb_raise(rb_eTypeError, "cannot exclude end value with non Integer begin value");
            }
            if (FIXNUM_P(e)) {
                return LONG2NUM(FIX2LONG(e) - 1);
            }
            return rb_funcall(e, '-', 1, INT2FIX(1));
        }
        return e;
    }
}

Возвращает максимальное значение в диапазоне. Возвращает nil если начальное значение диапазона больше конечного значения. Возвращает nil если начальное значение исключающего диапазона равно конечному значению.

Можно использовать необязательный блок для переопределения метода сравнения по умолчанию a <=> b.

(10..20).max    #=> 20
member?(obj) → true или false Показать исходный код
static VALUE
range_include(VALUE range, VALUE val)
{
    VALUE ret = range_include_internal(range, val, 0);
    if (ret != Qundef) return ret;
    return rb_call_super(1, &val);
}

Возвращает true если obj является элементом диапазона, false в противном случае.

("a".."z").include?("g")   #=> true
("a".."z").include?("A")   #=> false
("a".."z").include?("cc")  #=> false

Если вам нужно убедиться, что obj находится между begin и end, используйте cover?

("a".."z").cover?("cc")  #=> true

Если begin и end являются числовыми, include? ведет себя как cover?

(1..3).include?(1.5) # => true
min → obj Показать исходный код
min {| a,b | блок } → obj
min(n) → массив
min(n) {| a,b | блок } → массив
static VALUE
range_min(int argc, VALUE *argv, VALUE range)
{
    if (NIL_P(RANGE_BEG(range))) {
        rb_raise(rb_eRangeError, "cannot get the minimum of beginless range");
    }

    if (rb_block_given_p()) {
        if (NIL_P(RANGE_END(range))) {
            rb_raise(rb_eRangeError, "cannot get the minimum of endless range with custom comparison method");
        }
        return rb_call_super(argc, argv);
    }
    else if (argc != 0) {
        return range_first(argc, argv, range);
    }
    else {
        struct cmp_opt_data cmp_opt = { 0, 0 };
        VALUE b = RANGE_BEG(range);
        VALUE e = RANGE_END(range);
        int c = NIL_P(e) ? -1 : OPTIMIZED_CMP(b, e, cmp_opt);

        if (c > 0 || (c == 0 && EXCL(range)))
            return Qnil;
        return b;
    }
}

Возвращает минимальное значение в диапазоне. Возвращает nil если начальное значение диапазона больше конечного значения. Возвращает nil если начальное значение исключающего диапазона равно конечному значению.

Можно использовать необязательный блок для переопределения метода сравнения по умолчанию a <=> b.

(10..20).min    #=> 10
minmax → [obj, obj] Показать исходный код
minmax {| a,b | блок } → [obj, obj]
static VALUE
range_minmax(VALUE range)
{
    if (rb_block_given_p()) {
        return rb_call_super(0, NULL);
    }
    return rb_assoc_new(
        rb_funcall(range, id_min, 0),
        rb_funcall(range, id_max, 0)
    );
}

Возвращает массив из двух элементов, содержащий минимальное и максимальное значения в диапазоне.

Можно использовать необязательный блок для переопределения метода сравнения по умолчанию a <=> b.

size → число Показать исходный код
static VALUE
range_size(VALUE range)
{
    VALUE b = RANGE_BEG(range), e = RANGE_END(range);
    if (rb_obj_is_kind_of(b, rb_cNumeric)) {
        if (rb_obj_is_kind_of(e, rb_cNumeric)) {
            return ruby_num_interval_step_size(b, e, INT2FIX(1), EXCL(range));
        }
        if (NIL_P(e)) {
            return DBL2NUM(HUGE_VAL);
        }
    }
    else if (NIL_P(b)) {
        return DBL2NUM(HUGE_VAL);
    }

    return Qnil;
}

Возвращает количество элементов в диапазоне. И начало, и конец Range должны быть Numeric, в противном случае возвращается nil.

(10..20).size    #=> 11
('a'..'z').size  #=> nil
(-Float::INFINITY..Float::INFINITY).size #=> Infinity
step(n=1) {| obj | блок } → rng Показать исходный код
step(n=1) → итератор
step(n=1) → арифметическая_последовательность
rng % n → итератор
rng % n → арифметическая_последовательность
static VALUE
range_step(int argc, VALUE *argv, VALUE range)
{
    VALUE b, e, step, tmp;

    b = RANGE_BEG(range);
    e = RANGE_END(range);
    step = (!rb_check_arity(argc, 0, 1) ? INT2FIX(1) : argv[0]);

    if (!rb_block_given_p()) {
        const VALUE b_num_p = rb_obj_is_kind_of(b, rb_cNumeric);
        const VALUE e_num_p = rb_obj_is_kind_of(e, rb_cNumeric);
        if ((b_num_p && (NIL_P(e) || e_num_p)) || (NIL_P(b) && e_num_p)) {
            return rb_arith_seq_new(range, ID2SYM(rb_frame_this_func()), argc, argv,
                    range_step_size, b, e, step, EXCL(range));
        }

        RETURN_SIZED_ENUMERATOR(range, argc, argv, range_step_size);
    }

    step = check_step_domain(step);

    if (FIXNUM_P(b) && NIL_P(e) && FIXNUM_P(step)) {
        long i = FIX2LONG(b), unit = FIX2LONG(step);
        do {
            rb_yield(LONG2FIX(i));
            i += unit;          /* FIXABLE+FIXABLE never overflow */
        } while (FIXABLE(i));
        b = LONG2NUM(i);

        for (;; b = rb_big_plus(b, step))
            rb_yield(b);
    }
    else 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 (SYMBOL_P(b) && (NIL_P(e) || SYMBOL_P(e))) { /* symbols are special */
        VALUE iter[2];
        iter[0] = INT2FIX(1);
        iter[1] = step;

        b = rb_sym2str(b);
        if (NIL_P(e)) {
            rb_str_upto_endless_each(b, sym_step_i, (VALUE)iter);
        }
        else {
            rb_str_upto_each(b, rb_sym2str(e), EXCL(range), sym_step_i, (VALUE)iter);
        }
    }
    else if (ruby_float_step(b, e, step, EXCL(range), TRUE)) {
        /* 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) ? '<' : idLE;
        VALUE v = b;
        int i = 0;

        while (NIL_P(e) || RTEST(rb_funcall(v, op, 1, e))) {
            rb_yield(v);
            i++;
            v = rb_funcall(b, '+', 1, rb_funcall(INT2NUM(i), '*', 1, step));
        }
    }
    else {
        tmp = rb_check_string_type(b);

        if (!NIL_P(tmp)) {
            VALUE iter[2];

            b = tmp;
            iter[0] = INT2FIX(1);
            iter[1] = step;

            if (NIL_P(e)) {
                rb_str_upto_endless_each(b, step_i, (VALUE)iter);
            }
            else {
                rb_str_upto_each(b, e, EXCL(range), step_i, (VALUE)iter);
            }
        }
        else {
            VALUE args[2];

            if (!discrete_object_p(b)) {
                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, (VALUE)args);
        }
    }
    return range;
}

Итерируется по диапазону, передавая каждый n-й элемент в блок. Если begin и end являются числовыми, n добавляется на каждой итерации. В противном случае step вызывает succ для итерации по элементам диапазона.

Если блок не задан, вместо него возвращается итератор. В частности, итератор является Enumerator::ArithmeticSequence, если begin и end диапазона являются числовыми.

range = Xs.new(1)..Xs.new(10)
range.step(2) {|x| puts x}
puts
range.step(3) {|x| puts x}

производит:

 1 x
 3 xxx
 5 xxxxx
 7 xxxxxxx
 9 xxxxxxxxx

 1 x
 4 xxxx
 7 xxxxxxx
10 xxxxxxxxxx

См. Range для определения класса Xs.

to_a → массив Показать исходный код
static VALUE
range_to_a(VALUE range)
{
    if (NIL_P(RANGE_END(range))) {
        rb_raise(rb_eRangeError, "cannot convert endless range to an array");
    }
    return rb_call_super(0, 0);
}

Возвращает массив, содержащий элементы диапазона.

(1..7).to_a  #=> [1, 2, 3, 4, 5, 6, 7]
(1..).to_a   #=> RangeError: cannot convert endless range to an array
to_json(*args) Показать исходный код
# File ext/json/lib/json/add/range.rb, line 26
def to_json(*args)
  as_json.to_json(*args)
end

Сохраняет имя класса (Range) с JSON массивом аргументов a, который включает first (целое число), last (целое число) и exclude_end? (логическое значение) в виде JSON строки.

to_s → строка Показать исходный код
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);

    return str;
}

Преобразует этот объект диапазона в удобочитаемую форму (используя to_s для преобразования объектов begin и end).

Ruby Core © 1993–2017 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API