Примеры повторений. Например: повторяющиеся задания в приложениях, подгрузка контента в конце страницы или ежедневная рутина.
Одна из важнейших глав в этом курсе – это изучение циклов, как и в программирование в принципе. Циклы - такая же важная вещь, как условия или функции, без которых современного программирования бы не было.
Зачем они нужны? Давайте посмотрим на определение.
Цикл – это набор действий, который повторяется по некоторому условию.
В жизни – это каждодневная рутина, или отдельные её элементы (такая, как, например, поход в спортзал)
В приложениях – это ежедневные цикличные напоминания, задачи и прочее.
В играх – это повторяющиеся квесты и прокачка персонажа, пока не будет достигнут максимум.
В программировании существует два вида циклов – это потенциально бесконечные и конечные циклы.
Это циклы, которые работают, пока выполняется некоторое условие. До тех пор, пока условие возвращает True, тело цикла будет выполняться.
Это может быть потенциально бесконечно: либо пока с выполнением что-то не случится или пока пользователь сам не прервёт цикл.
Это циклы, которые проходят по каждому элементу последовательности. До тех пор, пока есть элементы в последовательности, тело цикла будет выполняться.
Цикл завершится, когда элементы закончатся или если будет выполнено досрочное завершение, например, с помощью инструкции break.
В этой главе мы будем рассматривать потенциально бесконечные циклы while
Цикл while по своей структуре очень похож на if. Чтобы сделать какое-то действие вам необходимо, чтобы сначала выполнилось условие. Если условие не выполняется, то и действие тоже не будет выполнено.
Главная разница в том, что if проверяет условие только один раз. while работает так: до тех пор, пока условие выполняется, будет выполняться тело цикла (иными словами, пока результат условия - True)
Предположим, вы собрались сходить в магазин. Вы написали список, отправились в магазин.
И вот покупка всех товаров, пока все купленные позиции не совпадут со списком покупок, это и будет циклом.
Условие здесь: "Не все продукты куплены".
На этой картинке вы можете наблюдать, как while работает. Обратите внимание, что это очень похоже на условие, однако при завершении блока кода, выполняющимся по True, программа возвращается к "голове" цикла.
Предположим, мы решили написать программу, которая выводит числа с 0 до 4 включительно.
На примере рассмотрите структуру для написания цикла. Обратите внимание, что в условие стоит строгое "меньше", а не "меньше или равно". Потому что иначе вывелись бы числа с 0 до 5 включительно.
Создайте программу "while_loops1.py"
Возьмите код из примера. Измените его так, чтобы выводились числа от числа, введенного пользователем, до 0 в обратном порядке.
Пример выполнения можете посмотреть на картинке.
Попробуйте написать "условно-бесконечный цикл". Для этого напишите примерно следующее:
while True:
print("Цикл выполняется...")
И понаблюдайте, как он будет работать. Сможет ли программа сама завершить цикл? Можно ли закрыть программу, пока цикл работает?
Чтобы останавливать запущенные программой циклы принудительно, используйте сочетание клавиш, Ctrl+C (работает на всех циклах всех языков и систем). При этом у вас возникнет ошибка прерывания.
Владельцам MacOS: будьте осторожны, если вы напишете эту программу и запустите, IDLE может и вовсе зависнуть, такое бывает. В такой ситуации, завершайте программу принудительно.
Обратите внимание, что писать внутри цикла вы можете что угодно, даже другие условия, выражения и другие циклы (о таком мы еще поговорим). В том числе вы можете делать ввод данных по цикла (через команду input).
При этом, каждый круг цикла называют итерацией.
Напомню, что внутри блоков кода вы можете использовать любые команды и даже объявлять новые блоки кода (например, написав условие внутри цикла). Такое очень часто применяется в программах.
P.S. Картинка - это то, как ИИ видит эту доставучую шутку со слоном :)
Для примера, давайте напишем небольшую раздражающую программку, по мотивам детского "купи слона" :)
Перепишите эту программу себе и запустите. Владельцы MacOS могут не беспокоиться: команда input позволяет приостанавливать ход программы и это не вгоняет в "ступор" систему, так что и вылетать не будет.
Внимание, вопрос! Как вы думаете, зачем вообще могут пригодиться "бесконечные циклы"? Сначала дайте ответ сами, а потом прочитайте объяснение в конце главы.
Положим, если "потенциальный покупатель" решит все же приобрести слона, и нам нужно будет завершить программу. Как это сделать?
Фактически, можно прописать условие в "голову" цикла, это и будет служить остановкой для цикла.
P.S. Под "головой" подразумевается место, где написано whileОбратите внимание: в этой версии программы нужно "дважды" задать вопрос. Это так по той причине, что для запуска цикла требуется переменная answer. Если не объявить её заранее – это вызовет исключение (ошибку)
Но что, если нужно сделать остановку внутри цикла? Или если нужно будет сделать несколько проверок для остановки?
Для этого нужно воспользоваться специальной командой, break.
Break прерывает полностью останавливает цикл, даже если условие все еще может выполняться. Это называется прерыванием цикла.
Создайте программу "count_of_elephants"
Программа должна работать похожим образом на ту, что была примерами выше, про слона. Но теперь сделайте так, что каждый раз, когда пользователь будет в ответе писать "куплю", "купил", "покупать" и тд., программа будет считать, сколько слонов он купил. Когда пользователь скажет "хватит", остановите программу и скажите, сколько слонов пользователь таки купил.
Внимание: для этой программы нужно использовать оператор in, который проверяет содержание подстроки (части строки) в строке (но не только). Вот пример использования:
print('слон' in 'слон в посудной лавке') # выведет в консоль True
print('Слон' in 'слон в посудной лавке') # выведет в консоль False
Интересный момент, о котором забывают разработчики. Так как while имеет много общего с if (я делаю на этом особый акцент, так как существуют языки, где бывает так, что цикл будет выполняться, пока условие НЕ выполняется), разработчики добавили возможность использовать else поле цикла.
Это бывает особенно удобно, когда нужно сделать какое-то действие сразу после завершения цикла.
Особенностью является то, что else плохо дружит с break: если цикл завершился не тем, что условие цикла вернуло False, а прерыванием через break, то else выполняться не будет.
На примере слева (или сверху) вы видите пример программы, где пользователь пытается отгадать пароль. При этом у пользователя есть ограниченное количество попыток, чтобы этот пароль отгадать.
Если пароль будет отгадан, программа завершит свое выполнение. Но если пользователь потратит все попытки - то сработает else.
И последнее, что вам может понадобиться, это оператор continue. По смыслу, этот оператор прерывает не полностью цикл, а только текущую итерацию цикла и переходит к следующей.
На словах это объяснить несколько тяжело, поэтому перепишите к себе программу с примера, и понаблюдайте, как работает эта программа.
Предположим, что вы наливаете воду 5 гостям в стаканы, однако один стакан треснул.
Создайте программу "guess_number"
Программа загадывает случайное число от 1 до 100. Пользователь должен угадать загаданное число. Если пользователь угадывает число — цикл должен остановиться с поздравлением. Если пользователь не отгадал, то программа сообщает, больше ли загаданное число, чем то, что предположил пользователь, или меньше.
Подумайте, за какое минимальное количество попыток можно отгадать число. Добавьте это минимальное число + 1 в ограничение: если пользователь не отгадает за это количество попыток число, то нужно написать "Ты проиграл"
Иногда возникает потребность делать сложные обходы с помощью циклов. Чтобы лучше понять, что это и зачем нужно, я сразу начну с примера.
Предположим, вы работаете официантом. В зале, где вы работаете, стоит 5 столов, за каждым столом по 4 тарелки. Вам нужно обойти все столы и осмотреть все тарелки на наличие грязных, и убрать их. Сколько всего вы тарелок просмотрите?
Нехитрой математикой можно посчитать: 4 стола умножить на 5 тарелок, всего 20.
По сути, это и был пример вложенного цикла. Вы обходите все столы и проверяете все тарелки на них, причем каждый раз сначала. Первым циклом здесь будет обход столов. Вторым, вложенным, – осмотр тарелок на каждом столе.
Вот так это бы выглядело в программе. Обратите внимание на отступы, а также на результат программы, когда её запустите.
Всего эта программа сделает 20 обходов. Можете представить себе, сколько будет обходов, если придется осматривать еще и вилку с ножом у каждой тарелки? :) Причем на осмотр каждой тарелки приходится какое-то время.
В программах также требуется время на выполнение операций. Это называют временной сложностью.
Обратите внимание на \t. Если вы запустите программу, то увидите, что он делает. Подробнее про \t, \n и прочее мы рассмотрим в главе про строки
Создайте программу "mars_rover.py"
На марс был отправлен робот, который собирает данные карты местности. Каждый участок он номерует согласно таблице:
0 — Пустая зона
1 — Камни
2 — Песок
3 — Лёд
4 — Аномалия (нужно сообщить о её обнаружении)
Нужно подсчитать, сколько всего было найдено участков каждого типа.
В программу вводятся данные в каждую отдельную строку. Пока пользователь не напишет STOP, он может вводить данные.
После слова STOP выведите, сколько было участков каждого типа.
Вводите тип участка. (0 - пустая, 1 - камни, 2 - песок, 3 - лед, 4 - аномалия, STOP - завершить программу)
0
0
1
2
STOP
Пустых - 2, Камни - 1, Песок - 1, Лед - 0, Аномалии - 0
– Так, стоп. А что за временная сложность?
– А, это. В общем временная сложность - это когда мы считаем, сколько времени потребуется на выполнение программы.
Когда мы говорим о том, сколько раз цикл выполняется, мы имеем в виду, сколько шагов или операций программа должна сделать для выполнения задачи.
ВНИМАНИЕ! Следующий раздел включает элементы математики и может быть сложнее для понимания, особенно для тех, кто в математике не так силен. Тему, о которой пойдет речь, важно изучать, особенно если вы хотите лучше понимать, как писать эффективные программы или вы готовитесь сдавать экзамены/писать олимпиады.
Ничего страшного, если вы не поймете эту тему с первого раза. Просто рекомендую хотя бы попытаться это сделать.
Немного остановимся на этом пункте, поскольку понимание временной сложности очень важно для программиста.
Основная проблема - это то, что на каждое выполнение действий требуется время, а программисту должно быть важно, чтобы его программа выполнялась за наименьшее возможное время. Это не только потому, что пользователь будет долго ждать, например, обработки заказа в приложении на доставку, но и в целом потому, что если алгоритм выполняется слишком долго - его можно считать неэффективным.
Алгоритмы оценивают с помощью О-нотации. Эта нотация обозначает алгоритмическую сложность или асимптотику алгоритма. С помощью О-нотации мы можем сравнить разные алгоритмы и сравнить, насколько каждый из них эффективен по времени, но самое главное - это насколько медленнее они станут при возрастании количества данных.
Помните пример про официанта? Его О-нотация будет иметь вид O(n*m), где n – это количество итераций внешних циклов(обход столов), а m – это количество итераций внутренних циклов (то есть тарелок).
Для чуть большего понимания: обычный линейный код имеет сложность О(1), так как на каждую строку приходится 1 операция. Такую сложность называют константной. Она так называется, потому что независимо от количества введенных данных выполняется фиксированное количество операций. Например:
print("hello!")
Если при этом вы захотите запустить цикл на 10 кругов, то его сложность будет О(10), но если вы не знаете, сколько будет кругов цикла, то пишут О(n). Такую сложность называют линейной, так как количество операций линейно зависит от входных данных (сколько операций напишете - столько и будет).
n = int(input("Сколько кругов цикла? "))
i = 0
while i < n:
i += 1
Ну а вложенные уже имеют как раз сложность О(n*m)
n = int(input("Сколько кругов цикла 1? "))
m = int(input("Сколько кругов цикла 2? "))
i_n = 0
while i_n < n:
i_n += 1
i_m = 0
while i_m < m:
i_m += 1
При этом если m == n, то такая сложность будет квадратичной и будет иметь нотацию O(n^2).
График роста количества опираций, в зависимости от сложности. Наглядно, какой алгоритм самый эффективный и наоборот. По горизонтальной шкале - исходное количество данных.
Помните задачу, в которой пользователь отгадывает число? Если вы не поняли алгоритм поиска этого числа, то вот как это работает.
Предположим, мы отгадываем число от 0 до 10, машина загадала число 1.
Самая эффективная тактика - начать с середины.
Так как программа нам подсказывает, больше ли загаданное, чем то, что мы предположили, то ма продолжаем искать число, выбрав среднее в половине от 0 до 5
Ну и далее, когда 2 нам не подошло, мы выбираем одно из двух оставшихся.
В случае если чисел 100, придерживаемся той же тактики.
Как в этом случае посчитать количество шагов? Каждый раз мы сокращаем диапазон для поиска в два раза, а среднее количество попыток равно 3. И тут-то мы подключаем математику (это будет сложно!) и замечаем, что:
Для диапазона в 100 чисел попыток будет в среднем 6-7
Для диапазона в 1000 чисел попыток будет в среднем 9-10
И так далее. Такой прирост полностью описывает логарифмическая функция, log(2)n (читается как логарифм n по основанию 2), где n – это начальное количество чисел.
Соответственно сложность поиска числа по такому алгоритму будет логарифмической, и соответствовать нотации О(ln n) (натуральный логарифм по числу n). Внимание! Это общая запись сложности алгоритмов, которые используют логарифм, если говорят про конкретную формулу, то будут писать полностью.
Попробуйте увеличивать диапазоны и самостоятельно проверить это, а вот тут вы можете проверить значение логарифма по основанию 2 для вашего выбранного диапазона.
Вы можете заметить, что независимо от начального количества данных, количество попыток, которых вам нужно сделать, чтобы отгадать число, растет очень медленно. Но если вы попробуете напрямую перебирать числа по порядку, то сложность будет возрастать линейно.
Вообще весь этот алгоритм поиска, где вам нужно делить пополам, описывает алгоритм бинарного поиска, который очень часто помогает в поиске элементов в массиве. Но до этого мы еще дойдем.
– Блин, это все что-то капец как сложно
– Да, есть такое. Но если ты начнешь это понимать, то это сильно тебя продвинет как специалиста, да и на собеседованиях часто спрашивают. Возможно, если у тебя хорошо с математикой, это потом сыграет ключевую роль в сложных алгоритмах обработки данных в будущем.
– А практика?
– Практика будет отдельно, когда уже будут изучены все основы. Но к разговору про сложность мы еще вернемся. Как и про алгоритмы поиска и сортировки.
Ответ на вопрос про бесконечные циклы:
Ну, не то, чтобы циклы могут быть истинно бесконечными, конечно. Однако конструкция while True используется в интерфейсах, играх, микроконтроллерах, где нет явного выхода из программы и код программы запускается на неопределенный срок. В частности также используется в вебе: часто такие "псевдобесконечные циклы" ожидают запросы пользователя к сайту или серверу, и такой цикл работать должен постоянно.