Что такое vector

Обновлено: 08.05.2024

Основы линейной алгебры для тех, кого это миновало в универе.

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

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

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

Примеры

В следующем примере показано, как добавить две Vector структуры.

Vector Структура

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

Представляет смещение в двумерном пространстве.

1. Векторы

Начертим какой-либо отрезок \(AB\). Один конец \(A\) назовём начальной точкой, а второй \(B\) — конечной точкой. Направление отрезка \(AB\) из точки \(A\) в точку \(B\) укажем с помощью стрелки. Таким образом получается направленный отрезок (см. илл. ниже).



Ваш браузер не поддерживает HTML5 видео

двумя заглавными буквами, поставив над ними стрелочку; первая буква показывает начальную точку, вторая — конечную точку, например, AB → (читается: вектор \(AB\));

маленькой буквой со стрелочкой над ней, например, a → (читается: вектор \(a\)).

Если начальная и конечная точки вектора совпадают, получается нулевой вектор, который обозначается как 0 → . Любую точку на плоскости можно считать нулевым вектором.

Длина отрезка \(AB\) называется длиной, или модулем, вектора AB → и обозначается так: AB → .

Данные записи — g → = 1 . 5 ; AB → = 3 — обозначают, что длина g → равна \(1.5\) единицам, а длина AB → равна \(3\) единицам.

Величины, с которыми встречаемся в естественных науках, бывают скалярными или векторными.

Скалярными называют величины, имеющие численное значение, но не имеющие направления.

Примеры — количество каких-нибудь предметов, длина, плотность.

Векторными величинами, или векторами, называют величины, имеющие и численное значение, и направление.

Например, если сказано, что автомобиль движется со скоростью \(100\) километров в час (то есть, дано численное значение скорости), то про его скорость известно не всё, потому что неизвестно, куда, в каком направлении он двигается. Поэтому примеры векторных величин — скорость, сила, перемещение.

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

Parvietojums_vekt.jpg

Запомни различие между расстоянием и перемещением.

Расстояние характеризуется только числовым значением, например, \(AB + BC + CD = 5\) км.

Перемещение — вектор AD → , соединяющий начальное и конечное положение тела, и его длина не равняется \(5\) км.

Например, можно проехать \(5\) км и вернуться обратно, перемещение же в этом случае будет равно \(0\) и обозначится как нулевой вектор.

Что такое вектор

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

Линейная алгебра: векторы

Физическое представление вектора: есть длина, направление и нет начальной точки отсчёта. Такой вектор можно как угодно двигать в пространстве

У аналитиков вектор представляется в виде упорядоченного списка чисел: это может быть любая информация, которую можно измерить и последовательно записать. Для примера возьмём рынок недвижимости, который нужно проанализировать по площади и цене домов — получаем вектор, где первая цифра отвечает за площадь, а вторая — за цену. Аналогично можно сортировать любые данные.

Линейная алгебра: векторы

Аналитическое представление вектора: данные можно перевести в числа

Математики обобщают оба подхода и считают вектор одновременно стрелкой и числом — это связанные понятия, перетекающие друг в друга в зависимости от задачи. В одних случаях удобней считать, а в других — показать всё графически. В обоих случаях перед нами вектор.

Линейная алгебра: векторы

Математическое представление вектора: данные можно перевести в числа или график

В дата-сайенс используется математическое представление вектора — программист может обработать данные и визуализировать результат. В отличие от физического представления, стрелки векторов в математике привязаны к системе координат Х и У — они не блуждают в пространстве, а исходят из нулевой точки.

Линейная алгебра: векторы

Векторная система координат с базовыми осями Х и Y. Место их пересечения — начало координат и корень любого вектора. Засечки на осях — это отрезки одной длины, которые мы будем использовать для определения векторных координат

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

Например, можно взять много векторов с ценами на недвижимость, как-то их проанализировать, усреднить и обучить на них алгоритм. Без векторов это были бы просто «рассыпанные» данные, а с векторами — порядок.

Аналог std::vector из C++11 на чистом C89 и как я его писал

image


Жилой массив людей. Нет, серьёзно.

Холивары между ценителями Си и приверженцами его ублюдка сына в лице C++ начались ещё до моего рождения и прекратятся разве что после смерти обоих этих языков и меня заодно.

Адепты великого творения Кернигана-Ритчи до последней секунды рабочего дня готовы доказывать приспешникам Страуструпа аксиомы про вечность Си и его невероятную гибкость.
Те в ответ по-свойски советуют им лучше порадоваться рабочему дню, ведь он вот-вот окажется последним – двадцать первому веку кроссплатформенный ассемблер не нужен.
Распаляясь, сторонники Си приводят миллионы давно прошедших через голову навылет тезисов "почему Си лучше C++", при этом каждый раз подчёркивая, что второй все достоинства первого растерял ещё будучи в отцовской утробе, попутно утратив лик человеческий.
Обвиняемая сторона в обиде не остаётся и.

а хотя постойте, о чём это я.

Я люблю Си, уважаю C++ и не переношу холивары (честно). При этом я осознаю, что в Си действительно не хватает многого, и яркий тому пример – отсутствие удобной работы с данными. В C++ эту проблему во многом решает STL и свойства самого языка. На мой студенческий взгляд, здесь особо отличается всем знакомый std::vector . Если стало интересно, как я реализовал его аналог средствами C89 – прошу под кат.

Операторы

Перемещает точку по заданному вектору и возвращает результат в виде точки.

Складывает два вектора и возвращает результат в виде вектора.

Делит указанный вектор на заданный скаляр и возвращает результирующий вектор.

Сравнивает два вектора на наличие равенства.

Создает объект Point со значениями X и Y данного вектора.

Создает Size из смещений данного вектора.

Сравнивает два вектора на наличие неравенства.

Умножает указанный скаляр на заданный вектор и возвращает результирующий вектор.

Умножает указанный скаляр на заданный скаляр и возвращает результирующий вектор.

Преобразует координатное пространство указанного вектора с использованием заданного значения Matrix.

Вычисляет скалярное произведение двух заданных векторных структур и возвращает результат в виде Double.

Вычитает один указанный вектор из другого.

Преобразует заданный вектор в отрицательный.

Предыстория

Вообще, с вышеописанной проблемой наверняка сталкивается каждый, кто переходит на Си с языка чуть более высокого уровня (в моём случае это были FreeBASIC и Free Pascal). Проблема отсутствия давно полюбившихся Redim и SetLength() вначале решается "в лоб кувалдой" при помощи realloc() . Потом приходят знания в обнимку с опытом, и вместо этого уже используется простенький самописный динамический массив.

Однако необходимость дублировать его код для каждого отдельно взятого типа данных с каждым разом всё сильнее вызывает раздражение. Туда же альтернативный вариант – использование указателей, требующее разыменований и приведений типов. А затем человеку попадает в руки C++ (или его аналог) и человек видит STL (или его аналог). Дальше можно прочитать в любом бульварном романе.

Тем не менее, влюбляются в тело, но любят душу. Если человек долгое время был в счастливых отношениях с Си, если в них уже появились проекты, то человеку вполне естественно хотеть сделать объект своей любви лучше – к обоюдной пользе. А человек в совершенствовании всегда на что-то ориентируется.

Короче говоря, это история о том, как любовь к Си заставила меня привнести в неё (него?) пресловутый std::vector – то, что мне нравилось в C++, которым (которой?) я в одно время увлёкся.

Комментарии

Объект Point представляет фиксированную позиции, а Vector представляет направление и величину (например, скорость или ускорение). Таким словами, конечные точки сегмента линии являются точками, но их разность является вектором. то есть направление и длина этого сегмента линии.

В XAML разделитель между X Y значениями и Vector может быть либо запятой, либо пробелом.

Некоторые языки и региональные параметры могут использовать символ запятой в качестве десятичного разделителя вместо символа точки. Обработка XAML для инвариантных региональных параметров по умолчанию имеет значение en-US в большинстве реализаций процессора XAML и ожидает, что точка является десятичным разделителем. Следует избегать использования символа запятой в качестве десятичного разделителя при указании Vector в XAML, поскольку это будет конфликтовать с преобразованием строкового типа Vector значения атрибута в X Y компоненты и.

Использование атрибута XAML

Значения XAML

x
Компонент X вектора. Дополнительные сведения см. в описании свойства X.

y
Компонент Y вектора. Дополнительные сведения см. в описании свойства Y.

Что дальше

В следующий раз разберём операции с векторами. Пока мы готовим материал — рекомендуем почитать интервью с Анастасией Никулиной. Анастасия ведёт ютуб-канал по дата-сайнс и работает сеньором дата-сайентистом в Росбанке.

Методы

Перемещает указанную точку по заданному вектору и возвращает результат в виде точки.

Складывает два вектора и возвращает результат в виде структуры Vector.

Извлекает угол (в градусах) между двумя заданными векторами.

Вычисляет векторное произведение двух векторов.

Вычисляет определитель двух векторов.

Делит указанный вектор на заданный скаляр и возвращает результат в виде Vector.

Определяет, является ли указанный объект Object структурой Vector. Если это так, проверяется, имеет ли структура те же значения X и Y, что и данный вектор.

Сравнивает два вектора на наличие равенства.

Сравнивает два указанных вектора на наличие равенства.

Возвращает хэш-код данного вектора.

Умножает указанный скаляр на заданный вектор и возвращает результат в виде Vector.

Умножает указанный вектор на заданный скаляр и возвращает результат в виде Vector.

Преобразует координатное пространство указанного вектора с использованием заданного значения Matrix.

Вычисляет скалярное произведение двух заданных векторов и возвращает результат в виде Double.

Отрицает данный вектор. Вектор имеет ту же величину, что и раньше, но теперь его направление изменилось на противоположное.

Нормализует данный вектор.

Преобразовывает строковое представление вектора в эквивалентную структуру Vector.

Вычитает указанный вектор из другого заданного вектора.

Возвращает строковое представление данной структуры Vector.

Возвращает строковое представление данной структуры Vector с заданными данными форматирования.

До нас хоть потоп

Уже было отмечено, что проблема отсутствия в Си встроенного динамического массива для произвольных типов не нова и по-разному решалась немало раз.
Вот те варианты реализации вектора, которые я нашёл буквально за пять минут в Google:

Все эти решения имеют как минимум один из следующих фатальных недостатков:

Реализация макросами конкретных функций управления.
Использовать макросы в качестве inline-функций – затея плохая. Об этом говорилось много раз, но разве мы устанем повторять?
Во-первых, при использовании макросов-функций тяжелее отслеживать и отлаживать ошибки, возникающие из-за неправильных типов аргументов.
Во-вторых, макросы-функции не умеют ничего возвращать, если не брать во внимание извращения с comma-оператором или отдельным аргументом под имя переменной для хранения результата.
В-третьих, из-за постоянных подстановок кода из макросов-функций, которые и на inline-то мало похожи, разбухает размер единицы трансляции. Отсюда следует увеличение размера выходного исполняемого файла и прочие радости жизни.
В-четвёртых, на макрос-функцию нельзя взять указатель.

Дублирование общих для любых векторов функций.
Например, разные функции освобождения для вектора int 'ов и вектора char 'ов. Под капотом они будут представлять собой всего-навсего вызов функции free() , глубоко безразличной к тому, что хранится в уничтожаемом буфере, равно как и к типу указателя на него.
Это опять же провоцирует увеличение объёма единиц трансляции, дублирование кода, а заодно и замусоривание пространства имён.

Работа со значениями через нетипизированные указатели.
Это обязывает всегда брать указатель на значение для добавления его даже в простой вектор примитивных типов (например int 'ов). А также не забываем о приведении типов и разыменованиях. Ну и о том, что в такой вектор можно потенциально засунуть значения разных типов, и никто нас об этом не предупредит.

Обозначение типа вектора как структуры.
Самый большой недостаток, при наличии одного которого даже полное отсутствие других уже не играет роли.
Во-первых, обращение к элементам вектора происходит через поле структуры. Для одномерного вектора это уже неудобно – стоит ли говорить о многомерных.
Во-вторых, все поля структуры, даже технические, свободно доступны пользователю.
Во-третьих, практически полная несовместимость между векторами разных типов.
В-четвёртых, для создания и удаления вектора требуется 2 вызова malloc() / free() соответственно – один на структуру и один на сам буфер вектора. Как нетрудно догадаться, для вектора размерности вызовов будет уже .
В-пятых, передать такой вектор в свою функцию можно только по указателю на структуру, поэтому синтаксис обращения к нему в функции будет слегка другим ( -> вместо . и всё такое прочее).

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

  1. Доступ к элементам вектора как к элементам обычного массива, вне зависимости от его размерности: vec[k] , vec[i][j] и т.д.
  2. Управление вектором с помощью обычных функций, обладающих типизированными аргументами и возвращаемым значением, в отличие от макросов.
  3. Отсутствие дублирующегося кода благодаря специализации только тех функций, которые принимают и/или возвращают значения пользовательского типа.
  4. Отсутствие у пользователя прямого доступа к технической информации вектора.
  5. Совместимость между векторами разных типов на уровне присваивания одного другому.
  6. Возможность пользователю при специализации вектора указать способ передачи и возврата значений: по значению или по ссылке (через указатель).
  7. Максимальная схожесть интерфейса вектора с таковым у std::vector из C++11.

Конструкторы

Инициализирует новый экземпляр структуры Vector.

Явные реализации интерфейса

этот элемент поддерживает инфраструктуру Windows Presentation Foundation (WPF) и не предназначен для непосредственного использования из кода. Описание этого члена см. в разделе ToString(String, IFormatProvider).

Как записывать

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

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

Основы линейной алгебры

Способы записи вектора

Скаляр

Помимо понятия вектора есть понятие скаляра. Скаляр — это просто одно число. Можно сказать, что скаляр — это вектор, который состоит из одной координаты.

Помните физику? Есть скалярные величины и есть векторные. Скалярные как бы описывают просто состояние, например, температуру. Векторные величины ещё и описывают направление.

Свойства

Возвращает длину данного вектора.

Возвращает квадрат длины данного вектора.

Получает или задает компонент X данного вектора.

Получает или задает компонент Y данного вектора.

Dive into C89

Заранее отвечу на вопрос, почему C89, а не хотя бы C99. Во-первых, это даёт поддержку компилятора из Visual Studio (хоть он мне и не нравится). Во-вторых, я сам очень люблю C99, но в данном случае почувствовал, что поставленную задачу можно решить и в более жёстких условиях. Как-никак, публикацию в "ненормальном программировании" надо оправдывать.

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

Однако потом мне на глаза попалась библиотека динамических строк для Си под названием Simple Dynamic Strings, написанная в своё время для Redis. Она использует другой подход: техническая информация о векторе хранится не в структуре вместе с указателем на него, а в виде заголовка прямо перед самим буфером вектора в памяти. Это позволяет оперировать вектором напрямую через типизированный указатель, при этом размещение технической информации всегда достоверно известно.

image

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

Таким образом мы реализовали возможности (1) и (4). Идём дальше.

Так как теперь вектор – это просто типизированный указатель, то мы, казалось бы, уже можем обобщить для разных типов векторов такие функции как, например, функцию освобождения, просто обозначив аргумент "указатель на освобождаемый вектор" как void* . Общеизвестно, что в void* можно неявно преобразовать любой другой указатель, равно как и наоборот.

Однако можем ли мы это проделать для других функций? Как ни странно, но да. У нас нет функций, оперирующих непосредственно с самими хранимыми значениями – их изначально предполагалось специализировать отдельно для каждого типа вектора. По сути мы оперируем лишь местами хранения значений, но не самими значениями. Следовательно, нам достаточно знать только размер одного элемента, который можно хранить в технической информации вектора и заполнять в функции его создания путём передачи соответствующего аргумента. Такой трюк позволяет нам обобщить для разных типов векторов вообще все функции, а специализировать на их основе только те, которые принимают и/или возвращают значения пользовательского типа.

Пункты (2) и (3) реализованы. А так как в Си нет объектов и любое значение может быть переприсвоено другой переменной буквально копированием памяти, то реализован и пункт (5). Продолжаем в том же духе.

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

  • присвоение указанным элементам вектора переданного значения;
  • возврат значения указанного элемента.

Известно, что значение может передаваться в функцию или возвращаться из неё либо по значению (пардон за каламбур), либо по ссылке. Для примитивных типов предпочтительнее первый вариант, тогда как для сложных структур – второй.
Ссылок а-ля C++ в Си конечно же нет, но их заменят нам указатели.

Устали от текста? вопрос риторический.
Тогда приведу для наглядности определения вариантов одних и тех же функций, принимающих/возвращающих переменные по значению и по ссылке соответственно.

Видно, что в обоих случаях отличие лишь в одном символе.

Уже в C89 оператор присваивания доступен для всех типов, а не только для примитивных. Это позволяет передачу и возврат по ссылке или по значению в специализируемых функциях указывать аргументами макроса-специализатора. Правда возникает резонный вопрос: а почему не указывать это одним аргументом сразу для передачи и возврата одновременно? А очень просто: возврат по значению удобнее и быстрее в случае примитивных типов, но значение может быть не определено в случае отсутствия в векторе запрошенного элемента. При возврате по ссылке в таком случае мы можем просто вернуть NULL . Короче говоря, это оставлено на усмотрение самого программиста.

В итоге реализован пункт (6). Пункт (7) можно также считать реализованным по совокупности всех предыдущих.

Как изображать

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

Основы линейной алгебры

Графическое представление скаляра. Записывается в круглых скобках

Вектор из двух чисел отображается в виде точки на плоскости осей Х и Y. Числа задают координаты вектора в пространстве — это такая инструкция, по которой нужно перемещаться от хвоста к стрелке вектора. Первое число показывает расстояние, которое нужно пройти вдоль оси Х; второе — расстояние по оси Y. Положительные числа на оси Х обозначают движение вправо; отрицательные — влево. Положительные числа на оси Y — идём вверх; отрицательные — вниз.

Представим вектор с числами −5 и 4. Для поиска нужной точки нам необходимо пройти влево пять шагов по оси Х, а затем подняться на четыре этажа по оси Y.

Основы линейной алгебры

Графическое представление числового вектора в двух измерениях

Вектор из трёх чисел отображается в виде точки на плоскости осей Х, Y и Z. Ось Z проводится перпендикулярно осям Х и У — это трёхмерное измерение, где вектор с упорядоченным триплетом чисел: первые два числа указывают на движение по осям Х и У, третье — куда нужно двигаться вдоль оси Z. Каждый триплет создаёт уникальный вектор в пространстве, а у каждого вектора есть только один триплет.

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

Основы линейной алгебры

Графическое представление числового вектора в трёх измерениях. Для примера мы взяли координаты −5, 2, 4

Помните, что все эти записи и изображения с точки зрения алгебры не имеют отношения к нашему реальному трёхмерному пространству. Вектор — это просто какое-то количество абстрактных чисел, собранных в строгом порядке. Вектору неважно, сколько там чисел и как их изображают люди. Мы же их изображаем просто для наглядности и удобства.

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

Заключение

Итоговая реализация библиотеки вектора на C89, готовая к практическому применению, находится здесь:

Простейший пример использования приведён в ReadMe.

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

Это первая моя статья на Хабре, поэтому прошу судить как можно строже. За косноязычие – особенно.

Линейная алгебра

Есть математика: она изучает абстрактные объекты и их взаимосвязи. Благодаря математике мы знаем, что если сложить два объекта с ещё двумя такими же объектами, то получится четыре объекта. И неважно, что это были за объекты: яблоки, козы или ракеты. Математика берёт наш вещественный мир и изучает его более абстрактные свойства.

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

Например, мы знаем, что если a + b = c , то a = c − b . Мы не знаем, что стоит на местах a, b или c, но для нас это такой абстрактный закон, который подтверждается практикой.

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

В программировании линейная алгебра нужна в дата-сайенс, где из упорядоченной информации создаются алгоритмы машинного обучения.

Если представить линейную алгебру в виде дома, то вектор — это кирпич, из которого всё состоит. Сегодня разберёмся, что такое вектор и как его понимать.

И зачем нам это всё

Вектор — это «кирпичик», из которого строится дата-сайенс и машинное обучение. Например:

  • На основании векторов получаются матрицы. Если вектор — это как бы линия, то матрица — это как бы плоскость или таблица.
  • Машинное обучение в своей основе — это перемножение матриц. У тебя есть матрица с данными, которые машина знает сейчас; и тебе нужно эту матрицу «дообучить». Ты умножаешь существующую матрицу на какую-то другую матрицу и получаешь новую матрицу. Делаешь так много раз по определённым законам, и у тебя обученная модель, которую на бытовом языке называют искусственным интеллектом.

Кроме того, векторы используются в компьютерной графике, работе со звуком, инженерном и просто любом вычислительном софте.

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

Читайте также: