Ruby/Матрицы и векторы: различия между версиями

Содержимое удалено Содержимое добавлено
м {{BookCat}}; избыточные <big /> и <font /> вокруг <source />; ссылки; пробелы.
м <source> -> <syntaxhighlight> (phab:T237267)
 
Строка 3:
Моё знакомство с реализацией матричного исчисления в Ruby началось ещё в мою бытность студентом [http://www.msiu.ru МГИУ]. И надо сказать, что это знакомство было неудачным. Дело в том, что простейшая программа вычисления определителя матрицы
 
<sourcesyntaxhighlight lang="ruby">require 'matrix'
p Matrix[[1, -2, 3], [3, 4, -5], [2, 4, 1]].det #=> -50</sourcesyntaxhighlight>
<small>( версия ruby1.9.1 выдает результат #=> (62/1) - прим. Sharipov.ru)</small>
 
давала неверный результат (правильный ответ — 62). Как выяснилось позднее, эта проблема связана со спецификой целочисленной арифметики в Ruby (одна вторая в Ruby — нуль). Предположив это, я решил, что проблема легко решится, если я преобразую элементы матрицы к типу чисел с плавающей запятой (классу <code>Float</code>):
 
<sourcesyntaxhighlight lang="ruby">require 'matrix'
p Matrix[[1.0, -2.0, 3.0], [3.0, 4.0, -5.0], [2.0, 4.0, 1.0]].det #=> 62.0</sourcesyntaxhighlight>
 
И в некоторых случаях это меня действительно выручало. Но не всегда. Стоило появиться делению на 3, как появлялись ненужные остатки и погрешности. И чем больше исходная матрица, тем больше вероятность появления таких остатков. В некоторых случаях это было несущественным, а в остальных приходилось прибегать к услугам специальных математических пакетов (например, [[w:Maxima|Maxima]]). Было жутко неудобно и обидно за такую «кривую реализацию». (Я тогда писал курсовой, который решал все варианты контрольной работы по предмету «Математическое программирование». Да простит меня преподаватель, который так и не понял секрета тотальной успеваемости группы.)
Строка 32:
Самый простейший способ создать матрицу — использовать метод «батарейка» (метод <code>[]</code> выглядит как индикатор заряда батареи на сотовом телефоне):
 
<sourcesyntaxhighlight lang="ruby">require 'mathn'
Matrix[[1, -2, 3], [3, 4, -5], [2, 4, 1]]</sourcesyntaxhighlight>
 
Этот способ создания мы уже́ видели раньше. Обратите внимание, что для использования матриц необходимо подключать библиотеку <code>mathn</code>.
Строка 41:
Немножко перефразируем вопрос: как преобразовать двумерный массив в матрицу? Пусть ответ будет неожиданным, но это делается при помощи того-же метода «батарейка»:
 
<sourcesyntaxhighlight lang="ruby">require 'mathn'
array = [[1, -2, 3], [3, 4, -5], [2, 4, 1]]
Matrix[*array]</sourcesyntaxhighlight>
 
Оператор <code>*</code> преобразует <code>array</code> в список параметров, что позволяет существенно упросить процесс создания матрицы. Более того, это единственно удобный метод её создания. Все остальные методы создания матрицы (например, метод <code>.new</code>) работают не столь изящно и интуитивно.
Строка 51:
И вот тут всплывает «недоделанность» матриц. Метода для изменения элемента матрицы в них нет! Для того, чтобы изменить элемент матрицы, надо преобразовать матрицу в массив, изменить элемент массива и преобразовать массив в матрицу. Примерно вот так (меняем элемент с индексом <code>0, 0</code>):
 
<sourcesyntaxhighlight lang="ruby">require 'mathn'
matrix = Matrix[[1, -2, 3], [3, 4, -5], [2, 4, 1]]
array = matrix.to_a
array[0][0] = 0
p Matrix[*array] #=> Matrix[[0, -2, 3], [3, 4, -5], [2, 4, 1]]</sourcesyntaxhighlight>
 
==== Исправляем оплошность разработчиков ====
Строка 61:
Для начала, рассматриваем поближе библиотеку <code>matrix</code> (исходник или описание в <code>fxri</code>) и выясняем, что для получения значения элемента используется метод «батарейка» с двумя аргументами. Вполне закономерно использовать метод «батарейка равно» (то есть <code>[]=</code>) для изменения элемента. Сейчас мы его и реализуем:
 
<sourcesyntaxhighlight lang="ruby">require 'mathn'
class Matrix
def []=(i, j, value)
Строка 70:
matrix = Matrix[[1, -2, 3], [3, 4, -5], [2, 4, 1]]
matrix[0, 0] = 5
p matrix #=> Matrix[[5, -2, 3], [3, 4, -5], [2, 4, 1]]</sourcesyntaxhighlight>
 
Почему не могли этого сделать разработчики, я так и не понял. Скорее всего по идеологическим соображениям («не дело, чтобы матрицы вели себя как простые массивы»).
Строка 84:
Во-первых, для Ruby вектор — это объект класса <code>Vector</code>. Подключается он одновременно с матрицами (класс <code>Matrix</code>). Во-вторых, вектор очень похож на массив, но с одним существенным отличием (определяющем полезность вектора): массивы и векторы по-разному складываются и вычитаются. Давайте рассмотрим небольшой пример:
 
<sourcesyntaxhighlight lang="ruby">require 'mathn'
array = [1, 2, 3, 4, 5]
vector = Vector[*array]
p vector + vector #=> Vector[2, 4, 6, 8, 10]
p array + array #=> [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]</sourcesyntaxhighlight>
 
Обратите внимание, что <code>Vector</code> создаётся точно также, как и матрица. По сути, вектор — это матрица, которая состоит лишь из одной строки. А матрица, в свою очередь, — это массив векторов.
Строка 103:
Реализуем метод Гаусса.
 
<sourcesyntaxhighlight lang="ruby">require 'mathn'
equation = [Vector[1, 2, 1, 1], Vector[1, 5, 6, 2], Vector[1, 5, 7, 10]]</sourcesyntaxhighlight>
 
Почему был выбран именно массив векторов, а не матрица или двумерный массив? Дело в том, что в методе Гаусса приходится выполнять такие векторные операции, как вычитание векторов и деление вектора на [[w:Скаляр|скаляр]]. Поэтому смысла создавать матрицу (векторных операций не предусмотрено) или двумерный массив (придётся реализовывать эти операции) нет. Кстати, вполне возможно создать массив векторов и из двумерного массива (что мы и сделаем в следующем примере). Итак, приступим к реализации прямого хода метода Гаусса:
 
<sourcesyntaxhighlight lang="ruby">require 'mathn'
equation = [[1, 2, 1, 1], [1, 5, 6, 2], [1, 5, 7, 10]].map{ |array| Vector[*array] }
 
# Прямой ход метода Гаусса
equation[0] /= equation[0][0]</sourcesyntaxhighlight>
 
И вот тут нас ждет неприятный сюрприз: у векторов не реализован метод <code>/</code>. Ну и ладно, мы, программисты, не гордые. Сами напишем!
 
<sourcesyntaxhighlight lang="ruby">require 'mathn'
 
class Vector
Строка 127:
 
# Прямой ход метода Гаусса
equation[0] /= equation[0][0]</sourcesyntaxhighlight>
 
Теперь работает (только со скалярами, но нам больше и не надо). Заканчиваем реализовывать прямой проход метода Гаусса:
 
<sourcesyntaxhighlight lang="ruby">require 'mathn'
 
class Vector
Строка 151:
equation[2] /= equation[2][2]
 
p equation #=> [Vector[1, 2, 1, 1], Vector[0, 1, 5/3, 1/3], Vector[0, 0, 1, 8]]</sourcesyntaxhighlight>
 
Судя по всему, программа работает. Кстати, обратите внимание, что результирующие векторы содержат рациональные дроби). Теперь добавим обратный ход метода Гаусса:
 
<sourcesyntaxhighlight lang="ruby">require 'mathn'
 
class Vector
Строка 181:
equation[0] -= equation[1] * equation[0][1]
 
p equation #=> [Vector[1, 0, 0, 19], Vector[0, 1, 0, -13], Vector[0, 0, 1, 8]]</sourcesyntaxhighlight>
 
Вроде решение получили. Но было бы замечательно, если бы выводилось не всё уравнение, а только столбец свободных членов. Задачка простенькая, но важная. Давайте её решим:
 
<sourcesyntaxhighlight lang="ruby">p equation.map{ |vector| vector.to_a }.transpose[-1] #=> [19, -13, 8]</sourcesyntaxhighlight>
 
Теперь задачу можно считать решённой. Жаль только, что программа работает только для уравнения 3×4. Надо бы добавить несколько итераторов, чтобы они самостоятельно определяли размерность уравнения. Для этого нужно проследить чередование индексов и записать для них итераторы:
 
<sourcesyntaxhighlight lang="ruby">require 'mathn'
 
class Vector
Строка 210:
}
 
p equation.map{ |vector| vector[-1] } #=> [19, -13, 8]</sourcesyntaxhighlight>
 
Обратите внимание, что <code>equation</code> задаётся через матрицу (которая, не без помощи метода <code>.row_vectors</code>, преобразуется в массив векторов). Также обратите внимание, что получить последний столбец можно посредством итератора <code>.map</code> и метода «батарейка».