Программирование на языке Ruby - Хэл Фултон
Шрифт:
Интервал:
Закладка:
5.12. Библиотека mathn
В программах, выполняющих большой объем математических вычислений, очень пригодится замечательная библиотека mathn, которую написал Кейдзу Исидзука (Keiju Ishitsuka). В ней есть целый ряд удобных методов и классов; кроме того, она унифицирует все классы Ruby для работы с числами так, что они начинают хорошо работать совместно.
Простейший способ воспользоваться этой библиотекой — включить ее с помощью директивы require и забыть. Поскольку она сама включает библиотеки complex, rational и matrix (в таком порядке), то вы можете этого не делать.
В общем случае библиотека mathn пытается вернуть «разумные» результаты вычислений. Например, при извлечении квадратного корня из Rational будет возвращен новый объект Rational, если это возможно; в противном случае Float. В таблице 5.1 приведены некоторые последствия загрузки этой библиотеки.
Таблица 5.1. Результаты вычислений в случае отсутствия и наличия библиотеки mathn
Выражение Без mathn С mathn Math.sqrt(Rational(9,16)) 0.75 Rational(3,4) 1/2 0 Rational(1,2) Matrix.identity(3)/3 Matrix[[0,0,0], [0,0,0],[0,0,0]] Matrix[[1/3,0,0], [0,1/3,0],[0,0,1/3]] Math.sqrt(64/25) 1.4142… Rational(8,5) Rational(1,10).inspect Rational(1,10) 1/10Библиотека mathn добавляет методы ** и power2 в класс Rational. Она изменяет поведение метода Math.sqrt и добавляет метод Math.rsqrt, умеющий работать с рациональными числами.
Дополнительная информация приводится в разделах 5.13 и 5.14.
5.13. Разложение на простые множители, вычисление НОД и НОК
В библиотеке mathn определены также некоторые новые методы в классе Integer. Так, метод gcd2 служит для нахождения наибольшего общего делителя (НОД) объекта, от имени которого он вызван, и другого числа.
n = 36.gcd2(120) # 12 k = 237.gcd2(79) # 79
Метод prime_division выполняет разложение на простые множители. Результат возвращается в виде массива массивов, в котором каждый вложенный массив содержит простое число и показатель степени, с которым оно входит в произведение.
factors = 126.prime_division # [[2,1], [3,2], [7,1]]
# To есть 2**1 * 3**2 * 7**1
Имеется также метод класса Integer.from_prime_division, который восстанавливает исходное число из его сомножителей. Это именно метод класса, потому что выступает в роли «конструктора» целого числа.
factors = [[2,1],[3,1],[7,1]]
num = Integer.from_prime_division(factors) # 42
Ниже показано, как разложение на простые множители можно использовать для отыскания наименьшего общего кратного (НОК) двух чисел:
require 'mathn'
class Integer
def lcm(other)
pf1 = self.prime_division.flatten
pf2 = other.prime_division.flatten
h1 = Hash[*pf1]
h2 = Hash[*pf2]
hash = h2.merge(h1) {|key,old,new| [old,new].max }
Integer.from_prime_division(hash.to_a)
end
end
p 15.1cm(150) # 150
p 2.1cm(3) # 6
p 4.1cm(12) # 12
p 200.1cm(30) # 600
5.14. Простые числа
В библиотеке mathn есть класс для порождения простых чисел. Итератор each возвращает последовательные простые числа в бесконечном цикле. Метод succ порождает следующее простое число. Вот, например, два способа получить первые 100 простых чисел:
require 'mathn'
list = []
gen = Prime.new
gen.each do |prime|
list << prime
break if list.size == 100
end
# или:
list = []
gen = Prime.new
100.times { list << gen.succ }
В следующем фрагменте проверяется, является ли данное число простым. Отметим, что если число велико, а машина медленная, то на выполнение может уйти заметное время:
require 'mathn'
class Integer
def prime?
max = Math.sqrt(self).ceil
max -= 1 if max % 2 == 0
pgen = Prime.new
pgen.each do |factor|
return false if self % factor == 0
return true if factor > max
end
end
end
31.prime? # true
237.prime? # false
1500450271.prime? # true
5.15. Явные и неявные преобразования чисел
Программисты, только начинающие изучать Ruby, часто удивляются, зачем нужны два метода to_i и to_int (и аналогичные им to_f и to_flt). В общем случае метод с коротким именем применяется для явных преобразований, а метод с длинным именем — для неявных.
Что это означает? Во-первых, в большинстве классов определены явные конверторы, но нет неявных. Насколько мне известно, методы to_int и to_flt не определены ни в одном из системных классов.
Во-вторых, в своих собственных классах вы, скорее всего, будете определять неявные конверторы, но не станете вызывать их вручную (если только не заняты написанием «клиентского» кода или библиотеки, которая пытается не конфликтовать с внешним миром).
Следующий пример, конечно, надуманный. В нем определен класс MyClass, который возвращает константы из методов to_i и to_int. Такое поведение лишено смысла, зато иллюстрирует идею:
class MyClass
def to_i
3
end
def to_int
5
end
end
Желая явно преобразовать объект класса MyClass в целое число, мы вызовем метод to_i:
m = MyClass.new x = m.to_i # 3
Но при передаче объекта MyClass какой-нибудь функции, ожидающей целое число, будет неявно вызван метод to_int. Предположим, к примеру, что мы хотим создать массив с известным начальным числом элементов. Метод Array.new может принять целое, но что если вместо этого ему будет передан объект MyClass?
m = MyClass.new
a = Array.new(m) # [nil,nil,nil,nil,nil]
Как видите, метод new оказался достаточно «умным», чтобы вызвать to_int и затем создать массив из пяти элементов.
Дополнительную информацию о поведении в другом контексте (строковом) вы найдете в разделе 2.16. См. также раздел 5.16.
5.16. Приведение числовых значений
Приведение можно считать еще одним видом неявного преобразования. Если некоторому методу (например, +) передается аргумент, которого он не понимает, он пытается привести объект, от имени которого вызван, и аргумент к совместимым типам, а затем сложить их. Принцип использования метода coerce в вашем собственном классе понятен из следующего примера:
class MyNumberSystem
def +(other)
if other.kind_of?(MyNumberSystem)
result = some_calculation_between_self_and_other
MyNumberSystem.new(result)
else
n1, n2 = other.coerce(self)
n1 + n2
end
end
end
Метод coerce возвращает массив из двух элементов, содержащий аргумент и вызывающий объект, приведенные к совместимым типам.
В данном примере мы полагаемся на то, что приведение выполнит тип аргумента. Но если мы хотим быть законопослушными гражданами, то должны реализовать приведение в своем классе, сделав его пригодным для работы с другими типами чисел. Для этого нужно знать, с какими типами мы в состоянии работать непосредственно, и при необходимости выполнять приведение к одному из этих типов. Если мы не можем сделать это самостоятельно, то должны обратиться за помощью к родительскому классу.
def coerce(other)
if other.kind_of?(Float)
return other, self.to_f
elsif other.kind_of?(Integer)
return other, self.to_i
else
super
end
end
Разумеется, это будет работать только, если наш объект реализует методы to_i и to_f.
Метод coerce можно применить для реализации автоматического преобразования строк в числа, как в языке Perl:
class String
def coerce(n)
if self['.']
[n, Float(self)]
else
[n, Integer(self)]
end
end
end
x = 1 + "23" # 24
y = 23 * "1.23" # 28.29
Мы не настаиваем на таком решении. Но рекомендуем реализовывать coerce при создании любого класса для работы с числовыми данными.