Понятие диапазона интуитивно понятно, но и у него имеются некоторые неочевидные особенности и способы применения. Одним из самых простых является числовой диапазон:
digits = 0..9
scalel = 0..10
scale2 = 0...10
Оператор ..
включает конечную точку, а оператор ...
не включает. (Если это вас неочевидно, просто запомните.) Таким образом, диапазоны digits
и scale2
из предыдущего примера одинаковы.
Но диапазоны могут состоять не только из целых чисел — более того, не только из чисел. Началом и концом диапазона в Ruby может быть любой объект. Однако, как мы вскоре увидим, не все диапазоны осмыслены или полезны.
Основные операции над диапазоном — обход, преобразование в массив, а также выяснение, попадает ли некоторый объект в данный диапазон. Рассмотрим разнообразные варианты этих и других операций.
6.2.1. Открытые и замкнутые диапазоны
Диапазон называется замкнутым , если включает конечную точку, и открытым — в противном случае:
r1 = 3..6 # Замкнутый.
r2 = 3...6 # Открытый.
a1 = r1.to_a # [3,4,5,6]
а2 = r2.to_a # [3,4,5]
Нельзя сконструировать диапазон, который не включал бы начальную точку. Можно считать это ограничением языка.
6.2.2. Нахождение границ диапазона
Методы first
и last
возвращают соответственно левую и правую границу диапазона. У них есть синонимы begin
и end
(это еще и ключевые слова, но интерпретируются как вызов метода, если явно указан вызывающий объект).
r1 = 3..6
r2 = 3...6
r1a, r1b = r1. first, r1.last # 3,6
r1c, r1d = r1.begin, r1.end # 3,6
r2a, r2b = r1.begin, r1.end # 3,6
Метод exclude_end?
сообщает, включена ли в диапазон конечная точка:
r1.exclude_end? # false
r2.exclude_end? # true
Обычно диапазон можно обойти. Для этого класс, которому принадлежат границы диапазона, должен предоставлять осмысленный метод succ
(следующий).
(3..6).each {|x| puts x } # Печатаются четыре строки
# (скобки обязательны).
Пока все хорошо. И тем не менее будьте очень осторожны при работе со строковыми диапазонами! В классе String
имеется метод succ
, но он не слишком полезен. Пользоваться этой возможностью следует только при строго контролируемых условиях, поскольку метод succ
определен не вполне корректно. (В определении используется, скорее, «интуитивно очевидный», нежели лексикографический порядок, поэтому существуют строки, для которых «следующая» не имеет смысла.)
r1 = "7".."9"
r2 = "7".."10"
r1.each {|x| puts x } # Печатаются три строки.
r2.each {|x| puts x } # Ничего не печатается!
Предыдущие примеры похожи, но ведут себя по-разному. Отчасти причина в том, что границы второго диапазона — строки разной длины. Мы ожидаем, что в диапазон входят строки "7"
, "8"
, "9"
и "10"
, но что происходит на самом деле?
При обходе диапазона r2
мы начинаем со значения "7"
и входим в цикл, который завершается, когда текущее значение окажется больше правой границы. Но ведь "7"
и "10"
— не числа, а строки, и сравниваются они как строки, то есть лексикографически. Поэтому левая граница оказывается больше правой, и цикл не выполняется ни разу.
А что сказать по поводу диапазонов чисел с плавающей точкой? Такой диапазон можно сконструировать и, конечно, проверить, попадает ли в него конкретное число. Это полезно. Но обойти такой диапазон нельзя, так как метод succ
отсутствует.
fr = 2.0..2.2
fr.each {|x| puts x } # Ошибка!
Почему для чисел с плавающей точкой нет метода succ
? Теоретически можно было бы увеличивать число на некоторое приращение. Но величина такого приращения сильно зависела бы от конкретной машины, при этом даже для обхода «небольшого» диапазона понадобилось бы гигантское число итераций, а полезность такой операции весьма сомнительна.
6.2.4. Проверка принадлежности диапазону
Зачем нужен диапазон, если нельзя проверить, принадлежит ли ему конкретный объект? Эта задача легко решается с помощью метода include?
:
r1 = 23456..34567
x = 14142
y = 31416
r1.include?(x) # false
r1.include?(у) # true
У этого метода есть также синоним member?
.
А как он работает? Как интерпретатор определяет, принадлежит ли объект диапазону? Просто путем сравнения с границами (поэтому проверка принадлежности диапазону возможна лишь, если определен осмысленный оператор <=>
). Следовательно, запись (a..b).include?(x)
эквивалентна x >= a and x <= b
. Еще раз предупреждаем: будьте осторожны со строковыми диапазонами!
Читать дальше
Конец ознакомительного отрывка
Купить книгу