【问题标题】:Scrabble word finder with wildcards带通配符的拼字游戏单词查找器
【发布时间】:2011-09-14 15:25:01
【问题描述】:

我遇到了问题,我之前似乎也遇到过类似的问题,但我无法为我找到可行的解决方案。

我目前正在使用 C#、MySQL、HTML5 和 Javascript 构建一个移动 Web 应用程序。该应用程序将用于帮助用户在玩拼字游戏等游戏时找到可能的单词。

我遇到的问题: 如何从包含来自用户字母输入的字典的 MySQL 数据库中获取正确的单词?

更多详情: - 用户可以输入任意数量的字母,也可以使用通配符(代表任意字母)。 - 如果用户输入“TEST”,结果不能包含超过 1 个 E 和 S 的单词以及超过 2 个 T 的单词,带有“TESTER”的结果会很糟糕。 - 结果不能包含比输入更多字母的单词。

更新:似乎 Trie 是 Eric Lippert here 建议的我的问题的解决方案。
问题是我是 C# 和 MySQL 的初学者,所以这里有一些后续问题:

  1. 如何从我的 MySQL 字典创建 Trie? (400k+ 字)
  2. 如何存储 Trie 以供将来快速访问?
  3. 如何使用 C# 访问 Trie 并从中提取单词?

非常感谢您的帮助!

【问题讨论】:

  • 嗨,这相当于在字典中查找某些字母的所有排列(这是 hard)但是如果您在这里搜索排列,您会发现很多算法得到你所有字母的排列 - 然后你只有必须找到正确的子集....(顺便说一句:通配符使情况变得更糟)
  • 为什么每个 CS 学生都应该参加数据结构课程的第 42 部分。
  • 您可以考虑为您的更新创建一些新问题;您可能会更多地关注一个新问题。另外:一段时间以来,我一直在计划写一篇关于如何使用 trie 解决您的问题的博客文章;我从来没有这样做过。如果我确实发布了那篇文章,我会回到这里并更新这个问题。

标签: c# mysql regex


【解决方案1】:

如何从包含来自用户字母输入的字典的 MySQL 数据库中获取正确的单词?

你没有。关系数据库表不是一种合适的数据结构,可以有效地解决这个问题。

你要做的是从字典中构建一个 trie 数据结构(或者,如果你真的很迷,你可以构建一个 dawg -- 一个有向无环词图 -- 这是一种压缩的 trie。)

一旦有了 trie/dawg,就可以非常便宜地针对给定机架测试字典中的 每个 单词,因为您可以“修剪”字典的整个巨大分支,而机架无法可能匹配。

让我们看一个小例子。假设您有字典“OP, OPS, OPT, OPTS, POT, POTS, SOP, SOPS, STOP, STOPS” 从中构建此树:(带有 $ 的节点是标记为“单词可以在此处结束”的节点.

           ^root^
           /  |  \
         O    P    S
         |    |   / \
         P$   O  O   T   
        / \   |  |   |
       T$  S$ T$ P$  O
       |      |  |   |
       S$     S$ S$  P$
                     |
                     S$

你有机架“OPS”——你是做什么的?

首先你说“我可以去 O 分支吗?”是的你可以。所以现在的问题是将“PS”与O分支匹配。你能去P支行吗?是的。它有一个词尾标记吗?是的,所以 OP 是匹配的。现在的问题是将“S”与 OP 分支匹配。你可以去T分支吗?不,你可以去S分支吗?是的。现在您有了空机架,您必须将其与 OPS 分支进行匹配。它有一个词尾标记吗?是的!所以 OPS 也匹配。现在回溯到根目录。

你能走P分支吗?是的。现在的问题是将 OS 与 P 分支相匹配。沿着 PO 分支并匹配 S - 失败。回溯到根。

再一次,你看到这是怎么回事。最后我们沿着 SOP 分支找到 SOP 的一个词尾,所以“SOP”与这个机架匹配。我们不走 ST 分支,因为我们没有 T。

我们尝试了字典中所有可能的单词,发现 OP、OPS 和 SOP 都匹配。但我们从来不用调查 OPTS、POTS、STOP 或 STOPS,因为我们没有 T。

您看到这种数据结构如何使其非常高效了吗?一旦您确定机架上没有字母作为单词的开头,您就不必调查以该开头的任何字典单词开始。如果您有 PO 但没有 T,则不必调查 POTSHERD 或 POTATO 或 POTASH 或 POTLATCH 或 POTABLE;所有那些昂贵且徒劳的搜索很快就会消失。

调整系统以处理“野生”图块非常简单;如果您有 OPS?,那么只需在 OPSA、OPSB、OPSC 上运行 26 次搜索算法......它应该足够快,以至于执行 26 次很便宜(或者如果您有两个空白,则执行 26 x 26 次。 )

这是专业拼字游戏 AI 程序使用的基本算法,当然它们还必须处理诸如棋盘位置、机架管理等问题,这使算法有些复杂。这个简单的算法版本将足够快,可以在机架上生成所有可能的单词。

不要忘记,如果字典不随时间变化,您当然只需要计算 trie/dawg 一次。从字典中构建 trie 可能很耗时,因此您可能希望这样做一次,然后想办法以适合重建的形式将 trie 存储在磁盘上快速从磁盘中。

您可以通过使用 trie 构建 DAWG 来优化内存使用。注意有很多重复,因为在英语中,很多词end是一样的,就像很多词begin一样。 trie 在开始时在共享节点方面做得很好,但在最后共享节点方面做得很糟糕。例如,您可以注意到“没有孩子的 S$”模式非常常见,并将 trie 转换为:

           ^root^
          / |  \
        O   P    S
        |   |   / \
        P$  O  O   T   
       /  \ |  |   |
      T$  | T$ P$  O
      |    \ | |   |
       \    \| /   P$
        \    |/    |
         \   |    /
          \  |   /  
           \ |  /
            \| /  
             |/
             |       
             S$

保存一大堆节点。然后你可能会注意到现在两个单词以 O-P$-S$ 结尾,两个单词以 T$-S$ 结尾,所以你可以将它进一步压缩为:

           ^root^
           / | \
          O  P  S
          |  | / \
          P$ O \  T   
         /  \|  \ |
         |   |   \|
         |   |    O
         |   T$   |
          \  |    P$
           \ |   /
            \|  /  
             | /
             |/   
             S$

现在我们有了这本词典的最小 DAWG。

进一步阅读:

http://dl.acm.org/citation.cfm?id=42420

http://archive.msdn.microsoft.com/dawg1

http://www.gtoal.com/wordgames/scrabble.html

【讨论】:

  • 如果您将此深度优先搜索的顺序排序以匹配语言字母分布,您可以更快地获得结果
  • 关于如何在磁盘上存储尝试的问题:msdn.microsoft.com/en-us/library/182eeyhh(v=VS.100).aspx .nets 可爱的 XML 序列化是你的朋友。
  • 如果你构建一个按字母顺序排列的单词 trie,它可能会快很多,不是吗?在您的示例中,trie 将具有 OP、OPS、OPST、OPSS、OPSST,每个词尾指针都指向其所有字谜。然后,您必须为原始字符串的每个子集检查一次特里树。如果我没记错的话,这将使它成为 O(2^n),其中 n 是机架中的字母数 - 对于标准的 7 机架来说,这不是问题,即使是巨大的字典。
  • @configurator:你会注意到我没有说那个团队是否向我提供了报价。他们没有。 (尽管这是一个有争议的问题,因为团队在几周后解散了。)在微软面试我的所有团队都没有聘用我。 VB 团队根据我的实习而不是面试结果向我提供了邀请。
  • @Joan:我的意思是面试官给招聘经理发了一封邮件,邮件的标题是 NO HIRE,意思是“不要给这个人发offer”。显然那天我的采访不太好,虽然一个有趣的缓解因素是那天有一场暴风雪,原本应该采访我的人都没有出现。一些面试官似乎有点措手不及。
【解决方案2】:

这是我解决问题的方法(当然假设您可以控制数据库,并且可以修改表/添加表,甚至可以控制数据库的原始负载)。

我的解决方案将使用 2 个表格 -> 一个表格将只是您的字典中每个可能的字母组合的列表,其中组件字母按字母顺序排序。 (IE TEST 为 ESTT,TESTER 为 ERSTT,DAD 为 ADD)。

第二张表将包含每个单词和对第一张表键的引用。

表一 - LetterInWord

Index Letters
1     ESTT
2     ESTTER
3     EST
4     ADD
5     APST

在表一中,您按字母顺序插入单词 letters - test 变为 estt

表二 - 单词

Index LetterInWordIndex  Word
1     1                  TEST
2     2                  TESTER
3     3                  SET
4     4                  ADD
5     4                  DAD
6     5                  SPAT
7     5                  PAST               

在表 2 中插入带有适当单词和索引参考的单词。

这将是一对多的关系 -> LetterInWord 表中的一个条目可能在 Words 表中具有多个条目

非通配符查找: 说我的输入字母是SETT 按字母顺序排列。

然后在查找中,从 LetterInWord 中选择所有“字母”,其中 Letters = value 并加入表 Words - 您在一个查询中的输出是仅包含这些字母的所有单词的列表

现在是通配符: 假设我的输入字母是 EST* 记住长度 - 4 去掉通配符 - 你得到 EST(确保你按字母顺序排序) 现在查找 Letters 包含 EST 且 Letters Length

这将返回 TEST、REST、SET 等

我不确定这是否是最有效的方法,但它确实有效。我过去曾用它从字典中查找单词,它具有合理的性能和最小的复杂性。

【讨论】:

  • 这是一个非常典型的算法来解决这个问题。当问题是“给定一个大小为 n 的机架,找到所有与机架完全匹配的 n 字母单词”时,它的表现非常好。不幸的是,当被问到“给定一个大小为 n 的机架,找到所有与机架匹配的 n 个或更少字母的单词”时,您的算法表现不佳。由于这是原始发布者实际面临的问题,因此我不推荐此算法。
【解决方案3】:

如果你只有字典,这将很难做到。如果您有能力制作新表或新列,我会:

创建一个包含单词列的表格,外加 26 列(每个字母一个) 运行一个存储的 proc/backend 进程,计算单词中每个字母的出现次数,并将它们放入相应的列中。

那么(忽略通配符)你可以这样做

从字典中选择tcount

对于你可以做的通配符和长度

实际上总是使用 length 子句,因为这样您就可以在其上建立索引以提高性能。

在查询期间其他任何事情都会异常缓慢

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-28
    • 1970-01-01
    • 1970-01-01
    • 2013-09-09
    • 2012-05-02
    相关资源
    最近更新 更多