класс Range
Представляет собой интервал — множество значений с началом и концом. Диапазоны можно создавать, используя литералы s..e и s...e, или с помощью ::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"]
Пользовательские объекты в диапазонах
Диапазоны можно создавать, используя любые объекты, которые можно сравнивать с помощью оператора <=>. Методы, которые обрабатывают диапазон как последовательность (#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, то rng будет включать конечный объект; в противном случае, он будет исключён.
Общедоступные методы экземпляров
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 является диапазоном, имеет эквивалентные начальное и конечное значения (сравнение производится с помощью ==) и имеет такое же значение флага 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)
{
return rb_funcall(range, rb_intern("include?"), 1, val);
} Возвращает true, если obj является элементом диапазона, и false в противном случае. Удобно, что оператор сравнения === используется в операциях case.
case 79 when 1..50 then print "low\n" when 51..75 then print "medium\n" when 76..100 then print "high\n" end
вывод:
high
# 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_CHECK(expr) \
do { \
VALUE val = (expr); \
VALUE v = rb_yield(val); \
if (FIXNUM_P(v)) { \
if (v == INT2FIX(0)) return val; \
smaller = (SIGNED_VALUE)v < 0; \
} \
else if (v == Qtrue) { \
satisfied = val; \
smaller = 1; \
} \
else if (v == Qfalse || v == Qnil) { \
smaller = 0; \
} \
else if (rb_obj_is_kind_of(v, rb_cNumeric)) { \
int cmp = rb_cmpint(rb_funcall(v, id_cmp, 1, INT2FIX(0)), v, INT2FIX(0)); \
if (!cmp) return val; \
smaller = cmp < 0; \
} \
else { \
rb_raise(rb_eTypeError, "wrong argument type %"PRIsVALUE \
" (must be numeric, true, false or nil)", \
rb_obj_class(v)); \
} \
} while (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(RFLOAT_VALUE(rb_Float(beg)));
int64_t high = double_as_int64(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)) {
VALUE low = rb_to_int(beg);
VALUE high = rb_to_int(end);
VALUE mid, org_high;
RETURN_ENUMERATOR(range, 0, 0);
if (EXCL(range)) high = rb_funcall(high, '-', 1, INT2FIX(1));
org_high = high;
while (rb_cmpint(rb_funcall(low, id_cmp, 1, high), low, high) < 0) {
mid = rb_funcall(rb_funcall(high, '+', 1, low), id_div, 1, INT2FIX(2));
BSEARCH_CHECK(mid);
if (smaller) {
high = mid;
}
else {
low = rb_funcall(mid, '+', 1, INT2FIX(1));
}
}
if (rb_equal(low, org_high)) {
BSEARCH_CHECK(low);
if (!smaller) return Qnil;
}
return satisfied;
}
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_cover(VALUE range, VALUE val)
{
VALUE beg, end;
beg = RANGE_BEG(range);
end = RANGE_END(range);
return r_cover_p(range, beg, end, val);
} Возвращает true, если obj находится между началом и концом диапазона.
Это проверяет, begin <= obj <= end , когда exclude_end? имеет значение false и begin <= obj < end , когда exclude_end? имеет значение true.
("a".."z").cover?("c") #=> true
("a".."z").cover?("5") #=> false
("a".."z").cover?("cc") #=> true
static VALUE
range_each(VALUE range)
{
VALUE beg, end;
RETURN_SIZED_ENUMERATOR(range, 0, 0, range_enum_size);
beg = RANGE_BEG(range);
end = RANGE_END(range);
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 (SYMBOL_P(beg) && SYMBOL_P(end)) { /* symbols are special */
VALUE args[2];
args[0] = rb_sym2str(end);
args[1] = EXCL(range) ? Qtrue : Qfalse;
rb_block_call(rb_sym2str(beg), rb_intern("upto"), 2, args, sym_each_i, 0);
}
else {
VALUE tmp = rb_check_string_type(beg);
if (!NIL_P(tmp)) {
VALUE args[2];
args[0] = end;
args[1] = EXCL(range) ? Qtrue : Qfalse;
rb_block_call(tmp, rb_intern("upto"), 2, args, each_i, 0);
}
else {
if (!discrete_object_p(beg)) {
rb_raise(rb_eTypeError, "can't iterate from %s",
rb_obj_classname(beg));
}
range_each_func(range, each_i, 0);
}
}
return range;
} Проходит по элементам диапазона, передавая каждый из них в блок.
Метод each может быть использован только если начальный объект диапазона поддерживает метод succ. Если у объекта нет определённого метода succ (например, у Float), возникает исключение TypeError.
Если блок не указан, возвращается итератор вместо него.
(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
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 является диапазоном, имеет эквивалентные начальное и конечное значения (сравнение производится с помощью 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 (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 LONG2FIX(hash);
} Вычисляет хэш-код для этого диапазона. Два диапазона с одинаковыми начальным и конечными точками (используя eql?) и одинаковым значением exclude_end? будут генерировать одинаковый хэш-код.
См. также Object#hash.
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) ||
linear_object_p(beg) || linear_object_p(end);
if (nv ||
!NIL_P(rb_check_to_integer(beg, "to_int")) ||
!NIL_P(rb_check_to_integer(end, "to_int"))) {
return r_cover_p(range, beg, end, val);
}
else if (RB_TYPE_P(beg, T_STRING) && RB_TYPE_P(end, T_STRING)) {
VALUE rb_str_include_range_p(VALUE beg, VALUE end, VALUE val, VALUE exclusive);
return rb_str_include_range_p(beg, end, val, RANGE_EXCL(range));
}
/* TODO: ruby_frame->this_func = rb_intern("include?"); */
return rb_call_super(1, &val);
} Возвращает true, если obj является элементом диапазона, и false в противном случае. Если begin и end являются числами, сравнение выполняется по величине значений.
("a".."z").include?("g") #=> true
("a".."z").include?("A") #=> false
("a".."z").include?("cc") #=> false
static VALUE
range_inspect(VALUE range)
{
return rb_exec_recursive(inspect_range, range, 0);
} Преобразует этот объект диапазона в удобочитаемую форму (используя inspect для преобразования начального и конечного объектов).
static VALUE
range_last(int argc, VALUE *argv, VALUE range)
{
if (argc == 0) return RANGE_END(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 (rb_block_given_p() || (EXCL(range) && !nm) || argc) {
return rb_call_super(argc, argv);
}
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 (!FIXNUM_P(e) && !rb_obj_is_kind_of(e, rb_cInteger)) {
rb_raise(rb_eTypeError, "cannot exclude non Integer end value");
}
if (c == 0) return Qnil;
if (!FIXNUM_P(b) && !rb_obj_is_kind_of(b,rb_cInteger)) {
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, если начальное значение диапазона больше конечного.
Можно указать необязательный блок для переопределения метода сравнения по умолчанию a <=> b.
(10..20).max #=> 20
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) ||
linear_object_p(beg) || linear_object_p(end);
if (nv ||
!NIL_P(rb_check_to_integer(beg, "to_int")) ||
!NIL_P(rb_check_to_integer(end, "to_int"))) {
return r_cover_p(range, beg, end, val);
}
else if (RB_TYPE_P(beg, T_STRING) && RB_TYPE_P(end, T_STRING)) {
VALUE rb_str_include_range_p(VALUE beg, VALUE end, VALUE val, VALUE exclusive);
return rb_str_include_range_p(beg, end, val, RANGE_EXCL(range));
}
/* TODO: ruby_frame->this_func = rb_intern("include?"); */
return rb_call_super(1, &val);
} Возвращает true, если obj является элементом диапазона, и false в противном случае. Если begin и end являются числами, сравнение выполняется по величине значений.
("a".."z").include?("g") #=> true
("a".."z").include?("A") #=> false
("a".."z").include?("cc") #=> false
static VALUE
range_min(int argc, VALUE *argv, VALUE range)
{
if (rb_block_given_p()) {
return rb_call_super(argc, argv);
}
else if (argc != 0) {
return range_first(argc, argv, range);
}
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;
}
} Возвращает минимальное значение в диапазоне. Возвращает nil если начальное значение диапазона больше конечного значения.
Можно указать необязательный блок для переопределения метода сравнения по умолчанию a <=> b.
(10..20).min #=> 10
static VALUE
range_size(VALUE range)
{
VALUE b = RANGE_BEG(range), e = RANGE_END(range);
if (rb_obj_is_kind_of(b, rb_cNumeric) && rb_obj_is_kind_of(e, rb_cNumeric)) {
return ruby_num_interval_step_size(b, e, INT2FIX(1), EXCL(range));
}
return Qnil;
} Возвращает количество элементов в диапазоне. И начало, и конец диапазона Range должны быть числовыми, в противном случае возвращается 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;
RETURN_SIZED_ENUMERATOR(range, argc, argv, range_step_size);
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 (SYMBOL_P(b) && SYMBOL_P(e)) { /* symbols are special */
VALUE args[2], iter[2];
args[0] = rb_sym2str(e);
args[1] = EXCL(range) ? Qtrue : Qfalse;
iter[0] = INT2FIX(1);
iter[1] = step;
rb_block_call(rb_sym2str(b), rb_intern("upto"), 2, args, sym_step_i, (VALUE)iter);
}
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) ? '<' : idLE;
VALUE v = b;
int i = 0;
while (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 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 (!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-й элемент в блок. Если начало и конец являются числовыми, n добавляется на каждой итерации. В противном случае step вызывает succ для итерирования по элементам диапазона.
Если блок не задан, вместо него возвращается перечислитель.
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_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, range);
return str;
} Преобразует этот объект диапазона в удобочитаемую форму (используя to_s для преобразования объектов начала и конца).
Ruby Core © 1993–2017 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.