1. Что такое числовой ребус?

Ребус - это зашифрованный пример, где каждая цифра заменена какой-то буквой. Одна и та же цифра в примере обязательно должна быть заменена одной и той же буквой. И наоборот - две разных цифры не могут быть заменены на одну и ту же букву.

И последнее правило: в примере, который мы хотим превратить в ребус, ни одно число не может начинаться с 0.

Примеры ребусов:

  • EIN + EIN + EIN + EIN = VIER (1+1+1+1 = 4, нем.);

  • ЛОДКА + ЛОДКА + ЛОДКА + ЛОДКА = РЕГАТА;

  • КНИГА + КНИГА + КНИГА = НАУКА;

  • SEND + MORE = MONEY (Посылайте больше денег, англ.);

  • КОТ + КТО = ТОК

И все их с лёгкостью будет решать наша программа!

2. Структура нашей программы

Я понимаю, что вам важен один лишь результат, поэтому вы можете сразу посмотреть в конец блога. Однако, не обижайтесь, если вы там ничего не поймёте и поэтому не сможете пользоваться. А мы продолжим.

Итак, программа должна:

  1. Узнавать ребус;

  2. Перебирать все возможные значения букв;

  3. Проверить, какие из них подходят;

  4. Выдать результаты.

Всё это довольно легко, но давайте начнём со самого сложного - перебора и проверки, так как 1-й и 4-й пункт будут занимать всего одну строчку, если знать, как осуществеляется вывод и ввод в Python. Но давайте всё по порядку.

2.1. Перебор.

Я решил использовать для перебора рекурсию. Поясню для начала, что такое рекурсия. Рекурсивная функция - это функция, которая в процессе её выполнения вызывает саму себя. Например, перебор всех ходов в шахматах - это рекурсия, потому что мы перебираем всевозможные свои ходы и для каждого своего хода заново вызываем ту же функцию оценки для следующих возможных ходов.

Вёрнемся к нашему перебору. Его структура должна выглядеть примерно так:

Функция перебор(пример):
    Найти букву в примере, которую ещё не заменили на цифру;
    ЕСЛИ такой буквы нету, ТО пример готов и должен проверяться, а перебор закончен ИНАЧЕ:
    Для каждой цифры от 0 до 9 выполнить:
            Если такая цифра уже используется в примере, она нам не подходит; взять следующию цифру;
            Каждую букву в примере заменить на эту цифру;
            Вызвать перебор для нового примера

А теперь перепишем это на питон:

def get_comb(s):
    print(s + '\r', end='')
    letter = ''
    for i in s:
        if (i.isalpha()): # i.isalpha() показывает, является ли буквой переменная i
                letter = i
                break
    if (not letter):
        check(s)
        return # заканчивает перебор, если пример готов
    for n in range(10):
        n = str(n)
        if (s.find(n) > -1): continue
        s2 = s.replace(letter, n)
        get_comb(s2)

Эта функция заменяет буквы в ребусе на числа, не пропуская ни одного варианта. Нам остаётся только написать функцию check(), которая будет проверять пример на правильность.

2.2. Функция check()

К счастью, нам не придётся писать длинную функцию, для того чтобы расшифровать наш пример и проверить его на правильность. Ведь для питона строка "10+29=39", это все равно что обычный набор символов, он его не поймёт.

Так вот, существует в питоне функция eval(). Эта функция назначена как раз для вычисления примеров и равенств (ведь 10+29=39 по сути своей, не пример, а равенство).

С этой чудестной утилитой функция check() будет занимать всего лишь 4 строчки:

def check(s):
    try:
        b = eval(s.replace("=", "=="))
    except: return
    if (b): print(s)

Эта функция проверяет, верное ли равенство мы собрали. Если там возникает ошибка (когда вначале числа стоит 0, eval() выдаёт ошибку), то мы считаем, что равенство неверно (и правильно делаем, ведь это нарушает правила числового ребуса).

Если же равенство собрано, то мы нашли решение ребуса и можем его вывести на экран.
Итак, осталось только собрать всё вместе и добавить ввод ребуса.

3. Программа целиком.

Теперь, вот вам весь наш код:

def check(s):
    try:
       b = eval(s.replace("=", "=="))
    except: return 
    if (b): print(s)

def get_comb(s):
    print(s + '\r', end='')
    letter = ''
    for i in s:
        if (i.isalpha()):
            letter = i
            break
    if (not letter):
        check(s)
        return
    for n in range(10):
        n = str(n)
        if (s.find(n) > -1): continue
        s2 = s.replace(letter, n)
        get_comb(s2)

s = (input("Введите ребус: ")).replace(" ", "") # убираем пробелы с ребуса
get_comb(s)

Я также застраховал этот код : вы можете вводить ребус, делая пробелы, например, вводя кто + кот = ток вместо кто+кот=ток.

Итак, теперь вы можете очень быстро решать ребусы и даже придумывать свои, если, конечно, вы установили Python и ваш компьютер не слишком медленный (например, у меня на компьютере некоторые ребусы занимают минуту-две времени).

Удачи!

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


  1. Dimano
    30.09.2023 16:50

    А почему выводит второй, неправильный результат?

    Введите ребус: КНИГА + КНИГА + КНИГА = НАУКА
    28375+28375+28375=85125
    98765+98765+98765=85495


    1. Dimano
      30.09.2023 16:50

      Разобрался не очищается последняя попытка, которая выводится на экран
      Можно в конце добавить
      print(' ' * 70, end='')

      У дочки в учебники математики были такие ребусы, нам их не задавали но интересно же.
      Самое простое с чего можно начать это когда в одном разряде и в слагаемых и в ответе стоит одинаковая буква, вариантов в зависимости от количества слагаемых немного.


  1. randomsimplenumber
    30.09.2023 16:50

    Пара замечаний:

    def check(s):

    Ожидается, что функция с таким названием что то вернет ;)

    n = str(n)

    Фу так делать. Когда переменная иногда int, иногда str - в большом куске кода можно запутаться. В вашем случае есть готовый метод, возвращающий '0123456789' ;)


    1. fish224 Автор
      30.09.2023 16:50

      Благодарю за Ваши замечания, учту в следующей статье


  1. IvanStrizak
    30.09.2023 16:50

    Из картинки...
    9567 - send
    1085 - more
    10752 - money
    Буква n из send это 6.
    Буква n в money это 7.
    чего не сходится....


    1. fish224 Автор
      30.09.2023 16:50

      Извиняюсь, у меня была опечатка.

      9567 + 1085 = 10652


  1. andy_p
    30.09.2023 16:50
    +4

    Такие задачи надо SAT солвером решать.


  1. wataru
    30.09.2023 16:50

    eval на пользовательском вводе — это очень, очень плохая идея.


    например, у меня на компьютере некоторые ребусы занимают минуту-две времени

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


    А так самое наивное решение


    1. fish224 Автор
      30.09.2023 16:50

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

      Я специально не стал этого делать, потому что 2 минуты на выполнение меня вполне устраивает.


  1. Syzd
    30.09.2023 16:50

    Подскажите print(s + '\r', end='') в python 2.7 не работает, а в 3.7 - работает. В чем дело? Как исправить, чтобы работало всюду?


    1. wataru
      30.09.2023 16:50

      По-моему, никак, питон3 и питон2 — это разные языки. На совместимость там забили.


    1. fish224 Автор
      30.09.2023 16:50

      У меня работает в python 3.9, а в питоне 2 этот код работать не будет, потому что версии языка заметно отличаются


      1. Syzd
        30.09.2023 16:50

        Вообще-то будет. Вот мой вариант, который может работать в Python 2.7 с латинскими символами.

        #Only for use in Python 2.6.0a2 and later
        from __future__ import print_function
        
        
        def check(s):
            try:
               b = eval(s.replace("=", "=="))
            except: return 
            if (b): print(s)
        
        def get_comb(s):
            print(s + '\r', end='')
            letter = ''
            for i in s:
                if (i.isalpha()):
                    letter = i
                    break
            if (not letter):
                check(s)
                return
            for n in range(10):
                n = str(n)
                if (s.find(n) > -1): continue
                s2 = s.replace(letter, n)
                get_comb(s2)
        
        s = (raw_input("Enter REBUS: ")).replace(" ", "") # remove space
        s = s.replace("=", "==")
        get_comb(s)