Практика · Цикли¶
Лекція: lecture.html · Тест: quiz.html · ДЗ: homework.md
Наскрізний приклад той самий, що в лекції, — кошик покупок. Тут ми руками зробимо все, про що йшлося:
- обійдемо список і накопичимо суму — і звіримо її з вбудованою
sum(); - порахуємо й знайдемо те, що треба, — з
breakі зelse; - обійдемо рядок, множину й словник трьома способами;
- подивимось, чому
rangeне список, і зберемо чек черезenumerateіzip; - напишемо
whileі побачимо, чого саме бракує нескінченному циклу; - зробимо таблицю множення вкладеними циклами;
- власноруч відтворимо пастку зі зміною списку під час обходу — і полагодимо її.
Кожен висновок перевіримо через assert: він мовчить, коли все правильно, і кричить, коли ні.
1 · Дані, з якими працюємо¶
Три структури: список назв, словник цін і список кількостей. Порядок у списках узгоджений — і саме на це ми потім спиратимемось у zip.
покупки = ["хліб", "молоко", "яблука", "мед", "сіль"]
ціни = {"хліб": 28.5, "молоко": 32.0, "яблука": 19.9, "мед": 145.0, "сіль": 12.0}
кількості = [2, 1, 3, 1, 1]
print("покупки :", покупки)
print("ціни :", ціни)
print("кількості:", кількості)
print("позицій у кошику:", len(покупки))
покупки : ['хліб', 'молоко', 'яблука', 'мед', 'сіль']
ціни : {'хліб': 28.5, 'молоко': 32.0, 'яблука': 19.9, 'мед': 145.0, 'сіль': 12.0}
кількості: [2, 1, 3, 1, 1]
позицій у кошику: 5
2 · Обхід списку: for не рахує, а бере елементи¶
Зверни увагу: жодного індексу. Змінна назва на кожному кроці перечіплюється на черговий елемент.
for назва in покупки:
print("у кошику:", назва)
# змінна циклу не зникає після циклу — вона тримає ОСТАННЄ значення
print("після циклу назва =", назва)
у кошику: хліб у кошику: молоко у кошику: яблука у кошику: мед у кошику: сіль після циклу назва = сіль
3 · Накопичення: сума цін¶
Накопичувач створюємо до циклу. Якби разом = 0 стояв із відступом, він обнулявся б на кожному кроці.
І одразу перевіримо себе вбудованою sum() — щоб побачити, що всередині неї нема магії, а є той самий цикл.
разом = 0
for ціна in ціни.values():
разом = разом + ціна # те саме, що разом += ціна
бібліотечна_сума = sum(ціни.values())
print("наш цикл дав :", round(разом, 2))
print("sum() дала :", round(бібліотечна_сума, 2))
assert round(разом, 2) == round(бібліотечна_сума, 2), "наш цикл розійшовся з sum()"
print("✅ збігається")
наш цикл дав : 237.4 sum() дала : 237.4 ✅ збігається
4 · Підрахунок: скільки позицій дорожчі за 30 грн¶
Лічильник — той самий накопичувач, тільки додаємо не ціну, а одиницю. Умова всередині циклу — це if із теми 11.
дорогих = 0
for назва, ціна in ціни.items():
if ціна > 30:
дорогих += 1
print("дорога позиція:", назва, "—", ціна)
print("усього дорожчих за 30:", дорогих)
assert дорогих == 2, "мали знайтися рівно дві: молоко і мед"
print("✅ рахунок правильний")
дорога позиція: молоко — 32.0 дорога позиція: мед — 145.0 усього дорожчих за 30: 2 ✅ рахунок правильний
5 · Пошук: break і else¶
break виходить із циклу, щойно знайшли. Гілка else виконається лише тоді, коли break не спрацював — тобто цикл дійшов до кінця сам.
Запустимо той самий пошук двічі: для наявного товару й для відсутнього.
print("шукаємо два різні товари одним і тим самим кодом\n")
# перший пошук — товар є
шукане = "мед"
for назва in покупки:
print(" перевіряю:", назва)
if назва == шукане:
print(" ЗНАЙШОВ:", назва)
знайдено_мед = True
break
else:
знайдено_мед = False
print(" немає такого в кошику")
print()
# другий пошук — товару немає
шукане = "сир"
for назва in покупки:
print(" перевіряю:", назва)
if назва == шукане:
print(" ЗНАЙШОВ:", назва)
знайдено_сир = True
break
else:
знайдено_сир = False
print(" немає такого в кошику")
assert знайдено_мед is True, "мед у кошику є"
assert знайдено_сир is False, "сиру в кошику немає"
print("\n✅ обидва пошуки дали очікуваний результат")
шукаємо два різні товари одним і тим самим кодом перевіряю: хліб перевіряю: молоко перевіряю: яблука перевіряю: мед ЗНАЙШОВ: мед перевіряю: хліб перевіряю: молоко перевіряю: яблука перевіряю: мед перевіряю: сіль немає такого в кошику ✅ обидва пошуки дали очікуваний результат
6 · Порожній список — і чому else однаково спрацює¶
Найконтрінтуїтивніший випадок: тіло циклу не виконалось жодного разу, а гілка else виконалась. Бо її умова — не «чи були кроки», а «чи був break».
порожній_кошик = []
кроків = 0
for назва in порожній_кошик:
кроків += 1
print("цей рядок не виконається ніколи")
else:
print("else виконався, хоча кроків було:", кроків)
assert кроків == 0, "у порожньому списку тіло не виконується"
print("✅ else залежить від break, а не від кількості кроків")
else виконався, хоча кроків було: 0 ✅ else залежить від break, а не від кількості кроків
7 · Обхід рядка: збираємо слово навпаки¶
Рядок — послідовність символів, тому обходиться так само, як список. Порівняємо результат зі зрізом [::-1] із теми 05.
слово = "молоко"
навпаки = ""
for символ in слово:
навпаки = символ + навпаки # приклеюємо СПЕРЕДУ — саме це й перевертає слово
print("вихідне слово:", слово)
print("наш цикл дав :", навпаки)
print("зріз [::-1] :", слово[::-1])
assert навпаки == слово[::-1], "перевертання розійшлося зі зрізом"
print("✅ збігається")
вихідне слово: молоко наш цикл дав : околом зріз [::-1] : околом ✅ збігається
8 · Обхід словника: три різні вікна¶
for k in словник дає ключі, .values() — значення, .items() — пари, які заголовок циклу одразу розпаковує у дві змінні.
print("1) по ключах:")
for назва in ціни:
print(" ", назва)
print("\n2) по значеннях:")
for ціна in ціни.values():
print(" ", ціна)
print("\n3) парами через .items():")
for назва, ціна in ціни.items():
print(" ", назва, "->", ціна)
1) по ключах:
хліб
молоко
яблука
мед
сіль
2) по значеннях:
28.5
32.0
19.9
145.0
12.0
3) парами через .items():
хліб -> 28.5
молоко -> 32.0
яблука -> 19.9
мед -> 145.0
сіль -> 12.0
9 · Обхід множини: працює, але порядок не гарантований¶
Множина з теми 09 не впорядкована. Якщо потрібен стабільний вивід — обходь sorted().
знижки = {"мед", "сіль", "хліб"}
print("як віддає множина (порядок довільний):")
for назва in знижки:
print(" ", назва)
print("\nяк віддає sorted() — завжди однаково:")
for назва in sorted(знижки):
print(" ", назва)
assert sorted(знижки) == ["мед", "сіль", "хліб"], "sorted має давати сталий порядок"
print("\n✅ sorted() робить обхід передбачуваним")
як віддає множина (порядок довільний):
мед
сіль
хліб
як віддає sorted() — завжди однаково:
мед
сіль
хліб
✅ sorted() робить обхід передбачуваним
10 · range: три форми — і чому це не список¶
range зберігає лише старт, стоп і крок, а числа обчислює на льоту. Мільйон значень коштує стільки ж байтів, скільки й п'ять.
import sys
print("range(5) ->", list(range(5)))
print("range(2, 6) ->", list(range(2, 6)))
print("range(2, 12, 3) ->", list(range(2, 12, 3)))
print("range(10, 0, -2) ->", list(range(10, 0, -2)))
print("range(5, 1) ->", list(range(5, 1)), " <- порожній, а не помилка")
print("\nсам обʼєкт range:", range(5), "— не список")
print("байтів у range(1_000_000):", sys.getsizeof(range(1_000_000)))
print("байтів у list(range(1_000_000)):", sys.getsizeof(list(range(1_000_000))))
# формула кількості: ceil((стоп - старт) / крок)
import math
assert len(range(2, 12, 3)) == math.ceil((12 - 2) / 3) == 4
assert len(range(5, 1)) == 0, "старт правіше за стоп при кроці +1 -> порожньо"
print("\n✅ формула кількості справдилась")
range(5) -> [0, 1, 2, 3, 4] range(2, 6) -> [2, 3, 4, 5] range(2, 12, 3) -> [2, 5, 8, 11] range(10, 0, -2) -> [10, 8, 6, 4, 2] range(5, 1) -> [] <- порожній, а не помилка сам обʼєкт range: range(0, 5) — не список байтів у range(1_000_000): 48 байтів у list(range(1_000_000)): 8000056 ✅ формула кількості справдилась
11 · enumerate: пронумерований чек¶
Індекс потрібен лише для показу людині, тому беремо start=1. Він зсуває лічильник, а не початок обходу.
for номер, назва in enumerate(покупки, start=1):
print(номер, ".", назва, "—", ціни[назва], "грн")
# і те саме без enumerate, «по-старому» — щоб побачити, наскільки довше
номери = []
for i in range(len(покупки)):
номери.append((i + 1, покупки[i]))
через_enumerate = list(enumerate(покупки, start=1))
print("\nrange(len(...)) дав:", номери[:2], "...")
print("enumerate дав :", через_enumerate[:2], "...")
assert номери == через_enumerate, "два записи мають давати те саме"
print("✅ результат однаковий, але enumerate коротший і працює не лише зі списком")
1
. хліб — 28.5 грн 2 . молоко — 32.0 грн 3 . яблука — 19.9 грн 4 . мед — 145.0 грн 5 . сіль — 12.0 грн range(len(...)) дав: [(1, 'хліб'), (2, 'молоко')] ... enumerate дав : [(1, 'хліб'), (2, 'молоко')] ... ✅ результат однаковий, але enumerate коротший і працює не лише зі списком
12 · zip: рахуємо чек по двох списках¶
Назви в одному списку, кількості в другому. zip зчіплює їх позиційно.
до_сплати = 0
for назва, скільки in zip(покупки, кількості):
рядок_чека = ціни[назва] * скільки
до_сплати += рядок_чека
print(f"{назва:<8} × {скільки} = {рядок_чека:>6.2f}")
print("-" * 26)
print(f"{'до сплати':<8} {до_сплати:>8.2f}")
# перевірка руками: 28.5*2 + 32.0*1 + 19.9*3 + 145.0*1 + 12.0*1
вручну = 28.5 * 2 + 32.0 * 1 + 19.9 * 3 + 145.0 * 1 + 12.0 * 1
assert round(до_сплати, 2) == round(вручну, 2) == 305.7
print("✅ чек збігається з ручним підрахунком")
хліб × 2 = 57.00 молоко × 1 = 32.00 яблука × 3 = 59.70 мед × 1 = 145.00 сіль × 1 = 12.00 -------------------------- до сплати 305.70 ✅ чек збігається з ручним підрахунком
13 · zip мовчки обрізає по найкоротшому¶
Найтихіша пастка теми: якщо списки різної довжини, зайві елементи просто не потраплять у цикл. Ані помилки, ані попередження.
неповні_кількості = [2, 1, 3, 1] # на один менше, ніж покупок
пари = []
for назва, скільки in zip(покупки, неповні_кількості):
пари.append(назва)
print("покупок :", len(покупки))
print("кількостей :", len(неповні_кількості))
print("пар утворилось :", len(пари))
print("хто НЕ потрапив :", set(покупки) - set(пари))
assert len(пари) == 4, "zip зупиняється на найкоротшому"
assert "сіль" not in пари, "сіль лишилась без пари й не потрапила в цикл"
print("✅ пастка відтворена: обхід тихо обрізався")
покупок :
5
кількостей : 4
пар утворилось : 4
хто НЕ потрапив : {'сіль'}
✅ пастка відтворена: обхід тихо обрізався
Якщо різна довжина — це помилка в даних, а не задум, у Python 3.10+ можна попросити явну перевірку через strict=True. Тоді замість мовчання буде ValueError.
try:
for назва, скільки in zip(покупки, неповні_кількості, strict=True):
pass
except ValueError as помилка:
print("ValueError:", помилка)
спрацювало = True
assert спрацювало, "strict=True мав кинути ValueError"
print("✅ strict=True перетворює тихий баг на гучну помилку")
ValueError: zip() argument 2 is shorter than argument 1 ✅ strict=True перетворює тихий баг на гучну помилку
14 · while: кроків невідомо наперед¶
Купуємо найдешевшу позицію, поки в гаманці вистачає грошей. Скільки разів це вийде — заздалегідь ми не знаємо, тому for тут не підходить.
import math
гаманець = 100.0
найдешевше = min(ціни.values())
куплено = 0
while гаманець >= найдешевше:
гаманець -= найдешевше
куплено += 1
print(f"куплено {куплено}-ту пачку, у гаманці лишилось {гаманець:.2f}")
print("\nнайдешевша позиція коштує:", найдешевше)
print("куплено всього :", куплено)
print("залишок :", round(гаманець, 2))
assert куплено == math.floor(100.0 / найдешевше) == 8
assert гаманець < найдешевше, "цикл вийшов саме тоді, коли умова стала хибною"
print("✅ кількість кроків збіглася з формулою")
куплено 1-ту пачку, у гаманці лишилось 88.00 куплено 2-ту пачку, у гаманці лишилось 76.00 куплено 3-ту пачку, у гаманці лишилось 64.00 куплено 4-ту пачку, у гаманці лишилось 52.00 куплено 5-ту пачку, у гаманці лишилось 40.00 куплено 6-ту пачку, у гаманці лишилось 28.00 куплено 7-ту пачку, у гаманці лишилось 16.00 куплено 8-ту пачку, у гаманці лишилось 4.00 найдешевша позиція коштує: 12.0 куплено всього : 8 залишок : 4.0 ✅ кількість кроків збіглася з формулою
15 · Чого бракує нескінченному циклу¶
Запускати справжній нескінченний цикл у зошиті не варто — він просто зависне. Тому зробимо те саме безпечно: додамо запобіжник і подивимось, що станеться, коли тіло не змінює умову.
гаманець = 100.0
крок = 0.0 # ← ось вона, помилка: тіло нічого не змінює
повторень = 0
ЗАПОБІЖНИК = 50
while гаманець > 0:
гаманець -= крок # виконується справно — і не змінює нічого
повторень += 1
if повторень >= ЗАПОБІЖНИК:
print("запобіжник спинив цикл після", повторень, "повторень")
break
print("гаманець після", повторень, "повторень:", гаманець)
print("умова гаманець > 0 усе ще:", гаманець > 0)
assert гаманець == 100.0, "значення не змінилось жодного разу"
assert повторень == ЗАПОБІЖНИК, "без запобіжника цикл крутився б вічно"
print("✅ саме так виглядає нескінченний цикл зсередини")
запобіжник спинив цикл після 50 повторень гаманець після 50 повторень: 100.0 умова гаманець > 0 усе ще: True ✅ саме так виглядає нескінченний цикл зсередини
16 · break проти continue на однакових даних¶
Один і той самий цикл, змінюється лише одне слово. Три різні відповіді — рівно ті самі, що в інтерактиві 4 лекції.
список_цін = [28.5, 32.0, 19.9, 145.0, 12.0]
# А · без керування — додаємо все
разом_pass = 0
for ціна in список_цін:
if ціна > 100:
pass # заглушка: нічого не робимо, і потік іде далі
разом_pass += ціна
# Б · continue — обриваємо ПОТОЧНИЙ крок, цикл живе далі
разом_continue = 0
for ціна in список_цін:
if ціна > 100:
continue
разом_continue += ціна
# В · break — виходимо з циклу назовсім
разом_break = 0
for ціна in список_цін:
if ціна > 100:
break
разом_break += ціна
print("pass ->", round(разом_pass, 2), " (додались усі пʼять)")
print("continue ->", round(разом_continue, 2), " (мед пропущено, сіль додано)")
print("break ->", round(разом_break, 2), " (на меді вийшли, солі не бачили)")
assert round(разом_pass, 2) == 237.4
assert round(разом_continue, 2) == 92.4
assert round(разом_break, 2) == 80.4
print("\n✅ усі три числа збіглися з лекцією")
pass -> 237.4 (додались усі пʼять) continue -> 92.4 (мед пропущено, сіль додано) break -> 80.4 (на меді вийшли, солі не бачили) ✅ усі три числа збіглися з лекцією
17 · Вкладені цикли: таблиця множення¶
Внутрішній цикл проходить повністю на кожен крок зовнішнього. Стеж за відступами: print() без аргументів належить зовнішньому циклу — він переводить рядок.
рядків, стовпців = 5, 5
таблиця = []
for рядок in range(1, рядків + 1):
рядок_чисел = []
for стовпець in range(1, стовпців + 1):
добуток = рядок * стовпець
рядок_чисел.append(добуток)
print(f"{добуток:>4}", end="")
print() # цей print — зовнішнього циклу, один раз на рядок
таблиця.append(рядок_чисел)
print("\nтіло внутрішнього циклу виконалось разів:", рядків * стовпців)
assert len(таблиця) == 5 and len(таблиця[0]) == 5
assert таблиця[2][3] == 3 * 4 == 12, "третій рядок, четвертий стовпець"
assert таблиця[4][4] == 25
print("✅ таблиця побудована правильно")
1 2 3 4 5 2 4 6 8 10 3 6 9 12 15 4 8 12 16 20 5 10 15 20 25 тіло внутрішнього циклу виконалось разів: 25 ✅ таблиця побудована правильно
18 · Пастка: зміна списку під час обходу¶
А тепер найдорожча помилка теми. У кошик випадково потрапило два сири — прибираємо їх прямо під час обходу. Помилки не буде. Відповідь буде неправильна.
кошик = ["хліб", "сир", "сир", "мед"]
print("до циклу :", кошик)
for назва in кошик:
if назва == "сир":
кошик.remove(назва)
print("після :", кошик)
print("сирів лишилось:", кошик.count("сир"))
assert кошик == ["хліб", "сир", "мед"], "саме такий неправильний результат і має вийти"
assert кошик.count("сир") == 1, "один сир проскочив повз обхід"
print("⚠️ один сир вижив — і жодної помилки при цьому не було")
до циклу : ['хліб', 'сир', 'сир', 'мед'] після : ['хліб', 'сир', 'мед'] сирів лишилось: 1 ⚠️ один сир вижив — і жодної помилки при цьому не було
Причина в прихованому лічильнику позиції. Після видалення елемента всі, хто стояв правіше, зсуваються вліво, а лічильник однаково йде вперед — і один елемент проскакує. Покажемо цей зсув явно.
кошик = ["хліб", "сир", "сир", "мед"]
i = 0
print("імітуємо те, що робить for усередині:\n")
while i < len(кошик):
назва = кошик[i]
print(f" i = {i}, беремо {назва!r}, список зараз {кошик}")
if назва == "сир":
кошик.remove(назва)
print(f" видалили -> список став {кошик}, а i все одно стане {i + 1}")
i += 1
print("\nрезультат:", кошик)
assert кошик == ["хліб", "сир", "мед"], "та сама неправильна відповідь, що й у справжньому for"
print("✅ ручна імітація дала точно те саме — отже, причина саме в лічильнику")
імітуємо те, що робить for усередині:
i = 0, беремо 'хліб', список зараз ['хліб', 'сир', 'сир', 'мед']
i = 1, беремо 'сир', список зараз ['хліб', 'сир', 'сир', 'мед']
видалили -> список став ['хліб', 'сир', 'мед'], а i все одно стане 2
i = 2, беремо 'мед', список зараз ['хліб', 'сир', 'мед']
результат: ['хліб', 'сир', 'мед']
✅ ручна імітація дала точно те саме — отже, причина саме в лічильнику
Як робити правильно¶
Три робочі способи. Найчастіший — обхід копії: копію ніхто не зсуває, бо змінюємо ми не її.
# спосіб 1 · обходимо копію, змінюємо оригінал
кошик = ["хліб", "сир", "сир", "мед"]
for назва in кошик[:]:
if назва == "сир":
кошик.remove(назва)
спосіб_1 = кошик
# спосіб 2 · збираємо новий список замість того, щоб псувати старий
кошик = ["хліб", "сир", "сир", "мед"]
залишок = []
for назва in кошик:
if назва != "сир":
залишок.append(назва)
спосіб_2 = залишок
# спосіб 3 · ідемо з кінця, коли потрібні саме індекси
кошик = ["хліб", "сир", "сир", "мед"]
for i in range(len(кошик) - 1, -1, -1):
if кошик[i] == "сир":
кошик.pop(i)
спосіб_3 = кошик
print("копія ->", спосіб_1)
print("новий ->", спосіб_2)
print("з кінця ->", спосіб_3)
assert спосіб_1 == спосіб_2 == спосіб_3 == ["хліб", "мед"]
print("✅ усі три способи прибрали обидва сири")
копія -> ['хліб', 'мед'] новий -> ['хліб', 'мед'] з кінця -> ['хліб', 'мед'] ✅ усі три способи прибрали обидва сири
Зі словником пастка гучніша: він не дає мовчки зіпсувати дані, а одразу кидає RuntimeError. Наступна клітинка навмисно падає — traceback тут і є навчальним матеріалом.
запаси = {"хліб": 0, "молоко": 3, "яблука": 0}
for назва, скільки in запаси.items():
if скільки == 0:
del запаси[назва] # ← RuntimeError: dictionary changed size during iteration
--------------------------------------------------------------------------- RuntimeError Traceback (most recent call last) Cell In[22], line 3 1 запаси = {"хліб": 0, "молоко": 3, "яблука": 0} ----> 3 for назва, скільки in запаси.items(): 4 if скільки == 0: 5 del запаси[назва] # ← RuntimeError: dictionary changed size during iteration RuntimeError: dictionary changed size during iteration
Ліки ті самі: обходь копію ключів. list(запаси) фіксує ключі до початку циклу.
запаси = {"хліб": 0, "молоко": 3, "яблука": 0}
for назва in list(запаси): # list() робить копію ключів ДО обходу
if запаси[назва] == 0:
del запаси[назва]
print("лишилось у запасах:", запаси)
assert запаси == {"молоко": 3}, "мали зникнути обидві нульові позиції"
print("✅ тепер словник змінюється безпечно")
лишилось у запасах: {'молоко': 3}
✅ тепер словник змінюється безпечно
19 · Ітератор одноразовий¶
zip і enumerate — не списки, а ітератори: вони пам'ятають позицію й уміють іти лише вперед. Другий обхід того самого об'єкта не дасть нічого, і помилки при цьому теж не буде.
пари = zip(покупки, кількості)
перший_прохід = 0
for назва, скільки in пари:
перший_прохід += 1
другий_прохід = 0
for назва, скільки in пари: # той самий обʼєкт, уже вичерпаний
другий_прохід += 1
print("кроків на першому проході:", перший_прохід)
print("кроків на другому проході:", другий_прохід)
assert перший_прохід == 5
assert другий_прохід == 0, "ітератор одноразовий — другий обхід порожній"
print("✅ пастка відтворена: помилки немає, кроків теж")
кроків на першому проході: 5 кроків на другому проході: 0 ✅ пастка відтворена: помилки немає, кроків теж
Побачити механіку зсередини можна руками: iter() просить ітератор, next() бере наступний елемент, а коли елементи скінчились — прилітає StopIteration. Саме його for ловить і тихо завершує цикл.
обхідник = iter(["хліб", "мед"])
print("сам обʼєкт:", обхідник)
print("next() ->", next(обхідник))
print("next() ->", next(обхідник))
try:
next(обхідник)
except StopIteration:
print("next() -> StopIteration: елементи скінчились")
кінець = True
assert кінець, "третій виклик next() мав дати StopIteration"
print("✅ ось що робить for на кожному кроці — і чому він завершується сам")
сам обʼєкт: <list_iterator object at 0x7f8143db72e0> next() -> хліб next() -> мед next() -> StopIteration: елементи скінчились ✅ ось що робить for на кожному кроці — і чому він завершується сам
Ліки прості: якщо результат потрібен більше одного разу, зафіксуй його списком.
пари = list(zip(покупки, кількості)) # список, а не ітератор
перший = 0
for _ in пари:
перший += 1
другий = 0
for _ in пари:
другий += 1
print("перший прохід:", перший, "· другий прохід:", другий)
assert перший == другий == 5, "список можна обходити скільки завгодно разів"
print("✅ список обходиться повторно, ітератор — ні")
перший прохід: 5 · другий прохід: 5 ✅ список обходиться повторно, ітератор — ні
Що далі — три рівні¶
🟢 Рівень 1¶
Візьми свій список покупок (щонайменше шість позицій) і словник цін до нього. Одним циклом
надрукуй пронумерований чек через enumerate(…, start=1), другим — порахуй суму.
Зроблено, якщо сума з твого циклу проходить assert round(разом, 2) == round(sum(ціни.values()), 2).
🟡 Рівень 2¶
Напиши цикл, який шукає в кошику найдорожчу позицію без вбудованої max(): заведи
змінні найдорожча_назва й найдорожча_ціна до циклу й оновлюй їх усередині.
Зроблено, якщо результат збігається з max(ціни, key=ціни.get), а на порожньому
словнику твій код не падає, а друкує «кошик порожній».
🔴 Рівень 3¶
Напиши цикл, який прибирає з кошика всі позиції, дорожчі за задану межу, не створюючи
нового списку — тобто змінюючи оригінал на місці. Перевір свій розвʼязок на списку, де
дорогі позиції стоять підряд (саме там ламається наївний обхід).
Зроблено, якщо для [10, 200, 300, 20] і межі 100 результат [10, 20], і ти
письмово пояснив, чому обхід копії або рух із кінця тут обовʼязковий.