Однажды моя девушка проходила курс по основам 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]

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

Комментарии (12)


  1. Zara6502
    28.05.2025 08:01

    я бы сделал проверку на ноль и убрал бы часть из формулы, мне лично одно ветвление кажется небольшой платой за сокращение вычислений.


    1. Zara6502
      28.05.2025 08:01

      на C# можно так:

      string getRouletteColor(int color) =>
        return n == 0 ? "Green" : (((int)(((n-1) % 18 + 1)/11) + n%2) % 2) == 0 ? "Black" : "Red";
      0: Green
      1: Red
      2: Black
      3: Red
      4: Black
      5: Red
      6: Black
      7: Red
      8: Black
      9: Red
      10: Black
      11: Black
      12: Red
      13: Black
      14: Red
      15: Black
      16: Red
      17: Black
      18: Red
      19: Red
      20: Black
      21: Red
      22: Black
      23: Red
      24: Black
      25: Red
      26: Black
      27: Red
      28: Black
      29: Black
      30: Red
      31: Black
      32: Red
      33: Black
      34: Red
      35: Black
      36: Red

      А еще можно написать в дипсик: "Напиши программу как бы ты решил эту задачу?"

      Deepseek

      Я бы решил эту задачу немного по-другому, чтобы сделать код более понятным и поддерживаемым. Вот мой вариант:

      using System;
      
      class RouletteColorPrinter
      {
          static void Main()
          {
              Console.WriteLine("Roulette numbers and their colors:");
              Console.WriteLine("-----------------------------------");
              
              for (int number = 0; number &lt;= 36; number++)
              {
                  string color = DetermineRouletteColor(number);
                  Console.WriteLine($"{number,2}: {color}");
              }
          }
      
          static string DetermineRouletteColor(int number)
          {
              if (number == 0) 
                  return "Green";
              
              int group = (number - 1) / 18;  // 0 for 1-18, 1 for 19-36
              int positionInGroup = (number - 1) % 18 + 1;
              
              bool isFirstHalfOfGroup = positionInGroup &lt;= 9;
              bool isEven = number % 2 == 0;
              
              // Определяем цвет на основе положения в группе и чётности
              return (isFirstHalfOfGroup == isEven) ? "Red" : "Black";
          }
      }
      

      Ключевые отличия моего решения:

      1. Более понятное именование:

        • DetermineRouletteColor вместо getRouletteColor

        • number вместо n или color

        • Осмысленные имена промежуточных переменных

      2. Логика разбита на понятные шаги:

        • Сначала определяем группу числа (1-18 или 19-36)

        • Затем позицию в группе

        • Проверяем, в первой ли половине группы находится число

        • Проверяем чётность

      3. Более читаемая логика определения цвета:

        • Вместо сложной формулы с несколькими операциями использую логические сравнения

        • Правило: если число находится в первой половине группы и чётное ИЛИ во второй половине и нечётное → Красное, иначе Чёрное

      4. Улучшенный вывод:

        • Заголовок для ясности

        • Форматирование чисел с выравниванием

        • Разделительная линия

      5. Отсутствие "магических чисел":

        • Все константы (18, 9) логически вытекают из структуры рулетки

      Такой код легче понимать, отлаживать и модифицировать в будущем, хотя он и делает то же самое, что и оригинальная формула.


      1. Zara6502
        28.05.2025 08:01

        еще попросил гигачат написать с нуля:

        Давай упростим решение, убрав лишние проверки и применяя одну простую формулу, которая определяет цвет ячейки на основании её номера. Вот оптимизированная версия без операторов if и лишних проверок:

        public string GetRouletteColor(int number)
        {
            if (number == 0) return "Зелёный";
            return ((((number - 1) % 18) < 10) ^ ((number % 2) == 0)) ? "Красное" : "Чёрное";
        }
        

        Объяснение формулы:

        1. Формула (number - 1) % 18:

          • Вычисляет остаток от деления уменьшенного на единицу номера ячейки на 18. Это помогает разбить диапазон ячеек (1–36) на две группы по 18 элементов каждая.

          • Например, числа от 1 до 18 будут иметь остатки от 0 до 17, а числа от 19 до 36 — снова от 0 до 17.

        2. Проверка условия < 10:

          • Используется для разделения каждой половины на красные и черные ячейки. Первые десять клеток первой и второй половинок окрашены красным, остальные — черным.

        3. Операция XOR (^):

          • Применяется для того, чтобы изменить порядок окраски между первой половиной (1–18) и второй половиной (19–36). Таким образом, чётные и нечётные номера правильно распределяются по красному и чёрному цвету.

        Результат вычисления будет однозначно определять цвет каждого номера без избыточных условий и циклов.

        Так что эта реализация делает решение компактнее и эффективнее, сокращая количество ветвлений программы.


        1. romangodun96 Автор
          28.05.2025 08:01

          Знак меньше уже как-будто кажется логическим условием (от которых мы избавляемся), а вот использование XOR мне нравится!


    1. romangodun96 Автор
      28.05.2025 08:01

      Ага, я так изначально и сделал. Действительно проще 0 (и еще 00 в американской) просто по умолчанию красить в зеленый. Просто для статьи захотелось охватить все случаи)


  1. CBET_TbMbI
    28.05.2025 08:01

    Любопытно. Даже не задумывался, как они раскрашены.

    Для полноты картины не хватает только теста, что и насколько быстрее работает: условия или такая формула.


  1. Octagon77
    28.05.2025 08:01

    А мне кажется, что это идеальная задача проверяющая способность кандидата набросать маааленькую нейросеть.


  1. kalapanga
    28.05.2025 08:01

    Смотрим самый последний код в статье

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

    Делаем в нём color_name от 0 до 36 и выкидываем формулу вообще!

    Шутка, если что... :)


    1. Zenitchik
      28.05.2025 08:01

      const color = (n)=>(n == 0 ? "green" : ["black","red"][!!(91447186090n&(1n<<BigInt(n)))])

      Я не знаю питон, я знаю JS )))