Однажды моя девушка проходила курс по основам python. Она показала мне небольшую задачку на использование if-else: "по номеру кармана (ячейки) на рулетке определите его цвет".

Казалось бы, все довольно просто — используем условные операторы и не знаем проблем! Но можно ли вывести математическую формулу, которая будет работать для всех ячеек? В этой статье я описал поиски такой формулы!

Как работает рулетка? 

Несмотря на громкий заголовок этого раздела, мы здесь не будем раскрывать тайну того, как обыграть казино, а просто посмотрим по каким правилам распределяются цвета на игровом поле. Для примера взят европейский вариант рулетки, тк американский отличается только тем, что там есть второе зеленое поле 00, которое не очень интересно в контексте нашей задачи

На колесе есть нумерация от 0 до 36. Числа (если не считать 0) делятся на 2 половины по 18 карманов. Каждая половина состоит из 2 секций: большая - 10 ячеек и малая - 8 ячеек:

  • карман 0 – зеленый;

  • с 1 по 10 карманы с нечётным номером имеют красный цвет, карманы с чётным номером – черный;

  • с 11 по 18 карманы с нечётным номером имеют черный цвет, карманы с чётным номером – красный;

  • с 19 по 28 карманы с нечётным номером имеют красный цвет, карманы с чётным номером – черный;

  • с 29 по 36 карманы с нечётным номером имеют черный цвет, карманы с чётным номером – красный.

Решение с помощью условных операторов:

def get_roulette_color(cell: int) -> str:
    if cell == 0:
        return "green"
      
    is_red_color = (
        (1 <= cell <= 10 and cell % 2 == 1) or
        (12 <= cell <= 18 and cell % 2 == 0) or
        (19 <= cell <= 27 and cell % 2 == 1) or
        (30 <= cell <= 36 and cell % 2 == 0)
    )
    return "red" if is_red_color else "black"

Постановка задачи

У нас есть n - номер поля на рулетке. Для начала рассмотрим крайний случай n = 0. Пусть наша формула выдает значение -1. Запомним, что эта ячейка зеленого цвета, тк она одна такая (в американской еще будет 00).

А вот для 0 < n <= 36 мы хотим вывести такую математическую формулу, чтобы она выдавала 1, если поле красное, и 0, если поле черное. Идеально, если формула будет достаточно универсальной, чтобы распространяться на рулетку бесконечной длинны, при условии, что цвета будут повторятся каждые 36 позиций.

Допускается использование стандартных арифметических операций - сложение (+), вычитание (-), деление (/), умножение (*). И в дополнение к ним еще целочисленное деление (//) и остаток от деления (%).

Поиски решения

Шаг 1. Разбиваем поле на половины

Давайте на первом шаге забудем про значение 0 и попробуем поделить наше поле на половины. Как бы пронумеруем с помощью формулы каждую ячейку от 1 до 18 по кругу. Для этого будем использовать остаток от деления (%) на 18:

Как мы видим получается довольно неплохой результат, но на числах кратных 18 (18 и 36) разница колоссальная — получаются нули. Давайте попробуем вариант n%19:

Получилось не очень. Теперь половина ячеек имеет сдвиг на 1, а на бесконечной рулетке этот сдвиг еще и будет увеличиваться каждые 18 позиций.

Попробуем зайти с другого конца. Что если мы оставим остаток от делений на 18, но вычтем из изначальной позиции 1. Тогда получится, что все ячейки пронумерованы неправильно, но неправильно в равной степени - на единицу. Нам останется только прибавить ее.

Получаем следующую формулу:

step_1 = (n-1) % 18 + 1

Шаг 2. Разбиваем половины на секции

Напомню, что каждая половина по 18 карманов бьется на 2 секции: большая — 10 штук и малая — 8 штук.

Тут все довольно просто. Мы берем нумерацию, полученную на первом шаге, и делаем целочисленное деление (//) на 11. Таким образом числа меньше 11 станут 0, а числа больше или равные станут 1.

step_2 = step_1 // 11

Шаг 3. Расставляем цвета

Как мы видели ранее, все правила покраски имеют одинаковый паттерн: "нечетные в такой-то секции имеют такой-то цвет, а четные — другой". Поэтому мы просто введем еще одно число, которое будет указывать на четность ячейки. Используем для этого, конечно же, остаток от деления на 2 (1 — нечетное, 0 — четное). 

Отлично, попробуем что-то сделать с этими числами. Например вычесть:

Получилось не то, что нам нужно. Есть чередование, которое потом можно будет использовать, но в больших секция 0 чередуется с -1 (с ними будет не очень удобно работать дальше), а в малых 0 чередуется с 1.

Давайте попробуем по-другому — сложить эти 2 числа:

Все намного лучше: мы избавились от отрицательных чисел.

Теперь давайте присмотримся — там где карман имеет красный цвет, стоит нечетное число (1), а там где черный цвет, стоит четное (0 или 2). Нам остался один шаг до того, чтобы получить результат "1 там где цвет красный, 0 там где цвет черный". Сделаем остаток от деления на 2 для нашей суммы:

step_3 = (step_2 + n%2) % 2

Шаг 4. Добавляем крайний случай

Мы помним что наша формула должна выдавать значение -1 при n=0, но сейчас в нуле у нас 1. Получается, что нам необходимо просто вычесть 2 из результата (1 - 2 = -1), но только при n = 0. Для этого нам понадобится еще одно число которое будет принимать значение 1 при n=0 и 0 во всех остальных случаях (как бы логическая переменная). Этим числом будет 1 // (n + 1)

Ура! Теперь можно бежать в ближайшее казино - проверять полученные результаты. Ну и конечно, финальная формула которую можно использовать:

Финальная формула

roulette_color = (((n-1)%18 + 1)//11 + n%2) %2 - 2*(1//(n + 1))

Использование в коде python

def get_roulette_color(n: int) -> str:
    color_name = {-1:"green",0:"black",1:"red"}
    roulette_color = (((n-1)%18 + 1)//11 + n%2) %2 - 2*(1//(n + 1))
    
    return color_name[roulette_color]

Спасибо за прочтение статьи! Надеюсь, она вам понравилась, буду рад вашей обратной связи в комментариях.