класс Range
Класс 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
Общедоступные методы класса
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, диапазон будет включать конечный объект; в противном случае — исключать.
Общедоступные методы экземпляров
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.
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
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"
# 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 и представит этот объект.
static VALUE
range_begin(VALUE range)
{
return RANGE_BEG(range);
} Возвращает объект, определяющий начало диапазона.
(1..10).begin #=> 1
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, либо всегда возвращать число. Неопределено, какое значение фактически выбирается на каждой итерации.
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.
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
static VALUE
range_each(VALUE range)
{
VALUE beg, end;
long i;
RETURN_SIZED_ENUMERATOR(range, 0, 0, range_enum_size);
beg = RANGE_BEG(range);
end = RANGE_END(range);
if (FIXNUM_P(beg) && NIL_P(end)) {
range_each_fixnum_endless(beg);
}
else if (FIXNUM_P(beg) && FIXNUM_P(end)) { /* fixnums are special */
return range_each_fixnum_loop(beg, end, range);
}
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)) range_each_fixnum_endless(beg);
if (FIXNUM_P(end)) return range_each_fixnum_loop(beg, end, range);
}
else {
if (NIL_P(end)) range_each_bignum_endless(beg);
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
static VALUE
range_end(VALUE range)
{
return RANGE_END(range);
} Возвращает объект, определяющий конец диапазона.
(1..10).end #=> 10 (1...10).end #=> 10
Возвращает массив, содержащий элементы в диапазоне.
(1..7).to_a #=> [1, 2, 3, 4, 5, 6, 7] (1..).to_a #=> RangeError: cannot convert endless range to an array
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
static VALUE
range_exclude_end_p(VALUE range)
{
return EXCL(range) ? Qtrue : Qfalse;
} Возвращает true если диапазон исключает своё конечное значение.
(1..5).exclude_end? #=> false (1...5).exclude_end? #=> true
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]
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.
Возвращает 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
static VALUE
range_inspect(VALUE range)
{
return rb_exec_recursive(inspect_range, range, 0);
} Преобразует этот объект диапазона в печатную форму (используя inspect для преобразования объектов begin и end).
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]
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");
}
VALUE b = RANGE_BEG(range);
if (rb_block_given_p() || (EXCL(range) && !nm) || argc) {
if (NIL_P(b)) {
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 };
int c = NIL_P(b) ? -1 : 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;
}
} Возвращает максимальное значение в диапазоне или массив максимальных значений в диапазоне, если задан целочисленный аргумент.
Для включительных диапазонов с концом максимальное значение диапазона совпадает с концом диапазона.
Если задан аргумент или блок, или self является исключительным, не числовым диапазоном, вызывается Enumerable#max (через super) с аргументом и/или блоком для получения максимальных значений, если self — диапазон без начала, в этом случае возбуждается RangeError.
Если self — исключительный целочисленный диапазон (начало и конец диапазона — целые числа), и не заданы аргументы или блок, возвращает последнее значение в диапазоне (на один меньше, чем конец). В противном случае, если self — исключительный числовой диапазон, возбуждается TypeError.
Возвращает nil, если начальное значение диапазона больше конечного значения. Возвращает nil, если начальное значение исключительного диапазона равно конечному значению. Возбуждает RangeError, если вызов осуществляется для бесконечного диапазона.
Примеры:
(10..20).max #=> 20
(10..20).max(2) #=> [20, 19]
(10...20).max #=> 19
(10...20).max(2) #=> [19, 18]
(10...20).max{|x, y| -x <=> -y } #=> 10
(10...20).max(2){|x, y| -x <=> -y } #=> [10, 11]
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
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
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.
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
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()) {
if (!rb_obj_is_kind_of(step, rb_cNumeric)) {
step = rb_to_int(step);
}
if (rb_equal(step, INT2FIX(0))) {
rb_raise(rb_eArgError, "step can't be 0");
}
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, если начало и конец диапазона являются числовыми.
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.
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
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–2020 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.