为什么不用 Python?,第 2 部分

作者:Collin Park

在开始下一个程序之前,我应该提到 Python 首页,它提供了最新版本的解释器和许多关于该语言的有用信息。特别是,这个教程 非常出色。不过,我仍然强烈建议您获取 Mark Lutz 和 David Ascher 合著的 Learning Python (O'Reilly 1999),它比在线教程解释得更透彻。您的 GNU/Linux 机器上也可能带有该教程和其他 Python 文档。我的笔记本电脑上的教程位于

/usr/doc/packages/pyth_doc/html/tut/tut.html

作为 Python 文档包的一部分,在我的情况下是 pyth_doc-1.5.1-11。最新的发行版可能同时包含 PDF 和 HTML 版本的文档,您也可以从 这里 或镜像站点下载它们。

说到发行版,我们需要考虑兼容性问题。我正在一台运行 SuSE Office'99 发行版的笔记本电脑上编写本文的大部分内容,该发行版包含这个版本的 Python

% python
Python 1.5.1 (#1, Sep 24 1998, 04:14:53)  [GCC 2.7.2.1] on linux2
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>>

我上面提到的在线教程要新得多;它自称为 Python 2.4.2 的一部分。本文中的所有程序都已在 Python 2.4 (SuSE 9.3) 和 Python 1.5.1 (SuSE 5.3 - Office'99) 上运行过。

好了,开始下一个程序。

您是否见过让您想编写程序来解决的谜题?几个月前,当我注意到当地报纸上的 数独 时,我就有这种感觉。

Why Not Python?, Part 2

正如您从上图看到的那样,这个谜题由一个 9x9 的矩阵组成,它像井字棋盘一样被划分为九个 3x3 的子矩阵。每个子矩阵、每行和每列都必须包含数字 1-9 中的每一个。

数据结构

编写一个程序来解决这个谜题应该不会太难。首先,您需要一个数据结构。您可以像这样将单元格从 0-80 编号

   0   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

或者,您可以为每一行创建一个数组,其中包含所有这些行数组的数组。您也可以用列来做。或者,您可以将每个子矩阵分组到一个一维或二维数组中,并形成一个包含这些子矩阵的一维或二维数组。

所有这些选项都是可能的,但是因为我们必须处理行、列和子矩阵,所以我决定坚持使用一维单元格数组,编号为 0-80。我还决定使用其他一些数据结构来处理行。

对于每个单元格,我们需要跟踪

  • 单元格中的数字(如果已知)

  • 单元格中可能出现的数字集合(如果确切的数字未知)

我们可以有一个数据结构,其中包含所有单元格的已知数字,另一个数据结构,其中包含所有单元格的可能数字集合。或者,我们可以有一个数据结构的单个数组,每个单元格对应一个数据结构。每个数据结构都将包含关于其单元格的所有信息,无论数字是已知还是未知。

某种东西——称之为程序员的直觉——让我感觉后一种选择会使编码更容易。

所以我们将有一个数组,编号为 0-80。数组中的每个元素都是一个数据结构,用于告诉我们数字(如果已知)。如果确切的数字未知,则数组会告诉我们可能数字的集合。i

算法

然而,数据结构本身不是程序;我们需要对其进行操作。程序必须做的,本质上是

  1. 读取指定的单元格的值,并对空白单元格做出一些初步的推断

  2. 填充空白单元格;也就是说,解决谜题)

  3. 打印出答案

回到 1970 年代,这个叫做“结构化编程”的新事物谈到了自顶向下设计和逐步求精。其思想是将大步骤分解为可以自信编码的小块。

我喜欢这个想法,但我也喜欢叫做“极限编程”的新新事物,它说要在编写代码之前编写测试用例。所以我决定在步骤 #1 中实践这个想法。我将一个谜题输入到代码中,并检查了一些空白单元格,以确保我们适当地限制了可能性。

步骤 1:求精、编码和测试

首先读取指定的单元格的值,并对空白单元格做出一些初步的推断。如果程序可以读取报纸并进行光学字符识别 (OCR) 以识别数字,那可能会很酷。但即使可以,OCR 部分仍然需要将数字传达给问题解决部分。让我们将自己限制在问题解决部分,并将 OCR 留给键盘后面的人。

那么,让程序从文件中读取数字。与其做任何花哨的事情,不如让它忽略空格。让每个数字代表它自己,并使用 - 来表示空白单元格。我们也可以允许 . 表示空白单元格,这就是我在 数独 留言板上看到的内容。因此,以下将是有效的输入

        1-234----
        ---567---
        --7---584
        63-7-4---
        48-----97
        ---1-5-43
        578---9--
        ---973---
        ----564-2

就像这样

        1.234.......567.....7...58463.7.4...48.....97
        ...1.5.43578...9.....973.......564.2

或者这样

         1 - 2 3 4 - - - -
         - - - 5 6 7 - - -
         - - 7 - - - 5 8 4
         6 3 - 7 - 4 - - -
         4 8 - - - - - 9 7
         - - - 1 - 5 - 4 3
         5 7 8 - - - 9 - -
         - - - 9 7 3 - - -
         - - - - 5 6 4 - 2

所有这些输入变体都意味着完全相同的事情。也就是说,所有这些都代表了从报纸上扫描的图像中显示的谜题。

现在,除了读取字符外,程序还应该在数据结构中填充适当的值。实际上,甚至在我们开始之前,我们应该初始化每个单元格的结构,以表明该值未知,但可以是任何值。换句话说,可能数字的集合将是 {1,2,3,4,5,6,7,8,9}。

然后,当我们读取值时,当我们找到一个已知值时,我们将“如果已知”部分设置为该值。然后,我们将从该单元格所在行、列和 3x3 子矩阵的 set_of_possibles 中删除该值。

行号范围为 (0,9),单元格 0-8 在第 0 行,单元格 9-17 在第 1 行,依此类推。我们可以通过取 int(pos/9) 来计算行号,其中 pos 是单元格位置。

列号范围为 (0,9),单元格 0,9,18,27... 在第 0 列;单元格 1,10,19,28... 在第 1 列,依此类推。列号通过取 (pos%9) 计算得出。

Why Not Python?, Part 2

子矩阵编号为 0-8。行号在 (0,3) 范围内且列号在 (0,3) 范围内的单元格位于子矩阵 0 中;列号在 (3,6) 范围内的单元格位于子矩阵 1 中,依此类推。这些子矩阵的布局如下

Why Not Python?, Part 2

因此,子矩阵 0 由单元格 0,1,2,9,10,11,18,19,20 组成。子矩阵 1 由单元格 3,4,5,12,13,14,21,22,23 组成。为了计算子矩阵编号,我们不需要确切的行号和列号;我们只需要 int(myrow/3) 和 int(mycol/3)

    

        mysub = int(myrow/3) * 3 + int(mycol/3)

为了测试代码的这一部分,我们输入上面的示例并使用 Python 调试器 (pdb) 来检查它

  • 虽然单元格 8(右上角)尚“未知”,但它可能是 6 或 9。

  • 单元格 72(左下角)可以是 3 或 9。

  • 子矩阵 2 包含单元格 8。

  • 子矩阵 6 包含单元格 72。

现在,为了完成步骤 1 的“逐步求精”部分,我们将每个单元格初始化为包含

        value=unknown
        set_of_possibles={1,2,3,4,5,6,7,8,9}

我们还从输入文件中读取每个值。如果值未知——“-”或“.”——则保持单元格不变。如果已知,则将数据结构的数字(“如果已知”部分)设置为该已知值。然后,清除该单元格的 set_of_possibles。最后,从该单元格所在行、列和 3x3 子矩阵的可能数字集合中删除该值。

好了,让我们开始编码吧!我们有一个单元格数组(Python 中的“列表”)。对于每个单元格,我们需要了解它属于哪些行、列和子矩阵。

Python 书籍谈到了“类”,这将给我一个尝试面向对象编程的机会。

好的,经过一番思考,这是我想出的。注意:与 第 1 部分 中的“Coconuts”程序不同,后者简短易懂,而这个程序我遇到了一些错误。详细信息在附录 A 中概述。

    1   class Cell:
    2      rows = [ [], [], [], [], [], [], [], [], [] ]    # nine each...
    3      columns = [ [], [], [], [], [], [], [], [], [] ]
    4      submatrices = [ [], [], [], [], [], [], [], [], [] ]
    5   
    6      # for step 1.1, do "cells[i]=Cell(i)" for i in range(0,81)
    7      def __init__(self,pos):
    8         # "pos" = position in the puzzle.  Valid values: range (0,81)
    9         global rows, columns, submatrices
   10         if pos not in range(0,81): raise Illegal_pos_in_Cells_initializer
   11         self.pos = pos
   12         self.value = 0
   13         self.set_of_possibles = range(1, 10)  # 1-9 INclusive
   14   
   15         # For step 1.2.2b, track which row, col, sub that I belong to.
   16         myrow = int(pos / 9)
   17         mycol = pos % 9
   18         mysub = int(myrow/3) * 3 + int(mycol/3)
   19   
   20         self.row = Cell.rows[myrow]
   21         self.col = Cell.columns[mycol]
   22         self.sub = Cell.submatrices[mysub]
   23   
   24         self.row.append(self)
   25         self.col.append(self)
   26         self.sub.append(self)
   27   
   28      def known(self):
   29         return (self.value != 0)
   30   
   31      # setvalue is used for 1.2.2
   32      def setvalue(self, val):
   33         # a couple of sanity checks
   34         if val not in range(1,9+1): raise val_must_be_between_1_and_9
   35         if val not in self.set_of_possibles: raise setting_impossible_value
   36         if self.known(): raise setvalue_called_but_already_known
   37   
   38         self.value = val              # 1.2.2a
   39   
   40         # Now do 1.2.2b
   41         for other in self.row + self.col + self.sub:
   42            if other is self: continue
   43            if other.known(): continue
   44            if val in other.set_of_possibles:
   45               # Python 1.5.1 had "remove", which isn't in the book.
   46               # Deprecated?
   47               x = other.set_of_possibles.index(val)
   48               del other.set_of_possibles[x]
   49   
   50   import sys
   51   def doit(argv):
   52      cells = []
   53      for i in range(0,81): cells.insert(i,Cell(i))          # 1.1
   54   
   55      # Here, any cell's set_of_possibles should be full
   56   
   57      if len(argv) > 1 and argv[1]:
   58         input = open(argv[1], 'r')
   59      else:
   60         input = sys.stdin
   61   
   62      all_input_lines = input.readlines()
   63      input.close()
   64   
   65      which_cell = 0
   66      for one_input_line in all_input_lines:
   67         for char in one_input_line:
   68            if char in '\t\n ': continue
   69            if char in '-.': 
   70               which_cell = which_cell + 1
   71            elif ord(char) in range (ord('1'), ord('9')+1):
   72               cells[which_cell].setvalue(ord(char)-ord('0'))
   73               which_cell = which_cell + 1
   74            if which_cell > 81: raise too_much_input
   75      pass   # so the debugger can break here
   76   
   77   # main begins here
   78   doit(sys.argv)

我将尝试解释一下。第 1-48 行描述了一个名为“Cell”的类,它具有

  • 三个列表:行、列和子矩阵(第 2-4 行)

  • 一个初始化函数(第 7-26 行)

  • 一个用于说明给定单元格的值是否已知的函数(第 28-29 行)

  • 一个实现该算法的函数(第 32-48 行)

类“Cell”的每个实例代表谜题中的一个单元格,并包括以下属性

  • pos:在谜题中的位置。0 表示左上角,80 表示右下角。

  • value:如果尚不知道则为零;否则,是 1 到 9 之间的数字(包括 1 和 9)。

  • set_of_possibles:一个 Python 列表,其中包含我们所知的单元格可能的值。一旦值已知,set_of_possibles 就会被清除(设置为空列表)。

  • row:一个单元格列表,其中包含与此单元格在同一行中的单元格;它实际上是对 Cell.rows 元素的引用。

  • col, sub:类似于“row”,但分别对应于单元格的列和子矩阵。

行、列和子矩阵的计算在第 16-18 行中进行。此 __init__ 函数假定它将为 range(0,81) 中“pos”的每个值精确调用一次。

对于设置单元格的值,调用“setvalue”函数。在第 34-36 行中进行了一些简单的检查——值是否合法?您是否将单元格设置为我们已经知道它不能是的值?——值本身在第 38 行中设置。第 41-48 行查找同一行、列或子矩阵中的所有单元格,并从 set_of_possibles 中删除“val”。

第 51-75 行描述了“doit”函数。我的原始版本没有将其作为函数,但现在将其放入函数中是为了更方便地进行调试。它初始化 cells[] 列表,然后决定(第 57-60 行)是从文件还是从 stdin 获取谜题。

第 66-74 行解释输入并为相应的单元格调用“setvalue”函数。

第 78 行只是告诉 Python,当运行时,它应该执行“doit”函数。

下一个问题是:代码真的有效吗?事实证明,pdb 不完全像 gdb,因为我必须修改模块才能调试它。我的意思是:您导入模块,导入 pdb,然后执行 pdb.run 函数。但是当您导入模块时,它会执行模块中的所有内容。这就是为什么我使用函数“doit”,它在第 51 行执行所有操作。为了调试 s0.py,我还必须注释掉第 78 行,像这样

     % vi s0.py              ## could have used any other editor...
     % pr -tn s0.py|tail
        69            if char in '-.': 
        70               which_cell = which_cell + 1
        71            elif ord(char) in range (ord('1'), ord('9')+1):
        72               cells[which_cell].setvalue(ord(char)-ord('0'))
        73               which_cell = which_cell + 1
        74            if which_cell > 81: raise too_much_input
        75      pass   # so the debugger can break here
        76   
        77   # main begins here
        78   # doit(sys.argv)     ## COMMENT OUT FOR DEBUGGING
     % 

解决了这个问题之后,回顾一下四个测试用例

  • 虽然单元格 8(右上角)尚“未知”,但它可能是 6 或 9。

  • 单元格 72(左下角)可以是 3 或 9。

  • 子矩阵 2 包含单元格 8。

  • 子矩阵 6 包含单元格 72。

那么让我们试试看!

     % python
     Python 1.5.1 (#1, Sep 24 1998, 04:14:53)  [GCC 2.7.2.1] on linux2
     Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
     >>> import s0

    ### The file is "s0.py" but when "import"ing, you don't type ".py"

     >>> import pdb
     >>> pdb.run('s0.doit(["s0.py", "1109.puz"])')

    ### For pdb.run, you have to qualify the name of the function with
    ### the module name.  

     > <string>(0)?()
     (Pdb) break s0.doit
    (Pdb) n
     > s0.py(51)doit()
     -> def doit(argv):
     (Pdb) l 70
      65        which_cell = 0
      66        for one_input_line in all_input_lines:
      67           for char in one_input_line:
      68              if char in '\t\n ': continue
      69              if char in '-.': 
      70                 which_cell = which_cell + 1
      71              elif ord(char) in range (ord('1'), ord('9')+1):
      72                 cells[which_cell].setvalue(ord(char)-ord('0'))
      73                 which_cell = which_cell + 1
      74              if which_cell > 81: raise too_much_input
      75        pass   # so the debugger can break here
     (Pdb) b 75

    ### I wanted to have a breakpoint at the end of the function doit,
    ### so I put in an artificial "pass" statement in line 75

     (Pdb) c
     > s0.py(75)doit()
     -> pass   # so the debugger can break here
     (Pdb) p cells[8].set_of_possibles
     [6, 9]
     (Pdb) p cells[8].known()
     0

    ### My first test case: cell 8 is not known, and its possible values
    ### are 6 and 9.  Whee!

     (Pdb) p cells[72].set_of_possibles
     [3, 9]
     (Pdb) p cells[72].known()
     0
     (Pdb) for x in Cell.submatrices[2]: print x.pos,
     6 7 8 15 16 17 24 25 26(Pdb) print 

    ### Sorry for the ugly formatting.  I wanted to print out the 
    ### "cell numbers" of each cell in the "submatrices" array and 
    ### do it in a single line.  It printed the cells in submatrices[2]
    ### and didn't start a new line -- so the Pdb prompt was stuck on
    ### the end of the line.  Saying "print" got us to a new line.

    ### Bottom line, though: submatrices[2] does contain cell 8.
    ### And submatrices[6] contains cell 72:

     (Pdb) for x in Cell.submatrices[6]: print x.pos,
     54 55 56 63 64 65 72 73 74(Pdb) print

     (Pdb) quit
     >>> 
     % 

这就是算法步骤 1 的代码。

在没有调试器的情况下运行

拥有调试器比没有调试器要好,但是调试器需要几个手动步骤——这很费力。我相信 Perl 的发明者 Larry Wall 将“懒惰”列为优秀程序员的一个属性。在测试方面,Larry 尤其正确。必须易于运行测试,否则人们——在这种情况下指的是我,可能也包括您——将无法正确地执行测试。结果将是错误的,要么是误报,要么是漏报。因此,最好不必使用调试器来测试我们的程序。因此,让我们编写步骤 3,“粘合”到步骤 1 上,并将这两个部分一起测试。

编写步骤 3

现在我们需要打印出答案。我们应该在这里做的是打印出每个单元格中的数字。如果我们在步骤 1 之后立即运行它,则某些单元格将是未知的。让我们将这些单元格打印为“-”,即以该程序稍后可以读取的形式。

我们可以将此添加到上次程序的末尾。

       for bor in range(0,81,9):
          for i in range(bor,bor+9):
             if cells[i].value > 0: print cells[i].value,        # usual case
             elif len(cells[i].set_of_possibles) > 0: print '-', # unknown
             else: print '?',                                    # inconsistent!!
          print                        # end of this row

结合第 1 部分和第 3 部分,我们得到

        % python
        Python 1.5.1 (#1, Sep 24 1998, 04:14:53)  [GCC 2.7.2.1] on linux2
        Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
        >>> import s1  
        >>> import pdb
        >>> pdb.run('s1.doit(["p1", "1109.puz"])')
        > <string>(0)?()
        (Pdb) n
        > <string>(1)?()
        (Pdb) n
        1 - 2 3 4 - - - -
        - - - 5 6 7 - - -
        - - 7 - - - 5 8 4
        6 3 - 7 - 4 - - -
        4 8 - - - - - 9 7
        - - - 1 - 5 - 4 3
        5 7 8 - - - 9 - -
        - - - 9 7 3 - - -
        - - - - 5 6 4 - 2
        --Return--
        > <string>(1)?()->None
        (Pdb) quit
        >>> 
        % 

它工作了,所以我更改了程序以在最后打印输出,并使 doit 函数在没有干预的情况下运行。这样,我就不必每次都运行调试器了。程序末尾现在看起来像这样


    % pr -tn s1.py | tail -12
       77      # Step 2 should go here
       78   
       79      # Code for step 3
       80      for bor in range(0,81,9):
       81         for i in range(bor,bor+9):
       82            if cells[i].value > 0: print cells[i].value,        # usual case
       83            elif len(cells[i].set_of_possibles) > 0: print '-', # unknown
       84            else: print '?',                                    # inconsistent!!
       85         print                        # end of this row
       86   
       87   # main begins here
       88   doit(sys.argv)     ## COMMENT OUT FOR DEBUGGING

它可以像这样处理输入

        % python s1.py 1109.puz 
        1 - 2 3 4 - - - -
        - - - 5 6 7 - - -
        - - 7 - - - 5 8 4
        6 3 - 7 - 4 - - -
        4 8 - - - - - 9 7
        - - - 1 - 5 - 4 3
        5 7 8 - - - 9 - -
        - - - 9 7 3 - - -
        - - - - 5 6 4 - 2
        %
 

查看上面内容,我意识到我以前的 C 程序员的自己违反了面向对象编程的原则之一——信息隐藏。“Cell”类应该完全封装单元格数据结构的内部,用户应该使用访问器函数(例如 setvalue)来访问内部。第 82 行和第 83 行“窥视”了数据结构内部,因此让我添加一个“getvalue”访问器函数,并更改步骤 3 代码以使用“known”函数。以下是更改的部分,带有“*”表示新行或更改的行

  47               x = other.set_of_possibles.index(val)
  48               del other.set_of_possibles[x]
  49   
* 50      def getvalue(self): return self.value
  51   
  52   import sys
  53   def doit(argv):
  ...
  81      # Code for step 3
  82      for bor in range(0,81,9):
  83         for i in range(bor,bor+9):
* 84            if cells[i].known(): print cells[i].getvalue(),     # usual case
* 85            else: print '-',                                    # unknown
  86         print                        # end of this row
  87   
  88   # main begins here

这个版本更简洁,而且它仍然可以在没有调试器的情况下运行。

我们将在本文的下一部分处理算法的步骤 2。

附录 A:代码是如何生成的

这是我上面代码的原始版本。它包含几个错误

    1   class Cell:
    2      rows = [ [], [], [], [], [], [], [], [], [] ]    # nine each...
    3      columns = [ [], [], [], [], [], [], [], [], [] ]
    4      submatrices = [ [], [], [], [], [], [], [], [], [] ]
    5   
    6      # for step 1.1, do "cells[i]=Cell(i)" for i in range(0,81)
    7      def __init__(self,pos):
    8         # "pos" = position in the puzzle.  Valid values: range (0,81)
    9         global rows, columns, submatrices
   10         if pos not in range(0,81): throw Illegal_pos_in_Cells_initializer
   11         self.pos = pos
   12         self.value = 0
   13         self.set_of_possibles = range(1, 10)  # 1-9 INclusive
   14   
   15         # For step 1.2.2b, track which row, col, sub that I belong to.
   16         myrow = int(pos / 9)
   17         mycol = pos % 9
   18         mysub = int(myrow/3) * 3 + int(mycol/3)
   19         # The above calculation of "mysub" may be a little obscure, so
   20         # let me explain.  
   21         # 
   22   
   23         self.row = rows[myrow]
   24         self.col = columns[mycol]
   25         self.sub = submatrices[mysub]
   26   
   27         rows[myrow].append(self)
   28         columns[mycol].append(self)
   29         submatrices[mysub].append(self)
   30   
   31      def known(self):
   32         return (self.value != 0)
   33   
   34      # setvalue is used for 1.2.2
   35      def setvalue(self, val):
   36         # a couple of sanity checks
   37         if val not in range(1,9+1): throw val_must_be_between_1_and_9
   38         if val not in self.set_of_possibles: throw setting_impossible_value
   39         if self.known(): throw setvalue_called_but_already_known
   40   
   41         self.value = val              # 1.2.2a
   42   
   43         # Now do 1.2.2b
   44         for other in self.row + self.col + self.sub:
   45            if other is self: continue
   46            if other.known(): continue
   47            if val in other.set_of_possibles:
   48               # Python 1.5.1 had "remove", which isn't in the book.
   49               # Deprecated?
   50               x = other.set_of_possibles.index(val)
   51               other.set_of_possibles.del(x)
   52   
   53   import sys
   54   # main begins here
   55   cells = []
   56   for i in range(0,81): cells.insert(i,Cells(i))          # 1.1
   57   
   58   # Here, any cell's set_of_possibles should be full
   59   
   60   if sys.argv[1]:
   61      input = open(sys.argv[1], 'r')
   62   else:
   63      input = sys.stdin
   64   
   65   all_input_lines = input.readlines()
   66   input.close()
   67   
   68   which_cell = 0
   69   for one_input_line in all_input_lines:
   70      for char in one_input_line:
   71         if char in '\t\n ': continue
   72         if char in '-.': 
   73            which_cell = which_cell + 1
   74         elif ord(char) in range (ord('1'), ord('9')+1):
   75            cells[which_cell].setval(ord(char)-ord('0'))
   76            which_cell = which_cell + 1
   77         if which_cell >= 81: throw too_much_input

在代码实际运行之前,我尝试和更改了六次。以下是发生的情况。当我想引发异常时,我写了“throw”而不是“raise”。虽然我读过精细的手册,但我忘记了魔法词,而 Python 不喜欢这样

        % python s0.py 
          File "s0.py", line 10
            if pos not in range(0,81): throw Illegal_pos_in_Cells_initializer
                                                                            ^
        SyntaxError: invalid syntax
        %

所以我将所有“throw”都更改为“raise”。

因为追加到列表是使用 LIST.append() 完成的,所以我不知何故认为删除应该以相同的方式完成。

        % python s0.py
          File "s0.py", line 51
            other.set_of_possibles.del(x)
                                     ^
        SyntaxError: invalid syntax
        %
 

正确的语法是

        del other.set_of_possibles[x]

然后,我犯了一个打字错误。我在第 56 行中键入了“Cells”而不是“Cell”。“Cell”是类的名称,“cells”是一个数组,或者在 Python 中是“列表”

        % python s0.py
        Traceback (innermost last):
          File "s0.py", line 56, in ?
            for i in range(0,81): cells.insert(i,Cells(i))          # 1.1
        NameError: Cells
        %
 

将其更改为“Cell(i)”解决了问题。

下一个错误更难修复;这是一个名称作用域问题

        % python s0.py
        Traceback (innermost last):
          File "s0.py", line 56, in ?
            for i in range(0,81): cells.insert(i,Cell(i))          # 1.1
          File "s0.py", line 23, in __init__
            self.row = rows[myrow]
        NameError: rows
        % 

在查阅了书籍后,我意识到我必须在这里使用“Cell.rows”。对类数据的全部引用都需要通过类名来限定,即“Cell”。因此,在修复了它以及“Cell.columns”和“Cell.submatrices”之后,情况看起来好多了。

在语法错误得到控制之后,还有一些其他的愚蠢错误需要修复,例如这个

        % python s0.py
        Traceback (innermost last):
          File "s0.py", line 60, in ?
            if sys.argv[1]:
        IndexError: list index out of range
        %
 

在没有首先检查 sys.argv 的长度的情况下,我无法确定 sys.argv[1] 是否存在。因此,现在我在访问 sys.argv 的长度之前检查了它的长度。

修复了这个问题之后,我尝试运行代码。它没有立即报错,而是等待输入。现在我开始有所进展了。我使用鼠标从上面复制粘贴了谜题

        % python s0.4.py
                 1 - 2 3 4 - - - -
                 - - - 5 6 7 - - -
                 - - 7 - - - 5 8 4
                 6 3 - 7 - 4 - - -
                 4 8 - - - - - 9 7
                 - - - 1 - 5 - 4 3
                 5 7 8 - - - 9 - -
                 - - - 9 7 3 - - -
                 - - - - 5 6 4 - 2
        Traceback (innermost last):
          File "s0.4.py", line 75, in ?
            cells[which_cell].setval(ord(char)-ord('0'))
        AttributeError: setval
        %
 

又一个打字问题!我使用了“setval”而不是“setvalue”,这很容易修复。

我将谜题数据放入一个名为 1109.puz 的文件中,并以这种方式运行程序;我不喜欢使用鼠标。

下一个问题是这个

        % python s0.5.py 1109.puz 
        Traceback (innermost last):
          File "s0.5.py", line 77, in ?
            if which_cell >= 81: raise too_much_input
        NameError: too_much_input
        %
 

which_cell 总是告诉我到目前为止我已经处理了多少个单元格。因此,在读取了最后一个单元格(数组中的第 80 个单元格)的信息之后,which_cell 将为 81。换句话说,这是一个差一错误。我将“>=”更改为“>”,之后我得到了

        % python s0.6.py 1109.puz 
        %
 

万岁!没有报错。最终版本就是本文主要部分中出现的内容。

Collin Park 在 Network Appliance 工作,他在那里使用 Linux 桌面和笔记本电脑。他在家从事数据恢复和其他与 Linux 相关的工作,他和妻子以及两个十几岁的女儿住在一起。他们都使用 Linux 来满足他们的计算需求。

加载 Disqus 评论