Tuples / Кортежи в Python
По аналогии со списками кортежи в Python — это стандартный тип, позволяющий хранить значения в виде последовательности. Они полезны в тех случаях, когда необходимо передать данные, не позволяя изменять их. Эти данные могут быть использованы, но в оригинальной структуре изменения не отобразятся.
В этом руководстве вы познакомитесь с кортежами Python в подробностях:
Кортеж Python
Эта структура данных используется для хранения последовательности упорядоченных и неизменяемых элементов.
Примечание: type() — это встроенная функция для проверки типа данных переданного параметра.
Кортежи могут включать однородные и разнородные значения. Но после объявления их уже нельзя будет поменять:
Последняя ошибка появилась из-за попытки поменять значения внутри кортежа.
Кортежи можно создавать и вот так:
Кортежи против списков
Как вы могли заметить, кортежи очень похожи на списки. По сути, они являются неизменяемыми списками. Это значит, что после создания кортежа хранимые в нем значения нельзя удалять или менять. Добавлять новые также нельзя:
Ошибка появляется, потому что нельзя добавлять новые элементы в кортеж, что работает в случае со списками.
Но зачем использовать этот тип данных, если он неизменяемый?
Кортежи не только предоставляют доступ только для чтения к элементам, но и работают быстрее списков. Рассмотрим в качестве примера следующий код:
Какую роль играет неизменяемость в случае с кортежами?
Добавить элемент в кортеж нельзя, поэтому появляется последняя ошибка AttributeError. Вот почему эта структура данных неизменна. Но всегда можно сделать вот так:
Это позволяет изменять оригинальный кортеж? Куда в таком случае делась их неизменяемость?
Суть в том, что id списка в кортеже не меняется несмотря на добавленный в него элемент 5.
Теперь вы знаете следующее:
Некоторые кортежи (которые содержат только неизменяемые объекты: строки и так далее) — неизменяемые, а другие (содержащие изменяемые типы, например, списки) изменяемые. Но это очень обсуждаемая тема среди программистов на Python и необходимы кое-какие познания, чтобы полностью понять ее. В целом же кортежи неизменяемые.
Так что если вы планируете использовать постоянный набор значений для перебора, используйте кортеж вместо списка. Он будет работать быстрее. Плюс, это безопаснее, ведь такой тип данных защищен от записи.
Если вы хотите узнать больше о списках Python, обязательно ознакомьтесь с этим руководством!
Стандартные операции с кортежами
Python предоставляет несколько способов для управления кортежами. Рассмотрим их на примерах.
Срезы
Можно использовать и отрицательные значения:
Индексы позволяют получать отдельные элементы, а с помощью срезов становятся доступны и подмножества. Для этого нужно использовать диапазоны индексов:
Частота в данном случае является опциональным параметром, а его значение по умолчанию равно 1.
Совет: значение частоты может быть и негативным, чтобы развернуть кортеж.
Объединение кортежей
Можно объединять кортежи для создания нового объекта. Операция объединения выполняет конкатенацию двух кортежей.
Разрешается объединять только определенные типы данных. Так, попытка соединить кортеж и список закончится ошибкой.
Умножение кортежей
Операция умножения приводит к тому, что кортеж повторяется несколько раз.
Функции кортежей
count() и len()
count() возвращает количество повторений элемента в кортеже.
len() — длину кортежа:
Функция может быть полезной, если кортеж вызывается? и нужно удостовериться, что он не пустой.
tuple()
Функция tuple() используется для конвертации данных в кортеж. Например, так можно превратить список в кортеж.
min() и max()
Функция max() q возвращает самый большой элемент последовательности, а min() — самый маленький. Возьмем следующий пример:
Эти функции можно использовать и для кортежей со строками.
С помощью этой функции можно вернуть сумму элементов в кортеже. Работает только с числовыми значениями.
sorted()
Чтобы получить кортеж с отсортированными элементами, используйте sorted() как в следующем примере:
Но важно отметить, что возвращаемый тип — список, а не кортеж. При этом последовательность в оригинальном объекте неизменна, а сам он остается кортежем.
Присваивание несколько кортежей
Кортежи можно использовать для присваивания нескольких значений одновременно. Вот так:
Выводы
Теперь вы знаете, что такое кортежи, как их создавать, какие самые распространенные операции, и как ими можно управлять. Также — распространенные методы структур Python. А в качестве бонуса научились присваивать нескольким переменным разные значения.
Кортежи (Visual Basic)
Создание экземпляров и использование кортежа
Вы создаете экземпляр кортежа, заключая в круглые скобки значения, разделенные запятыми. Каждое из этих значений затем превращается в поле кортежа. Например, следующий код определяет тройной (или 3-кортежный) элемент, а в качестве Date его первого значения в String качестве второго, а в Boolean качестве третьего.
поля Visual Basic кортежа доступны для чтения и записи; После создания экземпляра кортежа можно изменить его значения. В следующем примере изменяются два из трех полей кортежа, созданного в предыдущем примере, и отображается результат.
Создание экземпляров и использование именованного кортежа
Выводимые имена элементов кортежа
начиная с Visual Basic 15,3, Visual Basic может определять имена элементов кортежа. их не нужно назначать явным образом. Выводимые имена кортежей полезны при инициализации кортежа из набора переменных и если имя элемента кортежа должно совпадать с именем переменной.
поскольку элементы и переменные имеют одинаковое имя, компилятор Visual Basic может вывести имена полей, как показано в следующем примере.
чтобы включить выводимые имена элементов кортежа, необходимо определить версию компилятора Visual Basic для использования в файле проекта Visual Basic ( * vbproj):
номер версии может быть любой версией компилятора Visual Basic, начиная с 15,3. вместо того чтобы жестко программировать конкретную версию компилятора, можно также указать «Latest» в качестве значения LangVersion для компиляции с последней версией компилятора Visual Basic, установленного в системе.
Имя кандидата дублируется в кортеже.
Кортежи и структуры
Пользовательские элементы. Нельзя определить собственные свойства, методы или события для кортежа.
Проверка. Невозможно проверить данные, назначенные полям.
Неизменяемость. Visual Basic кортежи являются изменяемыми. В отличие от этого, пользовательская структура позволяет контролировать, является ли экземпляр изменяемым или неизменяемым.
если важны пользовательские члены, проверка свойств и полей или неизменность, следует использовать инструкцию Visual Basic Structure для определения пользовательского типа значения.
Кроме того, типы ValueTuple реализуют IStructuralComparable IStructuralEquatable интерфейсы и, которые позволяют определять пользовательские компараторы.
Назначение и кортежи
Visual Basic поддерживает присваивание между типами кортежей, которые имеют одинаковое число полей. Типы полей можно преобразовать, если выполняется одно из следующих условий.
Исходное и целевое поля имеют один и тот же тип.
Определено расширяющее (или неявное) преобразование исходного типа в целевой тип.
Другие преобразования в контексте назначений не учитываются. Рассмотрим возможные виды назначений между типами кортежей.
В приведенных ниже примерах можно использовать указанные переменные:
Все четыре из этих кортежей имеют одинаковое число полей (называемое арностью), а типы этих полей идентичны. Таким образом, все эти назначения работают:
Обратите внимание на то, что имена кортежей не назначаются. Значения полей назначаются в соответствии с порядком полей в кортеже.
Кортежи с разными числами полей не могут быть назначены:
Кортежи как возвращаемые значения методов
Метод может возвращать только одно значение. Однако часто требуется, чтобы вызов метода возвращал несколько значений. Существует несколько способов обойти это ограничение:
Можно создать пользовательский класс или структуру, свойства или поля которой представляют значения, возвращаемые методом. Таким решением является высокоплотное решение; для этого необходимо определить пользовательский тип, предназначенный только для извлечения значений из вызова метода.
Можно вернуть одно значение из метода и вернуть оставшиеся значения, передав их по ссылке к методу. Это включает дополнительные издержки при создании экземпляра переменной и риск непреднамеренной перезаписи значения переменной, которую вы передаете по ссылке.
Можно использовать кортеж, который предоставляет упрощенное решение для извлечения нескольких возвращаемых значений.
Мы можем вернуть кортеж из операции синтаксического анализа, если заключить вызов Int32.TryParse метода в собственный метод. В следующем примере NumericLibrary.ParseInteger вызывается Int32.TryParse метод и возвращается именованный кортеж с двумя элементами.
Затем можно вызвать метод с помощью следующего кода:
Свойства экземпляра кортежа доступны только для чтения; кортежи являются неизменяемыми. в Visual Basic кортежах и типах ValueTuple поля кортежей доступны для чтения и записи. кортежи являются изменяемыми.
Универсальные типы кортежей являются ссылочными типами. Использование этих типов кортежей означает выделение объектов. В критических путях это может заметно влиять на производительность приложения. Visual Basic кортежи и типы ValueTuple являются типами значений.
Кортежи в языках программирования. Часть 2
В предыдущей части я рассмотрел реализации кортежей в различных языках программирования (причем я рассматривал компилируемые не скриптовые языки со статической типизацией и классическим си-подобным синтаксисом, что вызвало удивление у некоторых читателей). В этой части я предлагаю выйти за рамки существующего и заняться по сути дизайном языка программирования. Исходные данные — такие же: компилируемый не скриптовый язык со статической типизацией и си-подобным синтаксисом, включающий императивную парадигму (хотя и не ограничивающийся ею разумеется).
В этой части мы попробуем помечтать и поэкспериментировать — а что вообще можно сделать с кортежами? Как выжать из них максимум возможностей? Как с их помощью сделать язык программирования мощнее и выразительнее, как вызвать восхищение у истинных Хакеров Кода и при этом не слишком запутать обычных программистов? Какие неожиданные возможности появляются в языке, если правильно и грамотно экстраполировать семантику кортежей в разных направлениях, и какие затруднения при этом возникают?
Итак, если вам нравится размышения и холивары на тему дизайна языков программирования, то прошу под кат.
СИНТАКСИС
Для начала определимся с синтаксисом. Можно долго рассматривать разные варианты оформления кортежей в коде — в фигурных скобках, в круглых, вообще без скобок… мне по ряду причин нравится вариант с фигурными, его и возьмем за основу (по крайней мере пока не возникнет реальная необходимость в другом синтаксисе). Итак, кортеж — это последовательность имен или выражений, перечисленных через запятую и заключенных в фигурные скобки. Как унифицированная инициализация в С++.
Возможно, имеют право на существование и другие варианты, но для наших целей этого будет пока достаточно.
Еще хотелось бы отметить простую идею создания кортежей из диапазонов и повторений. Диапазоны — способ задания кортежей для перечислимых элементов (к которым относятся целые числа и элементы перечислений)
Повторения — взятый из Ассемблера способ, позволяющий заполнить кортеж одним и тем же значением
В данной статье я этими способами пользоваться не буду, и упомянул их просто так — как красивую идею.
ОБЩИЕ ИДЕИ
В первой части я упомянул некую мысль, которая вызвала некоторое непонимание — о том, что кортеж «не совсем тип». Если открыть ту же Википедию, то там четко сказано
В некоторых языках программирования, например, Python или ML, кортеж — особый тип данных.
В языках программирования со статической типизацией кортеж отличается от списка тем, что элементы кортежа могут принадлежать разнымтипам и набор таких типов заранее определён типом кортежа, а значит и размер кортежа также определён. С другой стороны, коллекции (списки, массивы) имеют ограничение по типу хранимых элементов, но не имеют ограничения на длину.
Однако, что же я имел в виду, говоря что это «не совсем тип»? Иногда (и довольно часто) хочется иметь некую языковую конструкцию, которая бы позволяла локально группировать произвольные объекты на этапе компиляции. Некий групповой псевдоним, который был бы просто эквивалентом своих составных частей без введения каких-либо дополнительных сущностей. В действительности, это просто способ взглянуть на кортежи с несколько другой стороны. Например, множественный возврат из функций. Его вполне можно рассматривать как возврат одного объекта типа «кортеж»; но с другой стороны, операция присваивания значений при множественном возврате — всего лишь одна из множества операций; на примере Rust мы видели возможность другой операции над кортежами — сравнения на равенство. А что если разрешить выполнять над кортежами все операции? Сразу возникают различные вопросы — какие это могут быть операции, могут ли они выполняться над кортежами разной длины, над кортежем и одиночным значением? Также можно рассмотреть вопросы семантики передачи кортежей в функции (возможно например «раскрытие» кортежа при передаче в функцию, или выполнение функции над всеми элементами кортежа и т.д.). Безусловно, нам понадобятся удобные средства создания и декомпозиции кортежей, доступа к элементам кортежа. Возможно, что-то еще.
МНОЖЕСТВЕННЫЙ ВОЗВРАТ ИЗ ФУНКЦИЙ И ТИП VOID
Начнем с самого простого, и уже реализованного во многих языках — с множественного возврата из функций. Функция может возвращать несколько значений, и мы называем это возвратом кортежа из нескольких значений — точно так-же, как функция может принимать несколько аргументов. Напрашивается следующее обобщение: функция, возвращающая одно значение, возвращает кортеж из одного значения. По крайней мере, можно принять как пожелание, чтобы кортежи из одного значения свободно конвертировались в эти самые значения, и наоборот.
Дальнейшая экстраполяция приводит нас к типу void. Как известно, это специальный тип, используемый в большинстве языков программирования для обозначения того, что функция не возвращает результата. Создание объектов этого типа запрещено. Совсем не трудно догадаться, что void — это не полноценный тип, а обозначение для кортежа нулевой длины. Создание объектов такого типа в привычном смысле действительно невозможно; но если наш компилятор умеет работать с кортежами более продвинутым способом, то ничто не мешает нам ввести «псеводобъект» типа void в виде пустого кортежа <> и что-то с ним делать. Вопрос — что? И в каких случаях это может быть полезно? Пока просто отметим это и перейдем к следующим экстраполяциям.
МНОЖЕСТВЕННЫЕ ОПЕРАЦИИ
Мы рассмотрели множественный возврат из функций и связанное с ним множественное присваивание. Но присваивание — это всего лишь одна из множества возможных операций. По аналогии с присваиванием (с которого все и начиналось), попытаемся построить другие операции над кортежами:
Пока выглядит неплохо, хотя возможно немного настораживает мысль о том, как будут выглядеть такие выражения с большим количеством операций. Важно то, что каждый объект в выражении — кортеж из одинакового количества элементов. Отступление от этого правила порождает различные неоднозначности, которых при дизайне языка программирования лучше избегать. Тем ни менее рассмотреть их нужно обязательно (а по возможности и разрешить).
ГРУППОВЫЕ ОПЕРАЦИИ
Кроме множественных операций, еще весьма заманчивыми кажутся групповые операции над кортежем и одиночным значением. По сути, это первое отступление от правила одинакового количества элементов в каждом операнде выражения. Но хочется чтобы получилось красиво, поэтому пробуем:
Общий вид такой бинарной операции — «tuple operator value», или «value operator tuple» (большинство операций коммутативны); А под форму «value operator tuple» попадает присваивание одной переменной целого кортежа, и в частности — результата множественного возврата функции.
В ситуации с присваиванием множественного возврата из функции очевидно хочется, чтобы в «x» было записано первое возвращаемое значение, а следующие проигнорированы. Вот такая вот неожиданная неоднозначность. Но решать ее как-то надо — уж очень хочется иметь такой синтаксис. Он мог бы быть полезен не только для группового присваивания, но и еще в ряде интересных случаев. Например, индексация массива и даже доступ к полю структуры по имени — тоже операции.
Несмотря на кажущуюся очевидность выражений, формально arr[ < 1,2,3 >] — это форма «value operator tuple», где «value» — это arr, «tuple» — <1,2,3>, а «operator» — квадратные скобки. В отличие от присваивания кортежа единственному значению, здесь вопросов не возникает — результат такой операции должен быть кортежем < arr[1], arr[2], arr[3] >. Но для компилятора что присваивание, что индексация — всего лишь бинарные операции. Значит, выражение вида x = <1,2,3>должно разворачиваться в
Таким образом, вопрос о правильной организации операций над кортежем и единственным элементом пока остается открытым.
БИНАРНЫЕ ОПЕРАЦИИ НАД КОРТЕЖАМИ С РАЗНЫМИ РАЗМЕРАМИ
Рассмотрим более общий случай — размеры кортежей — операндов бинарной операции не совпадают. Что делать в этом случае? Как всегда, самое простое решение — запретить 🙂 Но попробуем разобраться… В Go и Rust предусмотрена специальная метапеременная «_» (подчеркивание), которую используют, когда какой-то из элементов кортежа справа от оператора присваивания не нужен. В нашем синтаксисе это будет выгледеть так:
Операция со вторым компонентом кортежа просто игнорируется. Использование метапеременной «_» в Go и Rust обязательно при множественном присваивании в случае, если какие-то результаты возвращаемого кортежа не нужны. Таким образом, в этих языках требование обязательного совпадения размеров кортежей сохраняется. Но в этих языках нет других множественных операций кроме присваивания; при попытке экстраполяции метапеременной «_» на другие операции получаются настолько интересные результаты, что их следует рассмотреть в отдельной главе.
Попробуем рассмотреть общий случай: что делать, если написано такое выражение (@ — обобщенная бинарная операция)
ДОСТУП К ЭЛЕМЕНТАМ
А сейчас следует обратиться еще к одной важной возможности — доступу к элементам кортежа.
Традиционно для индексации используются квадратные скобки, а для доступа по имени — точка; хотя вариант Swift с индексом через точку не так уж и плох, он неоднозначен при применении именованных числовых констант вместо чисел, да и вообще непривычен; я предпочту использовать квадратные скобки для доступа по индексу (важно — по константному индексу) и точку для доступа по имени (если оно есть)
Вроде все просто? На самом деле уже нет. Поскольку мы ввели множественные и групповые операции над кортежами, а к таковым относится и индексация «[ ]», и обращение к именованным членам структуры «.», то при использовании этих операций над кортежами, элементами которых являются сложные объекты (для которых определена индексация или «точка») — неясно что делать: обращаться к элементу кортежа или выполнять групповую операцию над всеми элементами кортежа?
Еще один интересный аспект — получение (конструирование) кортежей. По соглашению, принятому в начале статьи, мы используем фигурные скобки для простого конструирования объекта-кортежа. Однако, в некоторых случаях может понадобиться построить кортеж из другого кортежа путем исключения или добавления элементов. Синтаксически это можно сделать с использованием «множественной индексации», применяя по сути те же правила, что и к массивам или структурам.
Для получения кортежа можно было бы использовать множественную индексацию или диапазоны:
(операция индексации сама по себе содержит скобки, поэтому внутри квадратных скобок еще одни фигурные можно не писать)
При наличии у элементов составного объекта имен, можно обращаться по именам:
Следует отметить интересное следствие — поскольку кортежи индексируются константными индексами, то напрашивается идея приводить имена полей к константным индексам (и возможно наоборот)
Таким образом, есть как минимум две «специальные» операции, «точка» и «квадратные скобки», которые могут действовать на сам кортеж как целостный объект. Остальные операции для кортежа как-бы не определены, хотя можно предположить, что нам понадобится например конкатенация кортежей — плоское склеивание двух и более кортежей в один длинный. Поэтому встает открытым вопрос: нужно ли как-то выделять операции доступа непосредственно к элементам кортежа? Или правильнее выделять операции над каждым элементом кортежа?
ОТ ОПЕРАЦИЙ К ФУНКЦИЯМ
Любая операция эквивалентна некоторой функции. Например, унарная операция битовой инверсии
x может быть представлена как neg(x), а бинарное сложение x+y как sum(x, y);
поэтому, рассматривая операции над кортежами как множественные операции, возникает вопрос — что делать, если в таком выражении участвует вызов функции?
Для начала унарная операция:
по аналогии с «групповым присваиванием», мы должны развернуть кортеж следующим образом:
на первый взгляд это кажется вполне логично; сама функция принимает единственное значение и возвращает единственное значение; должна возвращать единственное значение, чтобы из них можно было составить кортеж.
Вероятно, аналогично могли бы раскрываться и функции с двумя аргументами. Например
Хотя надо признать, что синтаксис неявного множественного вызова функции для кортежей слишком неявен, и также как с операциями, напрашивается какой-то явный способ указания такого вызова (хотя сама по себе возможость такого вызова и неплоха).
С другой стороны, вспомним синтаксис Go, D, C∀: там кортеж, переданный в функцию, разворачивается внутри списка аргументов, заменяя собой соответствующее число аргументов. И в общем это тоже весьма логично и ожидаемо — но опять несовместимо с «групповой» сементикой! Возможно ли как-то разрешить это противоречие? И это мы еще не рассматривали сложные варианты, когда размерности кортежей-аргументов не совпадают, когда смешиваются кортежи и одиночные значения, когда мы хотим получить декартово произведение из результатов операции над элементами кортежей и т.д.
Решение, кажущееся достаточно неплохим, пришло как ни странно из С++. Там есть такая возможность, как шаблоны с переменным числом параметров, и для передачи пакета параметров (кстати тоже кортежа) в другой шаблон используется синтаксис с троеточием. Троеточие визуально маркирует, что данный аргумент «раскрывается» в данном контексте (и это очень важно для восприятия кода!). Важно, что это сразу видно в коде. Единственное что не видно — так это на сколько аргументов он раскрывается. Но если хочется указать подробно — ничто не мешает воспользоваться доступом к отдельным элементам кортежа
Наконец, самая сложная ситуация: функция с несколькими параметрами, и в некоторые (или все) параметры передаются кортежи, причем разной длины. Аналогично ситуации с бинарными операциями, существует множество возможностей раскрытия такого вызова: множественный вызов функции по миниальному количеству элементов, по максимальному с разными типами дополнения более коротких кортежей, декартово произведение и т.д. Все эти способы достаточно сложны для восприятия, и поэтому любой из них должен декларироваться только явно — например, с помощью соответствующего ключевого слова перед вызовом функции.
МЕТАПЕРЕМЕННАЯ «_»
Возвращаемся к рассмотрению поведения метапеременной «_», которая используется в некоторых языках для игнорирования элементов кортежа-источника при присваивании кортежей. Посмотрим, можно ли экстраполировать эту метапеременную на более сложные случаи (ведь присваивание — это всего лишь бинарная операция).
По аналогии, операция сложения числа 2 с «_» будет проигнорирована, но что получится в результате? Вообще существует две возможности: или оставить число 2 в результирующем кортеже, или распространить туда «_». В первом случае «_» может рассматриваться как «нейтральный элемент». для любой операции (то есть для любой операции «@» и любого аргумента «x» справедливо x @ _ == _ @ x == x). Например, выражение x = y *
(_ + z) может быть трансформировано в x = y *
z.
Однако тут не все однозначно. Например, унарную операцию смены знака «-x» можно записать как бинарную операцию вычитания числа из нуля «0-x». Если вместо «x» поставить «_», то вот такое выражение будет иметь разный смысл в зависимости от способа записи.
Во втором случае при появлении «_» в какой-то позиции кортежа все дальнейшие вычисления для этой позиции от узла синтаксического дерева, содержащего «_», до корня дерева (т.е. конца выражения, как правило — точки с запятой), отбрасываются (то есть справедливо x @ _ == _ @ x == _ ). То есть наличие хотя-бы одного «_» в i-том элементе кортежа означает, что все вычисления с i-тым элементом всех кортежей во всем выражении выкидываются.
Я затрудняюсь сказать, какой способ работы с «_» лучше. Это еще один вопрос, требующий тщательного обдумывания.
ВЛОЖЕННЫЕ КОРТЕЖИ
Еще один интересный аспект, практически не рассматриваемый в документации к языкам — вложенность кортежей. Наиболее очевидное и реально существующее (даже в языке Си) применение вложенных кортежей — инициализация вложенных структур.
Такой код инициализирует поля x, y, i, но оставляет неинициализированными z и j. Без вложенных кортежей это было бы не так наглядно (и кстати, это пример того как в операторе присваивания/инициализации участвуют кортежи разных размеров). Таким образом, вложенные кортежи имеют вполне конкретные варианты использования. Но это означает также необходимость продумать все особенности экстраполяции операций над вложенными кортежами.
ОПЕРАЦИИ СРАВНЕНИЯ
Операции сравнения кортежей формально должны образовывать кортеж элементов типа bool. Рассмотрим на примере операции равенства:
При сравнении кортежа с одиночным элементом интуитивно ожидаемое поведение также подразумевает «логическое И» между результатами сравнения каждого элемента кортежа с единственным значением:
В случае сравнения кортежей разной длины ситуация аналогична любым другим операциям — любая «подразумевающаяся по умолчанию» логика нежелательна ввиду возможности потенциальных ошибок (программист может забыть указать элемент) и отсутствия какой-то однозначной интуитивной интерпретации, преемлемой для всех.
ПРЕДВАРИТЕЛЬНЫЕ ИТОГИ
Поздравляю, если вы дочитали до этого места! Как видно, введение подобного синтаксиса с одной стороны позволяет красиво записывать некоторые простые выражения; с другой, вопросов и неоднозначностей гораздо больше чем ответов. Эта длинная статья — по сути выжимка по теме «кортежи и групповые операции» из моих личных заметок, которые я делал в течение достаточно долгого времени (обычно это происходит так — сижу, работаю, вдруг приходит какая-то идея — открываю Evernote или Wiznote и записываю). Возможно, у вас тоже появлялись схожие мысли и идеи, или появились в процессе чтения статьи — прошу в комментарии!



