Три рівні. Кожен наступний спирається на попередній, але братися можна за будь-який.
Пиши в окремому файлі homework_12.py або в новому зошиті — головне, щоб код
запускався й друкував результат.
Дозволено з бібліотек: math, sys, collections. Більше нічого не треба.
Функцій (тема 14) і спискових включень (тема 13) ми ще не проходили — усі завдання
розвʼязуються звичайними циклами, і саме так їх і треба розвʼязувати.
рівень 1Рівень 1 — База
1.1 · Свій кошик і свій чек
Створи власний список покупок (щонайменше шість позицій), словник цін до нього й список кількостей тієї самої довжини. Потім:
- одним циклом надрукуй пронумерований чек через
enumerate(…, start=1)— у форматі1. хліб × 2 = 57.00; - другим циклом накопич суму до сплати;
- третім циклом порахуй, скільки позицій дорожчі за середню ціну кошика.
Зроблено, якщо: чек друкується з нумерацією від 1, а наприкінці виконуються
assert round(до_сплати, 2) == round(sum(ціни[н] * к for н, к in zip(покупки, кількості)), 2)
assert len(покупки) == len(кількості) == len(ціни)
(перший assert — єдине місце в цьому ДЗ, де можна взяти генератор: він тут не для
розвʼязку, а для перевірки твого циклу незалежним способом).
1.2 · Три форми range без підглядання
Не запускаючи код, випиши на папірці, що дадуть ці пʼять викликів, і лише потім перевір себе:
range(4)
range(1, 5)
range(0, 10, 3)
range(10, 0, -3)
range(5, 5)
Зроблено, якщо: для кожного виклику ти надрукував list(...) і len(...), а для
двох останніх — письмово пояснив одним реченням, чому вийшло саме стільки значень.
Перевір формулу на всіх пʼятьох:
import math
for старт, стоп, крок in [(0, 4, 1), (1, 5, 1), (0, 10, 3), (10, 0, -3), (5, 5, 1)]:
очікувано = max(0, math.ceil((стоп - старт) / крок))
assert len(range(старт, стоп, крок)) == очікувано
1.3 · Слово навпаки й палiндром
Візьми будь-яке слово. Одним циклом збери його перевернуту версію (як у практиці),
другим — порахуй, скільки в ньому голосних ("аеєиіїоуюя").
Зроблено, якщо: виконуються
assert навпаки == слово[::-1]
assert голосних == sum(1 for с in слово if с in "аеєиіїоуюя")
і твій код правильно каже про "око" — що це паліндром, а про "мед" — що ні.
рівень 2Рівень 2 — Плюс
2.1 · Максимум без max()
Напиши цикл, який знаходить найдорожчу позицію в словнику цін: заведи
найдорожча_назва й найдорожча_ціна до циклу й оновлюй їх усередині.
Окремо подумай, чим ініціалізувати найдорожча_ціна. Нуль — погана ідея (а якщо всі
ціни відʼємні?). Варіант із None і перевіркою на першому кроці — хороша.
Зроблено, якщо: виконуються
assert найдорожча_назва == max(ціни, key=ціни.get)
assert найдорожча_ціна == max(ціни.values())
а на порожньому словнику твій код не падає з помилкою, а друкує «кошик порожній».
2.2 · Скільки разів трапляється кожна літера
Візьми речення щонайменше на 60 символів. Одним циклом по символах побудуй словник
«літера → кількість», використовуючи ідіому лічильник[с] = лічильник.get(с, 0) + 1.
Пробіли не рахуй.
Потім другим циклом знайди три найчастіші літери — без sorted і без Counter,
просто тричі пройшовши по словнику й щоразу забираючи максимум.
Зроблено, якщо: виконується
from collections import Counter
assert лічильник == dict(Counter(текст.replace(" ", "")))
assert трійка == [пара[0] for пара in Counter(текст.replace(" ", "")).most_common(3)]
і ти пояснив у коментарі, чому другий цикл довелось прогнати саме тричі, а не один раз.
2.3 · while, який точно завершиться
Гра «вгадай число» без введення з клавіатури: задумане число зашите в коді, а «гравець» щоразу називає середину поточного проміжку (це двійковий пошук). Цикл крутиться, поки не вгадав.
задумане = 73
низ, верх = 1, 100
спроб = 0
Зроблено, якщо: цикл завершується, друкує кожну спробу й наприкінці виконуються
assert спроба == задумане
assert спроб <= 7, "двійковий пошук на 100 числах не потребує більше 7 спроб"
і ти письмово відповів на два питання: (1) який саме рядок у тілі наближає умову до
хибності; (2) що станеться, якщо забути оновлювати низ і верх — і чому це та сама
помилка, що й нульовий крок в інтерактиві 6.
2.4 · Таблиця, яку читає людина
Побудуй вкладеними циклами таблицю множення 9×9 із шапкою: перший рядок і перший стовпець — це самі множники, відділені рискою.
Зроблено, якщо: таблиця вирівняна по колонках (підказка: f"{x:>4}"), а
збережений паралельно двовимірний список проходить
assert таблиця[0][0] == 1 and таблиця[8][8] == 81
assert таблиця[5][2] == 6 * 3
assert len(таблиця) == 9 and len(таблиця[3]) == 9
рівень 3Рівень 3 — Виклик
3.1 · Прибрати дорогі позиції на місці
Напиши цикл, який видаляє зі списку всі числа, більші за задану межу, змінюючи
оригінальний список — тобто не створюючи нового й не роблячи присвоєння
список = щось_інше. Ім'я має вказувати на той самий обʼєкт до й після.
Головна складність — там, де «дорогі» стоять підряд: саме на цьому ламається наївний обхід.
Зроблено, якщо: виконуються всі перевірки
дані = [10, 200, 300, 20]
той_самий = дані # другий ярлик на той самий обʼєкт
# ... твій код ...
assert дані == [10, 20]
assert той_самий == [10, 20], "ти створив новий список замість того, щоб змінити наявний"
assert id(дані) == id(той_самий)
і ще на трьох наборах: [500, 600, 700] → [], [1, 2, 3] → [1, 2, 3],
[] → []. Плюс письмове пояснення (2–3 речення), чому наївний варіант
for x in дані: if x > межа: дані.remove(x) дає для [10, 200, 300, 20] результат
[10, 300, 20].
3.2 · Свій enumerate і свій zip
Не використовуючи вбудовані enumerate і zip, збери такі самі результати
звичайними циклами:
мій_enumerate(послідовність, старт)— список кортежів(номер, елемент);мій_zip(а, б)— список кортежів, обрізаний по найкоротшому.
Функцій ми ще не вчили, тому просто напиши два блоки коду, які кладуть результат
у змінні мій_enumerate і мій_zip.
Зроблено, якщо: виконуються
покупки = ["хліб", "молоко", "яблука", "мед", "сіль"]
кількості = [2, 1, 3, 1]
assert мій_enumerate == list(enumerate(покупки, start=1))
assert мій_zip == list(zip(покупки, кількості))
assert len(мій_zip) == 4, "обрізати треба по коротшому, а не по довшому"
і ти пояснив, звідки в твоєму мій_zip береться число 4 — яким саме виразом ти його
отримав.
3.3 · Найдовша серія підряд
Дано список результатів матчів, наприклад
["П", "П", "Н", "П", "П", "П", "Л", "П"] («П» — перемога). Одним проходом знайди
найдовшу серію перемог підряд та її початковий індекс. Одним — означає, що список
можна обійти рівно один раз, без вкладених циклів.
Зроблено, якщо: виконуються
матчі = ["П", "П", "Н", "П", "П", "П", "Л", "П"]
assert найдовша_серія == 3
assert початок_серії == 3
а також на крайніх випадках: порожній список → серія 0; список суцільних «П» довжини 5 → серія 5 з початком 0; список без жодної «П» → серія 0. І поясни письмово, навіщо тут дві пари змінних — поточна серія й найкраща з побачених.
Підказки
- Накопичувач завжди створюють до циклу. Це стосується і суми, і лічильника, і «найкращого з побачених». Якщо всередині циклу є рядок, який щоразу повертає змінну до початкового стану, — він стоїть не там.
for i in range(len(...))майже завжди зайвий. Виняток один: коли індекс потрібен не для читання, а для запису в той самий список. У 3.1 це саме той випадок.- У 3.1 напрямок має значення. Якщо йти з кінця (
range(len(дані) - 1, -1, -1)), видалення елемента не зачіпає ті позиції, які ще не пройдені: зсуваються лише ті, що правіше, а ти туди вже не повернешся. Це найкоротший коректний розвʼязок. - У 2.3 обовʼязково друкуй кожну спробу. Якщо цикл раптом зависне, ти одразу побачиш, на якому проміжку він застряг, — і це буде відповіддю на друге питання.
- У 3.3 не намагайся оновлювати «найкращу» серію лише в кінці циклу. Оновлюй її на кожному кроці, де поточна серія стала довшою. Тоді випадок «найдовша серія в самому кінці списку» не потребуватиме окремої перевірки після циклу.
- Порожній вхід — перше, що варто перевірити. Порожній список, порожній словник,
range, який не дає жодного значення: у всіх трьох випадках тіло циклу не виконається жодного разу, і код має це пережити без помилки.