为拼字游戏编写算法

时间:2010-03-23 06:30:43

标签: algorithm

我正在处理类似填字游戏的问题,但我不知道如何设计算法。

例如:

  • 字典中有'car','apple'等字样。
  • “app”这个词出现在黑板上。
  • 有些字母像'l''e''c''r'....用于制作文字。

因此算法的任务是制作存储在字典中的正确单词。

app - > lapp - > leapp - > lecapp - > .... - > lappe - > eappc - > ... - > appl - >苹果(正确答案)

此算法的最佳解决方案是什么?

10 个答案:

答案 0 :(得分:11)

您可能对Gol和Jacobson(1988)撰写的研究论文"The World's Fastest Scrabble Program"感兴趣。算法以伪代码概述,因此需要花费一些工作才能将其塑造成可用的形式并将它们粘合在一起;但是,作者概述的程序非常有用。

答案 1 :(得分:9)

将您的字典存储为树,例如:

          *
          |
     +----+----+
     |         |
     A*        B
     |         |
  +--+--+      E*
  |     |      |
  P     S    +-+-+
  |     |    |   |
  P*    K*   A   E*
  |          |
+-+-+      +-+-+
|   |      |   |
E   L      D*  N*
|   |
A   E*
|
L*

感谢paxdiablo让我的树更具可读性。

这棵树有单词a,app,appeal,apple,ask,bead,bean,be和bee。标有星号的节点表示“如果我要停在这里,这将是一个有效的单词”,例如'be'下面的'e'下面的'e'。

当您找到一封您不知道的信件时,请使用通配符(即,挑选所有孩子并递归所有路径)。

你说填字游戏,但是你的“字母......制作单词”似乎表明了拼字游戏。这对两者都有效。不是最快,但速度很快。

感谢Andreas提醒我们这称为trie。

如果你想说“第二个字母是P”你会从根节点开始并取每个分支(这将是字母表中的每个字母,假设它是一个合适的字典)然后是“P”分支然后从那里继续。

答案 2 :(得分:5)

我之前实际上写了一个填字游戏程序(含糊不清但构造背后的理论是相同的)。

我有一个单词及其线索的数据库,可以按使用的时间排序(这样我就不会在后续运行中获得重复的填字游戏)。

你应该做的第一件事是设计你的图案(黑色,你不能把字母和白色放在哪里)。在动态创建模式时尝试将单词插入网格中非常耗时并且容易出错。如果你看大多数填字游戏,他们往往会遵循某些规则,以使其更容易。比如围绕一条对角线对称并且不允许四个白色单元格的方块(以便于选择合适的单词的任务)。

一旦你有了模式,然后你开始找到要放在其中的单词。这样,您就会知道“app”是单词的 start ,并且能够将搜索限制为以“app”开头的搜索,而不是每个包含“app”的单词。类似地,对于您在任何位置已知字母的单词。在已知位置定位带字母的单词比在单词中的任何起始位置评估这些字母要容易得多。

Mine最终用shell脚本编写(信不信由你),并使用来自Linux的字典作为单词搜索工具。如果你知道你有一个以“app”开头的5个字母的单词,那么它很容易使用:

grep '^app..$' words.txt

获取所有有效可能性的列表。

并且,当找到每个单词时,它被复制到包含单词和多个可能线索的clues.txt文件中。实际格式是使用{count,word,clue},其中同一个单词可能存在于具有不同线索的多行上 - 这允许grepsort的管道,以便较少使用的单词/线索浮动到顶部(每当使用一个单词/线索时,它的计数会增加,使得下次使用它的可能性降低。)

一旦该文件大小合适,程序将首先使用它来定位单词,并且只有在未找到单词的情况下,它才会恢复到需要手动干预的单词文件(无线索)。

它实际上最终做得很好。它的速度并不快,但我不需要每三秒钟产生一次 - 这是每周发送一次的社区通讯。


既然您已将问题更改为Scrabble变体,那实际上就更难了。

你需要考虑你的信件,董事会上的信件以及你需要评估更多地方的事实。这使得暴力方法更加困难。

作为初始剪切,我将做的是选择随机选择的可能性(棋盘上的起始位置和方向),然后使用与上面的填字游戏变体相同的算法来找到适合那里的所有单词。然后,如果您有满足该单词的字母,请将其(及其分数)存储在列表中。

请注意,您需要注意干扰电路板上的其他字词。

我会继续研究其中一种可能性:

  • 你的清单足够大可供选择。
  • 你没时间了。
  • 您已经检查了足够的可能性以满足您的能力水平。

最后一个很重要 - 如果你是初学者,你不想详尽地检查数百万种可能性。

然后,从列表中选择最佳移动(如果在初学者级别播放,则可能不是最佳移动 - 这完全取决于您希望计算机有多好)。

答案 3 :(得分:4)

Steven A. Gordon撰写了一篇有趣的论文,探讨如何搜索可能的Scrabble(我猜)动作(见Gordon's paper on GADDAG)。虽然在搜索行动和赢得Scrabble之间存在很大差距 - 正如文章所提到的 - 这与原始问题无关。

如果您发现直接阅读某些代码最有用,那么有一个很好的开源播放器, Quackle

答案 4 :(得分:1)

大多数拼字游戏论文都在谈论在整个棋盘上寻找最佳单词。但是如上所述,要解决您的问题,有一个非常简单的算法。

首先,您知道您想要的单词包含'app',并且您知道您可以制作的最大单词是七个字母长(板上已有3个字母,托盘中有4个字母)。因此,使用sql语句搜索数据库,例如:

从词典中选择单词LIKE'%app%'和len(word)< = 7

接下来,将所有七个字母放入一个数组{l,e,c,r,a,p,p}

一次一个地从数据库中读取每个单词。然后查看字典单词的每个字符,看它是否存在于数组中。如果在数组中找到字典单词的第一个字母,则删除该数组中的该元素,然后转到下一个字典字母。

如果在数组中找不到任何字典单词字母,则单词不符合条件,因此,请转到下一个单词。

如果您已查看字典中的所有字母并且已在数组中找到所有字母,则该字符合格,因此您将其写入列表。

请注意,将图块放入数组的原因是,一旦将字典单词中的字母与数组中的图块匹配,您需要删除该字母以进一步考虑,方法是删除该元素数组。

因此,例如,字典数据库返回单词“上诉”。前四个字母在数组中找到,这些元素被删除,只留下{l,c,r}在数组中。当你找到第五个字母'a'时,你将找不到它,所以这个词被取消资格。

'apple'这个词会有资格,让你的数组留下{c,r}。

用任何语言编写代码都非常容易。但是,这不是最快的方法。我自己正在寻找一种更快的方法!

答案 5 :(得分:0)

如果您正在尝试创建一个单词索引,以便您可以尝试“解决”(或创建)填字游戏,那么我猜您会从一个按长度索引的单词词典开始。然后你要创建另一个词典词典词典......第一个索引是按字总长度而第二个是长度,然后是字母位置,最后是字母(六个字母的单词,第二个字母是“i”) “例如)。

在构建此索引之后,您可以表达尝试根据对这些索引执行的集合操作设置或解决难题的每个步骤。 (例如,以“w”开头并以“k”结尾的8个字母单词将是以“w”开头的所有8个字母单词的交集,以及以“k”结尾的所有单词的交集 - 这不出所料地包括“作业” “)。当然,构建了我描述的索引数据结构后,可以通过对全局单词列表进行线性扫描,甚至对长度分离列表进行线性扫描,从而可以更有效地搜索可能的匹配。

一旦你有了这个基本的数据结构,那么程序的其余部分可能是树生成和遍历(当然还有回溯)。创建一个程序,生成所有可能性(使用所描述的数据结构),并且每当它“卡住”时,它都会回溯,直到找到新的可能性。

正如paxdiablo暗示的那样,你必须为生成器包含一大堆“单词”才能有合理的机会创建一个完整的“解决方案”。任何对填字游戏有经验的人都意识到,他们允许设定者采取相当多的自由(例如经常使用罗盘点,古老的术语和诗意的合同),以便让自己成为驼峰。

我没有亲自编写填字游戏生成器。我编写了密码求解器,它使用了类似但更简单的索引结构。 (为了找到zyzxw可能在密码中的每个单词,你将它“抽象”成一个模式:abacd。你的字典包含由其抽象索引的每个单词,你可以很容易地发现“每个”匹配“zyzxw”)。在那种情况下,在每个抽象开始的列表中的线性搜索相当快,即使你正在发现“uzz”和“zyzxw”确实可能是“例如”......。我还写了一个简单的“Jotto”游戏,它根本没有从索引中获益 - 在每个淘汰步骤中通过几千个或6个字母单词进行线性扫描,在我的旧版本中用了不到一秒的时间Mhz XT在现代PC计算的前期历史中。)

答案 6 :(得分:0)

寻找由Brian Sheppard(Maven的作者)撰写的名为“迈向拼字游戏的完美游戏”的博士论文。它内容丰富,非常有趣。但也很长。

答案 7 :(得分:0)

如果我正确理解了这个问题(你开始提示字母,字的子字符串,并尝试重新排列字母以获得正确的字),这是另一种解决方案:

你可以从倒退开始。 你已经在字典中有了单词,需要显示单词的一部分(子字符串)和单词中的字母列表,以便人们可以安排它们。考虑到所有这些,你可以从字典中的单词开始,创建一个距离为1的编辑单词图。

  

示例

     

apple 开始并继续删除一封信。这是一个小图(我没有绘制所有边缘以减少混乱):

 apple - > appe - >猿 - > ......  \\
 \ _-> appl - > app - > ...

删除信件时,请将其放在提示列表中。

  

提示:l,p

     

提示:l,e

当玩家使用列表中的字母组成原始单词时,您只接受正确的条目,这些条目是通向前一个父级的节点。您只需向后遍历图表即可找到原始单词。

  

示例

     

如果单词是 app 提示:l,p

     

如果用户给你l:appl你移动到app的prev节点   申请

     

如果用户给你e:appe,你移动到app的prev节点   在这种情况下。

     

用户输入的任何其他字母,您可以通过保留在当前节点来禁用。

答案 8 :(得分:0)

在给定的回合中得分最高的棋步不一定是获胜棋步。有时最好的举动是阻止对手的举动。取决于它是否隐藏在包中的字母或未隐藏,然后它改变策略。

如果袋子内容已知,则可以轻松计算对手托盘。那么最好的一步就是相对于你的对手的下一步行动,你得到最多的净分。

现在假设对手的托盘无法推断,因为袋子是隐藏的。尽管如此,袋子和对手托盘字母组合是已知的。这样就可以统计确定对手托盘中最有可能的字母。然后分析成本非常高,并且需要扫描一个巨大的空间来获取每个点和每个点的概率,以便确定最佳移动。

拼字游戏在某些形式中具有随机性而不是全部信息,这使得最佳移动成为统计问题。这与不存在随机性或隐藏信息的国际象棋不同,理论上的最佳棋步完全基于演绎推理,即使计算机的功能远不足以准确解决它。

答案 9 :(得分:-1)

您正在寻找的是您的anagram解算器能够找到“通配符”字母,以查看其他字母可以制作的字词。我有一个我写的字谜解算器,它完成了这个问题。我发现要做到这一点的一件重要事情,以及解算器的速度,是预先确定你的单词表中每个单词的字母数和分数。

For Instance您的表格应该像这样构建

word | a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z | score
-------------------------------------------------------------------------------------------------------------
test | 0 | 0 | 0 | 0 | 1 | 0 | 0 | h | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 4

正如您所看到的那样,单词,字母以及它们包含的字母数量和单词的分数都有一个单独的列。我提前创建了一个单独的脚本,只需为每个单词运行并为我填写,直到完成为止。

这是我编写的脚本,用于计算每个单词中的字母数以及分数并更新每条记录。在运行此脚本之前,必须先从一个只包含单词的表开始。一旦你运行它,你就完成了,除非你添加新单词,否则不必再运行它。

<?
include('/includes/connect.php');
$sql = "SELECT * FROM SOWPODS WHERE word LIKE 'z%' ORDER BY word ASC";
$result = mysql_query($sql);
while($row = mysql_fetch_array($result)) {
$string = $row['word'];
$rowwordid = $row['ID'];
echo $thisword = strtoupper($row['word']);
echo " - ";
for ($ii = 0; $ii < strlen($string); ++$ii) {
    $thisletter = strtolower($string{$ii});
    if ($thisletter == 'a') {
        $a = $a+1;
    } elseif ($thisletter == 'b') {
        $b = $b+1;
    } elseif ($thisletter == 'c') {
        $c = $c+1;
    } elseif ($thisletter == 'd') {
        $d = $d+1;
    } elseif ($thisletter == 'e') {
        $e = $e+1;
    } elseif ($thisletter == 'f') {
        $f = $f+1;
    } elseif ($thisletter == 'g') {
        $g = $g+1;
    } elseif ($thisletter == 'h') {
        $h = $h+1;
    } elseif ($thisletter == 'i') {
        $i = $i+1;
    } elseif ($thisletter == 'j') {
        $j = $j+1;
    } elseif ($thisletter == 'k') {
        $k = $k+1;
    } elseif ($thisletter == 'l') {
        $l = $l+1;
    } elseif ($thisletter == 'm') {
        $m = $m+1;
    } elseif ($thisletter == 'n') {
        $n = $n+1;
    } elseif ($thisletter == 'o') {
        $o = $o+1;
    } elseif ($thisletter == 'p') {
        $p = $p+1;
    } elseif ($thisletter == 'q') {
        $q = $q+1;
    } elseif ($thisletter == 'r') {
        $r = $r+1;
    } elseif ($thisletter == 's') {
        $s = $s+1;
    } elseif ($thisletter == 't') {
        $t = $t+1;
    } elseif ($thisletter == 'u') {
        $u = $u+1;
    } elseif ($thisletter == 'v') {
        $v = $v+1;
    } elseif ($thisletter == 'w') {
        $w = $w+1;
    } elseif ($thisletter == 'x') {
        $x = $x+1;
    } elseif ($thisletter == 'y') {
        $y = $y+1;
    } elseif ($thisletter == 'z') {
        $z = $z+1;
    }
}
$scorea = $a*1;
$scoreb = $b*4;
$scorec = $c*4;
$scored = $d*2;
$scoree = $e*1;
$scoref = $f*4;
$scoreg = $g*3;
$scoreh = $h*3;
$scorei = $i*1;
$scorej = $j*10;
$scorek = $k*5;
$scorel = $l*2;
$scorem = $m*4;
$scoren = $n*2;
$scoreo = $o*1;
$scorep = $p*4;
$scoreq = $q*10;
$scorer = $r*1;
$scores = $s*1;
$scoret = $t*1;
$scoreu = $u*2;
$scorev = $v*5;
$scorew = $w*4;
$scorex = $x*8;
$scorey = $y*3;
$scorez = $z*10;

$totalscore = $scorea + $scoreb + $scorec + $scored + $scoree + $scoref + $scoreg +     $scoreh + $scorei + $scorej + $scorek + $scorel + $scorem + $scoren + $scoreo + $scorep +      $scoreq + $scorer + $scores + $scoret + $scoreu + $scorev + $scorew + $scorex + $scorey + $scorez;
$SQL_update_count = "UPDATE TWL06 SET a = '$a', b = '$b', c = '$c', d = '$d', e = '$e', f = '$f', g = '$g', h = '$h', i = '$i', j = '$j', k = '$k', l = '$l', m = '$m', n= '$n', o = '$o', p = '$p', q = '$q', r = '$r', s = '$s', t = '$t', u = '$u', v = '$v', w = '$w', x = '$x', y = '$y', z = '$z', score = '$totalscore' WHERE ID = '$rowwordid'";
echo "<br>";
$result_update_count = mysql_query($SQL_update_count);

$a = 0;
$b = 0;
$c = 0;
$d = 0;
$e = 0;
$f = 0;
$g = 0;
$h = 0;
$i = 0;
$j = 0;
$k = 0;
$l = 0;
$m = 0;
$n = 0;
$o = 0;
$p = 0;
$q = 0;
$r = 0;
$s = 0;
$t = 0;
$u = 0;
$v = 0;
$w = 0;
$x = 0;
$y = 0;
$z = 0;
 }
?>

完成后,您所要做的就是创建一个脚本,对列中的字母进行计数,并将其与您给出的字母进行匹配。你将不得不首先爆炸这些字母并找出你所拥有的每个字母的数量。然后运行一个sql语句,找到那些字母或更少的数量。