🐞 fix(4.3人工智能导论及机器学习): 按照CS50AI课程note格式化文档
删除了大量“无辜”的无序列表
This commit is contained in:
@@ -29,55 +29,58 @@ $ python generate.py data/structure1.txt data/words1.txt output.png
|
||||
|
||||
## 背景
|
||||
|
||||
- 你如何生成一个填字游戏?考虑到填字游戏的结构(即网格中哪些方格需要填入字母),以及要使用的单词列表,问题就变成了选择哪些单词应该填入每个垂直或水平的方格序列。我们可以将这种问题建模为一个约束满足问题。每一个方格序列都是一个变量,我们需要决定它的值(在可能的单词域中哪个单词将被填入该序列)。考虑一下下面的字谜结构。
|
||||
你如何生成一个填字游戏?考虑到填字游戏的结构(即网格中哪些方格需要填入字母),以及要使用的单词列表,问题就变成了选择哪些单词应该填入每个垂直或水平的方格序列。我们可以将这种问题建模为一个约束满足问题。每一个方格序列都是一个变量,我们需要决定它的值(在可能的单词域中哪个单词将被填入该序列)。考虑一下下面的字谜结构。
|
||||
|
||||

|
||||
|
||||
- 在这个结构中,我们有四个变量,代表了我们需要填入这个字谜的四个单词(在上图中每个单词都用数字表示)。每个变量由四个值定义:它开始的行(`i`值),它开始的列(`j`值),单词的方向(纵向或横向down or across),以及单词的长度。例如,`变量1`将是一个由第1行(假设从顶部计数的0索引)、第1列(假设从左边计数的0索引)、方向为`across`和`4`的长度表示的变量。
|
||||
在这个结构中,我们有四个变量,代表了我们需要填入这个字谜的四个单词(在上图中每个单词都用数字表示)。每个变量由四个值定义:它开始的行(`i`值),它开始的列(`j`值),单词的方向(纵向或横向down or across),以及单词的长度。例如,`变量1`将是一个由第1行(假设从顶部计数的0索引)、第1列(假设从左边计数的0索引)、方向为`across`和`4`的长度表示的变量。
|
||||
|
||||
- 与许多约束满足问题一样,这些变量有一元和二元约束。变量的一元约束是由其长度决定的。例如,对于`变量1`来说,数值`BYTE`可以满足一元约束,但是数值`BIT`则不能(它有错误的字母数量)。因此,任何不满足变量的一元约束的值都可以立即从变量的域中删除。
|
||||
与许多约束满足问题一样,这些变量有一元和二元约束。变量的一元约束是由其长度决定的。例如,对于`变量1`来说,数值`BYTE`可以满足一元约束,但是数值`BIT`则不能(它有错误的字母数量)。因此,任何不满足变量的一元约束的值都可以立即从变量的域中删除。
|
||||
|
||||
- 变量的二元约束是由其与相邻变量的重合度给出的。`变量1`有一个邻居:`变量2`。`变量2`有两个邻居:`变量1`和`变量3`。对于每一对相邻的变量,这些变量都有一个重叠部分:一个它们共同的单方块。我们可以将这种重叠表示为每个变量有用相同字符位置的索引对。例如,`变量1`和`变量2`之间的重叠可以表示为一对`(1, 0)`,这意味着`变量1`在索引1处的字符必然与`变量2`在索引0处的字符相同(再次假设索引从0开始)。因此,`变量2`和`变量3`之间的重叠将被表示为一对`(3, 1)`,`变量2`的值的字符`3`必须与`变量3`的值的字符`1`相同。
|
||||
变量的二元约束是由其与相邻变量的重合度给出的。`变量1`有一个邻居:`变量2`。`变量2`有两个邻居:`变量1`和`变量3`。对于每一对相邻的变量,这些变量都有一个重叠部分:一个它们共同的单方块。我们可以将这种重叠表示为每个变量有用相同字符位置的索引对。例如,`变量1`和`变量2`之间的重叠可以表示为一对`(1, 0)`,这意味着`变量1`在索引1处的字符必然与`变量2`在索引0处的字符相同(再次假设索引从0开始)。因此,`变量2`和`变量3`之间的重叠将被表示为一对`(3, 1)`,`变量2`的值的字符`3`必须与`变量3`的值的字符`1`相同。
|
||||
|
||||
- 对于这个问题,我们将增加一个额外的约束条件,即所有的单词必须是不同的:同一个单词不应该在谜题中重复出现多次。
|
||||
对于这个问题,我们将增加一个额外的约束条件,即所有的单词必须是不同的:同一个单词不应该在谜题中重复出现多次。
|
||||
|
||||
- 那么,接下来的挑战是写一个程序来找到一个满意的赋值:为每个变量提供一个不同的词(来自给定的词汇表),从而满足所有的一元和二元约束。
|
||||
理解
|
||||
那么,接下来的挑战是写一个程序来找到一个满意的赋值:为每个变量提供一个不同的词(来自给定的词汇表),从而满足所有的一元和二元约束。
|
||||
|
||||
- 这个项目中有两个Python文件:`crossword.py`和`generate.py`。第一个文件已经完全为你写好了,第二个文件有一些函数留给你去实现。
|
||||
## 理解
|
||||
|
||||
- 首先,让我们看一下`crossword.py`。这个文件定义了两个类,`Variable`(代表填字游戏中的变量)和`Crossword`(代表填字游戏本身)。
|
||||
这个项目中有两个Python文件:`crossword.py`和`generate.py`。第一个文件已经完全为你写好了,第二个文件有一些函数留给你去实现。
|
||||
|
||||
- 注意,要创建一个变量,我们必须指定四个值:它的第`i`行,第`j`列,它的方向(常数`Variable.ACROSS`或常数`Variable.DOWN``),以及它的长度(`length``)。
|
||||
首先,让我们看一下`crossword.py`。这个文件定义了两个类,`Variable`(代表填字游戏中的变量)和`Crossword`(代表填字游戏本身)。
|
||||
|
||||
- 字谜类需要两个值来创建一个新的字谜:一个定义了字谜结构的`structure_file`(`_`用来代表空白单元格,任何其他字符都代表不会被填入的单元格)和一个定义了字词列表(每行一个)的`word_file`,用来作为游戏的词汇表。这些文件的三个例子可以在项目的数据目录中找到,也欢迎你自己创建。
|
||||
注意,要创建一个变量,我们必须指定四个值:它的第`i`行,第`j`列,它的方向(常数`Variable.ACROSS`或常数`Variable.DOWN``),以及它的长度(`length``)。
|
||||
|
||||
- 特别要注意的是,对于任何一个字谜对象的字谜,我们都会存储以下的数值:
|
||||
- `crossword.height`是一个整数,代表填字游戏的高度。
|
||||
- `crossword.width`是一个整数,代表填字游戏的宽度。
|
||||
- `crossword.structure`是一个二维列表,代表字谜的结构。对于任何有效的第i行和第j列,如果该单元格是空白的,`crossword.structure[i][j]`将为真(必须在该单元格中填入一个字符),否则将为假(该单元格中没有字符要填)。
|
||||
- `crossword.words`是一个包含所有单词的集合,在构建填字游戏的时候,可以从这些单词中提取。
|
||||
- `crossword.variables`是谜题中所有变量的集合(每个变量都是一个Variable对象)。
|
||||
- `crossword.overlaps`是一个字典,它将一对变量映射到它们的重合处。对于任何两个不同的变量v1和v2,如果这两个变量没有重叠,`crossword.overlaps[v1, v2]`将是`None`,如果这两个变量有重叠,则是一对整数`(i, j)`。这对`(i, j)`应被解释为:`v1`的第`i`个字符的值必须与`v2`的第`j`个字符的值相同。
|
||||
字谜类需要两个值来创建一个新的字谜:一个定义了字谜结构的`structure_file`(`_`用来代表空白单元格,任何其他字符都代表不会被填入的单元格)和一个定义了字词列表(每行一个)的`word_file`,用来作为游戏的词汇表。这些文件的三个例子可以在项目的数据目录中找到,也欢迎你自己创建。
|
||||
|
||||
- `Crossword`对象还支持一个方法`neighbors`,它可以返回与给定变量重叠的所有变量。也就是说,`crossword.neighbors(v1)`将返回一个与变量`v1`相邻的所有变量的集合。
|
||||
特别要注意的是,对于任何一个字谜对象的字谜,我们都会存储以下的数值:
|
||||
|
||||
- 接下来,看一下`generate.py`。在这里,我们定义了一个`CrosswordCreator`类,我们将用它来解决填字游戏。当一个`CrosswordCreator`对象被创建时,它得到一个填字游戏的属性,它应该是一个`Crossword`对象(因此具有上面描述的所有属性)。每个`CrosswordCreator`对象还得到一个域属性:一个字典,它将变量映射到该变量可能作为一个值的一组词。最初,这组词是我们词汇表中的所有词,但我们很快就会写函数来限制这些域。
|
||||
- `crossword.height`是一个整数,代表填字游戏的高度。
|
||||
- `crossword.width`是一个整数,代表填字游戏的宽度。
|
||||
- `crossword.structure`是一个二维列表,代表字谜的结构。对于任何有效的第i行和第j列,如果该单元格是空白的,`crossword.structure[i][j]`将为真(必须在该单元格中填入一个字符),否则将为假(该单元格中没有字符要填)。
|
||||
- `crossword.words`是一个包含所有单词的集合,在构建填字游戏的时候,可以从这些单词中提取。
|
||||
- `crossword.variables`是谜题中所有变量的集合(每个变量都是一个Variable对象)。
|
||||
- `crossword.overlaps`是一个字典,它将一对变量映射到它们的重合处。对于任何两个不同的变量v1和v2,如果这两个变量没有重叠,`crossword.overlaps[v1, v2]`将是`None`,如果这两个变量有重叠,则是一对整数`(i, j)`。这对`(i, j)`应被解释为:`v1`的第`i`个字符的值必须与`v2`的第`j`个字符的值相同。
|
||||
|
||||
- 我们还为你定义了一些函数,以帮助你测试你的代码:`print`将向终端打印你的填字游戏的一个给定的赋值(每个赋值,在这个函数和其他地方,是一个字典,将变量映射到它们相应的词)。同时,`save`将生成一个与给定作业相对应的图像文件(如果你无法使用这个函数,你需要`pip3 install Pillow`)。 `letter_grid`是一个被`print`和`save`使用的辅助函数,它为给定的赋值生成一个所有字符在其适当位置的2D列表:你可能不需要自己调用这个函数,但如果你想的话,欢迎你这样做。
|
||||
`Crossword`对象还支持一个方法`neighbors`,它可以返回与给定变量重叠的所有变量。也就是说,`crossword.neighbors(v1)`将返回一个与变量`v1`相邻的所有变量的集合。
|
||||
|
||||
- 最后,注意`solve`函数。这个函数做了三件事:首先,它调用`enforce_node_consistency`来强制执行填字游戏的节点一致性,确保变量域中的每个值都满足一元约束。接下来,该函数调用`ac3`来强制执行弧一致性,确保二元约束得到满足。最后,该函数在最初的空赋值(空字典dict())上调用`backtrack`,试图计算出问题的解决方案。
|
||||
接下来,看一下`generate.py`。在这里,我们定义了一个`CrosswordCreator`类,我们将用它来解决填字游戏。当一个`CrosswordCreator`对象被创建时,它得到一个填字游戏的属性,它应该是一个`Crossword`对象(因此具有上面描述的所有属性)。每个`CrosswordCreator`对象还得到一个域属性:一个字典,它将变量映射到该变量可能作为一个值的一组词。最初,这组词是我们词汇表中的所有词,但我们很快就会写函数来限制这些域。
|
||||
|
||||
- 不过,`enforce_node_consistency`、`ac3`和`backtrack`等函数还没有实现(以及其他函数)。这就是你的任务。
|
||||
我们还为你定义了一些函数,以帮助你测试你的代码:`print`将向终端打印你的填字游戏的一个给定的赋值(每个赋值,在这个函数和其他地方,是一个字典,将变量映射到它们相应的词)。同时,`save`将生成一个与给定作业相对应的图像文件(如果你无法使用这个函数,你需要`pip3 install Pillow`)。 `letter_grid`是一个被`print`和`save`使用的辅助函数,它为给定的赋值生成一个所有字符在其适当位置的2D列表:你可能不需要自己调用这个函数,但如果你想的话,欢迎你这样做。
|
||||
|
||||
最后,注意`solve`函数。这个函数做了三件事:首先,它调用`enforce_node_consistency`来强制执行填字游戏的节点一致性,确保变量域中的每个值都满足一元约束。接下来,该函数调用`ac3`来强制执行弧一致性,确保二元约束得到满足。最后,该函数在最初的空赋值(空字典dict())上调用`backtrack`,试图计算出问题的解决方案。
|
||||
|
||||
不过,`enforce_node_consistency`、`ac3`和`backtrack`等函数还没有实现(以及其他函数)。这就是你的任务。
|
||||
|
||||
## 明确
|
||||
|
||||
- 完成`grece_node_consistency`, `revise`, `ac3`, `assignment_complete`, `consistent`, `order_domain_values`, `selected_unassigned_variable`和`backtrack`在`generate.py`中的实现,这样如果有有解的话你的人工智能就能生成完整的字谜。
|
||||
完成`grece_node_consistency`, `revise`, `ac3`, `assignment_complete`, `consistent`, `order_domain_values`, `selected_unassigned_variable`和`backtrack`在`generate.py`中的实现,这样如果有有解的话你的人工智能就能生成完整的字谜。
|
||||
|
||||
- `enforce_node_consistency`函数应该更新`self.domains`,使每个变量都是节点一致的。
|
||||
- 回顾一下,当对每个变量来说,其域中的每个值都与该变量的一元约束一致时,就实现了节点一致性。在填字游戏的情况下,这意味着要确保变量域中的每个值的字母数与变量的长度相同。
|
||||
- 要从一个变量`v`的域中移除一个值`x`,因为`self.domains`是一个将变量映射到数值集的字典,你可以调用`self.domains[v].remove(x)`。
|
||||
- 这个函数不需要返回值。
|
||||
|
||||
- `revise`函数应该使变量x与变量y保持弧一致性。
|
||||
- `x`和`y`都是`Variable`对象,代表谜题中的变量。
|
||||
- 回顾一下,当`x`的域中的每一个值在`y`的域中都有一个不引起冲突的可能值时,`x`就与`y`保持弧一致性。(在填字游戏的背景下,冲突是指一个方格,两个变量对它的字符值意见不一)。
|
||||
@@ -123,7 +126,7 @@ $ python generate.py data/structure1.txt data/words1.txt output.png
|
||||
- 如果有可能生成一个令人满意的字谜,你的函数应该返回完整的赋值:一个字典,其中每个变量是一个键,值是该变量应该承担的单词。如果不可能产生令人满意的赋值,该函数应该返回`None`。
|
||||
- 如果你愿意,你可能会发现,如果你把搜索和推理交织在一起,你的算法会更有效率(比如每次做新的赋值时都要保持弧一致性)。我们不要求你这样做,但允许你这样做,只要你的函数仍然产生正确的结果。(正是由于这个原因,`ac3`函数允许一个`arcs`的参数,以防你想从不同的弧队列开始)。
|
||||
|
||||
- 除了要求你实现的函数外,你不应该修改`generate.py`中的任何其他东西,尽管你可以编写额外的函数和/或导入其他Python标准库模块。如果你熟悉`numpy`或`pandas`,你也可以导入它们,但是你不应该使用任何其他的第三方Python模块。你不应该修改`crossword.py`中的任何东西。
|
||||
除了要求你实现的函数外,你不应该修改`generate.py`中的任何其他东西,尽管你可以编写额外的函数和/或导入其他Python标准库模块。如果你熟悉`numpy`或`pandas`,你也可以导入它们,但是你不应该使用任何其他的第三方Python模块。你不应该修改`crossword.py`中的任何东西。
|
||||
|
||||
## 提示
|
||||
|
||||
|
||||
Reference in New Issue
Block a user