Сайт Игоря Кононученко   Статьи

Решение криптоарифметической задачи

20 декабря 2008

Начал читать книгу «Экспертные системы: принципы разработки и программирование». Книга массивная, много букв. В конце первой главы дается криптоарифметическая задачка donald+gerald = robert при условии, что d = 5. Взял листик бумаги и ручку, минут 5−10 ушло на решение. В книге дают задание написать программу, решающую эту задачу. Начал придумывать как бы я написал эту программу. В голову пришло решение «в лоб», используя перебор.

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

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
# у меня стоит питон 2.5 этой функции там еще нет, но в питоне 2.6 она уже есть
# эта функция перемешивает массив чисел создавая все возможные комбинации, и добавляя в итератор
def permutations(iterable, r=None):
    # permutations('ABCD', 2) --> AB AC AD BA BC BD CA CB CD DA DB DC
    # permutations(range(3)) --> 012 021 102 120 201 210
    pool = tuple(iterable)
    n = len(pool)
    r = n if r is None else r
    indices = range(n)
    cycles = range(n, n-r, -1)
    yield tuple(pool[i] for i in indices[:r])
    while n:
        for i in reversed(range(r)):
            cycles[i] -= 1
            if cycles[i] == 0:
                indices[i:] = indices[i+1:] + indices[i:i+1]
                cycles[i] = n - i
            else:
                j = cycles[i]
                indices[i], indices[-j] = indices[-j], indices[i]
                yield tuple(pool[i] for i in indices[:r])
                break
        else:
            return

import time
#декоратор для замера времени
def print_timing(func):
    def wrapper(*arg):
        t1 = time.time()
        res = func(*arg)
        t2 = time.time()
        print '%s took %0.3f s' % (func.func_name, (t2-t1))
        return res
    return wrapper


class Resolver(object):
#хардкодом инициализирую все что нужно
    item1 = "donald"
    item2 = "gerald"
    result = "robert"
    known_elem = {"d": 5}
   
    # создаю массив уникальных букв, собранных из 3 слов, 
	#вероятно есть более красивый способ на питоне написать такой код (по мере своего развития в питоне буду такой код писать)
    def get_letters_array(self):
        result = []
        s = self.item1+self.item2+ self.result
        arr = list(s)
        for letter in arr:
            if not letter in result and not letter in self.known_elem:
                result.append(letter)
        
        return result

#по порядку создаем хеш где каждой букве соответствует число из массива (и тут питон не идеален)
    def get_letters_numbers(self, letters_array, numbers):
        result = {}
        i = 0
        
        for letter in letters_array:
            result[letter]= numbers[i]
            i+=1
        return result
  
#строка преобразуется в числовое значение например "donald" => в число
    def get_number(self, letters_numbers, _str):
        result = []
        for s in list(_str):
            if s in self.known_elem:
                result.append(str(self.known_elem[s]))
                continue
            result.append(str(letters_numbers[s]))
            
        return int("".join(result))
        
 #создаем массив цифр для подстановок, если уже есть известный элемент удаляем его для сокращения количества итераций      
    def get_initial_numbers(self):
        arr = range(0, 10)
        if self.known_elem.values()[0] in arr:
            arr.remove(self.known_elem.values()[0] )
        return arr

   #проверяем чтоб число не начиналось с 0
    def firstNumberIs0(self, letters_numbers):
        return (letters_numbers[self.item1[0]]==0 or
               letters_numbers[self.item2[0]]==0 or
               letters_numbers[self.result[0]]==0)
       
#тут все начинается      
    @print_timing
    def resolve(self):
        letters_array = self.get_letters_array();
        current_iterantion = 0
        initial_numbers = self.get_initial_numbers()
        
        for numbers in permutations(initial_numbers):
            letters_numbers = self.get_letters_numbers(letters_array, numbers)
            item1 = self.get_number(letters_numbers, self.item1)
            item2 = self.get_number(letters_numbers, self.item2)
            result = self.get_number(letters_numbers, self.result)
            if item1 + item2 ==result:
                print "result found on %s iteration: %s + %s = %s" % (current_iterantion, item1, item2, result)
                break
            current_iterantion+=1
        return
            
            
        
if __name__ == '__main__':
    Resolver().resolve()

Результаты:
donald+gerald = robert если известно, что d = 5 :
result found on 103487 iteration: 526485 + 197485 = 723970
resolve took 9.399 s

если ничего не известно — подольше:
result found on 1917887 iteration: 526485 + 197485 = 723970
resolve took 183.831 s

Нашел еще задачку: send+more = money
result found on 2787378 iteration: 9567 + 1085 = 10652
resolve took 265.016 s

Сделал для себя несколько выводов. Метод перебора, довольно-таки дубовый и медленный. В следующий раз хочу попробовать что-то быстрое, может генетический алгортим. Понял, что нужно углубить знания в питоне для более эффективного написания кода.

Ctrl
Автоматизация простой операции
JavaScript-дерево
JavaScript обучение
Обучение верстке