Advertisement
Guest User

Kakuro Solver

a guest
Feb 16th, 2011
811
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 25.28 KB | None | 0 0
  1. #!/usr/bin/python
  2. # -*- coding: windows-1251 -*-
  3. from types import *
  4.  
  5.  
  6. class Kakuro:
  7.    # таблица с какурой
  8.    _table=None
  9.    # группы
  10.    _groups=None
  11.    # размеры таблицы
  12.    _lines = 0
  13.    _columns = 0
  14.  
  15.    # Таблица возможных слагаемых
  16.    # в виде _sum_variants[число слагаемых][сумма] = [ вариант1, вариант2 ... ]
  17.    _sum_variants = None
  18.    
  19.    # Исключения
  20.    class KakuroException(Exception): pass
  21.    class ParseException(KakuroException): pass
  22.    class SolveMaxIteration(KakuroException): pass
  23.    
  24.    def to_string(self):
  25.       ''' Возвращает какуро в читабельном виде '''
  26.       if self._table==None:
  27.          return ''
  28.      
  29.       result=''
  30.       for line in self._table:
  31.          for cell in line:
  32.            
  33.             cell_text=''
  34.             assert ( type(cell) == TupleType and len(cell) == 2 ) or cell=='#' or isinstance(cell, Cell)
  35.            
  36.             if cell=='#':              # серая клетка
  37.                cell_text = '###'
  38.             elif isinstance(cell, Cell):   # ячейка
  39.                if len(cell) == 9:
  40.                   cell_text = '.'
  41.                else:
  42.                   cell_text = cell
  43.             elif type(cell) == TupleType and len(cell)==2:   # начало группы
  44.                cell_list = list(cell)
  45.                cell_list = map(lambda x: str(x) if x != 0 else '##', cell_list)
  46.                cell_text += '\\'.join(cell_list)
  47.             else:
  48.                pass
  49.                #raise TypeError('unexpected cell type (%s)' % cell)
  50.            
  51.             result += '{:^23}'.format(cell_text)
  52.          result += '\n'
  53.          
  54.       return result[:-1]   # удаляем завершающий перенос строки
  55.            
  56.                
  57.                
  58.    def parse(self, text):
  59.       ''' Чтение какуры из строки
  60.      @type text: string
  61.      @param text: строковое представление какуры
  62.      
  63.      @raise ParseException
  64.      '''
  65.       table=[]
  66.       lines=text.split('\n');
  67.       for line in lines:
  68.          # разбиваем по пробелам
  69.          nodes=line.split()
  70.  
  71.          # пропускаем пустые строки
  72.          if len(nodes)==0:
  73.             continue
  74.  
  75.          # добавляем строку
  76.          table.append([])
  77.  
  78.          for node in nodes:
  79.  
  80.             if node[0]=='.':
  81.                table[-1].append(Cell())
  82.             elif node[0]=='#':
  83.                table[-1].append('#')
  84.             else:
  85.                pieces=node.split('\\')
  86.                if len(pieces)==2:   # значит ячейка с суммой блока
  87.                   table[-1].append((self._to_int(pieces[0], 0) , self._to_int(pieces[1], 0)))
  88.                else:                # пробуем получить числовое значение
  89.                   number=self._to_int(node)
  90.                   if number==None:
  91.                      raise Kakuro.ParseException('except number (%s)' % node)
  92.                      #raise ValueError('except number (%s)'%node)
  93.                   else:
  94.                      table[-1].append(Cell(number))
  95.      
  96.       self._table = table
  97.      
  98.       self._lines = len(table)   # количество строк
  99.       self._columns = len(table[0]) # по длине первой строка
  100.      
  101.       self._groups = None
  102.  
  103.    def solve(self):
  104.       if self._sum_variants == None:
  105.          self._get_sum_variants()
  106.      
  107.       if self._groups == None:
  108.          self._get_groups()
  109.      
  110.       table_changed = True
  111.      
  112.       iteration = 0
  113.       max_iterations = 500
  114.      
  115.       while table_changed:
  116.          iteration += 1
  117.          
  118.          print '\n',self
  119.          
  120.          if iteration >= max_iterations: raise self.SolveMaxIteration()
  121.                    
  122.          table_changed = False
  123.          for i,group in enumerate(self._groups):
  124.            
  125.             if group.solved():
  126.                continue
  127.            
  128.             group.solve()
  129.  
  130.             table_changed |= group.changed()
  131.          
  132.    def _get_sum_variants(self):
  133.       ''' Генерирует таблицу вариантов сумм '''
  134.       variants = {}
  135.      
  136.       for n in range(1,10):      # n - длина блока (количество слагаемых)
  137.          # Генерируем первую группу слагаемых
  138.          # Так как слагаемые повторяться не могут, первая группа будет иметь вид 1, 2, 3, ... n
  139.          summands = range(1,n+1);
  140.          # создаём коллекцию для текущей длины групп
  141.          variants[n] = {}
  142.          
  143.          # флаг, что все варианты для текущего n перебраны
  144.          next_n = False
  145.          
  146.          while not next_n:
  147.             group_sum = sum(summands)
  148.            
  149.             if not group_sum in variants[n]:
  150.                variants[n][group_sum] = []
  151.                
  152.             # помещаем в группу с суммой текущий вариант разложения
  153.             variants[n][group_sum].append(tuple(summands))
  154.            
  155.            
  156.             # Рассчитываем следующий вариант разложения
  157.             # Для этого пробегаемся по слагаемым с конца, прибавив к последнему единицу
  158.             # Если значение слагаемого больше, чем 10 - n + i, то прибавляем цифру к порядку старше
  159.             # Так например для группы из 5ти слагаемых _ _ _ _ _, начальная группы будет 1 2 3 4 5,
  160.             # а последняя 5 6 7 8 9, и в позиции 2 не может быть больше семёрки (7)
  161.             # i - индекс слагаемого
  162.             for i in range(n-1,-2,-1):
  163.                if i==-1:
  164.                   next_n=True
  165.                   break
  166.                
  167.                # прибавляем единицу в текущем порядке
  168.                summands[i]+=1
  169.                # если это число здесь может быть, то последующие цифры будут последовательными, т.е
  170.                # если 1 3 _ _ _, , тройка быть может (10 - 5 + 1 = 6 >= 3), значит следующие цифры: 1 3 4 5 6
  171.                if summands[i] <= 10-n+i:
  172.                   summands[i+1:] = range( summands[i]+1 ,10)[:n-i-1]
  173.                   break
  174.                
  175.       self._sum_variants = variants
  176.                
  177.          
  178.    def _get_groups(self):
  179.       ''' Получить группы из таблицы '''
  180.       groups = []
  181.       for line_index,line in enumerate(self._table):
  182.          for column_index,cell in enumerate(line):
  183.            
  184.             if type(cell) == TupleType and len(cell)==2:   # найдён начало блока
  185.                for i,sum in enumerate(cell):
  186.                   if sum != 0:
  187.                      groups.append( self._get_group( line_index, column_index, sum, bool(i) ) )
  188.      
  189.       self._groups = groups
  190.      
  191.    def _get_group(self,line,column,sum,horizontal=False):
  192.       ''' Получить группу по координатам начальной ячейки
  193.      @type line: int
  194.      @param line: Номер строки
  195.      @type column: int
  196.      @param columns:  Номер столбца
  197.      @type sum: int
  198.      @param sum: Сумма группы
  199.      @type horizontal: bool
  200.      @param horizontal: Группа горизонтальная
  201.      @rtype: Group
  202.      @return: Группа
  203.      '''
  204.       cells = []
  205.      
  206.       if horizontal: # горизонтальное направление
  207.          cols_range = range(column + 1,self._columns)
  208.          lines_range = [line] * len(cols_range)
  209.       else:    # вертикальное
  210.          lines_range = range(line + 1,self._lines)
  211.          cols_range = [column] * len(lines_range)
  212.      
  213.       coords = zip(lines_range,cols_range)
  214.       for line,column in coords:
  215.          cell=self._table[line][column]
  216.          
  217.          if cell=='#' or type(cell) == TupleType:  # конец группы
  218.             break
  219.          
  220.          cells.append(cell)
  221.      
  222.       return Group(sum,cells, self._sum_variants[len(cells)][sum] )
  223.      
  224.    def _to_int(self, value, default_value = None):
  225.       """ конвертирование строки в целое число
  226.      @type value: string
  227.      @param value: строковое представление числа
  228.      @type default_value: variable
  229.      @param default_value: значение, при ошибке конвертирования [None]
  230.      @rtype: int | type(default_value)
  231.      @return: число или default_value
  232.      """
  233.       try:
  234.          result=int(value)
  235.       except ValueError:
  236.          result=default_value
  237.       finally:
  238.          return result
  239.  
  240.    def __repr__(self):
  241.       return self.to_string()
  242.  
  243. class Group:
  244.    # Коллекция ячеек
  245.    _cells = None
  246.    # Сумма группы
  247.    _sum = 0
  248.    # Варианты сумм
  249.    _sum_variants = None
  250.    # Отгаданные цифры
  251.    _solved_digits = None
  252.    # Цифры, которых нет в группе
  253.    _invalid_digits = None
  254.    
  255.    # Изменена ли группа
  256.    _changed = False
  257.    
  258.    def __init__(self,sum,cells,sum_variants):
  259.       self._sum = sum
  260.       self._cells = tuple(cells)
  261.       self._sum_variants = map(lambda group: Digits(group), sum_variants)
  262.       self._solved_digits = Digits([])
  263.       self._invalid_digits = Digits([])
  264.    
  265.    def solve(self):
  266.       self._invalid_digits = Digits()
  267.      
  268.       for cell in self._cells:
  269.          # собираем отгаданные
  270.          if cell.solved():
  271.             self._solved_digits += cell
  272.            
  273.          # убираем из невозможных, возможные в текущей ячейка. В конце останутся невозможные вообще
  274.          self._invalid_digits -= cell
  275.      
  276.  
  277.       # отсеиваем варианты
  278.       self._filter_sum_variants()
  279.      
  280.       assert len(self._invalid_digits) != 9
  281.      
  282.       # из отсавшизся вариантов получаем невозможные цифры
  283.       self._invalid_digits += self._select_invalid_digits()
  284.      
  285.       assert len(self._invalid_digits) != 9
  286.      
  287.       # изменение ячеек
  288.       self._changed = False
  289.       for cell in self._cells:
  290.          if cell.solved(): continue
  291.          
  292.          # убираем из ячейки невозможные цифры
  293.          cell -= self._invalid_digits
  294.          # и уже отгаданные в этой группе
  295.          cell -= self._solved_digits
  296.                                
  297.          self._changed |= cell.changed()
  298.    
  299.    def solved(self):
  300.       ''' Решена ли группа '''
  301.       return len(self._solved_digits) == len(self._cells)
  302.      
  303.    def changed(self):
  304.       ''' Изменелась ли группа '''
  305.       return self._changed      
  306.    
  307.    def _filter_sum_variants(self):
  308.       ''' Оставляем варианты которые содержат все разгаданные слагаемые и не содержат ниодного недопустимого слагаемого '''
  309.       self._sum_variants = filter(lambda summands: self._solved_digits - summands == [] and summands & self._invalid_digits == [], self._sum_variants)
  310.      
  311.    def _select_invalid_digits(self):
  312.       ''' отсеиваем цифры, которые не могут быть в любом из возможных вариантов '''
  313.      
  314.       # отсеиваем цифры, которые не могут быть в любом из возможных вариантов
  315.       invalid = Digits()
  316.  
  317.       # нет ни в одной группе слагаемых
  318.       for summands in self._sum_variants:
  319.          invalid -= summands
  320.            
  321.       return invalid
  322.      
  323.    def __repr__(self):
  324.       result =  'Group ({sum}):'.format(sum=self._sum)
  325.       if self.solved():
  326.          result += '\n\tSOLVED'
  327.       result += '\n\tsolved=' + str(self._solved_digits)
  328.       result += '\n\tinvalid=' + str(self._invalid_digits)
  329.      
  330.       for i,cell in enumerate(self._cells):
  331.          result += '\n\t\t'+str(i+1)+'. ' + str(cell)
  332.          
  333.       return result
  334.  
  335. class Menu(object):
  336.    ''' Реализует многоуровневое консольное меню '''
  337.    # Шаблон меню, title - Заголовок меню/подменю, items - пункты
  338.    menu_template = "\n --- {title:^13} --- \n{items}"
  339.    # Разделитель пунктов меню
  340.    items_delimiter = '\n'
  341.    # Шаблон пункта меню index - номер пункта, title - заголовок
  342.    items_template = "{index}. {title}"
  343.    # Приглашение ввода:
  344.    prompt = "Input item index: "
  345.    # Заголовок  меню
  346.    menu_title = 'Menu'
  347.    # Текст пункта меню «наверх» (пункт скрыт, если None)
  348.    back_title = None
  349.    # Текст пункта выход (пункт скрыт, если None)
  350.    exit_title = None
  351.  
  352.    
  353.    # Структура меню
  354.    _struct = None
  355.    # Активная позиция
  356.    _active_position = 0
  357.    # Заголовок активного меню
  358.    _active_title = None
  359.    # Активное подменю
  360.    _submenu = None
  361.    
  362.    
  363.    def __init__(self,struct):
  364.       if type(struct) != TupleType:
  365.          raise TypeError('type of struct must be a tuple (%s)' % type(struct))
  366.      
  367.       self._struct = struct
  368.       self._submenu = struct
  369.       self._active_position = 0
  370.       self._active_title = self.menu_title
  371.    
  372.    def select(self,num,execute=True):
  373.       ''' Выбор пункта меню
  374.      @num: (int) Выбираемая позиция
  375.      @execute: (bool) Выполнить, если выбранный элемент это функция или выбросить исключение
  376.      '''
  377.       if num==0:
  378.          self.position = self.position/10    # Сдвигаем на порядок
  379.       else:
  380.          index = num-1
  381.          item = self._submenu[index]
  382.          if type(item[1]) == TupleType: # если второй элемент - это список
  383.             self._active_title = item[0]
  384.             self._submenu = item[1]
  385.             self._active_position = self._active_position*10 + num
  386.          elif execute:                 # в противном случае это функция, выполняем если разрешено
  387.             self._call_func(item[1],item[2:])   # выполняем функцию (item[1]) с параметрами item[2:]
  388.          else:
  389.             raise Exception('except sumbenu element in %s' % self._active_position*10+num)  # а значит ожидается подменю
  390.  
  391.      
  392.    @property
  393.    def position(self):
  394.       return self._active_position
  395.      
  396.    @position.setter
  397.    def position(self,position):
  398.       ''' Устанавливает текущую позицию в меню относительно корня'''
  399.       if type(position) != IntType:
  400.          raise TypeError('type of position must be a int (%s)' % type(position))
  401.      
  402.       # получаем подменю по индексам
  403.       submenu = self._struct
  404.      
  405.       if position>0:
  406.          position = str(position)   # переводим в строку, чтобы получать посимвольно
  407.          position = filter(lambda c: c != '0',position) # удаляем нулевые элементы
  408.          for i,level in enumerate(position):
  409.             index = int(i)-1
  410.             self.select(index,  level == len(position) - 1 ) # level == len(position) - 1 - элемент находится в конце позиции, значит выполняем его
  411.       else:
  412.          self._active_position = 0
  413.          self._active_title = self.menu_title
  414.    
  415.       self._submenu = submenu
  416.                
  417.    def show(self,position=None):
  418.       ''' Показать меню в текущей или заданной позиции
  419.      @position: (int) задать позицию
  420.      '''
  421.       if position!=None:
  422.          self.position = position
  423.      
  424.       items = []
  425.       for index,menu_item in enumerate(self._submenu):
  426.          items.append( self.items_template.format(index=index+1,title=menu_item[0]) )
  427.      
  428.       # Активно подменю
  429.       if self.position > 0 and self.back_title != None:
  430.          items.append(self.items_template.format(index=0,title=self.back_title))
  431.       elif self.exit_title != None:
  432.          items.append(self.items_template.format(index='Q',title=self.exit_title))
  433.        
  434.       print self.menu_template.format( title=self._active_title, items=self.items_delimiter.join(items) )
  435.      
  436.  
  437.    def activate(self):
  438.       ''' Активация интерактивного меню '''
  439.       self.show()
  440.       while True:
  441.          pos = raw_input(self.prompt)
  442.          
  443.          # выход, если введено 'q'
  444.          if pos=='q':
  445.             break
  446.          # повторный вывод меню, если пустой ввод
  447.          if pos=='':
  448.             self.show()
  449.             continue
  450.          
  451.          try:
  452.             pos = int(pos)
  453.          except ValueError:
  454.             continue
  455.          
  456.          try:
  457.             self.select(pos)
  458.          except:
  459.             continue
  460.          
  461.          self.show()
  462.  
  463.          
  464.      
  465.    def _call_func(self,func,args):
  466.       ''' Вызов функции с агрументами в виде списка '''
  467.       func(*args)
  468.  
  469. class Cell:
  470.    # допустимые цифры
  471.    _digits=None
  472.  
  473.    # результат последней операции
  474.    _last_operation_change=False
  475.    
  476.    def __init__(self, value = None):
  477.       self._digits = Digits(value)
  478.       self._test_numbers()
  479.      
  480.    def __repr__(self):
  481.       ' вывод ячейки на печать '
  482.       if len(self._digits)==1:
  483.          return str(self._digits.list()[0])
  484.       else:
  485.          return str(self._digits)
  486.  
  487.    def __add__(self, values):
  488.       ' операция + '
  489.       old = self._digits  
  490.      
  491.       self._digits += values
  492.      
  493.       self._last_operation_change = old == self._digits
  494.       self._test_numbers()
  495.       return self
  496.    
  497.    def __sub__(self, values):
  498.       ' операция - '
  499.       old = self._digits
  500.      
  501.       self._digits -= values
  502.  
  503.       self._last_operation_change = old == self._digits
  504.       self._test_numbers()
  505.       return self
  506.  
  507.    def __eq__(self, other):
  508.       ' оператор == '
  509.  
  510.       if isinstance(other, Digits):
  511.          return self._digits==other
  512.       elif isinstance(other, Cell):
  513.          return self._digits==other._digits
  514.       else:
  515.          return self._digits==other
  516.      
  517.    def __len__(self):
  518.       return len(self._digits)
  519.      
  520.    def solved(self):
  521.       ' Решена ли ячейка'
  522.       return len(self._digits)==1
  523.  
  524.    def changed(self):
  525.       ''' Изменена ли ячейка последней операцией '''
  526.       return self._last_operation_change
  527.    
  528.    def clone(self):
  529.       ' Клонирование (копирование) ячейки '
  530.       return Cell(self._digits)
  531.  
  532.    def copy(self):
  533.       ' Клонирование (копирование) ячейки '
  534.       return self.clone()
  535.  
  536.    def max(self):
  537.       ' Максимальное допустимое число '
  538.       return max(self._digits)
  539.  
  540.    def min(self):
  541.       ' Минимальное допустимое число '
  542.       return min(self._digits)
  543.  
  544.    def digits(self):
  545.       ''' Получить цифры ячейки '''
  546.       return self._digits
  547.    
  548.    def _test_numbers(self):
  549.       ' Проверка ячейки на пустоту. Если пуста, выбрасывается исключение '
  550.       if len(self._digits)<1:
  551.          raise Exception('error, empty cell')
  552.  
  553. class Digits:
  554.    '''
  555.   Набор цифр 1-9. Реализованы операции сложения и вычтания
  556.   '''
  557.    _digits=[]
  558.    
  559.    
  560.    def list(self):
  561.       ' Возвращает список цифр'
  562.       return self._digits
  563.  
  564.    def __init__(self, value = None):
  565.       """ Инициализация класса
  566.      @param value: цифра или список цифр
  567.      """
  568.      
  569.       if type(value)==TupleType:
  570.          value=list(value)
  571.      
  572.       if value==None:
  573.          self._digits=range(1, 10)
  574.       elif type(value) == IntType:
  575.          self._digits=[value]
  576.       elif type(value) == ListType:
  577.          self._digits=self._test_values(value)
  578.       else:
  579.          raise ValueError('except int or list of ints(%s)'%type(value))
  580.      
  581.    def __repr__(self):
  582.       ' на печать '
  583.       self._digits.sort()
  584.       return str(self._digits)
  585.      
  586.  
  587.    def __add__(self, values):
  588.       ' операция + '
  589.       values = self._test_values(values)
  590.  
  591.       result = self._digits + values
  592.      
  593.       # удаляем повторяющиесы числа
  594.       result=list(set(result))
  595.  
  596.       return Digits(result)
  597.  
  598.    def __and__(self,values):
  599.       ' операция & - пересечение'
  600.       values=self._test_values(values)
  601.      
  602.       result = list( set(self._digits) & set(values) )
  603.      
  604.       return Digits(result)
  605.      
  606.    def __or__(self,values):
  607.       ' операция | - объединение'
  608.       values=self._test_values(values)
  609.      
  610.       result = list( set(self._digits) | set(values) )
  611.      
  612.       return Digits(result)
  613.    
  614.    def __xor__(self,values):
  615.       ' операция ^ - инвертированное пересечение (обединение, без общей части)'
  616.       values=self._test_values(values)
  617.      
  618.       result = list( set(self._digits) ^ set(values) )
  619.      
  620.       return Digits(result)
  621.    
  622.    def __sub__(self, values):
  623.       ' операция - '
  624.       values=self._test_values(values)
  625.  
  626.       result = list(set(self._digits)-set(values))
  627.  
  628.       return Digits(result)
  629.  
  630.    def __eq__(self, other):
  631.       ' оператор == '
  632.  
  633.       if isinstance(other, Digits):
  634.          return self._digits==other._digits
  635.  
  636.       if type(other) != ListType:
  637.          other=[other]
  638.       else:
  639.          # если передан список, то удаляем дубликаты и сортируем
  640.          other=list(set(other))
  641.  
  642.       return self._digits==other
  643.    
  644.    def __invert__(self):
  645.       ' инвертирование (~) '
  646.       result = list(set(range(1, 10)) - set(self._digits))
  647.       return Digits(result)
  648.  
  649.    def __len__(self):
  650.       ' количество возможных цифр '
  651.       return len(self._digits)
  652.  
  653.    def __contains__(self, num):
  654.       ' оператор in '
  655.       return num in self._digits
  656.    
  657.    def _test_values(self, values):
  658.       ''' Проверка значения для операций.
  659.      @param values: значение list или int или Digits или Cell
  660.      @return: список значений
  661.      @rtype: list
  662.      '''
  663.       # если параметр не список, то оборачиваем его
  664.       if isinstance(values, Cell):
  665.          values = values.digits().list()
  666.       elif isinstance(values, Digits):
  667.          values = values.list()
  668.       elif type(values) == TupleType:
  669.          values = list(values)
  670.       elif type(values) != ListType:
  671.          values = [values]
  672.  
  673.       values = list(set(values))
  674.       for val in values:
  675.          if type(val) != IntType:
  676.             raise TypeError('value must be a int (%s)' % type(val))
  677.          if val<1 or val>9:
  678.             raise ValueError('value out of range 1...9 (%i)'%val)
  679.    
  680.       return values
  681.  
  682. '''
  683. # Digits и Cell тесты
  684. c = Cell()
  685. d = Digits()
  686. c_copy = c
  687. print 'd: ',d
  688. print 'c: ',c
  689. print
  690.  
  691. c -= [1,2,3]
  692. d -= [1,2,3]
  693. print 'd: ',d
  694. print 'c: ',c
  695. print 'c2:',c_copy
  696. print
  697.  
  698. ~d
  699. ~c
  700. print 'd: ',d
  701. print 'c: ',c
  702. print 'c2:',c_copy
  703. print
  704.  
  705. d -= [4,2,9]
  706. c -= [4,2,9]
  707. print 'd: ',d
  708. print 'c: ',c
  709. print 'c2:',c_copy
  710. print
  711.  
  712. d_copy = d
  713. d += ~d
  714. c += Digits([3,2,5,2,6,8])
  715. print 'd: ',d
  716. print 'd2: ',d_copy
  717. print 'c: ',c
  718. print 'c2:',c_copy
  719. print Digits([1,5,3,1,1,1,1,1,1])
  720. '''
  721.  
  722. '''
  723. # Menu тесты
  724. def action(val):
  725.   print 'do action %s' % val
  726.  
  727.  
  728. menu = Menu((
  729.               ('Submenu 1',(
  730.                    ('Item 1',action,1),
  731.                    ('Item 2',action,2)
  732.               )),
  733.               ('Submenu 2',(
  734.                    ('Action 3',action,3),
  735.                    ('Action 4',action,4),
  736.                    ('Action 5',action,5),
  737.               )),
  738.               ('Main action',action,'main')
  739.             ))
  740. menu.back_title = 'Back'
  741. menu.exit_title = 'Quit'
  742. menu.activate()
  743. '''
  744.  
  745.  
  746. # Kakuro тесты
  747. kak = Kakuro()
  748. f = open("kak1.txt",'r')
  749. kak.parse(f.read())
  750.  
  751. kak.solve()
  752. print kak
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement