Перейти к содержанию

412 уроков, 25 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 19 из 20 Начальный 15 мин 100 XP

Классика на циклах: 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 в виде функции

Как только задача начинает повторяться, её стоит завернуть в функцию: границу можно менять, а правила остаются в одном месте. Заодно получается место, где удобно проверить результат.

функция 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('правила выполняются')
Фрагмент: функция fizzbuzz объявлена выше. assert проверяет условие и роняет программу, если оно не выполнилось — так работают тесты.

Таблица умножения: один столбец

столбец для одного числа
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. Каждая ячейка превращается в строку, список склеивается разделителем, и вся строка готова.

строка таблицы через 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 'нет')
Проверь себя
0 / 8

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?

Карточки терминов
Запомнено: 0 / 9
Практика

Напечатай числа от 1 до 15 по правилам FizzBuzz: кратные трём — Fizz, кратные пяти — Buzz, кратные пятнадцати — FizzBuzz, остальные — само число. Собери слово из кусочков и выведи через word or n.

practice.py
Вопросы и ответы по уроку

Как решается 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-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Что дальше

Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.