Классика на циклах: FizzBuzz и таблица умножения
FizzBuzz и таблица умножения — два эталонных упражнения: на них проверяют циклы, остаток от деления, порядок условий и форматирование вывода.
Редакция Питоники
Есть две задачи, через которые проходят все: FizzBuzz — тест на циклы и условия, который дают на собеседованиях, и таблица умножения — тест на вложенные циклы и форматирование. Обе маленькие, и обе проверяют ровно то, чем программист пользуется ежедневно: остаток от деления, порядок условий и управление выводом. Разберём обе с ошибочными версиями по дороге — типовые ошибки здесь стоят половины баллов на собеседовании.
FizzBuzz: правила
Правила такие: печатаются числа от 1 до 15; вместо чисел, кратных трём, — слово Fizz, кратных пяти — Buzz, кратных и трём, и пяти, — FizzBuzz. Правильный ряд: 1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, FizzBuzz. Кратность проверяется остатком от деления: n % 3 == 0 значит «n делится на три без остатка».
Почему задача придумалась именно такой. На собеседовании по FizzBuzz проверяют не умение писать циклы — с этим у кандидата обычно всё в порядке, — а способность рассуждать: разложить правило на проверки, заметить, что одно условие перекрывает другое, и выбрать порядок. Именно на этом и сыплются чаще всего.
for n in range(1, 16):
if n % 3 == 0:
print('Fizz')
elif n % 5 == 0:
print('Buzz')
elif n % 15 == 0:
print('FizzBuzz')
else:
print(n)
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 Fizz
Посмотрите на последнюю строку: вместо FizzBuzz напечаталось Fizz. Пятнадцать делится на три, и первая же проверка перехватила число, не дав добраться до проверки на пятнадцать. Ветвь n % 15 == 0 жива, но до неё никто не доходит: кратные пятнадцати — подмножество кратных трём. Условия в цепочке if/elif работают по порядку, и порядок должен идти от самого частного случая к самому общему.
for n in range(1, 16):
if n % 15 == 0:
print('FizzBuzz')
elif n % 3 == 0:
print('Fizz')
elif n % 5 == 0:
print('Buzz')
else:
print(n)
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz
Теперь ряд закончился правильно. Сначала проверяется самый частный случай: если число делится на 15, оно делится и на 3, и на 5. Такое число заслуживает общего слова, а не частного. Поменяйте условия местами — и ошибка вернётся: порядок в цепочке if/elif — часть логики программы, а не вопрос оформления.
for n in range(1, 16):
word = ''
if n % 3 == 0:
word += 'Fizz'
if n % 5 == 0:
word += 'Buzz'
print(word or n)
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz
Второй подход вообще убирает конфликт условий: слово собирается из кусочков. Тройка добавляет Fizz, пятёрка — Buzz, а число пятнадцать получает оба кусочка по очереди. Здесь два независимых if вместо if/elif — число может заслужить оба слова сразу. Идиома print(word or n) подставляет число, когда слово осталось пустым: пустая строка ложна, и or отдаёт второй операнд.
Проверка делимости без знака процента
Знак % — привычный инструмент, но иногда полезно понимать, что под ним лежит. Делимость можно проверить и без него: если частное и остаток от деления согласованы, число делится нацело.
def divisible(a, b):
return a - (a // b) * b == 0
print(divisible(15, 3))
print(divisible(15, 5))
print(divisible(15, 7))
True True False
Формула a - (a // b) * b восстанавливает остаток от деления: целочисленное деление отбрасывает дробную часть, умножение возвращает отброшенное, вычитание даёт разницу. Пятнадцать делится на три и на пять, но не на семь. В FizzBuzz такую проверку писать не надо — % короче и понятнее, — но понимать её полезно, когда попадается код без знака процента.
FizzBuzz в виде функции
Как только задача начинает повторяться, её стоит завернуть в функцию: границу можно менять, а правила остаются в одном месте. Заодно получается место, где удобно проверить результат.
def fizzbuzz(n):
result = []
for i in range(1, n + 1):
word = ''
if i % 3 == 0:
word += 'Fizz'
if i % 5 == 0:
word += 'Buzz'
result.append(word or i)
return result
print(fizzbuzz(15))
[1, 2, 'Fizz', 4, 'Buzz', 'Fizz', 7, 8, 'Fizz', 'Buzz', 11, 'Fizz', 13, 14, 'FizzBuzz']
Функция возвращает список: числа остаются числами, слова — строками. Побочный плюс такой формы — результат можно проверить: assert fizzbuzz(15)[2] == 'Fizz', и тест поймает ошибку в правилах. Печатать внутри функции не стоит: тогда её нельзя ни проверить, ни переиспользовать.
assert fizzbuzz(15)[2] == 'Fizz' # третье число — Fizz
assert fizzbuzz(15)[14] == 'FizzBuzz' # пятнадцатое — FizzBuzz
print('правила выполняются')
Таблица умножения: один столбец
n = 7
for i in range(1, 10):
print(f'{n} x {i} = {n * i}')
7 x 1 = 7 7 x 2 = 14 7 x 3 = 21 7 x 4 = 28 7 x 5 = 35 7 x 6 = 42 7 x 7 = 49 7 x 8 = 56 7 x 9 = 63
Один цикл — один столбец таблицы: range(1, 10) перебирает множители от единицы до девяти, а f-строка собирает каждую строчку из текста и произведения. Подстановка значений в текст — отдельная тема курса строк; здесь достаточно того, что выражение в фигурных скобках вычисляется и печатается. Осталось превратить столбец в таблицу.
Полная таблица: цикл в цикле
for i in range(1, 10):
for j in range(1, 10):
print(f'{i * j:>3}', end='')
print()
1 2 3 4 5 6 7 8 9 2 4 6 8 10 12 14 16 18 3 6 9 12 15 18 21 24 27 4 8 12 16 20 24 28 32 36 5 10 15 20 25 30 35 40 45 6 12 18 24 30 36 42 48 54 7 14 21 28 35 42 49 56 63 8 16 24 32 40 48 56 64 72 9 18 27 36 45 54 63 72 81
Внешний цикл ходит по строкам, внутренний — по столбцам: девять на девять, восемьдесят одна итерация внутреннего тела. end='' отменяет перевод строки после каждой ячейки, поэтому строка таблицы собирается из девяти ячеек подряд, а пустой print() после внутреннего цикла закрывает строку. Спецификация i * j:>3 прижимает произведение вправо в колонке шириной три: однозначные числа получают два пробела перед собой, двузначные — один, и столбцы стоят ровно. Вложенные циклы разбирались в своём уроке, а выравнивание f-строк — в курсе строк.
Таблица, собранная из готовых строк
Собирать строку ячейка за ячейкой — рабочий способ, но в Python есть приём лучше: генератор списка плюс join. Каждая ячейка превращается в строку, список склеивается разделителем, и вся строка готова.
for i in range(1, 4):
row = ' '.join(f'{i * j:>4}' for j in range(1, 4))
print(row)
1 2 3 2 4 6 3 6 9
Тот же результат, но без end='' и без ручного print() в конце. Генератор внутри join отдаёт строки по одной, join склеивает их пробелами. Почему не просто str(i * j)? Без спецификации :>4 столбцы поедут: однозначное число займёт один символ, двузначное — два, и цифры встанут не друг под другом. Ширина в f-строке и есть то, что делает таблицу таблицей.
for i in range(1, 4):
row = ' '.join(str(i * j) for j in range(1, 4))
print(row)
1 2 3 2 4 6 3 6 9
Без ширины столбцы «плавают»: первая строка из одно- и двухзначных чисел, вторая — из двух- и трёхзначных. Для математической таблицы это выглядит небрежно, и это как раз тот случай, где важна не логика, а вид. Один форматный спецификатор — и таблица встаёт ровно.
for i in range(1, 4):
row = ' | '.join(f'{i} * {j} = {i * j}' for j in range(1, 4))
print(row)
1 * 1 = 1 | 1 * 2 = 2 | 1 * 3 = 3 2 * 1 = 2 | 2 * 2 = 4 | 2 * 3 = 6 3 * 1 = 3 | 3 * 2 = 6 | 3 * 3 = 9
Вариант для школьной таблицы, где нужно видеть само действие. Разделитель вынесен в строку, ячейки склеиваются им. Такой формат перестаёт быть числовой сеткой, но становится понятной таблицей умножения в привычном виде.
for i in range(1, 6):
print('*' * i)
* ** *** **** *****
Ещё одна классика на две строки: строка из i звёздочек печатается умножением строки на число — '*' * 3 даёт три звёздочки подряд. Цикл по количеству плюс повторение строки — так собираются простые картинки из символов и заготовки прогресс-баров.
Сумма цифр: остаток и целочисленное деление
n = 493
total = 0
while n > 0:
total += n % 10
n = n // 10
print(total)
16
Остаток от деления на десять отрезает последнюю цифру: 493 % 10 — это три. Целочисленное деление n // 10 укорачивает число: 493 становится 49. Число отдало по цифре за шаг — три, девять, четыре — и аккумулятор насчитал шестнадцать. while останавливается, когда цифры кончились; снова накопление, знакомое по прошлому уроку.
| Приём | Где встретился |
|---|---|
| n % 3 == 0 | Кратность: остаток от деления нулевой |
| if / elif по порядку | От частного условия к общему |
| word or n | Пустое значение заменяется числом |
| f'{x:>3}' | Ровные колонки таблицы |
| end='' и print() | Сборка строки из ячеек |
print(f'{5:>3}') # вправо: ' 5'
print(f'{5:<3}') # влево: '5 '
print(f'{5:^3}') # по центру: ' 5 '
Сначала предскажи ответ в голове — это главный навык программиста.
for n in range(1, 6):
if n % 2 == 0:
print(n)
else:
print('нечёт')
s = 0
n = 25
while n > 0:
s += n % 10
n = n // 10
print(s)
word = ''
if 4 % 2 == 0:
word += 'чёт'
print(word or 'нет')
1. Почему в FizzBuzz проверку n % 15 == 0 ставят первой?
2. Что напечатает print(word or n), если word осталась пустой строкой?
3. Что делает аргумент end='' в print?
4. Сколько раз выполнится тело внутреннего цикла в таблице девять на девять?
5. Что вернёт выражение 15 % 3?
6. Зачем в FizzBuzz условие n % 15 == 0 проверяют раньше остальных?
7. Почему для сборки строки таблицы удобно join, а не end=''?
8. Что вернёт divisible(15, 7), если divisible считает кратность через a - (a // b) * b == 0?
Напечатай числа от 1 до 15 по правилам FizzBuzz: кратные трём — Fizz, кратные пяти — Buzz, кратные пятнадцати — FizzBuzz, остальные — само число. Собери слово из кусочков и выведи через word or n.
Как решается FizzBuzz на Python?
Циклом по числам и проверкой остатка: n % 3 == 0 — Fizz, n % 5 == 0 — Buzz, n % 15 == 0 — FizzBuzz. Проверку кратности пятнадцати ставят первой, иначе её перехватит проверка тройки. Изящный вариант — собирать слово из кусочков и выводить word or n.
Урок 2: rangeПочему в FizzBuzz важно условие n % 15 == 0?
Числа вроде 15 и 30 делятся и на три, и на пять. Если проверять сначала кратность трём, такие числа получат Fizz и до FizzBuzz дело не дойдёт: условия if/elif срабатывают по порядку, и первое же истинное закрывает проверку.
Как напечатать таблицу умножения ровными колонками?
Вложенным циклом и f-строкой с шириной: f'{i * j:>3}' прижимает произведение вправо в колонке шириной три. Аргумент end='' держит ячейки в одной строке, а пустой print() после внутреннего цикла переводит строку.
Формат-спецификации f-строкЧто делает умножение строки на число в Python?
Повторяет строку: '*' * 5 даст пять звёздочек подряд. В цикле по количеству это собирает треугольники, рамки и прогресс-бары без вложенных переборов символов.
Как вывести таблицу умножения ровными колонками?
Вложенным циклом и f-строкой с шириной: f'{i * j:>3}' прижимает произведение вправо в колонке шириной три. Аргумент end='' держит ячейки в одной строке, а пустой print() после внутреннего цикла переводит строку. Альтернатива — собрать строку через join из готовых ячеек.
Формат-спецификации f-строкКак проверить, делится ли число на k без оператора %?
Через целочисленное деление: a - (a // b) * b == 0. Частное отбрасывает дробную часть, умножение возвращает отброшенное, вычитание даёт остаток. В FizzBuzz так делать не надо — % короче, — но понимание пригодится в чужом коде.
Почему FizzBuzz дают на собеседованиях?
Задача проверяет не цикл, а рассуждение: разложение правила на проверки, понимание того, что одно условие перекрывает другое, и выбор порядка. Именно на порядке условий кандидаты и спотыкаются чаще всего — сначала нужно общее правило, потом частные.
Понравился урок? Сошлитесь на него
«Сначала проверяется самый частный случай: если число делится на 15, оно делится и на 3, и на 5.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Циклы · Урок 2
range в Python: цикл со счётчиком
range делает цикл for счётчиком: n повторов от нуля, диапазон от начала до конца, шаг и обратный отсчёт. Разбираем все три формы range на примерах с запуском.
Циклы · Урок 8
Вложенные циклы в Python: цикл в цикле
Цикл внутри цикла: дни и дела, план зала кинотеатра, таблица умножения и пары «каждый с каждым» — как читается вложенность и во сколько раз она умножает число шагов.
Циклы · Урок 18
Числа в цикле Python: суммы, факториалы, Фибоначчи
Сумма, факториал и Фибоначчи — три классические задачи на один каркас: аккумулятор со стартовым значением и цикл, который шаг за шагом вносит вклад.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
json · Урок 10
Обход данных: циклы по JSON-спискам
for по списку словарей, извлечение полей, суммирование выручки и list comprehension — превращаем выгрузку заказов в отчёт.
python цикл по jsonpython цикл по orders
Функции · Урок 3
Return в Python: функция возвращает результат
print показывает значение человеку, return отдаёт программе. Учимся возвращать результат, класть его в переменную и использовать в выражениях — и не путать return с print.
print и return python
Циклы · Урок 1
Цикл for в Python: первый цикл
Первый цикл for: компьютер повторяет одну строку за вас. Перебираем список, разбираем переменную цикла и тело с отступами — и видим, зачем цикл вместо копипасты.
цикл for pythonpython цикл for