ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 04.12.2023
Просмотров: 135
Скачиваний: 1
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
53 ссылка funcList начнет указывать на него. Однако значение ссылки mainList при этом не изменится, и со значениями по ссылке mainList также ничего не произойдет (напомним, что операция среза создает новый объект, не изменяя старый).
Методы split и join
Строки имеют два полезных метода, которые пригодятся при работе со списками.
Метод split позволяет разрезать строку (string) на отдельные слова
(«токены»). В качестве разделителя может выступать пробел, символ табуляции или перевода строки. Этот метод не изменяет строку и возвращает список строк-токенов.
Например, если запустить такую программу, то будет напечатано ['red',
'green', 'blue'] (рис. 71). Количество разделителей между токенами не играет роли.
Рис. 71. Пример использования split
Чтобы научиться читать числа из одной строки, нужно научиться еще одной функции – map. Функция map принимает два параметра: первый – это функции, а второй – iterable элементов, к которому нужно применить эту функцию. В результате получается iterable с результатом применения функции к каждому элементу списка параметра.
Например, код на рис. 72 напечатает [3, 5, 4] – список с результатом применения функции len к списку ['red', 'green', 'blue'].
54
Рис. 72. Пример использования map
Метод split в сочетании с функцией map удобно использовать для считывания списка чисел, записанных в одну строку и разделенных пробелами. Такое считывание будет выглядеть так, как представлено на рис.
73.
Рис. 73. Пример использования split и map
Сначала осуществляется считывание строки, затем выполняется метод split, который создает список токенов, состоящих из цифр, а затем к каждому токену применяется функция int. В результате этого получается список цифр.
Метод join позволяет объединить iterable строк, используя ту строку, к которой он применен, в качестве разделителя. Например, код на рис. 74 выведет Veni, Vidi, Vici. Строка ', ' выступает в качестве разделителя, который будет вставляться после каждой строки из списка-параметра (кроме последней).
Рис. 74. Пример использования join
Метод join позволяет быстро и коротко выводить списки чисел.
Проблема в том, что он умеет принимать в качестве параметра только iterable
55 строк. Но с помощью функции map мы можем легко получить iterable из списка чисел, применив к каждому элементу функцию str. Вывод списка чисел numList, разделенных пробелами, будет выглядеть так, как представлено на рис. 75.
Рис. 75. Пример использования join
Полезные методы работы со списками
К переменным типа список можно применять методы, некоторые из которых приведем ниже.
Методы, не изменяющие список и возвращающие значение:
− count(x) – подсчитывает число вхождений значения x в список, работает за время O(N);
− index(x) – находит позицию первого вхождения значения x в список, работает за время O(N);
− index(x, from) – находит позицию первого вхождения значения x в список, начиная с позиции from, работает за время O(N).
Методы, не возвращающие значение, но изменяющие список:
− append(x) – добавляет значение x в конец списка;
− extend(otherList) – добавляет все содержимое списка otherList в конец списка. В отличие от операции + изменяет объект, к которому применен, а не создает новый;
− remove(x) – удаляет первое вхождение числа x в список, работает за время O(N);
− insert(index, x) – вставляет число x в список так, что оно оказывается на позиции index. Число, стоявшее на позиции index, и все числа правее него сдвигаются на один вправо. Работает за время O(N);
56
− reverse() – разворачивает список (меняет значение по ссылке, а не создает новый список как myList[::–1]). Работает за время O(N).
Методы, возвращающие значение и изменяющие список:
− pop() – возвращает последний элемент списка и удаляет его;
− pop(index) – возвращает элемент списка на позиции index и удаляет его, работает за время O(N).
Обработка списков
Рассмотрим такую задачу: необходимо выбрать все нечетные элементы списка myList и удалить их из него.
Попробуем решить задачу «в лоб»: просто будем перебирать все позиции в строке и, если на этой позиции стоит нечетное число, будем удалять его (рис. 76).
Рис. 76. Пример обработки списков
Такое решение будет работать неправильно в ситуации, когда в списке есть хоть одно нечетное число. Это связано с тем, что объект без названия с типом iterable и значением range(len(numbers)) сгенерируется один раз, когда интерпретатор впервые дойдет до этого места, и уже никогда не изменится.
Если в процессе мы выкинем из списка numbers хоть одно значение, то в процессе перебора всех индексов выйдем за пределы нашего списка. range, используемый в for, не будет менять свое значение, если в процессе работы изменились параметры функции range.
Решение можно переписать с помощью while (рис. 77).
57
Рис. 77. Пример обработки списков
Такое решение будет работать, но оно не очень эффективно. Каждый раз при удалении элемента нам придется совершать количество операций, пропорциональное длине списка. Итоговое количество операций в худшем случае будет пропорционально квадрату количества элементов в списке.
Если нет очень строгого ограничения в памяти, в задачах, где нужно удалить часть элементов списка, гораздо проще создать новый список, в который нужно добавлять только подходящие элементы (рис. 78).
Рис. 78. Пример обработки списков
Сложность такого решения пропорциональна длине исходного списка, что намного лучше.
1 2 3 4 5 6
Тема 2. Управление данными с использованием Python
2.1. Сортировка данных
Сортировка. Сравнение кортежей и списков
Сортировка списков
58
В программировании очень часто удобнее работать с отсортированными данными. В языке Питон существует возможность отсортировать списки двумя способами. Рассмотрим их на примере решения простой задачи о сортировке последовательности чисел по неубыванию (то есть как по возрастанию, но, возможно, с одинаковыми числами). Первый способ упорядочить его приведен на рис. 79.
Рис. 79. Пример сортировки списков
В этом примере используется метод sort, применяемый к списку. Этот метод изменяет содержимое списка – после применения метода sort элементы в списке становятся упорядоченными. Такой метод определен только для объектов типа список, его нельзя применить к кортежу, iterable или строке.
Второй способ состоит в применении функции sorted, которая возвращает отсортированный список, но не изменяет значение своего параметра (рис. 80).
Рис. 80. Пример сортировки списков
Использование функции sorted оправдано в случае, если исходные данные нужно сохранить в неизменном виде с какой-то целью. Например, sorted можно использовать внутри своей функции для создания отсортированной копии, чтобы не портить переданный нам список.
Чтобы отсортировать список по невозрастанию (убыванию), необходимо передать в метод или функцию именованный параметр reverse.
59
Например, это будет выглядеть как myList.sort(reverse=True) или sorted(myList, reverse=True).
Функция sorted может принимать в качестве параметра не только список, но и что угодно итерируемое: кортежи, iterable или строки (рис. 81).
Рис. 81. Пример сортировки списков
При этом sorted всегда возвращает список, т. е. вывод этой программы будет такой:
[1, 2, 3]
[0, 2, 4, 6, 8, 10]
['a', 'b', 'c']
Сортировку можно применять к спискам, все элементы которых сравнимы между собой. Обычно это однородные списки (состоящие из элементов одного типа) или, в редких случаях, целые и вещественные числа вперемешку.
Сравнение кортежей и списков
Два кортежа или списка можно также сравнивать между собой.
Например, выражение (1, 2, 3) < (2, 3, 4) будете истинным, а [1, 2, 3] < [1, 2] – ложным. Сравнение кортежей и списков происходит поэлементно, как и сравнение строк.
Как только на каких-то позициях кортежа или списка встретились различные элементы, взаимный порядок кортежей такой же, как у этих элементов.
Если же различий найдено не было, то меньше тот кортеж, который короче, как при сравнении строк.
Естественно, сравниваемые кортежи или списки должны содержать на соответствующих позициях сравнимые элементы. Попытка сравнить кортеж
(1, 2) с кортежем («Some text», 42) приведет к ошибке (а сравнение (1, 2) с
60
(42, «Some text») к ошибке не приведет). Обычно сравниваются кортежи, состоящие из элементов одинакового типа.
Это свойство кортежей можно использовать для решения сложных задач на сортировку.
Именованный параметр key
Пусть для каждого человека заданы его рост и имя. Необходимо упорядочить список людей по росту, а список людей одинакового роста – в алфавитном порядке. При решении этой задачи достаточно хранить описание каждого человека в виде кортежа, в котором первым элементом будет рост, а вторым – фамилия.
Рассмотрим пример: для каждого человека заданы рост и имя.
Необходимо упорядочить список людей по возрастанию роста, а список людей одинакового роста – в алфавитном порядке (рис. 82).
Рис. 82. Пример работы с данными
В этом примере удалось составить кортеж, который содержит параметры сравниваемых людей ровно в нужном порядке. Часто встречаются более сложные ситуации. Рассмотрим ту же задачу, но теперь список людей нужно упорядочить по убыванию их роста, а список людей одинакового роста по-прежнему должен быть упорядочен по алфавиту. Простое использование reversed=True не приведет к желаемому результату: имена людей одинакового роста будут стоять не в алфавитном порядке.
61
Здесь можно применить хитрость и превратить рост каждого человека в отрицательное число, модуль которого равен исходному росту. После этого список можно просто упорядочить по возрастанию – самые высокие люди будут иметь наименьший отрицательный «рост», по которому происходит сравнение в первую очередь. Перед выводом необходимо превратить рост обратно в положительное число (рис. 83).
Рис. 83. Пример работы с данными
Этот код малопонятен и плох.
Параметр key в функции sort
Для реализации нестандартных сортировок лучше не уродовать исходные данные, а использовать параметр key, передающийся в функцию сортировки.
Значением этого параметра должна быть функция, которая применяется к каждому элементу списка, и затем сравнение элементов происходит по значению этой функции (оно называется ключом).
Рассмотрим такой пример: необходимо упорядочить введённые строки по длине, а в случае равной длины оставить их в том порядке, в котором они шли во входном файле. Например, для входных строк ''c'', ''abb'', ''b'' правильным ответом должно быть ''c'', ''b'', ''abb'' (''c'' идет раньше ''b'', т. к. они имеют равную длину, а ''c'' стояло во входных данных раньше ''b'').
62
К счастью, сортировка, используемая в Питоне, обладает свойством устойчивости (stable), т. е. для элементов с равным ключом сохраняется их взаимный порядок.
Решение этой задачи представлено на рис. 84.
Рис. 84. Пример использования параметра key
В качестве еще одного примера рассмотрим задачу о сортировке точек на плоскости, заданных парой целых координат x и y по неубыванию расстояния от начала координат. В данном случае в качестве функции для генерации ключа, по которому будут сравниваться элементы, мы напишем свою функцию, которая будет возвращать квадрат расстояния от точки до начала координат. Квадрат расстояния мы используем для того, чтобы оставаться в целых числах и избавиться от необходимости считать квадратный корень (медленно и неточно) (рис. 85).
Рис. 85. Пример использования параметра key
Здесь каждый элемент списка – кортеж из двух чисел. Именно такой параметр принимает наша функция. Использование функции hypot из
63 библиотеки math (чтобы не писать свою функцию подсчета ключа) невозможно – она ожидает на вход два числовых параметра, а не кортеж.
Структуры в Питоне
Для хранения сложных записей во многих языках есть специальные типы данных, такие как struct в C++ или record в Паскале.
Переменная типа структура содержит в себе несколько именованных полей. Например, возвращаясь к задаче сортировки людей по убыванию роста, нам было бы удобно хранить описание каждого человека в виде структуры с двумя полями: рост и имя.
В чистом виде типа данных «структура» в стандарте языка Питон нет.
Есть несколько способов реализации аналога структур: namedtuple из библиотеки collections, использование словарей (будет рассмотрено в следующих лекциях) или использование классов в качестве структур.
Рассмотрим на примере последний способ.
Условие задачи: список людей нужно упорядочить по убыванию роста, но список людей одинакового роста должен быть представлен в алфавитном порядке. Решение с использованием классов в качестве структур будет выглядеть так, как представлено на рис. 86.
Рис. 86. Пример использования структур
64
Для того чтобы пользоваться классами как структурами, мы создаем новый тип данных Man. В описании класса мы перечисляем имена всех полей и их значения по умолчанию.
В дальнейшем мы можем создавать объекты класса Man (это делается строкой man = Man()), которые сначала инициализируют свои поля значениями по умолчанию. Доступ к полям класса осуществляется через точку.
Функция сравнения принимает объект класса и генерирует ключ, по которому эти объекты будут сравниваться при сортировке.
Использование структур для описания сложных объектов намного предпочтительнее, чем использование кортежей. При количестве параметров больше двух использование кортежей запутывает читателя и писателя кода, т. к. совершенно невозможно понять, что хранится в badNamedTuple[13], и легко понять, что хранится в goodNamedStruct.goodNamedField.
Лямбда-функции
В ряде случаев функции, используемые для получения ключа сортировки, так просты, что хочется не оформлять их стандартным образом, а написать прямо на месте и даже не давать им имени.
Это можно осуществить с помощью лямбда-функций, которые могут заменить собой функции, содержащие в своем теле только оператор return.
Запись лямбда-функции, возводящей число в квадрат, может выглядеть так, как представлено на рис. 87.
Рис. 87. Пример использования лямбда-функции
Это эквивалентно привычной записи функции (рис. 88).