Большинство программ работает не с отдельными переменными, а с набором переменных.
Например, программа может обрабатывать информацию об учащихся класса, считывая список
учащихся с клавиатуры или из файла, при этом изменение количества учащихся в классе
не должно требовать модификации исходного кода программы.
Раньше мы сталкивались с задачей обработки элементов последовательности,
например, вычисляя наибольший элемент последовательности.
Но при этом мы не сохраняли всю последовательность в памяти компьютера.
Онако, во многих задачах нужно именно сохранять всю последовательность, например,
если бы нам требовалось вывести все элементы последовательности
в возрастающем порядке («отсортировать последовательность»).
Для хранения таких данных можно использовать структуру данных,
называемую в питоне список (в большинстве же языков
программирования используется другой термин «массив»).
Список представляет собой последовательность элементов, пронумерованных
от 0, как символы в строке.
Список можно задать перечислением элементов списка в квадратных скобках, например, список можно задать так:
В списке primes — 6 элементов, а именно,
primes[0] == 2,
primes[1] == 3,
primes[2] == 5,
primes[3] == 7,
primes[4] == 11,
primes[5] == 13.
Список rainbow состоит из 7 элементов, каждый из которых
является строкой.
Также как и символы строки, элементы списка можно индексировать отрицательными
числами с конца, например,
primes[-1] == 13,
primes[-6] == 2.
Длину списка, то есть количество элементов в нем, можно узнать при помощи функции
len, например, len(A) == 6.
Рассмотрим несколько способов создания и считывания списков. Прежде всего можно создать
пустой список (не содержащий элементов, длины 0), в конец списка можно добавлять элементы
при помощи метода append.
Например, если программа получает на вход
количество элементов в списке n, а потом n элементов
списка по одному в отдельной строке, то организовать считывание списка можно так:
A = []
for i in range(int(input()):
A.append(int(input())
В этом примере создается пустой список, далее считывается количество элементов
в списке, затем по одному считываются элементы списка и добавляются в его конец.
Для списков целиком определены следующие операции: конкатенация списков
(добавление одного списка в конец другого) и повторение списков (умножение списка
на число). Например:
A = [1, 2, 3]
B = [4, 5]
C = A + B # [1, 2, 3, 4, 5]
D = B * 3 # [4, 5, 4, 5, 4, 5]
В результате список C будет равен [1, 2, 3, 4, 5], а список
D будет равен [4, 5, 4, 5, 4, 5].
Это позволяет по-другому организовать процесс считывания списков: сначала считать размер списка и создать список
из нужного числа элементов, затем организовать цикл по переменной i начиная
с числа 0 и внутри цикла считывается i-й элемент списка:
A = [0] * int(input())
for i in range(len(A)):
A[i] = int(input())
Вывести элементы списка A можно одной инструкцией print(A),
при этом будут выведены квадратные скобки вокруг элементов списка и запятые между
элементами списка.
Такой вывод неудобен, чаще требуется просто вывести все элементы списка в одну строку или по одному элементу в строке.
Приведем два примера, также отличающиеся организацией цикла:
for i in range(len(A)):
print(A[i])
Здесь в цикле меняется индекс элемента i, затем выводится элемент
списка с индексом i.
for elem in A:
print(elem, end = ' ')
В этом примере элементы списка выводятся в одну строку, разделенные пробелом,
при этом в цикле меняется не индекс элемента списка, а само значение переменной
(например, в цикле for elem in ['red', 'green', 'blue'] переменная
elem будет последовательно принимать значения 'red',
'green', 'blue'.
Методы split и join
Элементы списка могут вводиться по одному в строке, в этом случае строку можно
считать функцией input(). После этого можно использовать метод строки
split, возвращающий список строк, разрезав исходную строку
на части по пробелам. Пример:
A = input().split()
Если при запуске этой программы ввести строку 1 2 3, то список
A будет равен ['1', '2', '3'].
Обратите внимание, что список будет состоять из строк, а не из чисел.
Если хочется получить список именно из чисел, то можно затем элементы списка по одному преобразовать в числа:
for i in range(len(A)):
A[i] = int(A[i])
У метода split есть необязательный параметр, который
определяет, какая строка будет использоваться в качестве разделителя
между элементами списка.
Например, метод split('.') вернет список, полученный разрезанием исходной строки по символам
'.'.
Используя «обратные» методы можно вывести список при помощи однострочной команды.
Для этого используется метод строки join.
У этого метода один параметр: список строк.
В результате получается строка, полученная соединением элементов списка (которые переданы
в качестве параметра) в одну строку, при этом между элементами списка вставляется
разделитель, равный той строке, к которой применяется метод. Например программа
A = ['red', 'green', 'blue']
print(' '.join(A)) # -> red green blue
print(''.join(A)) # -> redgreenblue
print('***'.join(A)) # -> red***green***blue
выведет строки 'red green blue', redgreenblue
и red***green***blue.
Если же список состоит из чисел, то эта функция не сработает, программа упадёт.
О том, как с этим «бороться», будет дальше.
Ввод-вывод и ограничения
Во всех задачах этого листка программа получает на вход строку из целых чисел, разделенных пробелами.
В конце строки также могут быть пробелы.
Данную строку необходимо считать в список целых чисел.
Все задачи нужно решить без использования срезов, дополнительных списков, а также методов списков (если явно не указано обратное).
A: Четные индексы
Выведите все элементы списка с четными индексами
(то есть A[0], A[2], A[4], ...).
Достаточно типичная задача — обработка этих цветов по очереди.
Другой сюжет был в теме про циклы: даны два числа $a$ и $b$, нужно вывести числа последовательно от $a$ до $b$ с учётом того, что $b$ может быть меньше.
Типичное решение выгядит так:
if b > a:
for i in range(a, b + 1):
print(i)
else:
for i in range(a, b - 1, -1):
print(i)
Не самое плохое решение.
Но если вместо простого вывода там была бы обработка на несколько строчек, то их пришлось бы продублировать.
В питоне range — это тоже объект, его можно положить в переменную:
if b > a:
my_range = range(a, b + 1):
else:
my_range = range(a, b - 1, -1):
for i in my_range:
print(i)
# Ещё примеры
another_range = range(50, 10, -3)
for i in my_range: print(i**2, end=', ') # -> 0, 1, 4, 9, 16, 25, 36, 49, 64, 81,
lst = list(another_range)
print(lst) # -> [50, 47, 44, 41, 38, 35, 32, 29, 26, 23, 20, 17, 14, 11]
Объединяет эти объекты то, что из них можно последовательно извлекать элементы.
Такие объекты называется итерируемыми.
Их в питоне достаточно много: списки и кортежи позволяют итерироваться по их элементам,
строки позволяют итерироваться по отдельным буквам (вернее символам), из которых они состоят.
Открытые текстовые файлы позволяют итерироваться по строчкам.
Итерируемые объекты могут участвовать во многих конструкциях.
Например, при переборе элементов по одному:
for элементы in итерируемый_объект:
обработать(элемент)
или для сбора их всех в список:
list(итерируемый_объект)
Итак, итерируемые объекты — это такие объекты, из которых можно последовательно извлекать элементы.
C: Больше предыдущего
Дан список чисел. Выведите все элементы списка, которые больше предыдущего элемента.
1 5 2 4 3
5 4
IDE
Итерация по элементам и функция enumerate
Очень часто при последовательном обработке элементов списка нам не важны индексы.
В этом случае гораздо лучше вместо перебора индексов
A = [2, 4, 6, 8]
for i in range(len(A)):
print(A[i])
сразу делать перебор элементов списка.
A = [2, 4, 6, 8]
for el in A:
print(el)
Такой код легче читается (в нём меньше скобок и лишних переменных).
Кроме того, переменную el можно изменять, не модифицируя при этом сам список.
В случаях, когда вместе с элементами нужны также и их индексы, идеоматический подход в питоне выглядит так:
for i, color in enumerate(('red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'violet')):
print(i+1, '-th color of rainbow is ', color, sep='')
Функция enumerate возвращает пары из порядкового номера элемента начиная с нуля и собственно самого элемента.
В данном примере ещё лучше воспользоваться параметром start, тогда нумерация начнётся не с нуля, а с указанного в параметре start значения:
for i, color in enumerate(('red', 'orange', 'yellow', 'green', 'cyan', 'blue', 'violet'), start=1):
print(i, '-th color of rainbow is ', color, sep='')
Вот визуализация того, как это работает для превращения строки с числами в список чисел.
D: Первый положительный
Выведите индекс первого положительного элемента в данном списке.
Он обязательно есть в списке.
В этой задаче нельзя пользоваться инструкцией if. Элементы списка
нужно просматривать с начала до нахождения нужного элемента.
-1 0 1
2
1
0
IDE
E: Первый положительный - 2
Выведите индекс первого положительного элемента в данном списке.
Если такого элемента нет, программа должна вывести число -1.
В этой задаче нельзя пользоваться инструкцией if внутри цикла.
-1 0 1
2
0
-1
IDE
Бесконечность
Когда мы ищем что-то самое большое или самое маленькое, то часто удобно сначала положить в переменную что-то, что уж точно меньше (ну или больше), чем всё,
что нам может встретиться.
В питоне для этого можно использовать бесконечности:
$\infty = $ float('inf'), $-\infty = $ float('-inf')
x = float('inf')
x > 10000 # True
x > 1098465164864134681315 # True
x > 1e300 # True
Ещё один плюс бесконечностей в том, что их легко отличить от «обычных» чисел.
Если в переменной осталась бесконечность, значит ничего дельного мы не встретили.
F: Наибольший элемент
Дан список чисел. Выведите значение наибольшего элемента в списке, а затем
индекс этого элемента в списке. Если наибольших элементов несколько,
выведите индекс первого из них. Гарантируется, что в списке есть хотя бы один элемент.
1 2 3 2 1
3 2
IDE
Функции map, enumerate, zip и т.д.
Есть несколько полезных функций, которые умеют брать итерируемый объект или несколько объектов, и возвращать новый итерируемый объект, элементы которого преобразованы каким-нибудь образом.
Например, функция map берёт на вход какую-нибудь функцию и последовательно выдаёт элементы итерируемого объекта, к которым применили её применили.
Это всё кажется достаточно сложным, но это очень удобно и не особо сложно.
Например, список чисел можно получить из строки сразу:
Это работает так.
nums.split() берёт строку и нарезает её в список строк, получается ['12', '179', '90'];
map(int, input().split()) создаёт объект, который выдёргивает по одному элементы из этого списка строк и применяет к каждому функцию int
. В данном случае объект будет по одному возвращать строки-числа, превращённые в числа;
list(map(int, input().split())) наконец функция list выдёргивает все элементы из итерируемого объекта и собирает из них список.
Здесь в список сразу соберутся готовые числа.
На выходе получится список [12, 179, 90].
Если нужно считать список действительных чисел, то нужно заменить тип
int на тип float.
Также map удобно применять для вывода списка чисел в строку.
Обычный join здесь не поможет, так как умеет склеивать только строки.
Но map позволяет превратить что угодно в строки:
Функция zip берёт на вход несколько итерируемых объектов и возвращает пары (тройки и т.д.) из нулевых элементов, первых элементов и т.д.
Важно, что zip выдаст столько пар/троек, сколько в наименьшем списке/итерируемом объекте.
Дан список чисел. Определите, сколько в этом списке элементов, которые
больше двух своих соседей и выведите количество таких элементов.
1 0 1 0 1
1
IDE
H: Наименьший положительный
Выведите значение наименьшего из всех положительных элементов в списке.
Известно, что в списке есть хотя бы один положительный элемент, а значения
всех элементов списка по модулю не превосходят 1000.
5 -4 3 -2 1
1
IDE
Генераторы списков
Для создания списка, заполненного одинаковыми элементами, можно использовать
оператор повторения списка, например:
A = [0] * n
Для создания списков, заполненных по более сложным формулам можно использовать
генераторы списков: выражения, позволяющие заполнить список некоторой формулой.
Общий вид генератора следующий:
[ выражение for переменная in что-то_итерируемое]
где переменная — идентификатор некоторой
переменной, что-то_итерируемое — список значений,
который принимает данная переменная (как правило, полученный при помощи функции range),
выражение — некоторое выражение, которым будут заполнены
элементы списка, как правило, зависящее от использованной в генераторе переменной.
Вот несколько примеров использования генераторов.
Создать список, состоящий из n нулей можно и при помощи генератора:
A = [ 0 for i in range(n)]
Создать список, заполненный квадратами целых чисел можно так:
A = [ i ** 2 for i in range(n)]
Если нужно заполнить список квадратами чисел от 1 до n,
то можно изменить параметры функции range на
range(1, n + 1):
A = [ i ** 2 for i in range(1, n + 1)]
Вот так можно получить список, заполненный случайными
числами от 1 до 9 (используя функцию randint
из модуля random):
A = [ randint(1, 9) for i in range(n)]
А в этом примере список будет состоять из строк, считанных
со стандартного ввода: сначала нужно ввести число элементов
списка (это значение будет использовано в качестве аргумента
функции range), потом — заданное количество строк:
A = [ input() for i in range(int(input()))]
I: Ближайшее число
Дан список чисел и некоторое число.
Найдите в данном списке элемент, ближайший к заданному.
В первой строке заданы элементы списка (целые числа, не превосходящие по модулю \(10^9\)).
Гарантируется, что в списке есть хотя бы один элемент.
Во второй строке дано одно целое число \(x\), не превосходящее по модулю \(10^9\)
Выведите значение элемента списка, ближайшее к \(x\). Если таких чисел несколько, выведите первое из них.
6 5 4 2 1
3
4
1 2 4 5 6
3
2
1 2 3
2
2
IDE
J: Шеренга
Андрей перешёл в другую школу. На уроке физкультуры ему понадобилось определить своё место в строю.
Помогите ему это сделать.
Программа получает на вход невозрастающую последовательность натуральных чисел,
означающих рост каждого человека в строю. После этого вводится число X — рост Андрея.
Все числа во входных данных натуральные и не превышают 200.
Выведите номер, под которым Андрей должен встать в строй. Если в строю есть люди с одинаковым ростом,
таким же, как у Андрея, то он должен встать после них.
В этой задаче нельзя использовать цикл for, инструкцию break, инструкцию if. Задача решается одним циклом while.
165 163 160 160 157 157 155 154
162
3
165 163 160 160 157 157 155 154
160
5
IDE
Полезные хаки при обработке списков
Довольно часто бывает нужно найти во входных данных какое-нибудь особое значение, но мы не уверены, что там такое есть.
Грубо говоря, программа будет выглядеть так:
i = 0
while i < len(данные) and данные[i] не_подходят:
i += 1
код_который_разбирает_случаи_нашлось_или_не_нашлось
либо так:
for i in range(len(данные)):
if данные[i] подходят:
...
код_который_разбирает_случаи_нашлось_или_не_нашлось
Общего в них то, что мы каждый раз проверяем и выход за границу списка, и условие.
А можно вместо этого добавить в конец списка особый элемент, который заведомо удовлетворяет условию.
Это позволяет избавиться от одной из проверок.
Да и разбирать случай, в котором нужные данные не нашлись, тоже часто становится не нужно.
Также добавлять специальные элементы в начало или конец списка часто удобно в случаях, когда нужно учитывать не ровно один элемент, а дополнительно что-то
про его соседей.
Эта добавка может позволить не разбирать дополнительно специальные случаи в начале или конце списка, когда соответствующего соседа у элемента просто нет.
K: Количество различных элементов
Дан список, упорядоченный по неубыванию элементов в нем.
Определите, сколько в нем различных элементов.
1 2 2 3 3 3
3
IDE
L: Наименьший нечетный
Выведите значение наименьшего нечетного элемента списка, а если в списке
нет нечетных элементов — выведите число 0.
В этой задаче вам ничего не известно о входных числах.
Решения, работающие только при каких-то предположениях о
размере входного числа, приниматься не будут.
В решении этой задачи нельзя использовать вспомогательный список.
0 1 2 3 4
1
2 4 6 8 10
0
IDE
M: Переставить в обратном порядке
Переставьте элементы данного списка в обратном порядке, затем выведите
элементы полученного списка.
Вам нужно не просто вывести элементы списка от первого до последнего,
а требуется изменить значения элементов самого списка, поменяв местами
A[0] c A[n - 1],
A[1] с A[n - 2], а затем вывести элементы списка подряд с начала.
В этой задаче нельзя использовать срезы и вспомогательный список.
1 2 3 4 5
5 4 3 2 1
IDE
N: Переставить соседние
Переставьте соседние элементы списка (A[0] c A[1],
A[2] c A[3] и т.д.).
Если элементов нечетное число, то последний элемент остается на своем месте.
В решении этой задачи нельзя использовать вспомогательный список.
1 2 3 4 5
2 1 4 3 5
IDE
O: Переставить min и max
Дан список. Поменяйте местами минимальный и максимальный элемент этого списка.
Гарантируется, что в списке есть хотя бы один элемент. Гарантируется, что в списке
ровно один элемент, равный минимальному, и ровно один элемент, равный максимальному.
3 4 5 2 1
3 4 1 2 5
IDE
P: Соседи одного знака
Дан список чисел. Если в нем есть два соседних элемента одного знака
(то есть оба положительных или оба отрицательных), выведите эти числа.
Если соседних элементов одного знака нет - не выводите ничего. Если таких пар соседей
несколько - выведите первую такую пару.
В этой задаче нужно использовать цикл while, нельзя использовать инструкции
break и инструкцию if внутри цикла.
-1 2 3 -1 -2
2 3
IDE
Q: Циклический сдвиг вправо
Циклически сдвиньте элементы списка вправо
(A[0] переходит на место A[1],
A[1] на место A[2], ...,
последний элемент переходит на место A[0]).
В этой задаче нельзя использовать срезы и вспомогательный список.
Гарантируется, что в списке есть хотя бы один элемент.
Задача должна быть решена с использованием минимально
возможного количества операций присваивания
(при этом операция присваивания вида a, b = b, a
считается за две операции).
1 2 3 4 5
5 1 2 3 4
IDE
Многократный проход по списку: квадратичные решения
В задачах R-W «наивное» решение потребует $n$ проходов по списку длины $n$.
В итоге будет выполнено порядка $n^2$ операций.
Такие решения называют квадратичными и пишут, что они имеют асимптотику $O(n^2)$.
И хотя каждую из этих задач можно решить более эффективно, в рамках этой темы этого не требуется.
R: Количество совпадающих пар
Дан список чисел. Посчитайте, сколько в нем пар элементов, равных друг другу.
Считается, что любые два элемента, равные друг другу образуют одну пару,
которую необходимо посчитать.
1 2 3 2 3
2
1 1 1 1 1
10
IDE
S: Два ближайших
Дан список целых чисел, содержащий как минимум два элемента.
Найдите в нем два ближайших элемента (то есть два элемента с минимальной
абсолютной разностью).
Программа должа вывести два числа: индексы двух элементов массива, абсолютная величина разности которых минимальна.
7 0 4 2 5 9
2 4
IDE
T: Количество различных элементов - 2
Дан список. Посчитайте, сколько в нем различных элементов, не изменяя самого списка,
не используя дополнительные списки (или строки) и срезы.
3 2 1 2 3
3
IDE
U: Медиана
В списке — нечетное число элементов, при этом все элементы различны. Найдите
медиану списка: элемент, который стоял бы ровно посередине списка, если список отсортировать.
При решении этой задачи нельзя модифицировать данный список (в том числе и сортировать его),
использовать вспомогательные списки.
Программа должна вывести единственное число — значение медианного элемента в списке.
6 1 9 2 3 4 8
4
IDE
V: Уникальные элементы
Дан список. Выведите те его элементы, которые встречаются в списке только один раз.
Элементы нужно выводить в том порядке, в котором они встречаются в списке.
В этой задаче нельзя модицифицировать список, использовать
вспомогательные списки, строки, срезы.
1 2 2 3 3 3
1
IDE
W: Самое частое число
Дан список. Не изменяя его и не используя дополнительные списки,
определите, какое число в этом списке встречается чаще всего.
Если таких чисел несколько, выведите любое из них.
1 2 3 2 3 3
3
IDE
X: Числа \(k\)-боначчи
Назовем последовательность чисел последовательностью \(k\)-боначчи, если
каждый элемент этой последовательности является суммой \(k\) предыдущих
членов последовательности. В частности, последовательность \(2\)-боначчи
является последовательностью Фибоначчи.
Более формально, \(i-й\) элемент последовательности \(k_i\) равен
1, если \(0\le i\le k - 1\) и равен сумме \(k\) предыдущих членов последовательности
\(k_{i-1} + k_{i-2} + ... + k_{i-k}\) при \(i\ge k\).
Даны два числа \(k\) и \(n\) (\(k\ge 2\), \(n\ge0\)). Вычислите
\(n\)-й член последовательности \(k\)-боначчи
\(k_n\).
3 6
17
100 0
1
IDE
Y: Кузнечики
\(N\) кузнечиков стоят в ряд. Для каждого кузнечика задана числовая характеристика — длина его прыжка.
Если длина прыжка кузнечика равна \(l\), то он за один прыжок перепрыгивает через \(l\) других кузнечиков.
Каждую секунду последний кузнечик прыгает к началу ряда, перепрыгивает через столько кузнечиков, чему равна
длина его прыжка, и становится между двумя другими кузнечиками.
В первой строке входных данных задана расстановка кузнечиков (длины их прыжков). Во второй строке входных данных
задано число секунд \(t\). Опеределите и выведите на экран расстановку кузнечиков через \(t\) секунд. Все длины прыжков —
натуральные числа, меньшие, чем число кузнечиков в ряду.
В этой задаче нельзя использовать срезы, методы, изменяющие количество элементов в списке.
1 2 3 4 2
2
4 1 2 2 3
IDE
Z: Ферзи
Известно, что на доске 8×8 можно расставить 8 ферзей так, чтобы они не били
друг друга. Вам дана расстановка 8 ферзей на доске, определите, есть ли среди них
пара бьющих друг друга.
Программа получает на вход восемь пар чисел, каждое число от 1 до 8 - координаты 8 ферзей.
Если ферзи не бьют друг друга, выведите слово NO, иначе выведите YES.
1 7
2 4
3 2
4 8
5 6
6 1
7 3
8 5
NO
1 8
2 7
3 6
4 5
5 4
6 3
7 2
8 1
YES
IDE
Под капотом списка
Устройство списка достаточно интересно.
По сути список — это массив ссылок на объекты-элементы списка.
При этом при добавлении элементов выделенная под ссылки память расширяется
в такой последовательности: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, …
и дальше по формуле l -> 1.125(l+1) + 6.
Дан массив из \(N\) (\(1 \le N \le 100000\)) целых чисел и число \(K\)
(\(|K| < 100000 \)).
Циклически сдвиньте массив на \(|K|\) элементов вправо, если \(K\) – положительное и влево, если отрицательное
число.
Программа получает на вход массив из \(N\) элементов, затем число \(K\).
Решение должно иметь сложность \(O(N)\), то есть не должно зависеть от \(K\).
Дополнительным списком пользоваться нельзя (и увеличивать размер списка тоже нельзя).
5 3 7 4 6
3
7 4 6 5 3
IDE
ZB★: Кратное трём (не про списки)
Дано число. В этом числе необходимо изменить одну цифру таким образом, чтобы новое число делилось на 3 и было бы максимально возможным.
В исходном числе нужно обязательно изменить одну цифру, даже если исходное число уже делилось на 3.
Программа получает на вход одно длинное натуральное число. Длина числа может достигать миллиона цифр.
Программа должна вывести другое натуральное число, удовлетворяющее условиям:
Новое число должно отличаться от данного ровно одной цифрой.
Новое число должно делиться на 3.
Новое число должно быть максимально возможным из всех таких чисел.