💻 Примеры кода

⚡ Приёмы практикума на одном экране

products = ["Bread", "Butter", "Milk"]
if "Milk" in products:              # защита от ValueError
    print(products.index("Milk"))

words = ["Py", "py", "Py"]
print(sorted(set(w.lower() for w in words)))   # уникальные без учёта регистра

for i in range(len(products) - 1, -1, -1):     # удаление — только с конца
    pass
ЗадачаИнструментПример
Найти элемент безопасноin + index()if x in lst: lst.index(x)
Уникальные значенияset() + sorted()sorted(set(words))
Элемент по критериюmax(key=...)max(words, key=len)
Удалить из списка на местеdel с концаrange(len(x)-1, -1, -1)
Символ ↔ кодord()/chr()chr(ord("A") + 1)
Топ-3 ошибки: звать index() без проверки in (ValueError) · терять порядок появления, когда достаточно set() без порядка · удалять элементы циклом с начала и сбивать индексы.

Практикум 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) — обратное преобразование. Зашифровать — значит прибавить сдвиг к каждому коду, расшифровать — вычесть тот же сдвиг. Это учебный аналог шифра Цезаря; для реальной защиты данных такой метод не подходит.

⚠️ Проверить по документации: таблицы кодов символов — ord() и chr().

Что делать дальше

Полные условия всех тринадцати задач — в заданиях, готовые решения, включая упаковку товаров по ящикам и отчёт по студентам, — в решениях.