Книга: Программирование на языке Ruby

6.2.8. Нестандартные диапазоны

6.2.8. Нестандартные диапазоны

Рассмотрим пример диапазона, состоящего из произвольных объектов. В листинге 6.2 приведен класс для работы с римскими числами.

Листинг 6.2. Класс для работы с римскими числами

class Roman
 include Comparable
 I,IV,V,IX,X,XL,L,XC,C,CD,D,CM,M =
  1, 4, 5, 9, 10, 40, 50, 90, 100, 400, 500, 900, 1000
 Values = %w[M CM D CD С XC L XL X IX V IV I]
 def Roman.encode(value)
  return "" if self == 0
  str = ""
  Values.each do |letters|
   rnum = const_get(letters)
   if value >= rnum
    return(letters + str=encode(value-rnum))
   end
  end
  str
 end
 def Roman.decode(rvalue)
  sum = 0
  letters = rvalue.split('')
  letters.each_with_index do |letter,i|
   this = const_get(letter)
   that = const_get(letters[i+1]) rescue 0
   op = that > this ? :- : :+
   sum = sum.send(op,this)
  end
  sum
 end
 def initialize(value)
  case value
   when String
    @roman = value
    @decimal = Roman.decode(@roman)
   when Symbol
    @roman = value.to_s
    @decimal = Roman.decode(@roman)
   when Numeric
    @decimal = value
    @roman = Roman.encode(@decimal)
  end
 end
 def to_i
  @decimal
 end
 def to_s
  @roman
 end
 def succ
  Roman.new(@decima1 +1)
 end
 def <=>(other)
  self.to_i <=> other.to_i
 end
end
def Roman(val)
 Roman.new(val)
end

Сначала несколько слов о самом классе. Его конструктору можно передать строку, символ (представляющий число, записанное римскими цифрами) или Fixnum (число, записанное обычными арабскими цифрами). Внутри выполняется преобразование и сохраняются обе формы. Имеется вспомогательный метод Roman, это просто сокращенная запись вызова Roman.new. Методы класса encode и decode занимаются преобразованием из арабской формы в римскую и наоборот.

Для простоты я опустил контроль данных. Кроме того, предполагается, что римские цифры представлены прописными буквами.

Метод to_i, конечно же, возвращает десятичное значение, a to_s — число, записанное римскими цифрами. Метод succ возвращает следующее римское число: например, Roman(:IV).succ вернет Roman(:V).

Оператор сравнения сравнивает десятичные эквиваленты. Мы включили с помощью директивы include модуль Comparable, чтобы получить доступ к операторам «меньше» и «больше» (реализация которых опирается на наличие метода сравнения <=>).

Обратите внимание на использование символов в следующем фрагменте:

op = that > this ? :- : :+
sum = sum.send(op,this)

Здесь мы решаем, какую будем выполнять операцию (она обозначается символом): сложение или вычитание. Это не более чем краткий способ выразить следующую идею:

if that > this
 sum -= this
else
 sum += this
end

Второй вариант длиннее, зато более понятен.

Поскольку в этом классе есть метод succ и полный набор операторов сравнения, его можно использовать для конструирования диапазонов. Пример:

require 'roman'
y1 = Roman(:MCMLXVI)
y2 = Roman(:MMIX)
range = y1..y2 # 1966..2009
range.each {|x| puts x}      # Выводятся 44 строки.
epoch = Roman(:MCMLXX)
range.include?(epoch)        # true
doomsday = Roman(2038)
range.include?(doomsday)     # false
Roman(:V) == Roman(:IV).succ # true
Roman(:MCM) < Roman(:MM)     # true

Оглавление книги


Генерация: 1.467. Запросов К БД/Cache: 3 / 0
поделиться
Вверх Вниз