355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Хэл Фултон » Программирование на языке Ruby » Текст книги (страница 16)
Программирование на языке Ruby
  • Текст добавлен: 24 сентября 2016, 06:40

Текст книги "Программирование на языке Ruby"


Автор книги: Хэл Фултон



сообщить о нарушении

Текущая страница: 16 (всего у книги 56 страниц) [доступный отрывок для чтения: 20 страниц]

5.11. Комплексные числа

Стандартная библиотека complex предназначена для работы с комплексными числами в Ruby. Большая ее часть не требует пояснений.

Для создания комплексного числа применяется следующая несколько необычная нотация:

z = Complex(3,5) # 3+5i

Необычно в ней то, что имя метода совпадает с именем класса. В данном случае наличие скобок указывает на то, что это вызов метода, а не ссылка на константу. Вообще говоря, имена методов не похожи на константы, и я не рекомендую начинать имена методов с прописной буквы, разве что в подобных специальных случаях. (Отметим, что имеются также методы Integer и Float; вообще, имена, начинающиеся с прописной буквы, зарезервированы для методов, которые выполняют преобразование данных и аналогичные действия.)

Метод im преобразует вещественное число в мнимое (по существу, умножая его на i). Поэтому представлять комплексные числа можно и с помощью более привычной нотации:

а = 3.im     # 3i

b = 5 – 2.im # 5-2i

Если вас больше интересуют полярные координаты, то можно обратиться к методу polar:

2 – Complex.polar(5,Math::PI/2.0) # Радиус, угол.

В классе Complex имеется также константа I, которая представляет число i – квадратный корень из минус единицы:

z1 = Complex(3,5)

z2 = 3 + 5*Complex::I # z2 == z1

После загрузки библиотеки complex некоторые стандартные математические функции изменяют свое поведение. Тригонометрические функции – sin, sinh, tan и tanh (а также некоторые другие, например, ехр и log) начинают принимать еще и комплексные аргументы. Некоторые функции, например sqrt, даже возвращают комплексные числа в качестве результата.

x = Math.sqrt(Complex(3,5)) # Приближенно Complex(2.1013, 1.1897)

y = Math.sqrt(-1) # Complex(0,1)

Дополнительную информацию ищите в любой полной документации, в частности на сайте ruby-doc.org.

5.12. Библиотека mathn

В программах, выполняющих большой объем математических вычислений, очень пригодится замечательная библиотека mathn, которую написал Кейдзу Исидзука (Keiju Ishitsuka). В ней есть целый ряд удобных методов и классов; кроме того, она унифицирует все классы Ruby для работы с числами так, что они начинают хорошо работать совместно.

Простейший способ воспользоваться этой библиотекой – включить ее с помощью директивы require и забыть. Поскольку она сама включает библиотеки complex, rational и matrix (в таком порядке), то вы можете этого не делать.

В общем случае библиотека mathn пытается вернуть «разумные» результаты вычислений. Например, при извлечении квадратного корня из Rational будет возвращен новый объект Rational, если это возможно; в противном случае Float. В таблице 5.1 приведены некоторые последствия загрузки этой библиотеки.

Таблица 5.1. Результаты вычислений в случае отсутствия и наличия библиотеки mathn


Math.sqrt(Rational(9,16))0.75Rational(3,4)
1/20Rational(1,2)
Matrix.identity(3)/3Matrix[[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).inspectRational(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 при создании любого класса для работы с числовыми данными.

5.17. Поразрядные операции над числами

Иногда требуется работать с двоичным представлением объекта Fixnum. На прикладном уровне такая необходимость возникает нечасто, но все-таки возникает.

Ruby обладает всеми средствами для таких операций. Для удобства числовые константы можно записывать в двоичном, восьмеричном или шестнадцатеричном виде. Поразрядным операциям И, ИЛИ, ИСКЛЮЧАЮЩЕЕ ИЛИ и НЕ соответствуют операторы &, |, ^ и ~.

x = 0377       # Восьмеричное (десятичное 255)

y = 0b00100110 # Двоичное (десятичное 38)

z = 0xBEEF     # Шестнадцатеричное (десятичное 48879)

а = x | z      # 48895 (поразрядное ИЛИ)

b = x & z      # 239 (поразрядное И)

с = x ^ z      # 48656 (поразрядное ИСКЛЮЧАЮЩЕЕ ИЛИ)

d = ~ y        # -39 (отрицание или дополнение до 1)

Метод экземпляра size позволяет узнать размер слова для той машины, на которой исполняется программа.

size # Для конкретной машины возвращает 4.

Имеются операторы сдвига влево и вправо (<< и >>соответственно). Это логические операторы сдвига, они не затрагивают знаковый бит (хотя оператор >> распространяет его).

x = 8

y = -8

а = x >> 2 # 2

b = y >> 2 # -2

с = x << 2 # 32

d = y << 2 # -32

Конечно, если сдвиг настолько велик, что дает нулевое значение, то знаковый бит теряется, поскольку -0 и 0 – одно и то же.

Квадратные скобки позволяют трактовать числа как битовые массивы. Бит с номером 0 всегда является младшим, вне зависимости от порядка битов в конкретной машинной архитектуре.

x = 5         # То же, что 0b0101

а = x[0]      # 1

b = x[1]      # 0

с = x[2]      # 1

d = x[3]      # 0

# И так далее # 0

Присваивать новые значения отдельным битам с помощью такой нотации невозможно (поскольку Fixnum хранится как непосредственное значение, а не как ссылка на объект). Но можно имитировать это действие путем сдвига 1 влево на нужное число позиций с последующим выполнением операции ИЛИ или И.

# Выполнить присваивание x[3] = 1 нельзя,

# но можно поступить так:

x |= (1<<3)

# Выполнить присваивание x[4] = 0 нельзя,

# но можно поступить так:

x &= ~(1<<4)

5.18. Преобразование системы счисления

Ясно, что любое целое число можно представить в любой системе счисления, поскольку хранятся эти числа в двоичном виде. Мы знаем, что Ruby умеет работать c целыми константами, записанными в любой из четырех наиболее популярных систем. Следовательно, разговор о преобразовании системы счисления может вестись только применительно к числам, записанным в виде строк.

Вопрос о преобразовании строки в целое рассмотрен в разделе 2.24. Для преобразования числа в строку проще всего воспользоваться методом to_s, которому можно еще передать основание системы счисления. По умолчанию оно равно 10, но в принципе может быть любым вплоть до 36 (когда задействованы все буквы латинского алфавита).

237.to_s(2)  # "11101101"

237.to_s(5)  # "1422"

237.to_s(8)  # "355"

237.to_s     # "237"

237.to_s(16) # "ed"

237.to_s(30) # "7r"

Другой способ – обратиться к методу % класса String:

hex = "%x" % 1234 # "4d2"

oct = "%о" % 1234 # "2322"

bin = "%b" % 1234 # "10011010010"

Метод sprintf тоже годится:

str = sprintf(str,"Nietzsche is %xn",57005)

# str теперь равно: "Nietzsche is deadn"

Если нужно сразу же вывести преобразованное в строку значение, то подойдет и метод printf.

5.19. Извлечение кубических корней, корней четвертой степени и т.д.

В Ruby встроена функция извлечения квадратного корня (Math.sqrt), поскольку именно она применяется чаще всего. А если надо извлечь корень более высокой степени? Если вы еще не забыли математику, то эта задача не вызовет затруднений.

Можно, например, воспользоваться логарифмами. Напомним, что е в степени x – обратная функция к натуральному логарифму x и что умножение чисел эквивалентно сложению их логарифмов.

x = 531441

cuberoot = Math.exp(Math.log(x)/3.0)   # 81.0

fourthroot = Math.exp(Math.log(x)/4.0) # 27.0

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

include Math

y = 4096

cuberoot = y**(1.0/3.0)     # 16.0

fourthroot = y**(1.0/4.0)   # 8.0

fourthroot = sqrt(sqrt(y))  # 8.0 (то же самое)

twelfthroot = y**(1.0/12.0) # 2.0

Отметим, что во всех примерах мы пользовались при делении числами с плавающей точкой (чтобы избежать отбрасывания дробной части).

5.20. Определение порядка байтов

Интересно, что производители компьютеров никак не могут договориться, в каком порядке лучше хранить двоичные байты. Следует ли размещать старший бит по большему или по меньшему адресу? При передаче сообщения по проводам нужно сначала посылать старший или младший бит?

Хотите верьте, хотите нет, но решение не произвольно. Существуют убедительные аргументы в пользу обеих точек зрения (обсуждать их здесь мы не будем).

Вот уже больше двадцати лет, как для описания противоположных позиций применяются термины «остроконечный» (little-endian) и «тупоконечный» (big-endian). Кажется, впервые их употребил Дэнни Коэн (Danny Cohen); см. его классическую статью "On Holy Wars and a Plea for Peace" (IEEE Computer, October 1981). Взяты они из романа Джонатана Свифта «Путешествия Гулливера».

Обычно нам безразличен порядок байтов в конкретной машинной архитектуре. Но как быть, если все-таки его нужно знать?

Можно воспользоваться показанным ниже методом. Он возвращает одну из строк LITTLE, BIG или OTHER. Решение основано на том факте, что директива l выполняет упаковку в машинном формате, а директива N распаковывает в сетевом порядке байтов (по определению тупоконечном).

def endianness

 num = 0x12345678

 little = "78563412"

 big = "12345678"

 native = [num].pack('1')

 netunpack = native.unpack('N')[0]

 str = "%8x" % netunpack

 case str

  when little

   "LITTLE"

 when big

  "BIG"

 else

  "OTHER"

 end

end

puts endianness # В данном случае печатается "LITTLE"

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

5.21. Численное вычисление определенного интеграла

Я очень хорошо владею дифференциальным и интегральным исчислением…

У.С.Джильберт, «Пираты Пензанса», акт 1

Для приближенного вычисления определенного интеграла имеется проверенная временем техника. Любой студент, изучавший математический анализ, вспомнит, что она называется суммой Римана.

Приведенный ниже метод integrate принимает начальное и конечное значения зависимой переменной, а также приращение. Четвертый параметр (который на самом деле параметром не является) – это блок. В блоке должно вычисляться значение функции от переданной в него зависимой переменной (здесь слово «переменная» употребляется в математическом, а не программистском смысле). Необязательно отдельно определять функцию, которая вызывается в блоке, но для ясности мы это сделаем.

def integrate(x0, x1, dx=(x1-x0)/1000.0)

 x = x0

 sum = 0

 loop do

  y = yield(x)

  sum += dx * y

  x += dx

  break if x > x1

 end

 sum

end

def f(x)

 x**2

end

z = integrate(0.0,5.0) {|x| f(x) }

puts z, "n" # 41.7291875

Здесь мы опираемся на тот факт, что блок возвращает значение, которое может быть получено с помощью yield. Кроме того, сделаны некоторые допущения. Во-первых, мы предполагаем, что x0 меньше x1 (в противном случае получится бесконечный цикл). Читатель сам легко устранит подобные огрехи. Во-вторых, мы считаем, что функцию можно вычислить в любой точке заданной области. Если это не так, мы получим хаотическое поведение. (Впрочем, подобные функции все равно, как правило, не интегрируемы – по крайней мере, на указанном интервале. В качестве примера возьмите функцию f(x)=x/(x-3) в точке x=3.)

Призвав на помощь полузабытые знания об интегральном исчислении, мы могли бы вычислить, что в данном случае результат равен примерно 41.666 (5 в кубе, поделенное на 3). Почему же ответ не так точен, как хотелось бы? Из-за выбранного размера приращения; чем меньше величина dx, тем точнее результат (ценой увеличения времени вычисления).

Напоследок отметим, что подобная методика более полезна для действительно сложных функций, а не таких простых, как f(x) = x**2.

5.22. Тригонометрия в градусах, радианах и градах

При измерении дуг математической, а заодно и «естественной» единицей измерения является радиан. По определению, угол в один радиан соответствует длине дуги, равной радиусу окружности. Немного поразмыслив, легко понять, что угол 2π радиан соответствует всей окружности.

Дуговой градус, которым мы пользуемся в повседневной жизни, – пережиток древневавилонской системы счисления по основанию 60: в ней окружность делится на 360 градусов. Менее известна псевдометрическая единица измерения град, определенная так, что прямой угол составляет 100 град (а вся окружность – 400 град).

При вычислении тригонометрических функций в языках программирования по умолчанию чаще всего используются радианы, и Ruby в этом отношении не исключение. Но мы покажем, как производить вычисления и в градусах, и в градах для тех читателей, которые по образованию не инженеры, а по происхождению не древние вавилоняне.

Поскольку число любых угловых единиц в окружности – константа, можно легко переходить от одних единиц к другим. Мы определим соответствующие константы и будем пользоваться ими в коде. Для удобства поместим их в модуль Math.

module Math

 RAD2DEG = 360.0/(2.0*PI)  # Радианы в градусы.

 RAD2GRAD = 400.0/(2.0*РI) # Радианы в грады.

end

Теперь можно определить и новые тригонометрические функции. Поскольку мы всегда преобразуем в радианы, то будем делить на определенные выше коэффициенты. Можно было бы поместить определения функций в тот же модуль Math, но мы этого делать не стали.

def sin_d(theta)

 Math.sin(theta/Math::RAD2DEG)

end

def sin_g(theta)

 Math.sin(theta/Math::RAD2GRAD)

end

Функции cos и tan можно было бы определить аналогично.

С функцией atan2 дело обстоит несколько сложнее. Она принимает два аргумента (длины противолежащей и прилежащей сторон прямоугольного треугольника). Поэтому мы преобразуем результат, а не аргумент:

def atan2_d(y,x)

 Math.atan2(у,x)/Math::RAD2DEG

end

def atan2_g(y,x)

 Math.atan2(y, x)/Math::RAD2GRAD

end

5.23. Неэлементарная тригонометрия

В ранних версиях Ruby не было функций arcsin и arccos. Равно как и гиперболических функций sinh, cosh и tanh. Их определения были приведены в первом издании этой книги, но сейчас они являются стандартной частью модуля Math.

5.24. Вычисление логарифмов по произвольному основанию

Чаще всего мы пользуемся натуральными логарифмами (по основанию е, часто натуральный логарифм обозначается как ln), иногда также десятичными (по основанию 10). Эти функции реализованы в методах Math.log и Math.log10 соответственно.

В информатике, а в особенности в таких ее областях, как кодирование и теория информации, обычно применяются логарифмы по основанию 2. Например, так вычисляется минимальное число битов, необходимых для представления числа. Определим функцию с именем log2:

def log2(x)

 Math.log(x)/Math.log(2)

end

Ясно, что обратной к ней является функция 2**x (как обращением ln x служит Math::Е**x или Math.exp(x)).

Эта идея обобщается на любое основание. В том маловероятном случае, если вам понадобится логарифм по основанию 7, можно поступить так:

def log7(x)

 Math.log(x)/Math.log(7)

end

На практике знаменатель нужно вычислить один раз и сохранить в виде константы.


    Ваша оценка произведения:

Популярные книги за неделю