Функция map() в Python
Если вы учитесь программировать, то функция map() в Python — это ваша возможность повысить свой уровень и упростить решение некоторых задач.
Представьте себе: вы хотите стать более эффективным разработчиком. Вы хотите, чтобы ваш код компилировался быстрее. Вы хотите произвести впечатление на коллег своими глубокими знаниями в области программирования. Если что-то из этого находит отклик у вас в душе, значит, вы попали в нужное место.
Функция map() в Python — это функция, которая позволяет вам преобразовывать весь итерируемый объект с помощью другой функции. Ключевой концепцией здесь являются преобразования, которые могут включать:
Если вы запустите этот пример, вы получите: [5, 6, 7, 8, 9, 10].
Что при использовании цикла for происходит под капотом?
Мы можем быть довольны результатом преобразования списка строк в список чисел, но давайте подумаем о том, что только что сделал наш код.
Мы сказали компьютеру пройтись по каждому члену («5», «6», «7» и т.д.), преобразовать член, а затем сохранить его в новом списке. Хотя использование цикла for для преобразования списка является функциональным, это не оптимальное решение.
Функция map() в Python
По сути, мы лишь создали переменную, хранящую список строк, которые мы хотим преобразовать в числа.
Давайте разберем, как работает приведенный выше код. Синтаксис функции map() в Python следующий:
map() — это просто имя функции, здесь ничего особенного.
Перейдем к последней строке кода. Опять же, будем работать изнутри:
list() принимает наши недавно преобразованные итерируемые элементы и сообщает компьютеру, что эти элементы являются частью списка. print() выводит наш новый список.
Что при использовании функции map() происходит под капотом?
Вместо перебора каждого члена списка строк функция map() преобразовала весь список строк в список чисел. Вы сэкономили память, и ваш код сработал быстрее. Поставленная задача выполнена наиболее оптимальным способом.
Заключение
Однако не стоит забывать что, функция map() в Python – это только начало. В этом языке есть еще множество различных трюков, которые помогут вам писать более элегантный код и эффективнее решать поставленные задачи.
map() в Python
Python map() — это встроенная функция, которая позволяет обрабатывать и преобразовывать все элементы в итерируемом объекте без использования явного цикла for, методом, широко известным как сопоставление (mapping). map() полезен, когда вам нужно применить функцию преобразования к каждому элементу в коллекции или в массиве и преобразовать их в новый массив.
map() — один из инструментов, поддерживающих стиль функционального программирования в Python.
Из этой статьи вы узнаете:
Обладая этими знаниями, вы сможете эффективно использовать map() в своих программах или, в качестве альтернативы, использовать списковое включение (list comprehensions) или выражения-генераторы (generator expressions), чтобы сделать ваш код более питоническим и читабельным.
Для лучшего понимания работы map() вам были бы полезны некоторые знания о том, как работать с итерациями (iterables), циклами, функциями (functions) и лямбда-функций (lambda functions).
Функциональный стиль в Python
В функциональном программировании вычисления выполняются путем объединения функций, которые принимают аргументы и возвращают конкретное значение (или значения). Эти функции не изменяют свои входные аргументы и не изменяют состояние программы. Они просто предоставляют результат данного вычисления. Такие функции обычно называются чистыми функциями (pure functions).
Теоретически программы, построенные с использованием функционального стиля, проще:
Функциональное программирование обычно использует списки, массивы и другие итерационные объекты для представления данных вместе с набором функций, которые работают с этими данными и преобразовывают их. Когда дело доходит до обработки данных в функциональном стиле, обычно используются как минимум три метода:
По словам Гвидо ван Россума, на Python в большей степени влияют императивные языки программирования, чем функциональные языки:
Я никогда не считал, что Python находится под сильным влиянием функциональных языков, независимо от того, что люди говорят или думают. Я был более знаком с императивными языками, такими как C и Algol 68, и хотя я сделал функции первоклассными объектами (first-class objects), я не рассматривал Python как язык функционального программирования. (Источник)
Однако еще в 1993 году сообщество Python требовало некоторых функций функционального программирования. Они просили:
В этом руководстве мы рассмотрим одну из этих функциональных возможностей — встроенную карту функций map(). Вы также узнаете, как использовать составные части списковых включений (comprehensions) и выражения генератора (generator expressions), чтобы получить ту же функциональность, что и map(), в питоническом и удобочитаемом виде.
Начало работы с map() в Python
Иногда вы можете столкнуться с ситуациями, когда вам нужно выполнить одну и ту же операцию со всеми элементами массива, чтобы создать новый массив. Самый быстрый и распространенный подход к этой проблеме — использовать цикл for в Python. Однако вы также можете решить эту проблему без явного использования циклов, используя map().
В следующих трех разделах вы узнаете, как работает map() и как вы можете использовать егр для обработки и преобразования итераций без циклов.
Что такое map()
map() перебирает элементы итерируемого массива (или коллекции) и возвращает новый массив (или итерируемый объект), который является результатом применения функции преобразования к каждому элементу исходного итерабельного массива.
Согласно документации, map() принимает функцию и итерацию (или несколько итераций) в качестве аргументов и возвращает итератор, который выдает преобразованные элементы по запросу. Сигнатура функции map определяется следующим образом:
map() применяет функцию к каждому элементу в итерируемом цикле и возвращает новый итератор, который по запросу возвращает преобразованные элементы. function может быть любая функция Python, которая принимает принимать аргументы, равное количеству итераций, которые вы передаете map().
Примечание. Первый аргумент map() — это объект функция, что означает, что вам нужно передать функцию, не вызывая ее. То есть без пары скобок.
Первый аргумент map() — функция преобразования. Другими словами, это функция, которая преобразует каждый исходный элемент в новый (преобразованный) элемент. Несмотря на то, что документация Python вызывает эту функцию аргумента, она может быть любой вызываемой Python. Сюда входят встроенные функции, классы, методы, лямбда-функции и пользовательские функции.
Операция, выполняемая map(), обычно известна как сопоставление, потому что она сопоставляет каждый элемент во входном итерируемом элементе с новым элементом в итоговом итерируемом. Для этого map() применяет функцию преобразования ко всем элементам во входной итерации.
Чтобы лучше понять map(), предположим, что вам нужно взять список числовых значений и преобразовать его в список, содержащий квадратное значение каждого числа в исходном списке. В этом случае вы можете использовать цикл for и написать что-то вроде этого:
Когда вы запускаете этот цикл для чисел, вы получаете список квадратных значений. Цикл for перебирает числа и применяет к каждому значению операцию возведения в квадрат. Наконец, он сохраняет полученные значения в squared.
Вы можете добиться того же результата без использования явного цикла for, используя map(). Взгляните на следующую реализацию приведенного выше примера:
square() — это функция преобразования, которая преобразует число в его квадратное значение. Вызов map() применяет square() ко всем значениям и возвращает итератор, который возвращает квадратные значения. Затем вызывается list() для map(), чтобы создать объект списка, содержащий квадратные значения.
Поскольку map() написан на C и сильно оптимизирован, его внутренний подразумеваемый цикл может быть более эффективным, чем обычный цикл for в Python. Это одно из преимуществ использования map().
Второе преимущество использования map() связано с потреблением памяти. С помощью цикла for вам нужно сохранить весь список в памяти вашей системы. С помощью map() вы получаете элементы по запросу, и только один элемент находится в памяти вашей системы в данный момент.
Примечание. В Python 2.x map() возвращает список. Это поведение изменилось в Python 3.x. Теперь map() возвращает объект map, который является итератором, выдающим элементы по запросу. Вот почему вам нужно вызвать list(), чтобы создать желаемый объект списка.
В качестве другого примера предположим, что вам нужно преобразовать все элементы в списке из строки в целое число. Для этого вы можете использовать map() вместе с int() следующим образом:
map() применяет int() к каждому значению в str_nums. Поскольку map() возвращает итератор (объект map), вам понадобится вызов list(), чтобы вы могли превратить его в объект списка. Обратите внимание, что исходная последовательность не изменяется в процессе
Использование map() с различными видами функций
Вы можете использовать любую функцию Python, вызываемую с помощью map(). Единственным условием будет то, что вызываемый объект принимает аргумент и возвращает конкретное и полезное значение. Например, вы можете использовать классы, экземпляры, реализующие специальный метод с именем __call__ , методы экземпляра, методы класса, статические методы и функции.
Есть несколько встроенных функций, которые вы можете использовать с map(). Рассмотрим следующие примеры:
Вы можете использовать любую встроенную функцию с map() при условии, что функция принимает аргумент и возвращает значение.
Когда дело доходит до использования map(), вы обычно видите использование лямбда-функции в качестве первого аргумента. лямбда-функции удобны, когда вам нужно передать функцию на основе выражений в map(). Например, вы можете повторно реализовать пример квадратных значений с помощью лямбда-функции следующим образом:
лямбда-функции весьма полезны, когда дело доходит до использования map(). Они могут играть роль первого аргумента map(). Вы можете использовать лямбда-функции вместе с map () для быстрой обработки и преобразования ваших итераций.
Обработка множественных итераций с помощью map()
Если вы предоставляете несколько итераций для map(), тогда функция преобразования должна принимать столько аргументов, сколько итераций, которые вы передаете. Каждая итерация map() будет передавать одно значение из каждой итерации в качестве аргумента функции. Итерация останавливается в конце самой короткой итерации.
Рассмотрим следующий пример, в котором используется pow():
pow() принимает два аргумента, x и y, и возвращает x в степени y. На первой итерации x будет 1, y будет 4, а результат будет 1. Во второй итерации x будет 2, y будет 5, а результат будет 32, и так далее. Последняя итерация равна длине самой короткой итерации, которой в данном случае является first_it.
Этот метод позволяет объединить две или более итерации числовых значений, используя различные виды математических операций. Вот несколько примеров, в которых лямбда-функции используются для выполнения различных математических операций с несколькими входными итерациями:
В первом примере используется операция вычитания, чтобы объединить две итерации по три элемента в каждой. Во втором примере складывается значения трех итераций.
Функция map в Python
Для грамотного использования любой функции или метода, рекомендуется сначала рассмотреть их внутреннюю реализацию: понять, какие аргументы они получают и какое значение возвращают. Функция map принимает два аргумента: iterable и function (итерируемый объект и функция) и применяет функцию к каждому элементу объекта. Возвращаемое значение — объект map. Он является итератором, который можно конвертировать в список или множество с помощью встроенных функций.
В этом материале разберем в подробностях аргументы и возвращаемое значение функции map.
Первый аргумент: функция
Обычные функции
Есть список чисел, которые нужно удвоить. Первый вариант — перебрать список с помощью цикла for. Второй — использовать генерацию списка. Но очевидно, что можно задействовать и функцию map.
Можно сделать и что-нибудь посложнее, например, вернуть 0 на месте четных чисел, а нечетные вывести как есть.
Идея должна быть ясна — применение одной функции к каждому элементу итерируемого объекта.
Анонимные функции
Вместе с функцией map можно использовать и анонимные функции. Это довольно частый сценарий.
У анонимных функциях следующий синтаксис:
Эти функции нужны для краткосрочного использования. Они могут иметь любое количество аргументов, а возвращаемое значение определяется выражением. Их можно найти не только в Python, но и в других языках программирования.
Рассмотрим предыдущую проблему с помощью анонимной функции. Есть список элементов и необходимость их удвоить. В таком случае у лямбда-функции есть один аргумент ( x ), который возвращает значение, умноженное на 2 ( x*2 ).
Также функцию map можно использовать и с методами. Методы похожи на функции, но есть некоторые отличия. В частности, методы связаны с ассоциированными с ними объектами.
Встроенные функции
В стандартной библиотеке Python много доступных функций, которые можно использовать в map. Все они перечислены в документации Python.
Второй аргумент: итерируемый объект
Самый распространенный итерируемый объект — списки, но бывают и объекты других типов. Итерируемый объект — это объект с определенным количеством значений, которые можно перебрать, например, с помощью цикла for. Множества, кортежи, словари также являются итерируемыми объектами и их можно использовать в качестве аргументов для функции map. Вот некоторые примеры.
Итерируемый объект может быть и словарем.
Или списком кортежей. Можно создать новый список, который будет содержать только каждый третий элемент каждого кортежа. Или даже изменить каждый третий элемент.
Теперь перейдем к третьему аспекту — возвращаемому значению.
Возвращаемое значение: итератор
Функция map возвращает объект map, который является итератором. Его можно превратить в список, множество или кортеж с помощью встроенной функции.
Использование функции map в Python
Published on September 11, 2020
Введение
Встроенная в Python функция map() используется для применения функции к каждому элементу итерируемого объекта (например, списка или словаря) и возврата нового итератора для получения результатов. Функция map() возвращает объект map (итератор), который мы можем использовать в других частях нашей программы. Также мы можем передать объект map в функцию list() или другой тип последовательности для создания итерируемого объекта.
Функция map() имеет следующий синтаксис:
Вместо использования цикла for функция map() дает возможность применить функцию к каждому элементу итерируемого объекта. Это повышает производительность, поскольку функция применяется только к одному элементу за раз без создания копий элементов в другом итерируемом объекте. Это особенно полезно при обработке больших наборов данных. Также map() может принимать несколько итерируемых объектов в качестве аргументов функции, отправляя в функцию по одному элементу каждого итерируемого объекта за раз.
Использование функции Lambda
Синтаксис map() с функцией lambda выглядит следующим образом:
С таким списком мы можем реализовать функцию lambda с выражением, которое хотим применить к каждому элементу в нашем списке:
Чтобы применить выражение к каждому из наших чисел, мы можем использовать map() и lambda :
Для немедленного получения результатов мы распечатаем список объекта map :
Реализация определяемой пользователем функции
Аналогично lambda мы можем использовать определенную функцию для применения к итерируемому объекту. Функции lambda более полезны при использовании выражения с одной строкой, определяемые пользователем функции лучше подходят для более сложных выражений. Если же нам нужно передать в функцию другой элемент данных, применяемый к итерируемому объекту, определяемые пользователем функции будут удобнее для чтения.
Например, в следующем итерируемом объекте каждый элемент является словарем, содержащим различные детали о каждом из существ в нашем аквариуме:
Мы вызовем assign_to_tank() с нашим списком словарей и новый номер резервуара, который нам нужно заменить для каждого существа:
Вывод программы будет выглядеть следующим образом:
Мы присвоили новый номер резервуара нашему списку словарей. Используя функцию, которую мы определяем, мы включаем map() для эффективного применения функции к каждому элементу списка.
Использование встроенной функции с несколькими итерируемыми объектами
Здесь у нас списки целых чисел, которые мы хотим использовать с pow() :
Затем мы передадим pow() в качестве функции в map() и укажем два списка в качестве итерируемых объектов:
Если мы передадим map() итерируемый объект, который будет длиннее другого итерируемого объекта, map() остановит расчеты после достижения конца наиболее короткого объекта. В следующей программе мы дополним base_numbers тремя дополнительными числами:
В расчетах программы ничего не изменится, и результат будет точно таким же:
Мы использовали функцию map() со встроенной функцией Python и посмотрели на одновременную обработку нескольких итерируемых объектов. Мы увидели, что map() продолжит обрабатывать несколько итерируемых объектов, пока не достигнет конца объекта, содержащего меньше всего элементов.
Заключение
В этом обучающем модуле мы узнали о различных способах использования функции map() в Python. Теперь вы можете использовать map() с собственной функцией, с функцией lambda и с любыми другими встроенными функциями. Также вы можете реализовать map() с функциями, для которых требуется несколько итерируемых объектов.
В этом обучающем модуле мы распечатали результаты map() в формате списка для демонстрационных целей. В наших программах мы обычно будем использовать возвращаемый объект map для дальнейших манипуляций с данными.
Функции map, filter и zip
Теперь, когда мы с вами рассмотрели базовые типы данных языка Python, отметим несколько весьма полезных и часто используемых функций. И начнем с функции map. Она позволяет преобразовывать элементы итерируемого объекта в соответствии с некоторой указанной функцией:
Давайте, я поясню работу этой функции на таком простом примере. Предположим, что у нас есть упорядоченный список:
и мы хотим получить список из квадратов этих чисел. Для этого создадим функцию возведения числа в квадрат:
и вызовем функцию map:
Обратите внимание, мы передаем ссылку на функцию, то есть, записываем ее имя без круглых скобок. В результате переменная b будет ссылаться на итератор map и мы из предыдущего занятия знаем, что для перебора всех значений можно воспользоваться функцией next:
Или же сформировать новый список с помощью функции list:
То есть, функция map к каждому элементу списка lst применяет функцию sq и возвращает объект-генератор для последовательного выбора новых, преобразованных значений. В некотором смысле, это эквивалент вот такого выражения-генератора:
или вот такого списка:
Причем, во всех случаях функция sq или какая-либо другая должна принимать только один аргумент. Если ее записать, например, так:
То возникнет ошибка. А вот возвращать она может несколько аргументов, например, так:
получим список кортежей:
Подобные преобразования можно выполнять с любыми типами данных, например, строками:
На выходе получим список с длинами соответствующих строк:
Если нужно применить встроенные строковые методы, то это делается так:
И в результате получаем следующий список:
[‘МОСКВА’, ‘РЯЗАНЬ’, ‘СМОЛЕНСК’, ‘ТВЕРЬ’, ‘ТОМСК’]
Довольно часто первым аргументом функции map указывают анонимные (лямбда) функции, например, так:
Получим строки, записанные наоборот:
[‘авксоМ’, ‘ьназяР’, ‘кснеломС’, ‘ьревТ’, ‘ксмоТ’]
Почему такая операция так преобразует строки мы с вами уже говорили, когда рассматривали строки и их срезы.
Далее, так как функция map вторым аргументом принимает любой итерируемый объект, то мы можем результат работы первой функции map:
использовать во второй функции map:
То есть, строки коллекции b:
[‘МосквА’, ‘РязАнь’, ‘Смоленск’, ‘Тверь’, ‘Томск’]
были преобразованы в список символов, отсортированных по возрастанию и на выходе мы получили общий список с этими вложенными списками.
Очень часто в программах на Python функцию map используют для ввода с клавиатуры нескольких чисел через какой-либо разделитель, например, пробел. Если мы все запишем вот в таком виде:
и будем вводить целые числа через пробел, то при выполнении функции int возникнет ошибка, т.к. пробел – это не цифровой символ. Хорошо, давайте тогда разобьем эту строку по пробелам:
В результате у нас будет список из строк, в которых записаны введенные числа. Но функцию int можно применять к конкретной строке, а не к списку, т.е. вот такая операция:
опять приведет к ошибке. И здесь нам на помощь приходит функция map. Реализуем наш ввод вот таким образом:
И мы получим генератор для получения введенных чисел. Но это все удобнее преобразовать сразу к списку:
И теперь, при вводе любого количества чисел через пробел, будем получать упорядоченный список, состоящий из чисел. Это бывает очень удобно.
Функция filter
Следующая аналогичная функция – это filter. Само ее название говорит, что она возвращает элементы, для которых, переданная ей функция возвращает True:
Предположим, у нас есть список
из которого нужно выбрать все нечетные значения. Для этого определим функцию:
И далее, вызов функции filter:
На выходе получаем итератор, который можно перебрать так:
Или, с помощью цикла:
Или же преобразовать итератор в список:
Конечно, в качестве функции здесь можно указывать лямбда-функцию и в нашем случае ее можно записать так:
И это бывает гораздо удобнее, чем объявлять новую функцию.
Функцию filter можно применять с любыми типами данных, например, строками. Пусть у нас имеется вот такой кортеж:
и мы вызываем метод строк isalpha, который возвращает True, если в строке только буквенные символы. В результате в консоли увидим:
Москва Смоленск Тверь Томск
Функция zip
Следующая весьма полезная функция позволяет объединять между собой соответствующие элементы упорядоченных коллекций. Например, у нас имеется два списка:
И вызывая для них функцию zip:
Получим итератор, который возвращает следующую коллекцию:
То есть, у нас были объединены в кортеж соответствующие элементы этих двух списков.
Давайте теперь добавим еще один итерируемый объект – строку:
И вызовем функцию zip для всех этих трех объектов:
В результате получим коллекцию:
Смотрите, мы здесь имеем всего четыре кортежа, в каждом из которых по три элемента. То есть, все оставшиеся символы строки «abracadabra» были просто отброшены. Получается, что функция zip формирует выходной список, длина которого равна длине наименьшей из указанных коллекций. Если, например, мы уменьшим коллекцию a до двух элементов:
то на выходе также получим список из двух элементов:
Вот в этом и заключается удобство этой функции: она позволяет автоматически объединить несколько списков в наборы кортежей из соответствующих значений.
Следующие задания для самостоятельного решения не связаны с материалом этого урока, а охватывают все предыдущие занятия. Попробуйте реализовать их на Python и проверить свои знания.
Задания для самоподготовки
1. Поставить в соответствие следующим английским символам русские буквы:
h – х, e – е, l – л, o – о, w – в, r – р, d – д
и преобразовать строку «hello world!» в русские символы.
t = «»»Куда ты скачешь гордый конь,
И где опустишь ты копыта?
О мощный властелин судьбы!
Не так ли ты над самой бездной,
На высоте, уздой железной
Россию поднял на дыбы?»»»
Необходимо выделить каждое второе слово из этого стихотворения и представить результат в виде упорядоченного списка. (Подумайте как реализовать алгоритм с наименьшими затратами по памяти).
3. Реализовать алгоритм для нахождения всех делителей натурального числа N. Число N вводится с клавиатуры. Для начала можно реализовать простым перебором всех N возможных чисел (делителей). Затем, подумайте, как можно оптимизировать по скорости этот алгоритм.
Видео по теме
#1. Первое знакомство с Python Установка на компьютер
#2. Варианты исполнения команд. Переходим в PyCharm
#3. Переменные, оператор присваивания, функции type и id
#4. Числовые типы, арифметические операции
#5. Математические функции и работа с модулем math
#6. Функции print() и input(). Преобразование строк в числа int() и float()
#7. Логический тип bool. Операторы сравнения и операторы and, or, not
#8. Введение в строки. Базовые операции над строками
#9. Знакомство с индексами и срезами строк
#10. Основные методы строк
#11. Спецсимволы, экранирование символов, row-строки
#12. Форматирование строк: метод format и F-строки
#14. Срезы списков и сравнение списков
#15. Основные методы списков
#16. Вложенные списки, многомерные списки
#17. Условный оператор if. Конструкция if-else
#18. Вложенные условия и множественный выбор. Конструкция if-elif-else
#19. Тернарный условный оператор. Вложенное тернарное условие
#20. Оператор цикла while
#21. Операторы циклов break, continue и else
#22. Оператор цикла for. Функция range()
#23. Примеры работы оператора цикла for. Функция enumerate()
#24. Итератор и итерируемые объекты. Функции iter() и next()
#25. Вложенные циклы. Примеры задач с вложенными циклами
#26. Треугольник Паскаля как пример работы вложенных циклов
#27. Генераторы списков (List comprehensions)
#28. Вложенные генераторы списков
#29. Введение в словари (dict). Базовые операции над словарями
#30. Методы словаря, перебор элементов словаря в цикле
#31. Кортежи (tuple) и их методы
#32. Множества (set) и их методы
#33. Операции над множествами, сравнение множеств
#34. Генераторы множеств и генераторы словарей
#35. Функции: первое знакомство, определение def и их вызов
#36. Оператор return в функциях. Функциональное программирование
#37. Алгоритм Евклида для нахождения НОД
#38. Именованные аргументы. Фактические и формальные параметры
#39. Функции с произвольным числом параметров *args и **kwargs
#40. Операторы * и ** для упаковки и распаковки коллекций
#41. Рекурсивные функции
#42. Анонимные (lambda) функции
#43. Области видимости переменных. Ключевые слова global и nonlocal
#44. Замыкания в Python
#45. Введение в декораторы функций
#46. Декораторы с параметрами. Сохранение свойств декорируемых функций
#47. Импорт стандартных модулей. Команды import и from
#48. Импорт собственных модулей
#49. Установка сторонних модулей (pip install). Пакетная установка
#50. Пакеты (package) в Python. Вложенные пакеты
#51. Функция open. Чтение данных из файла
#52. Исключение FileNotFoundError и менеджер контекста (with) для файлов
#53. Запись данных в файл в текстовом и бинарном режимах
#54. Выражения генераторы
#55. Функция-генератор. Оператор yield
#56. Функция map. Примеры ее использования
#57. Функция filter для отбора значений итерируемых объектов
#58. Функция zip. Примеры использования
#59. Сортировка с помощью метода sort и функции sorted
#60. Аргумент key для сортировки коллекций по ключу
#61. Функции isinstance и type для проверки типов данных
#62. Функции all и any. Примеры их использования
#63. Расширенное представление чисел. Системы счисления
#64. Битовые операции И, ИЛИ, НЕ, XOR. Сдвиговые операторы
#65. Модуль random стандартной библиотеки
© 2021 Частичное или полное копирование информации с данного сайта для распространения на других ресурсах, в том числе и бумажных, строго запрещено. Все тексты и изображения являются собственностью сайта





































































