我遇到了一个问题,似乎有些人遇到过类似问题,但我找不到适合我的解决方案。
我目前正在使用C#,MySQL,HTML5和Javascript构建移动Web应用程序。该应用程序将用于帮助用户在玩Scrabble等游戏时找到可玩的单词。
我遇到的问题: 如何从包含用户字母输入字典的MySQL数据库中获取正确的单词?
更多细节: - 用户可以输入任意数量的字母,也可以使用通配符(代表任何字母)。 - 如果用户输入“TEST”,则结果不能包含超过1 E和S的单词以及超过2 T的单词,其中包含“TESTER”的结果将是错误的。 - 结果不能包含字母数多于输入的字词。
更新:似乎Trie是Eric Lippert here建议的解决我问题的方法。
问题是我是C#和MySQL的初学者,所以这里有一些后续问题:
非常感谢您的帮助!
答案 0 :(得分:23)
如何从包含用户字母输入字典的MySQL数据库中获取正确的单词?
你没有。关系数据库表不是一个合适的数据结构,可以根据需要有效地解决这个问题。
您所做的是从字典中构建 trie 数据结构(或者,如果您真的是buff,则构建 dawg - 有向无环字图 - 这是一种压缩特里图。)
一旦你有了trie / dawg,在字典中针对给定的机架测试每个单词变得非常便宜,因为你可以“删除”字典的整个巨大分支,而机架不能可能匹配。
让我们看一个小例子。假设您有字典“OP,OPS,OPT,OPTS,POT,POTS,SOP,SOPS,STOP,STOPS”从中构建此trie :(带有$的节点是标记为“word can end here”的节点)
^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次)。 )
这是专业Scrabble AI程序使用的基本算法,当然它们还必须处理诸如电路板位置,机架管理等问题,这会使算法有些复杂化。这个简单的算法版本足够快,可以在机架上生成所有可能的单词。
不要忘记,如果字典没有随时间变化,您只需要计算一次的trie / dawg 。从字典中构建trie可能非常耗时,因此您可能希望一次然后找出一些方法以便以重建的形式将trie存储在磁盘上它很快从磁盘上传来。
您可以通过构建一个DAWG来优化内存使用量。注意有很多重复,因为在英语中,很多单词结束相同,就像很多单词开始一样。 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
答案 1 :(得分:0)
以下是我将如何解决问题(假设您当然可以控制数据库,并且可以修改表/添加表,甚至可以控制数据库的原始负载)。
我的解决方案将使用2个表 - >一个表只是您字典中每个可能的字母组合的列表,其中组件字母按字母顺序排序。 (IE TEST将是ESTT,TESTER将是ERSTT,DAD将是ADD)。
第二个表将包含每个单词和对表一的键的引用。
表一 - LetterInWord
Index Letters
1 ESTT
2 ESTTER
3 EST
4 ADD
5 APST
在表1中,您按字母顺序插入单词 - 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”,其中Letters = value并加入表Words - 您在一个查询中的输出是仅包含这些字母的所有单词的列表
现在为外卡: 说我输入的字母是EST * 记住长度 - 4 剥去通配符 - 你得到EST(确保你按字母顺序排序) 现在查看Letters包含EST和字母长度< = 4加入Words表
的所有情况那将返回TEST,REST,SET等
我不确定这是否是最有效的方法,但它确实有效。我过去曾用它来从字典中进行单词查找,并且它具有合理的性能和最小的复杂性。
答案 2 :(得分:-1)
如果您拥有的只是字典,那将很难做到。如果您能够制作新表或新列,我会:
创建一个包含该列的列的表,以及26列(每个字母一列) 运行存储的proc / backend进程,计算单词中每个字母的出现次数,并将它们放入相应的列中。
然后(忽略通配符)你可以做
从词典中选择单词,其中tcount< = 2且ecount< = 1且scount< = 1
对于你可以做的通配符和长度< = number_of_letters
实际上总是使用length子句,因为您可以对其进行索引以提高性能。
在查询期间,其他任何事情都会异常缓慢