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

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

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


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



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

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

4.3.4. Прочие замечания

Если вы распространяете вместе со своей программой справочники сообщений, то лучше собрать пакет с помощью системы RubyGems или библиотеки setup.rb. Дополнительную информацию по этому поводу вы найдете в разделе 17.2.

При установке пакета, собранного RubyGems, справочники сообщений копируются в каталоги вида:

(gem-packages-installed-dir)/myapp-x.x.x/data/locale/

Такие каталоги уже включены в путь поиска для библиотеки gettext, поэтому ваша программа будет локализована даже без явной установки переменной окружения GETTEXT_PATH.

В случае сборки пакета с помощью библиотеки setup.rb справочники сообщений помещаются в каталог (system-dir)/share/locale/. И в этом случае локализация достигается без установки переменной GETTEXT_PATH.

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

4.4. Заключение

В этой главе мы рассмотрели один из самых сложных аспектов программирования – проблему интернационализации кода. При этом нам понадобился материал из двух предыдущих глав, так как интернационализация тесно связана со строками и регулярными выражениями.

Мы видели, что в Ruby некоторые задачи решаются просто благодаря наличию библиотеки jcode и сопутствующих инструментов. Заодно мы познакомились с наборами символов вообще и с набором Unicode в частности.

Мы узнали, что регулярные выражения в общем случае лучше поддерживают Unicode, чем средства работы со строками, а также рассмотрели методы pack и unpack с точки зрения полезности для манипулирования Unicode-строками.

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

Детально рассмотрев вопрос о строках и регулярных выражениях, вернемся на главную дорогу. Глава 5 посвящена численному анализу в языке Ruby.

Глава 5. Численные методы

Дважды [члены Парламента] задавали мне вопрос: «А скажите, мистер Бэббидж, если вы заложите в эту машину неверные числа, то получите правильный результат?» Не могу даже представить себе, насколько извращенно должен мыслить человек, задающий такие вопросы.

Чарльз Бэббидж

Числа – самый первичный тип данных, естественный для любого компьютера. Придется сильно постараться, чтобы найти такую область знания, в которой нет места числам. Будь вы бухгалтером или конструктором воздухоплавательных аппаратов, без чисел вам не обойтись. В этой главе мы обсудим различные способы обработки, преобразования и анализа числовых данных.

Как и всякий современный язык, Ruby прекрасно умеет работать с любыми числами – как целыми, так и с плавающей точкой. В нем есть полный набор ожидаемых математических операторов и функций, а вместе с тем и кое-какие приятные сюрпризы: классы Bignum, BigDecimal и Rational.

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

5.1. Представление чисел в языке Ruby

Если вы знакомы с любым другим языком программирования, то представление чисел в Ruby не вызовет у вас никакого удивления. Объект класса Fixnum может представлять число со знаком или без знака:

237  # Число без знака (положительное).

+237 # То же, что и выше.

-237 # Отрицательное число.

Если число длинное, то между любыми цифрами можно вставлять знак подчеркивания. Это сделано исключительно для удобства, назначении константы никак не сказывается. Обычно подчерки вставляются в те же места, где бухгалтеры вставляют пробелы:

1048576   # Число в обычной записи.

1_048_576 # То же самое значение.

Целые числа можно представлять и в других системах счисления (по основанию 2, 8 и 16). Для этого в начале ставятся префиксы 0b, 0 и соответственно.

0b10010110 # Двоичное.

0b1211     # Ошибка!

01234      # Восьмеричное (основание 8).

01823      # Ошибка!

0xdeadbeef # Шестнадцатеричное (основание 16) .

0xDEADBEEF # То же самое.

0xdeadpork # Ошибка!

В числах с плавающей точкой десятичная точка должна присутствовать, а показатель степени, возможно со знаком, необязателен:

3.14         # Число пи, округленное до сотых.

-0.628       # -2*pi, поделенное на 10, округленное до тысячных.

6.02е23      # Число Авогадро.

6.626068е-34 # Постоянная Планка.

В классе Float есть константы, определяющие минимальные и максимальные значения чисел с плавающей точкой. Они машиннозависимы. Вот некоторые наиболее важные:

Float::MIN     # 2.2250738585072е-308 (на конкретной машине)

Float::МАХ     # 1.79769313486232е+308

Float::EPSILON # 2.22044604925031е-16

5.2. Основные операции над числами

Обычные операции сложения, вычитания, умножения и деления в Ruby, как и во всех распространенных языках программирования, обозначаются операторами +, -, *, /. Операторы в большинстве своем реализованы в виде методов (и потому могут быть переопределены).

Возведение в степень обозначается оператором **, как в языках BASIC и FORTRAN. Эта операция подчиняется обычным математическим правилам.

а = 64**2   # 4096

b = 64**0.5 # 8.0

с = 64**0   # 1

d = 64**-1  # 0.015625

При делении одного целого числа на другое дробная часть результата отбрасывается. Это не ошибка, так и задумано. Если вы хотите получить результат с плавающей точкой, позаботьтесь о том, чтобы хотя бы один из операндов был числом c плавающей точкой.

3 / 3     # 3

5 / 3     # 1

3 / 4     # 0

3.0 / 4   # 0.75

3 / 4.0   # 0.75

3.0 / 4.0 # 0.75

Если вы работаете с переменными и сомневаетесь относительно их типа, воспользуйтесь приведением типа к Float или методом to_f:

z = x.to_f / у z = Float(x) / y

См. также раздел 5.17 «Поразрядные операции над числами».

5.3. Округление чисел с плавающей точкой

Кирк: Какие, вы говорите, у нас шансы выбраться отсюда?

Спок: Трудно сказать точно, капитан. Приблизительно 7824.7 к одному.

Стар Трек, «Миссия милосердия»

Метод round округляет число с плавающей точкой до целого:

pi = 3.14159

new_pi = pi.round  # 3

temp = -47.6

temp2 = temp.round # -48

Иногда бывает нужно округлить не до целого, а до заданного числа знаков после запятой. В таком случае можно воспользоваться функциями sprintf (которая умеет округлять) и eval:

pi = 3.1415926535

pi6 = eval(sprintf("%8.6f",pi)) # 3.141593

pi5 = eval(sprintf("%8.5f",pi)) # 3.14159

pi4 = eval(sprintf("%8.4f",pi)) # 3.1416

Это не слишком красиво. Поэтому инкапсулируем оба вызова функций в метод, который добавим в класс Float:

class Float

 def roundf(places)

  temp = self.to_s.length

  sprintf("%#{temp}.#{places}f",self).to_f

 end

end

Иногда требуется округлять до целого по-другому. Традиционное округление n+0.5 с избытком со временем приводит к небольшим ошибкам; ведь n+0.5 все-таки ближе к n+1, чем к n. Есть другое соглашение: округлять до ближайшего четного числа, если дробная часть равна 0.5. Для реализации такого правила можно было бы расширить класс Float, добавив в него метод round2:

class Float

 def round2

  whole = self.floor

  fraction = self – whole

  if fraction == 0.5

   if (whole % 2) == 0

    whole

   else

    whole+1

   end

  else

   self.round

  end

 end

end

a = (33.4).round2 # 33

b = (33.5).round2 # 34

с = (33.6).round2 # 34

d = (34.4).round2 # 34

e = (34.5).round2 # 34

f = (34.6).round2 # 35

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

Ну а если мы хотим округлять до заданного числа знаков после запятой, но при этом использовать метод «округления до четного»? Тогда нужно добавить в класс Float также метод roundf2:

class Float

 # Определение round2 такое же, как и выше.

 def roundf2(places)

  shift = 10**places

  (self * shift).round2 / shift.to_f

 end

end

a = 6.125

b = 6.135

x = a.roundf2(a) #6.12

y = b.roundf2(b) #6.13

У методов roundf и roundf2 есть ограничение: большое число с плавающей точкой может стать непредставимым при умножении на большую степень 10. На этот случай следовало бы предусмотреть проверку ошибок.

5.4. Сравнение чисел с плавающей точкой

Печально, но факт: в компьютере числа с плавающей точкой представляются неточно. В идеальном мире следующий код напечатал бы «да», но на всех машинах где мы его запускали, печатается «нет»:

x = 1000001.0/0.003

y = 0.003*x

if y == 1000001.0

 puts "да"

else

 puts "нет"

end

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

Из-за этой неустранимой неточности при сравнении чисел с плавающей точкой мы можем оказаться в ситуации (продемонстрированной выше), когда с практической точки зрения два числа равны, но аппаратура упрямо считает их различными.

Ниже показан простой способ выполнения сравнения с «поправкой», когда числа считаются равными, если отличаются не более чем на величину, задаваемую программистом:

class Float

 EPSILON = 1e-6 # 0.000001

 def == (x)

  (self-x).abs < EPSILON

 end

end

x = 1000001.0/0.003

y = 0.003*x

if y == 1.0 # Пользуемся новым оператором ==.

 puts "да" # Теперь печатается "да".

else

 puts "нет"

end

В зависимости от ситуации может понадобиться задавать разные погрешности. Для этого определим в классе Float новый метод equals?. (При таком выборе имени мы избежим конфликта со стандартными методами equal? и eql?; последний, кстати, вообще не следует переопределять).

class Float

 EPSILON = 1e-6

 def equals?(x, tolerance=EPSILON)

  (self-x).abs < tolerance

 end

end

flag1 = (3.1416).equals? Math::PI # false

flag2 = (3.1416).equals?(Math::PI, 0.001) # true

Можно также ввести совершенно новый оператор для приближенного сравнения, назвав его, например, =~.

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

5.5. Форматирование чисел для вывода

Для вывода числа в заданном формате применяется метод printf из модуля Kernel. Он практически не отличается от одноименной функции в стандартной библиотеке С. Дополнительную информацию см. в документации по методу printf.

x = 345.6789

i = 123

printf("x = %6.2fn", x) # x = 345.68

printf("x = %9.2en", x) # x = 3.457e+02

printf("i = %5dn i)    # i = 123

printf("i = %05dn", i)  # i = 00123

printf("i = %-5dn, i)  # i = 123

Чтобы сохранить результат в строке, а не печатать его немедленно, воспользуйтесь методом sprintf. При следующем обращении возвращается строка:

str = sprintf ("%5.1f",x) # "345.7"

Наконец, в классе String есть метод %, решающий ту же задачу. Слева от знака % должна стоять форматная строка, а справа – единственный аргумент (или массив значений), результатом является строка.

# Порядок вызова: 'формат % значение'

str = "%5.1f" % x           # "345.7"

str = "%6.2f, %05d" % [x,i] # "345.68, 00123"

5.6. Вставка разделителей при форматировании чисел

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

def commas(x)

str = x.to_s.reverse

str.gsub!(/([0-9]{3})/,"\1,")

str.gsub(/,$/,"").reverse

end

puts commas(123)     # "123"

puts commas(1234)    # "1,234"

puts commas(12345)   # "12,435"

puts commas(123456)  # "123,456"

puts commas(1234567) # "1,234,567"

5.7. Работа с очень большими числами

Управлять массами все равно что управлять немногими: дело в частях и в числе.

Сунь-Цзы[9]9
  Трактат «Искусство войны».


[Закрыть]

При необходимости Ruby позволяет работать с произвольно большими целыми числами. Переход от Fixnum к Bignum производится автоматически, прозрачно для программиста. В следующем разделе результат оказывается настолько большим, что преобразуется из объекта Fixnum в Bignum:

num1 = 1000000   # Один миллион (10**6)

num2 = num1*num1 # Один триллион (10**12)

puts num1        # 1000000

puts num1.class  # Fixnum

puts num2        # 1000000000000

puts num2.class  # Bignum

Размер Fixnum зависит от машинной архитектуры. Вычисления с объектами Bignum ограничены только объемом памяти и быстродействием процессора. Конечно, они потребляют больше памяти и выполняются несколько медленнее, тем не менее операции над очень большими целыми (сотни знаков) реальны.

5.8. Использование класса BigDecimal

Стандартная библиотека bigdecimal позволяет работать с дробями, имеющими много значащих цифр. Число хранится как массив цифр, а не преобразуется в двоичное представление. Тем самым достижима произвольная точность, естественно, ценой замедления работы.

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

if (3.2 – 2.0) == 1.2

 puts "равны"

else

 puts "не равны" # Печатается "не равны"!

end

В подобной ситуации на помощь приходит класс BigDecimal. Однако в случае бесконечных периодических дробей проблема остается. Другой подход обсуждается в разделе 5.9 «Работа с рациональными числами».

Объект BigDecimal инициализируется строкой. (Объекта типа Float было бы недостаточно, поскольку погрешность вкралась бы еще до начала конструирования BigDecimal.) Метод BigDecimal эквивалентен BigDecimal.new; это еще один особый случай, когда имя метода начинается с прописной буквы. Поддерживаются обычные математические операции, например + и *. Отметим, что метод to_s может принимать в качестве параметра форматную строку. Дополнительную информацию вы найдете на сайте ruby-doc.org.

require 'bigdecimal'

x = BigDecimal("3.2")

y = BigDecimal("2.0")

z = BigDecimal("1.2")

if (x – y) == z

 puts "равны" # Печатается "равны"!

else

 puts "не равны"

end

а = x*y*z

a.to_s        # "0.768Е1" (по умолчанию: научная нотация)

a.to_s("F")   # "7.68" (обычная запись)

Если необходимо, можно задать число значащих цифр. Метод precs возвращает эту информацию в виде массива, содержащего два числа: количество использованных байтов и максимальное число значащих цифр.

x = BigDecimal ("1.234",10)

y = BigDecimal("1.234",15)

x.precs # [8, 16]

y.precs # [8, 20]

В каждый момент число использованных байтов может оказаться меньше максимального. Максимум может также оказаться больше запрошенного вами (поскольку BigDecimal пытается оптимизировать использование внутренней памяти). У обычных операций (сложение, вычитание, умножение и деление) есть варианты принимающие в качестве дополнительного параметра число значащих цифр. Если результат содержит больше значащих цифр, чем указано, производится округление до заданного числа знаков.

a = BigDecimal("1.23456")

b = BigDecimal("2.45678")

# В комментариях "BigDecimal:objectid" опущено.

c = a+b          # <'0.369134Е112(20)>

c2 = a.add(b,4)  # <'0.3691Е1',8(20)>

d = a-b          # <'-0.122222E1',12(20)>

d2 = a.sub(b,4)  # <'-0.1222E1',8(20)>

e = a*b          # <'0.30330423168E116(36)>

e2 = a.mult(b,4) # <'0.3033E1',8(36)>

f = a/b          # <'0.502511417383729922907221E0',24(32)>

f2 = a.div(b,4)  # <'0.5025E0',4(16)>

В классе BigDecimal определено и много других функций, например floor, abs и т.д. Как и следовало ожидать, имеются операторы % и **, а также операторы сравнения, к примеру <. Оператор == не умеет округлять свои операнды – эта обязанность возлагается на программиста.

В модуле BigMath определены константы E и PI с произвольной точностью. (На самом деле это методы, а не константы.) Там же определены функции sin, cos, exp и пр.; все они принимают число значащих цифр в качестве параметра. Следующие подбиблиотеки являются дополнениями к BigDecimal.

bigdecimal/math     Модуль BigMath

bigdecimal/jacobian Методы для вычисления матрицы Якоби

bigdecimal/ludcmp   Модуль LUSolve, разложение матрицы в произведение верхнетреугольной и нижнетреугольной

bigdecimal/newton   Методы nlsolve и norm

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

5.9. Работа с рациональными числами

Класс Rational позволяет (во многих случаях) производить операции с дробями с «бесконечной» точностью, но лишь если это настоящие рациональные числа (то есть частное от деления двух целых чисел). К иррациональным числам, например π или e, он неприменим.

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

r = Rational(1,2) # 1/2 или 0.5

s = Rational(1,3) # 1/3 или 0.3333...

t = Rational(1,7) # 1/7 или 0.14...

u = Rational(6,2) # "то же самое, что" 3.0

z = Rational(1,0) # Ошибка!

Результатом операции над двумя рациональными числами, как правило, снова является рациональное число.

r+t # Rational(9, 14)

r-t # Rational(5, 14)

r*s # Rational(1, 6)

r/s # Rational(3, 2)

Вернемся к примеру, на котором мы демонстрировали неточность операций над числами с плавающей точкой (см. раздел 5.4). Ниже мы выполняем те же действия над рациональными, а не вещественными числами и получаем «математически ожидаемый» результат:

x = Rational(1000001,1)/Rational(3,1000)

y = Rational(3,1000)*x

if y == 1000001.0

 puts "да" # Теперь получаем "да"!

else

 puts "нет"

end

Конечно, не любая операция дает рациональное же число в качестве результата:

x = Rational (9,16) # Rational(9, 16)

Math.sqrt(x)        # 0.75

x**0.5 # 0.75

x**Rational(1,2)    # 0.75

Однако библиотека mathn в какой-то мере изменяет это поведение (см. раздел 5.12).

5.10. Перемножение матриц

Стандартная библиотека matrix предназначена для выполнения операций над числовыми матрицами. В ней определено два класса: Matrix и Vector.

Следует также знать о прекрасной библиотеке NArray, которую написал Масахиро Танака (Masahiro Tanaka) – ее можно найти на сайте www.rubyforge.org. Хотя эта библиотека не относится к числу стандартных, она широко известна и очень полезна. Если вы предъявляете повышенные требования к быстродействию, нуждаетесь в особом представлении данных или желаете выполнять быстрое преобразование Фурье, обязательно ознакомьтесь с этим пакетом. Впрочем, для типичных применений стандартной библиотеки matrix должно хватить, поэтому именно ее мы и рассмотрим.

Чтобы создать матрицу, мы, конечно же, обращаемся к методу класса. Сделать это можно несколькими способами. Самый простой – вызвать метод Matrix.[] и перечислить строки в виде массивов. Ниже мы записали вызов на нескольких строчках, но, разумеется, это необязательно:

m = Matrix[[1,2,3],

           [4,5,6],

           [7,8,9]]

Вместо этого можно вызвать метод rows, передав ему массив массивов (в таком случае «дополнительные» скобки необходимы). Необязательный параметр сору, по умолчанию равный true, указывает, надо ли скопировать переданные массивы или просто сохранить на них ссылки. Оставляйте значение true, если нужно защитить исходные массивы от изменения, и задавайте false, если это несущественно.

Row1 = [2,3]

row2 = [4,5]

m1 = Matrix.rows([row1,row2])       # copy=true

m2 = Matrix.rows([row1,row2],false) # He копировать.

row1[1] = 99                        # Теперь изменим row1.

p m1                                # Matrix[[2, 3], [4, 5]]

p m2                                # Matrix[[2, 99], [4, 5]]

Можно задать матрицу и путем перечисления столбцов, если воспользоваться методом columns. Ему параметр сору не передается, потому что столбцы в любом случае расщепляются, так как во внутреннем представлении матрица хранится построчно:

m1 = Matrix.rows([[1,2],[3,4]])

m2 = Matrix.columns([[1,3],[2,4]]) # m1 == m2

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

Некоторые специальные матрицы, особенно квадратные, конструируются проще. Так, тождественную матрицу конструирует метод identity (или его синонимы I и unit):

im1 = Matrix.identity(3) # Matrix[[1,0,0],[0,1,0],[0,0,1]]

im2 = Matrix.I(3)        # То же самое.

im3 = Matrix.unit(3)     # То же самое.

Более общий метод scalar строит диагональную матрицу, в которой все элементы на диагонали одинаковы, но не обязательно равны 1:

sm = Matrix.scalar(3,8) # Matrix[[8,0,0],[0,8,0],[0,0,8]]

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

dm = Matrix.diagonal(2,3,7) # Matrix[[2,0,0],[0,3,0],[0,0,7]]

Метод zero создает нулевую матрицу заданной размерности (все элементы равны 0):

zm = Matrix.zero(3) # Matrix[[0,0,0],[0,0,0],[0,0,0]]

Понятно, что методы identity, scalar, diagonal и zero создают квадратные матрицы.

Чтобы создать матрицу размерности 1×N или N×1, воспользуйтесь методом row_vector или column_vector соответственно.

а = Matrix.row_vector(2,4,6,8)    # Matrix[[2,4,6,8]]

b = Matrix.column_vector(6,7,8,9) # Matrix[[6],[7],[8],[9]]

К отдельным элементам матрицы можно обращаться, указывая индексы в квадратных скобках (оба индекса заключаются в одну пару скобок). Отметим, что не существует метода []=. По той же причине, по которой его нет в классе Fixnum: матрицы – неизменяемые объекты (такое решение было принято автором библиотеки).

m = Matrix[[1,2,3],[4,5,6]]

puts m[1,2] # 6

Индексация начинается с 0, как и для массивов в Ruby. Возможно, это противоречит вашему опыту работы с матрицами, но индексация с 1 в качестве альтернативы не предусмотрена. Можно реализовать эту возможность самостоятельно:

# Наивный подход... не поступайте так!

class Matrix

 alias bracket []

 def [] (i,j)

  bracket(i-1,j-1)

 end

end

m = Matrix[[1,2,3],[4,5,6],[7,8,9]]

p m[2,2] # 5

На первый взгляд, этот код должен работать. Большинство операций над матрицами даже будет давать правильный результат при такой индексации. Так в чем же проблема? В том, что мы не знаем деталей внутренней реализации класса Matrix. Если в нем для доступа к элементам матрицы всегда используется собственный метод [], то все будет хорошо. Но если где-нибудь имеются прямые обращения к внутреннему массиву или применяются иные оптимизированные решения, то возникнет ошибка. Поэтому, решившись на такой трюк, вы должны тщательно протестировать новое поведение.

К тому же необходимо изменить методы row и vector. В них индексы тоже начинаются с 0, но метод [] не вызывается. Я не проверял, что еще придется модифицировать.

Иногда необходимо узнать размерность или форму матрицы. Для этого есть разные методы, например row_size и column_size.

Метод row_size возвращает число строк в матрице. Что касается метода column_size, тут есть одна тонкость: он проверяет лишь размер первой строки. Если по каким-либо причинам матрица не прямоугольная, то полученное значение бессмысленно. Кроме того, поскольку метод square? (проверяющий, является ли матрица квадратной) обращается к row_size и column_size, его результат тоже нельзя считать стопроцентно надежным.

m1 = Matrix[[1,2,3],[4,5,6],[7,8,9]]

m2 = Matrix[[1,2,3],[4,5,6],[7,8]]

m1.row_.size   # 3

m1.column_size # 3 m2.row_size # 3

m2.column_size # 3 (неправильно)

m1.square?     # true

m2.square?     # true (неправильно)

Решить эту мелкую проблему можно, например, определив метод rectangular?.

class Matrix

 def rectangular?

  arr = to_a

  first = arr[0].size

  arr[1..-1].all? {|x| x.size == first }

 end

end

Можно, конечно, модифицировать метод square?, так чтобы сначала он проверял, является ли матрица прямоугольной. В таком случае нужно будет изменить метод column_size, чтобы он возвращал nil для непрямоугольной матрицы.

Для вырезания части матрицы имеется несколько методов. Метод row_vectors возвращает массив объектов класса Vector, представляющих строки (см. обсуждение класса Vector ниже.) Метод column_vectors работает аналогично, но для столбцов. Наконец, метод minor возвращает матрицу меньшего размера; его параметрами являются либо четыре числа (нижняя и верхняя границы номеров строк и столбцов), либо два диапазона.

m = Matrix[[1,2,3,4],[5,6,7,8],[6,7,8,9]]

rows = m.row_vectors # Три объекта Vector.

cols = m.column_vectors # Четыре объекта Vector.

m2 = m.minor(1,2,1,2) # Matrix[[6,7,],[7,8]]

m3 = m.minor(0..1,1..3) # Matrix[[[2,3,4],[6,7,8]]

К матрицам применимы обычные операции: сложение, вычитание, умножение и деление. Для выполнения некоторых из них должны соблюдаться ограничения на размеры матриц-операндов; в противном случае будет возбуждено исключение (например, при попытке перемножить матрицы размерностей 3×3 и 4×4).

Поддерживаются стандартные преобразования: inverse (обращение), transpose (транспонирование) и determinant (вычисление определителя). Для целочисленных матриц определитель лучше вычислять с помощью библиотеки mathn (раздел 5.12).

Класс Vector – это, по существу, частный случай одномерной матрицы. Его объект можно создать с помощью методов [] или elements; в первом случае параметром является развернутый массив, а во втором – обычный массив и необязательный параметр сору (по умолчанию равный true).

arr = [2,3,4,5]

v1 = Vector[*arr]               # Vector[2,3,4,5]

v2 = Vector.elements(arr)       # Vector[2,3,4,5]

v3 = Vector.elements(arr,false) # Vector[2,3,4,5]

arr[2] = 7                      # теперь v3 – Vector[2,3,7,5].

Метод covector преобразует вектор длины N в матрицу размерности N×1 (выполняя попутно транспонирование).

v = Vector[2,3,4]

m = v.covector # Matrix[[2,3,4]]

Поддерживается сложение и вычитание векторов одинаковой длины. Вектор можно умножать на матрицу и на скаляр. Все эти операции подчиняются обычным математическим правилам.

v1 = Vector[2,3,4]

v2 = Vector[4,5,6]

v3 = v1 + v2        # Vector[6,8,10]

v4 = v1*v2.covector # Matrix![8,10,12],[12,15,18],[16,20,24]]

v5 = v1*5           # Vector[10,15,20]

Имеется метод inner_product (скалярное произведение):

v1 = Vector[2,3,4]

v2 = Vector[4,5,6]

x = v1.inner_product(v2) # 47

Дополнительную информацию о классах Matrix и vector можно найти в любом справочном руководстве, например воспользовавшись командной утилитой ri, или на сайте ruby-doc.org.


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

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