Практикум 7 закрепляет списки, кортежи и строки на девяти типовых операциях: от безопасного поиска элемента до простого шифра на кодах символов. Примеры идут от простого к сложному. Весь вывод в комментариях получен запуском кода.
Пример 1. Безопасный поиск через index()
list.index() возвращает позицию элемента, но выбрасывает ValueError, если элемента нет — поэтому перед вызовом всегда стоит проверка in.
# 01_safe_index.py
products = ["Bread", "Butter", "Cheese", "Milk", "Eggs"]
target = "Milk"
if target in products:
print(products.index(target) + 1)
else:
print(f"Товар {target} отсутствует")
# 4
missing = "Juice"
if missing in products:
print(products.index(missing) + 1)
else:
print(f"Товар {missing} отсутствует")
# Товар Juice отсутствует
try:
products.index("Juice")
except ValueError as error:
print("ValueError:", error)
# ValueError: 'Juice' is not in list
Что происходит: проверка in — это отдельный полный проход по списку, поэтому для очень больших списков это не бесплатно, но для учебных задач читаемость важнее микрооптимизации. Третий блок показывает, что происходит без проверки — то же самое можно было бы обернуть в try/except вместо if.
Пример 2. Уникальные слова без учёта регистра
Задача: получить список уникальных слов в алфавитном порядке, не различая "Python" и "python".
# 02_unique_words.py
text = "Python is a great language. Python is popular and Python is powerful."
words = [word.strip(".").lower() for word in text.split()]
unique_sorted = sorted(set(words))
print("Всего слов:", len(words), "уникальных:", len(unique_sorted))
print(unique_sorted)
# Всего слов: 12 уникальных: 8
# ['a', 'and', 'great', 'is', 'language', 'popular', 'powerful', 'python']
Почему так: .strip(".") убирает точку в конце предложения, .lower() приводит регистр к единому виду, set() убирает повторы, а sorted() возвращает список в алфавитном порядке — множество (set) само по себе порядок не гарантирует.
Пример 3. Элемент по критерию: max(key=len)
key у max()/min() задаёт, по какому значению сравнивать элементы, не меняя сами элементы.
# 03_max_key.py
sentence = "Programming in Python is both fun and educational"
words = sentence.split()
longest = max(words, key=len)
print("Самое длинное слово:", longest, "длина:", len(longest))
# Самое длинное слово: Programming длина: 11
Что происходит: max() вычисляет len(word) для каждого слова и возвращает то слово, для которого это значение оказалось наибольшим — не саму длину, а исходный элемент списка.
Пример 4. Перегруппировка списка с сохранением порядка
Задача — переместить элементы меньше 5 в конец списка, не потеряв взаимный порядок внутри каждой группы. Два прохода по списку решают это без ручного управления индексами.
# 04_stable_partition.py
numbers = [4, 7, 1, 6, 3, 8, 2]
big = [n for n in numbers if n >= 5]
small = [n for n in numbers if n < 5]
print("Было:", numbers)
print("Стало:", big + small)
# Было: [4, 7, 1, 6, 3, 8, 2]
# Стало: [7, 6, 8, 4, 1, 3, 2]
Почему так: каждый список comprehension проходит по numbers один раз и сохраняет исходный относительный порядок совпавших элементов. Склейка big + small ставит "большие" числа впереди, а "маленькие" — в конец, при этом внутри каждой группы порядок как в исходном списке.
Пример 5. Уникальные суммы пар чисел
Классическая связка «вложенный цикл для перебора пар + set для отсева дублей суммы».
# 05_pair_sums.py
values = [3, 5, 9, 3]
pair_sums = set()
for i in range(len(values)):
for j in range(i + 1, len(values)):
pair_sums.add(values[i] + values[j])
print(sorted(pair_sums, reverse=True))
# [14, 12, 8, 6]
Что происходит: внутренний цикл начинается с i + 1, чтобы не складывать элемент сам с собой и не считать одну и ту же пару дважды в другом порядке. set автоматически убирает повторяющиеся суммы — например, разные пары могут дать одинаковый результат.
Пример 6. Удаление элементов из списка — только с конца
Если удалять элементы по индексу, двигаясь с начала списка, индексы оставшихся элементов сдвигаются, и часть из них будет пропущена.
# 06_delete_from_end.py
transactions = [(100, -50, -30), (-200, 100, 50), (-50, -50)]
print("Суммы:", [sum(t) for t in transactions])
# Суммы: [20, -50, -100]
correct = transactions.copy()
for index in range(len(correct) - 1, -1, -1):
if sum(correct[index]) < 0:
del correct[index]
print("После очистки:", correct)
# После очистки: [(100, -50, -30)]
Почему так: range(len(correct) - 1, -1, -1) идёт от последнего индекса к нулевому. Когда элемент удаляется, все индексы после него меняются, но раз мы уже прошли их (двигаемся назад), это не сбивает следующие проверки. Проход с начала при del потребовал бы отдельного отслеживания сдвига.
Пример 7. Список внутри кортежа остаётся изменяемым
Кортеж запрещает менять свои элементы по индексу, но если один из элементов — список, сам список остаётся полностью изменяемым объектом.
# 07_mutable_in_tuple.py
employees = [
("Alice", ["Review documents", "Call clients"]),
("Bob", ["Check emails"]),
]
name, tasks = employees[0]
tasks.append("Prepare presentation")
print(employees)
# [('Alice', ['Review documents', 'Call clients', 'Prepare presentation']), ('Bob', ['Check emails'])]
try:
employees[0][0] = "Carol"
except TypeError as error:
print("TypeError:", error)
# TypeError: 'tuple' object does not support item assignment
Что происходит: tasks — обычная ссылка на тот же объект-список, что лежит внутри кортежа employees[0], поэтому tasks.append(...) меняет данные, видимые через employees. А вот employees[0][0] = "Carol" пытается заменить первый элемент кортежа — это запрещено независимо от того, что лежит внутри.
Пример 8. Проверка подпоследовательности указателем
Нужно проверить, встречаются ли символы искомой последовательности в списке в том же порядке, но не обязательно подряд.
# 08_subsequence.py
digits = ["3", "6", "3", "2", "9", "3", "9", "4", "3", "9", "0"]
sequence = "943"
position = 0
for digit in digits:
if position < len(sequence) and digit == sequence[position]:
position += 1
print("Последовательность найдена:", position == len(sequence))
# Последовательность найдена: True
sequence2 = "900" # в digits только один символ "0" (в самом конце) — второй "0" найти негде
position2 = 0
for digit in digits:
if position2 < len(sequence2) and digit == sequence2[position2]:
position2 += 1
print("Последовательность найдена:", position2 == len(sequence2))
# Последовательность найдена: False
Почему так: position — это указатель на следующий ожидаемый символ искомой последовательности. Он продвигается только когда текущий элемент списка совпал с ожидаемым, поэтому лишние элементы между совпадениями не мешают. Если к концу списка указатель не дошёл до конца последовательности — совпадений не хватило.
Пример 9. Коды символов: ord(), chr() и сдвиг
Простейший шифр сдвига: каждый символ заменяется на символ с кодом ASCII, увеличенным на фиксированное число.
# 09_ascii_cipher.py
shift = 3
message = "Python"
encoded = [ord(char) + shift for char in message]
print("Коды со сдвигом:", encoded)
# Коды со сдвигом: [83, 124, 119, 107, 114, 113]
decoded = "".join(chr(code - shift) for code in encoded)
print("Расшифровано:", decoded)
# Расшифровано: Python
Что происходит: ord(char) возвращает числовой код символа, chr(code) — обратное преобразование. Зашифровать — значит прибавить сдвиг к каждому коду, расшифровать — вычесть тот же сдвиг. Это учебный аналог шифра Цезаря; для реальной защиты данных такой метод не подходит.
Что делать дальше
Полные условия всех тринадцати задач — в заданиях, готовые решения, включая упаковку товаров по ящикам и отчёт по студентам, — в решениях.