Algorithm 如何从字母矩阵中找到可能的单词列表[Boggle Solver]

Algorithm 如何从字母矩阵中找到可能的单词列表[Boggle Solver],algorithm,puzzle,boggle,Algorithm,Puzzle,Boggle,最近我在我的iPhone上玩了一个叫做“扰码”的游戏。你们中的一些人可能知道这个游戏是Boggle。基本上,当游戏开始时,你会得到一个字母矩阵,如下所示: F X I E A M L O E W B X A S T U 游戏的目标是找到尽可能多的单词,这些单词可以通过将字母链接在一起形成。你可以从任何字母开始,所有围绕它的字母都是公平的游戏,然后,一旦你转到下一个字母,所有围绕该字母的字母都是公平的游戏,以前使用过的字母除外。例如,在上面的表格中,我可以想出LOB、TUX、SEA、FAME等词

最近我在我的iPhone上玩了一个叫做“扰码”的游戏。你们中的一些人可能知道这个游戏是Boggle。基本上,当游戏开始时,你会得到一个字母矩阵,如下所示:

F X I E
A M L O
E W B X
A S T U
游戏的目标是找到尽可能多的单词,这些单词可以通过将字母链接在一起形成。你可以从任何字母开始,所有围绕它的字母都是公平的游戏,然后,一旦你转到下一个字母,所有围绕该字母的字母都是公平的游戏,以前使用过的字母除外。例如,在上面的表格中,我可以想出LOB、TUX、SEA、FAME等词。这些词必须至少有3个字符,并且不能超过NxN个字符,在这个游戏中是16个,但在一些实现中可能会有所不同。虽然这个游戏很有趣,也很容易上瘾,但我显然不是很擅长,我想通过制作一个程序来欺骗一点,这个程序会给我尽可能好的单词,单词越长,你得到的分数就越多

资料来源:

不幸的是,我对算法或其效率等方面不是很在行。我的第一次尝试使用字典~2.3MB,并进行线性搜索,试图将组合与字典条目相匹配。这需要很长时间才能找到可能的单词,因为你每轮只能得到2分钟,这根本不够

我很想看看是否有Stackoverflowers能想出更有效的解决方案。我主要是使用大3 ps:Python、PHP和Perl来寻找解决方案,尽管java或C++的任何东西都很酷,因为速度是必不可少的。 目前的解决办法:

亚当·罗森菲尔德,巨蟒,20多岁 约翰·福伊,巨蟒,~3s 肯特·弗雷德里克,Perl,~1s 大流士·培根,巨蟒,~1秒 rvarcher,VB.NET,~1s Paolo Bergantino,PHP,~5s~2s本地
首先,阅读一位C语言设计师是如何解决相关问题的:

像他一样,你可以从一本字典和canonacalize单词开始,通过从字母顺序排列的字母数组到可以从这些字母拼写的单词列表创建一本字典


接下来,从黑板上开始创建可能的单词并查找它们。我怀疑这会让你走得很远,但肯定有更多的技巧可以加快速度

随着搜索的继续,搜索算法是否会不断减少单词列表

例如,在上面的搜索中,只有13个字母可以有效地将单词的起始字母减少到原来的一半

当您添加更多字母排列时,将进一步减少可用单词集,从而减少必要的搜索


我从这里开始。

您将要获得的最快解决方案可能包括将您的词典存储在一个数据库中。然后,创建一个由三元组x、y、s组成的队列,其中队列中的每个元素对应于一个单词的前缀s,该前缀可以在网格中拼写,并在位置x、y处结束。使用N x N个元素初始化队列,其中N是网格的大小,网格中每个正方形对应一个元素。然后,算法进行如下操作:

While the queue is not empty: Dequeue a triple (x, y, s) For each square (x', y') with letter c adjacent to (x, y): If s+c is a word, output s+c If s+c is a prefix of a word, insert (x', y', s+c) into the queue 输出:

eleven
eleventh
elevon
entente
entone
ethene
ethenol
evolve
evolvent
hellhole
helvell
hooven
letten
looten
nettle
nonene
nonent
nonlevel
notelet
novelet
novelette
novene
teenet
teethe
teevee
telethon
tellee
tenent
tentlet
theelol
toetoe
tonlet
toothlet
tootle
tottle
vellon
velvet
velveteen
venene
vennel
venthole
voeten
volent
volvelle
volvent
voteen
Found "pic" directions from (4,0)(p) go  → →
Found "pick" directions from (4,0)(p) go  → → ↑
Found "pickman" directions from (4,0)(p) go  → → ↑ ↑ ↖ ↑
Found "picket" directions from (4,0)(p) go  → → ↑ ↗ ↖
Found "picked" directions from (4,0)(p) go  → → ↑ ↗ ↘
Found "pickle" directions from (4,0)(p) go  → → ↑ ↘ →
[法]、[西]、[伊]、[伊]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]、[美]w、mem、mes、lob、lox、lei、leo、lim、oil、olm、ewe、eme、wax、wae、waw、wem、wea、wea、was、waw、wae、bob、blo、bub、but、ast、ase、asa、awl、awe、awe、awa、aes、swa、sew、sea、saw、tux、tux、tut、tut、tut、tut、twa、twa、twa、tst、utu、fama、fama、fama、fame、,“伊克西尔”、“伊玛目”、“阿米尔”、“安博”、“阿克斯”、“米米”、“米玛”、“默剧”、“米洛”、“英里”、“喵喵”、“米色”、“梅色”、“梅色”、“洛洛”、“洛波”、“利马”、“莱姆”、“四肢”、“莉莉”、“爱美”、“奥利奥”、“双簧管”、“双簧管”、“埃米姆”、“埃米尔”、“东方”、“轻松”、“瓦美”、“瓦瓦”、“瓦瓦”、“威姆”、“西方”、“韦斯”、“瓦特”、“瓦瓦”、“瓦瓦”、“瓦瓦”、“瓦瓦”、“瓦瓦”、“瓦瓦”、“瓦瓦”、“瓦瓦”、“瓦瓦”、“瓦瓦”、“瓦瓦”、“瓦瓦”、“瓦瓦”、“瓦瓦”、“瓦瓦”、“瓦瓦”、“瓦瓦”、“瓦瓦”、“瓦瓦”、“瓦瓦”、“瓦瓦”、“瓦瓦”、“瓦瓦‘blob’、‘bleo’、‘bubo’、‘asem’、‘stut’、‘swam’、‘seme’、‘seam’、‘seax’、‘sasa’、‘sawt’、‘TUTUTU’、‘tuts’、‘twae’、‘twas’、‘twae’、‘twae’、‘ilima’、‘amble’、‘mambo’、‘maxim’、‘mease’、‘mesem’、‘limax’、‘LIMMES’、‘limbo’、‘emesa’、‘EMOBO’、‘emesa’、‘EMOBO’、‘awest’、,‘embole’、‘wamble’、‘semese’、‘semble’、‘sawbwa’、‘sawbwa’]

注意:此程序不输出单字母单词,也不按单词长度过滤。这很容易添加,但与问题无关。如果某些单词可以用多种方式拼写,它还会多次输出。如果给定的单词可以用多种不同的方式拼写,最坏的情况是:网格中的每个字母都是相同的,例如“a”和“a”单词l
ike'aaaaaaaa'在您的字典中,那么运行时间将以惊人的指数级增长。在算法完成后,过滤掉重复项并进行排序是很简单的。

我需要更多地考虑一个完整的解决方案,但作为一个方便的优化,我想知道是否值得根据字典中的所有单词预先计算一个数字和三字母组合的频率表,并以此来确定搜索的优先级。我会用单词的起始字母。因此,如果您的词典包含“印度”、“水”、“极端”和“非凡”等词,那么您的预计算表可能是:

'IN': 1
'WA': 1
'EX': 2

然后按照共性的顺序搜索这些数字图,首先是EX,然后是WA/in

我建议根据单词制作字母树。树将由字母结构组成,如下所示:

letter: char
isWord: boolean
graph = { (x, y):set([(x0,y0), (x1,y1), (x2,y2)]), }
然后你建立一棵树,每个深度加上一个新字母。换句话说,在第一个层次上,字母表;然后,从每棵树上,会有另外26个条目,依此类推,直到你拼出所有的单词。抓住这棵解析过的树,它会使所有可能的答案更快地查找

使用此解析树,您可以非常快速地找到解决方案。以下是伪代码:

BEGIN: 
    For each letter:
        if the struct representing it on the current depth has isWord == true, enter it as an answer.
        Cycle through all its neighbors; if there is a child of the current node corresponding to the letter, recursively call BEGIN on it.
这可以通过一点动态规划来加速。例如,在您的示例中,两个“A”都位于一个“E”和一个“W”的旁边,从它们碰到的点来看,这两个“A”是相同的。我没有足够的时间来真正解释这方面的代码,但我认为你可以收集到这个想法


另外,如果你在谷歌上搜索Boggle solver,我相信你会找到其他解决方案。

好笑。几天前,由于同一个该死的游戏,我几乎贴出了同样的问题!但是我没有,因为我只是在谷歌上搜索,得到了我想要的所有答案。

你可以将问题分成两部分:

某种搜索算法,将枚举网格中可能的字符串。 一种测试字符串是否为有效单词的方法。 理想情况下,2还应该包括一种测试字符串是否是有效单词前缀的方法——这将允许您删减搜索并节省大量时间

Adam Rosenfield的Trie是2的解决方案。它很优雅,可能是你的算法专家更喜欢的,但有了现代语言和现代计算机,我们可能会更懒一点。此外,正如肯特所建议的,我们可以通过丢弃表格中没有字母的单词来减少字典的大小。下面是一些python:

def make_lookups(grid, fn='dict.txt'):
    # Make set of valid characters.
    chars = set()
    for word in grid:
        chars.update(word)

    words = set(x.strip() for x in open(fn) if set(x.strip()) <= chars)
    prefixes = set()
    for w in words:
        for i in range(len(w)+1):
            prefixes.add(w[:i])

    return words, prefixes
i、 图[x,y]是从位置x,y可以到达的坐标集。我还将添加一个虚拟节点None,它将连接到所有内容

构建它有点笨拙,因为有8个可能的位置,您必须进行边界检查。下面是一些相应笨拙的python代码:

def make_graph(grid):
    root = None
    graph = { root:set() }
    chardict = { root:'' }

    for i, row in enumerate(grid):
        for j, char in enumerate(row):
            chardict[(i, j)] = char
            node = (i, j)
            children = set()
            graph[node] = children
            graph[root].add(node)
            add_children(node, children, grid)

    return graph, chardict

def add_children(node, children, grid):
    x0, y0 = node
    for i in [-1,0,1]:
        x = x0 + i
        if not (0 <= x < len(grid)):
            continue
        for j in [-1,0,1]:
            y = y0 + j
            if not (0 <= y < len(grid[0])) or (i == j == 0):
                continue

            children.add((x,y))
最后,我们进行深度优先搜索。基本程序是:

搜索到达特定节点。 检查到目前为止的路径是否是单词的一部分。如果没有,请不要进一步探索此分支。 检查到目前为止的路径是否为单词。如果是,请添加到结果列表中。 探索到目前为止不属于该路径的所有儿童。 Python:

def find_words(graph, chardict, position, prefix, results, words, prefixes):
    """ Arguments:
      graph :: mapping (x,y) to set of reachable positions
      chardict :: mapping (x,y) to character
      position :: current position (x,y) -- equals prefix[-1]
      prefix :: list of positions in current string
      results :: set of words found
      words :: set of valid words in the dictionary
      prefixes :: set of valid words or prefixes thereof
    """
    word = to_word(chardict, prefix)

    if word not in prefixes:
        return

    if word in words:
        results.add(word)

    for child in graph[position]:
        if child not in prefix:
            find_words(graph, chardict, child, prefix+[child], results, words, prefixes)
以以下方式运行代码:

grid = ['fxie', 'amlo', 'ewbx', 'astu']
g, c = make_graph(grid)
w, p = make_lookups(grid)
res = set()
find_words(g, c, None, [], res, w, p)
并检查res以查看答案。下面是为您的示例找到的单词列表,按大小排序:

 ['a', 'b', 'e', 'f', 'i', 'l', 'm', 'o', 's', 't',
 'u', 'w', 'x', 'ae', 'am', 'as', 'aw', 'ax', 'bo',
 'bu', 'ea', 'el', 'em', 'es', 'fa', 'ie', 'io', 'li',
 'lo', 'ma', 'me', 'mi', 'oe', 'ox', 'sa', 'se', 'st',
 'tu', 'ut', 'wa', 'we', 'xi', 'aes', 'ame', 'ami',
 'ase', 'ast', 'awa', 'awe', 'awl', 'blo', 'but', 'elb',
 'elm', 'fae', 'fam', 'lei', 'lie', 'lim', 'lob', 'lox',
 'mae', 'maw', 'mew', 'mil', 'mix', 'oil', 'olm', 'saw',
 'sea', 'sew', 'swa', 'tub', 'tux', 'twa', 'wae', 'was',
 'wax', 'wem', 'ambo', 'amil', 'amli', 'asem', 'axil',
 'axle', 'bleo', 'boil', 'bole', 'east', 'fame', 'limb',
 'lime', 'mesa', 'mewl', 'mile', 'milo', 'oime', 'sawt',
 'seam', 'seax', 'semi', 'stub', 'swam', 'twae', 'twas',
 'wame', 'wase', 'wast', 'weam', 'west', 'amble', 'awest',
 'axile', 'embox', 'limbo', 'limes', 'swami', 'embole',
 'famble', 'semble', 'wamble']

加载字典需要几秒钟的时间,但在我的机器上,其余的都是即时的。

对于字典加速,有一个通用的转换/过程可以提前大大减少字典比较

鉴于上面的网格仅包含16个字符,其中一些字符是重复的,您可以通过简单地过滤掉包含无法获得的字符的条目,大大减少字典中的总键数

我认为这是一个明显的优化,但看到没有人这样做,我要提的是

仅仅在输入过程中,它就将我的字典从200000个键减少到了2000个键。这至少减少了内存开销,而且这肯定会导致某个地方的速度增加,因为内存不是无限快的

Perl实现 我的实现有点重,因为我非常重视能够知道每个提取字符串的确切路径,而不仅仅是其中的有效性

我还做了一些调整,理论上允许一个有洞的网格运行,并且假设输入正确,网格上有不同大小的线,并且它以某种方式排列

正如前面所怀疑的那样,早期过滤器是我的应用程序中迄今为止最重要的瓶颈,评论指出该行将其从1.5s扩展到7.5s

在执行时,它似乎认为所有的单个数字都在它们自己的有效单词上,但我很确定这是由于字典文件的工作方式

它有点臃肿,但至少我重用了cpan

其中一些部分灵感来自现有的实现 塔提斯,我已经想到了一些

建设性的批评和改进的方法欢迎/我注意到他从来没有这样做过,但这更有趣

更新新标准

!/usr/bin/perl 严格使用; 使用警告; { 此包管理网格中的给定路径。 它是一个按顺序排列的矩阵节点数组 方便快捷的路径打印功能 以及将路径扩展为新路径。 用法: 我的$p=前缀->新路径=>[$startnode]; my$c=$p->child$extensionNode; 打印$c->当前单词; 包前缀; 使用驼鹿; 有路径=> isa=>ArrayRef[MatrixNode], is=>“rw”, 默认值=>sub{[]}, ; 有当前单词=> isa=>“Str”, is=>“rw”, lazy_build=>1, ; 创建此对象的克隆 路途较长 $o->child$图上的连续节点; 子代{ 我的$self=shift; my$newNode=shift; my$f=前缀->新建; 必须手动执行此操作,否则会修改其他记录的路径 推送{$f->{path},{$self->{path}},$newNode; 返回$f; } 从左到右遍历$o->path以获取它所表示的字符串。 子构建当前字{ 我的$self=shift; 返回join q{},map{${u0->{value}}@{$self->{path}; } 返回此路径上最右边的节点 亚尾部{ 我的$self=shift; 返回$self->{path}->[-1]; } 漂亮的格式$o->path 子pp_路径{ 我的$self=shift; 我的@path= 映射{'['.$\>{x_位置}.'.$\>{y_位置}.]} @{$self->{path}; return[.join,,@path.]; } 漂亮的格式$o 分页{ 我的$self=shift; 返回$self->current_word.=>。$self->pp_路径; } __包\元->使\不可变; } { 跟踪节点数据的基本包 无需查看网格。 我本可以使用数组或散列,但结果很糟糕。 一旦矩阵启动并运行起来,它就不太关心行/列了, 它只是一个点的海洋,每个点都有相邻的点。 相对定位只在将其映射回用户空间时才真正有用 包矩阵节点; 使用驼鹿; 具有x_位置=>isa=>Int',is=>rw',required=>1; 具有y_位置=>isa=>'Int',is=>'rw',必需=>1; has value=>isa=>Str',is=>rw',required=>1; 有兄弟姐妹=> isa=>ArrayRef[MatrixNode], is=>“rw”, 默认值=>sub{[]} ; 它不是隐式的单向连接,在理论上更有效 使链接同时双向运行,但这太难编程了。 除此之外,这还不够慢,不需要关心。 子添加_同级{ 我的$self=shift; 我的$sibling=班次; 推送{$self->siblines},$sibling; } 导出从此节点开始的路径的简便方法 sub to_路径{ 我的$self=shift; 返回前缀->新路径=>[$self]; } __包\元->使\不可变; } { 包装矩阵; 使用驼鹿; 有行=> isa=>ArrayRef', is=>“rw”, 默认值=>sub{[]}, ; has regex=> isa=>“Regexp”, is=>“rw”, lazy_build=>1, ; 有单元格=> isa=>ArrayRef', is=>“rw”, lazy_build=>1, ; 子添加_行{ 我的$self=shift; 按{$self->rows},[@"; } 从这里开始,这些函数中的大多数都只是构建器函数, 或实用程序来帮助构建东西。 有些只是为了让我更容易处理。 真正有用的就是添加行 其余的将被计算、存储并准备就绪 在调用->单元格或->正则表达式时从->单元格。 遍历所有单元格并生成覆盖它们的正则表达式。 子构建正则表达式{ 我的$self=shift; my$chars=q{}; 对于我的$cell@{$self->cells}{ $chars.=$cell->value; } $chars=[^$chars]; 返回qr/$chars/i; } 转换普通单元格,即:[x][y]=0 到智能单元,即:[x][y]=对象x,y 我们只是暂时保留这种格式 这样我们就可以查看并连接邻近的信息。 在完成相邻操作后,电网应视为不工作。 子转换{ 我的$self=shift; 我的$x=班次; 我的$y=班次; 我的$v=$self->\读$x,$y; my$n=矩阵节点->新建 x_位置=>$x, y_位置=>$y, 值=>$v, ; $self->写入$x、$y、$n; 返回$n; } 浏览当前可用的行/列,并将它们冻结为对象。 子构建单元{ 我的$self=shift; 我的@out=; 我的@rows=@{$self->{rows}; 对于我的$x 0..$行{ 下一步除非定义$self->{rows}->[$x]; my@col=@{$self->{rows}->[$x]} ; 为了我的$y 0$上校{ 下一步除非定义$self->{rows}->[$x]->[$y]; 推出@out,$self->\u转换$x,$y; } } 为了我的$c@out{ 对于我的$n$self->\u邻居$c->x\u位置,$c->y\u位置{ $c->add_sibling$self->{rows}->[$n->[0]]->[$n->[1]]; } } 返回\@出; } 给定x,y,返回引用有效邻居的点数组。 次近邻{ 我的$self=shift; 我的$x=班次; 我的$y=班次; 我的@out=; 对于我的$sx-1,0,1{ 下一步如果$sx+x<0; 下一步如果未定义$self->{rows}->[$sx+$x]; 为了我的$sy-1,0,1{ 下一步如果$sx==0&&$sy==0; 下一步,如果$sy+$y<0; 接下来,如果未定义$self->{rows}->[$sx+$x]->[$sy+$y]; 推出@out,[$sx+$x,$sy+$y]; } } 返回@out; } sub有第二排{ 我的$self=shift; 我的$x=班次; 返回定义的$self->{rows}->[$x]; } 子单元有子单元{ 我的$self=shift; 我的$x=班次; 我的$y=班次; 返回定义的$self->{rows}->[$x]->[$y]; } 亚读{ 我的$self=shift; 我的$x=班次; 我的$y=班次; 返回$self->{rows}->[$x]->[$y]; } 分写{ 我的$self=shift; 我的$x=班次; 我的$y=班次; 我的$v=班次; $self->{rows}->[$x]->[$y]=$v; 返回$v; } __包\元->使\不可变; } 使用Tree::Trie; 子读取器{ 我的$fn=班次; 我的$re=班次; my$d=Tree::Trie->new; 字典加载 打开我的$fh,'新; 字典加载
打开我的$fh,“我的答案与这里的其他答案一样有效,但我将发布它,因为它看起来比其他Python解决方案快一点,因为字典设置更快。我将其与John Fouhy的解决方案进行了对比。设置后,解决问题的时间缩短了

网格=fxie amlo ewbx astu.split nrows,ncols=lengrid,lengrid[0] 可以作为解决方案的字典单词必须只使用网格的 字母和的长度>=3。不区分大小写匹配。 进口稀土 字母表=.joinset.joingrid bogglable=re.compile'['+alphabet+']{3,}$',re.I.match words=setword.rstrip'\n'表示打开的“words”中的单词,如果是bogglableword 前缀=单词中单词的setword[:i] 对于范围为2的i,lenword+1 def解决方案: 对于y,枚举网格中的行: 对于x,行中的字母: 对于扩展字母x,y中的结果: 产量结果 def扩展前缀,路径: 如果前缀为单词: 产生前缀、路径 对于nx,ny在邻居路径[-1]: 如果nx、ny不在路径中: prefix1=前缀+网格[ny][nx] 如果前缀中有前缀X1: 对于扩展前缀x1,路径+nx,ny中的结果: 产量结果 定义邻域x,y: 对于范围为max0、x-1、minx+2、ncols的nx: 对于范围为Max0、y-1、miny+2、nrows的纽约: 产量nx,ny 示例用法:

d = MakeTrie('/usr/share/dict/words')
print(BoggleWords(['fxie','amlo','ewbx','astu'], d))
# Print a maximal-length word and its path:
print max(solve(), key=lambda (word, path): len(word))
编辑:筛选出长度小于3个字母的单词


编辑2:我很好奇为什么Kent Fredric的Perl解决方案速度更快;它使用正则表达式匹配而不是一组字符。在Python中使用同样的方法可以使速度提高一倍。

对VB不感兴趣?:我无法抗拒。我解决这个问题的方式与这里介绍的许多解决方案不同

我的时间是:

正在将字典和单词前缀加载到哈希表中:.5到1秒。 查找单词:平均不超过10毫秒。 编辑:web主机服务器上的词典加载时间比我的家用计算机长约1到1.5秒

我不知道随着服务器负载的增加,时间会恶化到什么程度

我在.Net中以网页的形式编写了我的解决方案

我正在使用原始问题中引用的词典

字母不能在单词中重复使用。只能找到3个字符或更长的单词

我使用的是一个包含所有唯一单词前缀和单词的哈希表,而不是trie。我不知道trie,所以我在那里学到了一些东西。在完整单词之外创建一个单词前缀列表的想法最终让我的时间降到了一个可观的数字

有关更多详细信息,请阅读代码注释

代码如下:

Imports System.Collections.Generic
Imports System.IO

Partial Class boggle_Default

    'Bob Archer, 4/15/2009

    'To avoid using a 2 dimensional array in VB I'm not using typical X,Y
    'coordinate iteration to find paths.
    '
    'I have locked the code into a 4 by 4 grid laid out like so:
    ' abcd
    ' efgh
    ' ijkl
    ' mnop
    ' 
    'To find paths the code starts with a letter from a to p then
    'explores the paths available around it. If a neighboring letter
    'already exists in the path then we don't go there.
    '
    'Neighboring letters (grid points) are hard coded into
    'a Generic.Dictionary below.



    'Paths is a list of only valid Paths found. 
    'If a word prefix or word is not found the path is not
    'added and extending that path is terminated.
    Dim Paths As New Generic.List(Of String)

    'NeighborsOf. The keys are the letters a to p.
    'The value is a string of letters representing neighboring letters.
    'The string of neighboring letters is split and iterated later.
    Dim NeigborsOf As New Generic.Dictionary(Of String, String)

    'BoggleLetters. The keys are mapped to the lettered grid of a to p.
    'The values are what the user inputs on the page.
    Dim BoggleLetters As New Generic.Dictionary(Of String, String)

    'Used to store last postition of path. This will be a letter
    'from a to p.
    Dim LastPositionOfPath As String = ""

    'I found a HashTable was by far faster than a Generic.Dictionary 
    ' - about 10 times faster. This stores prefixes of words and words.
    'I determined 792773 was the number of words and unique prefixes that
    'will be generated from the dictionary file. This is a max number and
    'the final hashtable will not have that many.
    Dim HashTableOfPrefixesAndWords As New Hashtable(792773)

    'Stores words that are found.
    Dim FoundWords As New Generic.List(Of String)

    'Just to validate what the user enters in the grid.
    Dim ErrorFoundWithSubmittedLetters As Boolean = False

    Public Sub BuildAndTestPathsAndFindWords(ByVal ThisPath As String)
        'Word is the word correlating to the ThisPath parameter.
        'This path would be a series of letters from a to p.
        Dim Word As String = ""

        'The path is iterated through and a word based on the actual
        'letters in the Boggle grid is assembled.
        For i As Integer = 0 To ThisPath.Length - 1
            Word += Me.BoggleLetters(ThisPath.Substring(i, 1))
        Next

        'If my hashtable of word prefixes and words doesn't contain this Word
        'Then this isn't a word and any further extension of ThisPath will not
        'yield any words either. So exit sub to terminate exploring this path.
        If Not HashTableOfPrefixesAndWords.ContainsKey(Word) Then Exit Sub

        'The value of my hashtable is a boolean representing if the key if a word (true) or
        'just a prefix (false). If true and at least 3 letters long then yay! word found.
        If HashTableOfPrefixesAndWords(Word) AndAlso Word.Length > 2 Then Me.FoundWords.Add(Word)

        'If my List of Paths doesn't contain ThisPath then add it.
        'Remember only valid paths will make it this far. Paths not found
        'in the HashTableOfPrefixesAndWords cause this sub to exit above.
        If Not Paths.Contains(ThisPath) Then Paths.Add(ThisPath)

        'Examine the last letter of ThisPath. We are looking to extend the path
        'to our neighboring letters if any are still available.
        LastPositionOfPath = ThisPath.Substring(ThisPath.Length - 1, 1)

        'Loop through my list of neighboring letters (representing grid points).
        For Each Neighbor As String In Me.NeigborsOf(LastPositionOfPath).ToCharArray()
            'If I find a neighboring grid point that I haven't already used
            'in ThisPath then extend ThisPath and feed the new path into
            'this recursive function. (see recursive.)
            If Not ThisPath.Contains(Neighbor) Then Me.BuildAndTestPathsAndFindWords(ThisPath & Neighbor)
        Next
    End Sub

    Protected Sub ButtonBoggle_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles ButtonBoggle.Click

        'User has entered the 16 letters and clicked the go button.

        'Set up my Generic.Dictionary of grid points, I'm using letters a to p -
        'not an x,y grid system.  The values are neighboring points.
        NeigborsOf.Add("a", "bfe")
        NeigborsOf.Add("b", "cgfea")
        NeigborsOf.Add("c", "dhgfb")
        NeigborsOf.Add("d", "hgc")
        NeigborsOf.Add("e", "abfji")
        NeigborsOf.Add("f", "abcgkjie")
        NeigborsOf.Add("g", "bcdhlkjf")
        NeigborsOf.Add("h", "cdlkg")
        NeigborsOf.Add("i", "efjnm")
        NeigborsOf.Add("j", "efgkonmi")
        NeigborsOf.Add("k", "fghlponj")
        NeigborsOf.Add("l", "ghpok")
        NeigborsOf.Add("m", "ijn")
        NeigborsOf.Add("n", "ijkom")
        NeigborsOf.Add("o", "jklpn")
        NeigborsOf.Add("p", "klo")

        'Retrieve letters the user entered.
        BoggleLetters.Add("a", Me.TextBox1.Text.ToLower.Trim())
        BoggleLetters.Add("b", Me.TextBox2.Text.ToLower.Trim())
        BoggleLetters.Add("c", Me.TextBox3.Text.ToLower.Trim())
        BoggleLetters.Add("d", Me.TextBox4.Text.ToLower.Trim())
        BoggleLetters.Add("e", Me.TextBox5.Text.ToLower.Trim())
        BoggleLetters.Add("f", Me.TextBox6.Text.ToLower.Trim())
        BoggleLetters.Add("g", Me.TextBox7.Text.ToLower.Trim())
        BoggleLetters.Add("h", Me.TextBox8.Text.ToLower.Trim())
        BoggleLetters.Add("i", Me.TextBox9.Text.ToLower.Trim())
        BoggleLetters.Add("j", Me.TextBox10.Text.ToLower.Trim())
        BoggleLetters.Add("k", Me.TextBox11.Text.ToLower.Trim())
        BoggleLetters.Add("l", Me.TextBox12.Text.ToLower.Trim())
        BoggleLetters.Add("m", Me.TextBox13.Text.ToLower.Trim())
        BoggleLetters.Add("n", Me.TextBox14.Text.ToLower.Trim())
        BoggleLetters.Add("o", Me.TextBox15.Text.ToLower.Trim())
        BoggleLetters.Add("p", Me.TextBox16.Text.ToLower.Trim())

        'Validate user entered something with a length of 1 for all 16 textboxes.
        For Each S As String In BoggleLetters.Keys
            If BoggleLetters(S).Length <> 1 Then
                ErrorFoundWithSubmittedLetters = True
                Exit For
            End If
        Next

        'If input is not valid then...
        If ErrorFoundWithSubmittedLetters Then
            'Present error message.
        Else
            'Else assume we have 16 letters to work with and start finding words.
            Dim SB As New StringBuilder

            Dim Time As String = String.Format("{0}:{1}:{2}:{3}", Date.Now.Hour.ToString(), Date.Now.Minute.ToString(), Date.Now.Second.ToString(), Date.Now.Millisecond.ToString())

            Dim NumOfLetters As Integer = 0
            Dim Word As String = ""
            Dim TempWord As String = ""
            Dim Letter As String = ""
            Dim fr As StreamReader = Nothing
            fr = New System.IO.StreamReader(HttpContext.Current.Request.MapPath("~/boggle/dic.txt"))

            'First fill my hashtable with word prefixes and words.
            'HashTable(PrefixOrWordString, BooleanTrueIfWordFalseIfPrefix)
            While fr.Peek <> -1
                Word = fr.ReadLine.Trim()
                TempWord = ""
                For i As Integer = 0 To Word.Length - 1
                    Letter = Word.Substring(i, 1)
                    'This optimization helped quite a bit. Words in the dictionary that begin
                    'with letters that the user did not enter in the grid shouldn't go in my hashtable.
                    '
                    'I realize most of the solutions went with a Trie. I'd never heard of that before,
                    'which is one of the neat things about SO, seeing how others approach challenges
                    'and learning some best practices.
                    '
                    'However, I didn't code a Trie in my solution. I just have a hashtable with 
                    'all words in the dicitonary file and all possible prefixes for those words.
                    'A Trie might be faster but I'm not coding it now. I'm getting good times with this.
                    If i = 0 AndAlso Not BoggleLetters.ContainsValue(Letter) Then Continue While
                    TempWord += Letter
                    If Not HashTableOfPrefixesAndWords.ContainsKey(TempWord) Then
                        HashTableOfPrefixesAndWords.Add(TempWord, TempWord = Word)
                    End If
                Next
            End While

            SB.Append("Number of Word Prefixes and Words in Hashtable: " & HashTableOfPrefixesAndWords.Count.ToString())
            SB.Append("<br />")

            SB.Append("Loading Dictionary: " & Time & " - " & String.Format("{0}:{1}:{2}:{3}", Date.Now.Hour.ToString(), Date.Now.Minute.ToString(), Date.Now.Second.ToString(), Date.Now.Millisecond.ToString()))
            SB.Append("<br />")

            Time = String.Format("{0}:{1}:{2}:{3}", Date.Now.Hour.ToString(), Date.Now.Minute.ToString(), Date.Now.Second.ToString(), Date.Now.Millisecond.ToString())

            'This starts a path at each point on the grid an builds a path until 
            'the string of letters correlating to the path is not found in the hashtable
            'of word prefixes and words.
            Me.BuildAndTestPathsAndFindWords("a")
            Me.BuildAndTestPathsAndFindWords("b")
            Me.BuildAndTestPathsAndFindWords("c")
            Me.BuildAndTestPathsAndFindWords("d")
            Me.BuildAndTestPathsAndFindWords("e")
            Me.BuildAndTestPathsAndFindWords("f")
            Me.BuildAndTestPathsAndFindWords("g")
            Me.BuildAndTestPathsAndFindWords("h")
            Me.BuildAndTestPathsAndFindWords("i")
            Me.BuildAndTestPathsAndFindWords("j")
            Me.BuildAndTestPathsAndFindWords("k")
            Me.BuildAndTestPathsAndFindWords("l")
            Me.BuildAndTestPathsAndFindWords("m")
            Me.BuildAndTestPathsAndFindWords("n")
            Me.BuildAndTestPathsAndFindWords("o")
            Me.BuildAndTestPathsAndFindWords("p")

            SB.Append("Finding Words: " & Time & " - " & String.Format("{0}:{1}:{2}:{3}", Date.Now.Hour.ToString(), Date.Now.Minute.ToString(), Date.Now.Second.ToString(), Date.Now.Millisecond.ToString()))
            SB.Append("<br />")

            SB.Append("Num of words found: " & FoundWords.Count.ToString())
            SB.Append("<br />")
            SB.Append("<br />")

            FoundWords.Sort()
            SB.Append(String.Join("<br />", FoundWords.ToArray()))

            'Output results.
            Me.LiteralBoggleResults.Text = SB.ToString()
            Me.PanelBoggleResults.Visible = True

        End If

    End Sub

End Class

令人惊讶的是,没有人尝试使用PHP版本

这是John Fouhy的Python解决方案的PHP版本

虽然我从其他人的答案中得到了一些提示,但这大部分是从约翰那里抄来的

$boggle = "fxie
           amlo
           ewbx
           astu";

$alphabet = str_split(str_replace(array("\n", " ", "\r"), "", strtolower($boggle)));
$rows = array_map('trim', explode("\n", $boggle));
$dictionary = file("C:/dict.txt");
$prefixes = array(''=>'');
$words = array();
$regex = '/[' . implode('', $alphabet) . ']{3,}$/S';
foreach($dictionary as $k=>$value) {
    $value = trim(strtolower($value));
    $length = strlen($value);
    if(preg_match($regex, $value)) {
        for($x = 0; $x < $length; $x++) {
            $letter = substr($value, 0, $x+1);
            if($letter == $value) {
                $words[$value] = 1;
            } else {
                $prefixes[$letter] = 1;
            }
        }
    }
}

$graph = array();
$chardict = array();
$positions = array();
$c = count($rows);
for($i = 0; $i < $c; $i++) {
    $l = strlen($rows[$i]);
    for($j = 0; $j < $l; $j++) {
        $chardict[$i.','.$j] = $rows[$i][$j];
        $children = array();
        $pos = array(-1,0,1);
        foreach($pos as $z) {
            $xCoord = $z + $i;
            if($xCoord < 0 || $xCoord >= count($rows)) {
                continue;
            }
            $len = strlen($rows[0]);
            foreach($pos as $w) {
                $yCoord = $j + $w;
                if(($yCoord < 0 || $yCoord >= $len) || ($z == 0 && $w == 0)) {
                    continue;
                }
                $children[] = array($xCoord, $yCoord);
            }
        }
        $graph['None'][] = array($i, $j);
        $graph[$i.','.$j] = $children;
    }
}

function to_word($chardict, $prefix) {
    $word = array();
    foreach($prefix as $v) {
        $word[] = $chardict[$v[0].','.$v[1]];
    }
    return implode("", $word);
}

function find_words($graph, $chardict, $position, $prefix, $prefixes, &$results, $words) {
    $word = to_word($chardict, $prefix);
    if(!isset($prefixes[$word])) return false;

    if(isset($words[$word])) {
        $results[] = $word;
    }

    foreach($graph[$position] as $child) {
        if(!in_array($child, $prefix)) {
            $newprefix = $prefix;
            $newprefix[] = $child;
            find_words($graph, $chardict, $child[0].','.$child[1], $newprefix, $prefixes, $results, $words);
        }
    }
}

$solution = array();
find_words($graph, $chardict, 'None', array(), $prefixes, $solution);
print_r($solution);
如果你想试试的话,这里有一个例子。虽然在我的本地机器上需要~2秒,但在我的Web服务器上需要~5秒。不管是哪种情况,速度都不是很快。尽管如此,它还是非常可怕,所以我可以想象时间可以显著缩短。任何关于如何实现这一点的建议都会得到赞赏。PHP缺少元组,这使得坐标变得非常复杂与rd和我的 无法理解到底发生了什么根本没有帮助


编辑:一些修复使它在本地花费不到1秒。

当我看到问题陈述时,我想Trie。但看到其他几张海报也使用了这种方法,我就寻找另一种不同的方法。唉,Trie方法的性能更好。我在我的机器上运行了Kent的Perl解决方案,在调整它以使用我的字典文件之后,运行了0.31秒。我自己的perl实现需要0.54秒才能运行

这就是我的方法:

创建转换哈希以对合法转换进行建模

遍历所有16^3个可能的三个字母组合

在循环中,排除非法转换并重复访问 同一个正方形。形成所有合法的3个字母序列,并将其存储在散列中。 然后循环浏览字典中的所有单词

排除太长或太短的单词 在每个单词上滑动一个三个字母的窗口,看看它是否在步骤2中的三个字母组合中。排除失败的单词。这将消除大多数不匹配项。 如果仍然没有消除,使用递归算法,看看是否可以通过在拼图中创建路径来形成单词。此部分速度较慢,但很少调用。 把我找到的单词打印出来

我尝试了3个字母和4个字母的序列,但是4个字母的序列减慢了程序的速度

在我的代码中,我使用/usr/share/dict/words作为字典。它是MAC OS X和许多Unix系统的标准配置。如果需要,可以使用其他文件。要破解不同的谜题,只需更改变量@puzzle。这将很容易适应较大的矩阵。您只需更改%transitions哈希和%LegaltTransitions哈希即可

这个解决方案的优点是代码短,数据结构简单

以下是Perl代码,它使用了太多全局变量,我知道:

#!/usr/bin/perl
use Time::HiRes  qw{ time };

sub readFile($);
sub findAllPrefixes($);
sub isWordTraceable($);
sub findWordsInPuzzle(@);

my $startTime = time;

# Puzzle to solve

my @puzzle = ( 
    F, X, I, E,
    A, M, L, O,
    E, W, B, X,
    A, S, T, U
);

my $minimumWordLength = 3;
my $maximumPrefixLength = 3; # I tried four and it slowed down.

# Slurp the word list.
my $wordlistFile = "/usr/share/dict/words";

my @words = split(/\n/, uc(readFile($wordlistFile)));
print "Words loaded from word list: " . scalar @words . "\n";

print "Word file load time: " . (time - $startTime) . "\n";
my $postLoad = time;

# Define the legal transitions from one letter position to another. 
# Positions are numbered 0-15.
#     0  1  2  3
#     4  5  6  7
#     8  9 10 11
#    12 13 14 15
my %transitions = ( 
   -1 => [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],
    0 => [1,4,5], 
    1 => [0,2,4,5,6],
    2 => [1,3,5,6,7],
    3 => [2,6,7],
    4 => [0,1,5,8,9],
    5 => [0,1,2,4,6,8,9,10],
    6 => [1,2,3,5,7,9,10,11],
    7 => [2,3,6,10,11],
    8 => [4,5,9,12,13],
    9 => [4,5,6,8,10,12,13,14],
    10 => [5,6,7,9,11,13,14,15],
    11 => [6,7,10,14,15],
    12 => [8,9,13],
    13 => [8,9,10,12,14],
    14 => [9,10,11,13,15],
    15 => [10,11,14]
);

# Convert the transition matrix into a hash for easy access.
my %legalTransitions = ();
foreach my $start (keys %transitions) {
    my $legalRef = $transitions{$start};
    foreach my $stop (@$legalRef) {
        my $index = ($start + 1) * (scalar @puzzle) + ($stop + 1);
        $legalTransitions{$index} = 1;
    }
}

my %prefixesInPuzzle = findAllPrefixes($maximumPrefixLength);

print "Find prefixes time: " . (time - $postLoad) . "\n";
my $postPrefix = time;

my @wordsFoundInPuzzle = findWordsInPuzzle(@words);

print "Find words in puzzle time: " . (time - $postPrefix) . "\n";

print "Unique prefixes found: " . (scalar keys %prefixesInPuzzle) . "\n";
print "Words found (" . (scalar @wordsFoundInPuzzle) . ") :\n    " . join("\n    ", @wordsFoundInPuzzle) . "\n";

print "Total Elapsed time: " . (time - $startTime) . "\n";

###########################################

sub readFile($) {
    my ($filename) = @_;
    my $contents;
    if (-e $filename) {
        # This is magic: it opens and reads a file into a scalar in one line of code. 
        # See http://www.perl.com/pub/a/2003/11/21/slurp.html
        $contents = do { local( @ARGV, $/ ) = $filename ; <> } ; 
    }
    else {
        $contents = '';
    }
    return $contents;
}

# Is it legal to move from the first position to the second? They must be adjacent.
sub isLegalTransition($$) {
    my ($pos1,$pos2) = @_;
    my $index = ($pos1 + 1) * (scalar @puzzle) + ($pos2 + 1);
    return $legalTransitions{$index};
}

# Find all prefixes where $minimumWordLength <= length <= $maxPrefixLength
#
#   $maxPrefixLength ... Maximum length of prefix we will store. Three gives best performance. 
sub findAllPrefixes($) {
    my ($maxPrefixLength) = @_;
    my %prefixes = ();
    my $puzzleSize = scalar @puzzle;

    # Every possible N-letter combination of the letters in the puzzle 
    # can be represented as an integer, though many of those combinations
    # involve illegal transitions, duplicated letters, etc.
    # Iterate through all those possibilities and eliminate the illegal ones.
    my $maxIndex = $puzzleSize ** $maxPrefixLength;

    for (my $i = 0; $i < $maxIndex; $i++) {
        my @path;
        my $remainder = $i;
        my $prevPosition = -1;
        my $prefix = '';
        my %usedPositions = ();
        for (my $prefixLength = 1; $prefixLength <= $maxPrefixLength; $prefixLength++) {
            my $position = $remainder % $puzzleSize;

            # Is this a valid step?
            #  a. Is the transition legal (to an adjacent square)?
            if (! isLegalTransition($prevPosition, $position)) {
                last;
            }

            #  b. Have we repeated a square?
            if ($usedPositions{$position}) {
                last;
            }
            else {
                $usedPositions{$position} = 1;
            }

            # Record this prefix if length >= $minimumWordLength.
            $prefix .= $puzzle[$position];
            if ($prefixLength >= $minimumWordLength) {
                $prefixes{$prefix} = 1;
            }

            push @path, $position;
            $remainder -= $position;
            $remainder /= $puzzleSize;
            $prevPosition = $position;
        } # end inner for
    } # end outer for
    return %prefixes;
}

# Loop through all words in dictionary, looking for ones that are in the puzzle.
sub findWordsInPuzzle(@) {
    my @allWords = @_;
    my @wordsFound = ();
    my $puzzleSize = scalar @puzzle;
WORD: foreach my $word (@allWords) {
        my $wordLength = length($word);
        if ($wordLength > $puzzleSize || $wordLength < $minimumWordLength) {
            # Reject word as too short or too long.
        }
        elsif ($wordLength <= $maximumPrefixLength ) {
            # Word should be in the prefix hash.
            if ($prefixesInPuzzle{$word}) {
                push @wordsFound, $word;
            }
        }
        else {
            # Scan through the word using a window of length $maximumPrefixLength, looking for any strings not in our prefix list.
            # If any are found that are not in the list, this word is not possible.
            # If no non-matches are found, we have more work to do.
            my $limit = $wordLength - $maximumPrefixLength + 1;
            for (my $startIndex = 0; $startIndex < $limit; $startIndex ++) {
                if (! $prefixesInPuzzle{substr($word, $startIndex, $maximumPrefixLength)}) {
                    next WORD;
                }
            }
            if (isWordTraceable($word)) {
                # Additional test necessary: see if we can form this word by following legal transitions
                push @wordsFound, $word;
            }
        }

    }
    return @wordsFound;
}

# Is it possible to trace out the word using only legal transitions?
sub isWordTraceable($) {
    my $word = shift;
    return traverse([split(//, $word)], [-1]); # Start at special square -1, which may transition to any square in the puzzle.
}

# Recursively look for a path through the puzzle that matches the word.
sub traverse($$) {
    my ($lettersRef, $pathRef) = @_;
    my $index = scalar @$pathRef - 1;
    my $position = $pathRef->[$index];
    my $letter = $lettersRef->[$index];
    my $branchesRef =  $transitions{$position};
BRANCH: foreach my $branch (@$branchesRef) {
            if ($puzzle[$branch] eq $letter) {
                # Have we used this position yet?
                foreach my $usedBranch (@$pathRef) {
                    if ($usedBranch == $branch) {
                        next BRANCH;
                    }
                }
                if (scalar @$lettersRef == $index + 1) {
                    return 1; # End of word and success.
                }
                push @$pathRef, $branch;
                if (traverse($lettersRef, $pathRef)) {
                    return 1; # Recursive success.
                }
                else {
                    pop @$pathRef;
                }
            }
        }
    return 0; # No path found. Failed.
}

我对Java的尝试。读取文件和构建trie大约需要2秒,解决这个难题大约需要50毫秒。我使用了问题中链接的词典,它有一些我不知道的英语单词,如fae、ima

0 [main] INFO gineer.bogglesolver.util.Util  - Reading the dictionary
2234 [main] INFO gineer.bogglesolver.util.Util  - Finish reading the dictionary
2234 [main] INFO gineer.bogglesolver.Solver  - Found: FAM
2234 [main] INFO gineer.bogglesolver.Solver  - Found: FAME
2234 [main] INFO gineer.bogglesolver.Solver  - Found: FAMBLE
2234 [main] INFO gineer.bogglesolver.Solver  - Found: FAE
2234 [main] INFO gineer.bogglesolver.Solver  - Found: IMA
2234 [main] INFO gineer.bogglesolver.Solver  - Found: ELI
2234 [main] INFO gineer.bogglesolver.Solver  - Found: ELM
2234 [main] INFO gineer.bogglesolver.Solver  - Found: ELB
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AXIL
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AXILE
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AXLE
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AMI
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AMIL
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AMLI
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AME
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AMBLE
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AMBO
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AES
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWL
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWEST
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MIX
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MIL
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MILE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MILO
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MAX
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MAE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MAW
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MEW
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MEWL
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MES
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MESA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIM
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIMA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIMAX
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIME
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIMES
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIMB
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIMBO
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIMBU
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LEI
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LEO
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LOB
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LOX
2250 [main] INFO gineer.bogglesolver.Solver  - Found: OIME
2250 [main] INFO gineer.bogglesolver.Solver  - Found: OIL
2250 [main] INFO gineer.bogglesolver.Solver  - Found: OLE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: OLM
2250 [main] INFO gineer.bogglesolver.Solver  - Found: EMIL
2250 [main] INFO gineer.bogglesolver.Solver  - Found: EMBOLE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: EMBOX
2250 [main] INFO gineer.bogglesolver.Solver  - Found: EAST
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAF
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAX
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAME
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAMBLE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WEA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WEAM
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WEM
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WEA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WES
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WEST
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAS
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WASE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAST
2250 [main] INFO gineer.bogglesolver.Solver  - Found: BLEO
2250 [main] INFO gineer.bogglesolver.Solver  - Found: BLO
2250 [main] INFO gineer.bogglesolver.Solver  - Found: BOIL
2250 [main] INFO gineer.bogglesolver.Solver  - Found: BOLE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: BUT
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AES
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWL
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWEST
2250 [main] INFO gineer.bogglesolver.Solver  - Found: ASE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: ASEM
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AST
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SEA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SEAX
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SEAM
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SEMI
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SEMBLE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SEW
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SEA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SWAM
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SWAMI
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SAW
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SAWT
2250 [main] INFO gineer.bogglesolver.Solver  - Found: STU
2250 [main] INFO gineer.bogglesolver.Solver  - Found: STUB
2250 [main] INFO gineer.bogglesolver.Solver  - Found: TWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: TWAE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: TWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: TWAE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: TWAS
2250 [main] INFO gineer.bogglesolver.Solver  - Found: TUB
2250 [main] INFO gineer.bogglesolver.Solver  - Found: TUX
源代码由6个类组成。如果这不是StackOverflow的正确做法,请告诉我,我会在下面发布它们

引擎 Enginer.bogglesolver.Solver giner.bogglesolver.trie.trie giner.bogglesolver.util.Constants giner.bogglesolver.util.util
我知道我已经迟到了,但我不久前用PHP做了一个——也只是为了好玩

在0.90108秒内找到75个单词133分

F………X………I………E。。。。。。。。。。。。。。。 A、M、L、O。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。 E、W、B、X A…………S…………T…………U

给出程序实际执行的一些指示-每个字母都是它开始查看模式的位置,而每个“.”显示它尝试采用的路径。“.”越多,搜索就越深入


如果你想要密码,请告诉我。。。这是一个可怕的PHP和HTML的混合体,从来就不打算看到光明,所以我不敢在这里发布:P

我花了3个月的时间研究10个最佳点密集5x5 Boggle Bockers boards问题的解决方案

这个问题现在已经解决,并在5个网页上进行了全面披露。有问题请与我联系

board analysis算法使用显式堆栈通过具有直接子信息的有向无环字图和时间戳跟踪机制伪递归地遍历board方块。这很可能是世界上最先进的词典数据结构

该方案在四核上每秒评估约10000块非常好的电路板。9500多分

父网页:

DeepSearch.c-

组件网页:

最佳记分牌-

高级词汇结构-

电路板分析算法-

并行批处理-

-
这一综合性的工作只会让要求最好的人感兴趣。

我意识到这个问题的时间已经过去了,但由于我自己正在研究一个解决方案,在谷歌搜索时偶然发现了这个问题,我想我应该发布一个我的参考,因为它似乎与其他一些问题有点不同

我选择在游戏板上使用平面数组,并对板上的每个字母进行递归搜索,从一个有效的邻居遍历到另一个有效的邻居,如果索引中有有效的前缀,则扩展当前字母列表的搜索。当遍历当前单词的概念时,它是一系列索引,而不是组成单词的字母。检查索引时,将索引转换为字母并完成检查

索引是一个蛮力字典,有点像trie,但允许Pythonic查询 这是索引的一部分。如果单词“cat”和“cate”在列表中,您可以在字典中找到:

   d = { 'c': ['cat','cater'],
     'ca': ['cat','cater'],
     'cat': ['cat','cater'],
     'cate': ['cater'],
     'cater': ['cater'],
   }
因此,如果当前_单词是'ca',您就知道它是一个有效的前缀,因为d中的'ca'返回True,所以继续板遍历。如果当前的_单词是'cat',那么你知道它是一个有效的单词,因为它是一个有效的前缀,d['cat']中的'cat'也返回True

如果感觉像这样,允许一些可读代码看起来不会太慢。和其他人一样,这个系统的费用是读取/建立索引。解决电路板问题相当麻烦


代码位于。它是故意垂直和天真的,有很多明确的有效性检查,因为我想理解问题,而不是用一堆魔术或晦涩的东西把它弄糟。

只是为了好玩,我在bash中实现了一个。 它不是超快速的,而是合理的

我有。它将字典预编译为trie,并使用两个字母序列频率来消除单词中永远不会出现的边缘,从而进一步加快处理速度

它在0.35毫秒内解决了示例板的问题,额外的6毫秒启动时间主要与将trie加载到内存有关

解决办法如下:

["swami"; "emile"; "limbs"; "limbo"; "limes"; "amble"; "tubs"; "stub";
 "swam"; "semi"; "seam"; "awes"; "buts"; "bole"; "boil"; "west"; "east";
 "emil"; "lobs"; "limb"; "lime"; "lima"; "mesa"; "mews"; "mewl"; "maws";
 "milo"; "mile"; "awes"; "amie"; "axle"; "elma"; "fame"; "ubs"; "tux"; "tub";
 "twa"; "twa"; "stu"; "saw"; "sea"; "sew"; "sea"; "awe"; "awl"; "but"; "btu";
 "box"; "bmw"; "was"; "wax"; "oil"; "lox"; "lob"; "leo"; "lei"; "lie"; "mes";
 "mew"; "mae"; "maw"; "max"; "mil"; "mix"; "awe"; "awl"; "elm"; "eli"; "fax"]

以下是我的java实现:

Trie构建耗时0小时0分1秒532毫秒 单词搜索花费了0小时0分0秒92毫秒

eel eeler eely eer eke eker eld eleut elk ell 
elle epee epihippus ere erept err error erupt eurus eye 
eyer eyey hip hipe hiper hippish hipple hippus his hish 
hiss hist hler hsi ihi iphis isis issue issuer ist 
isurus kee keek keeker keel keeler keep keeper keld kele 
kelek kelep kelk kell kelly kelp kelper kep kepi kept 
ker kerel kern keup keuper key kyl kyle lee leek 
leeky leep leer lek leo leper leptus lepus ler leu 
ley lleu lue lull luller lulu lunn lunt lunule luo 
lupe lupis lupulus lupus lur lure lurer lush lushly lust 
lustrous lut lye nul null nun nupe nurture nurturer nut 
oer ore ort ouphish our oust out outpeep outpeer outpipe 
outpull outpush output outre outrun outrush outspell outspue outspurn outspurt 
outstrut outstunt outsulk outturn outusure oyer pee peek peel peele 
peeler peeoy peep peeper peepeye peer pele peleus pell peller 
pelu pep peplus pepper pepperer pepsis per pern pert pertussis 
peru perule perun peul phi pip pipe piper pipi pipistrel 
pipistrelle pipistrellus pipper pish piss pist plup plus plush ply 
plyer psi pst puerer pul pule puler pulk pull puller 
pulley pullus pulp pulper pulu puly pun punt pup puppis 
pur pure puree purely purer purr purre purree purrel purrer 
puru purupuru pus push puss pustule put putt puture ree 
reek reeker reeky reel reeler reeper rel rely reoutput rep 
repel repeller repipe reply repp reps reree rereel rerun reuel 
roe roer roey roue rouelle roun roup rouper roust rout 
roy rue ruelle ruer rule ruler rull ruller run runt 
rupee rupert rupture ruru rus rush russ rust rustre rut 
shi shih ship shipper shish shlu sip sipe siper sipper 
sis sish sisi siss sissu sist sistrurus speel speer spelk 
spell speller splurt spun spur spurn spurrer spurt sput ssi 
ssu stre stree streek streel streeler streep streke streperous strepsis 
strey stroup stroy stroyer strue strunt strut stu stue stull 
stuller stun stunt stupe stupeous stupp sturnus sturt stuss stut 
sue suer suerre suld sulk sulker sulky sull sully sulu 
sun sunn sunt sunup sup supe super superoutput supper supple 
supplely supply sur sure surely surrey sus susi susu susurr 
susurrous susurrus sutu suture suu tree treey trek trekker trey 
troupe trouper trout troy true truer trull truller truly trun 
trush truss trust tshi tst tsun tsutsutsi tue tule tulle 
tulu tun tunu tup tupek tupi tur turn turnup turr 
turus tush tussis tussur tut tuts tutu tutulus ule ull 
uller ulu ululu unreel unrule unruly unrun unrust untrue untruly 
untruss untrust unturn unurn upper upperer uppish uppishly uppull uppush 
upspurt upsun upsup uptree uptruss upturn ure urn uro uru 
urus urushi ush ust usun usure usurer utu yee yeel 
yeld yelk yell yeller yelp yelper yeo yep yer yere 
yern yoe yor yore you youl youp your yourn yoy 
注: 我在这个线程的开头使用了字典和字符矩阵。这段代码是在我的MacBookPro上运行的,下面是关于这台机器的一些信息

型号名称:MacBook Pro 型号标识符:MacBookPro8,1 处理器名称:英特尔酷睿i5 处理器速度:2.3 GHz 处理器数量:1 核心总数:2 每个核心的二级缓存:256 KB 三级缓存:3 MB 内存:4 GB 启动ROM版本:MBP81.0047.B0E
SMC版本系统:1.68f96

我认为您可能会花费大部分时间尝试匹配字母网格无法构建的单词。所以,我要做的第一件事就是努力加快这一步,这将让你在大部分的道路上

为此,我将把网格重新表示为一个可能的移动表,您可以通过查看的字母转换对其进行索引

首先,从整个字母表a=0、B=1、C=2……中为每个字母分配一个数字。。。等等

让我们举一个例子:

h b c d
e e g h
l l k l
m o f p
现在,让我们使用我们通常使用的字母表,您可能每次都想使用相同的整个字母表:

 b | c | d | e | f | g | h | k | l | m |  o |  p
---+---+---+---+---+---+---+---+---+---+----+----
 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11
然后制作一个二维布尔数组,告诉您是否有某个字母转换可用:

     |  0  1  2  3  4  5  6  7  8  9 10 11  <- from letter
     |  b  c  d  e  f  g  h  k  l  m  o  p
-----+--------------------------------------
 0 b |     T     T     T  T     
 1 c |  T     T  T     T  T
 2 d |     T           T  T
 3 e |  T  T     T     T  T  T  T
 4 f |                       T  T     T  T
 5 g |  T  T  T  T        T  T  T
 6 h |  T  T  T  T     T     T  T
 7 k |           T  T  T  T     T     T  T
 8 l |           T  T  T  T  T  T  T  T  T
 9 m |                          T     T
10 o |              T        T  T  T
11 p |              T        T  T
 ^
 to letter
然后通过在表中查找这些转换来检查是否允许这些转换:

[6][ 3] : T
[3][ 8] : T
[8][ 8] : T
[8][10] : T
如果他们都被允许,这个词就有可能被找到

例如,可以在第四次从m到e:helmet的转换中排除单词helmet,因为表中的条目为false

仓鼠这个词可以被排除,因为第一个h到一个过渡是不允许的,甚至在你的表中都不存在

现在,对于剩下的很少几个你没有删掉的单词,试着按照你现在的方式或者其他答案中的建议,在表格中找到它们。这是为了避免由于网格中相同字母之间的跳转而导致的误报。例如,表格允许使用“帮助”一词,但网格不允许

有关此想法的一些进一步性能改进提示:

不要使用2D数组,而是使用1D数组,只需自己计算第二个字母的索引即可。因此,与上面的12x12阵列不同,制作一个长度为144的1D阵列。如果您始终使用相同的字母表,即标准英文字母表使用26x26=676x1数组,即使并非所有字母都显示在网格中,您也可以将索引预计算到该1D数组中,您需要测试该数组以匹配词典中的单词。例如,上面示例中“hello”的索引为

hello (6, 3, 8, 8, 10):
42 (from 6 + 3x12), 99, 104, 128
-> "hello" will be stored as 42, 99, 104, 128 in the dictionary
将此概念扩展到以一维数组表示的三维表格,即所有允许的3个字母组合。这样,您可以立即删除更多的单词,并将每个单词的数组查找次数减少1:对于“hello”,您只需要3个数组查找:hel、ell、llo。顺便说一句,建立这个表会很快,因为你的网格中只有400个可能的3字母移动

预先计算网格中需要包含在表中的移动的索引。对于上面的示例,您需要将以下条目设置为“True”:

(0,0) (0,1) -> here: h, b : [6][0]
(0,0) (1,0) -> here: h, e : [6][3]
(0,0) (1,1) -> here: h, e : [6][3]
(0,1) (0,0) -> here: b, h : [0][6]
(0,1) (0,2) -> here: b, c : [0][1]
.
:
还可以用16个条目的一维数组表示游戏网格,并在3中预先计算表格。将索引包含在此数组中。 我相信如果你使用这种方法,你可以让你的代码运行得非常快,如果你有预先计算好的字典,并且已经加载到内存中

顺便说一句:如果你正在制作一个游戏,另一件很好的事情就是在后台立即运行这些东西。开始生成和解决 当用户仍在看应用程序的标题屏幕并将手指放在按下Play键的位置时,取消第一个游戏。然后在用户玩上一个游戏时生成并解决下一个游戏。这应该给你很多时间来运行你的代码

我喜欢这个问题,所以我可能会在未来几天的某个时候尝试用Java实现我的建议,看看它会如何实际执行。。。我会在这里发布代码

更新:

好的,我今天有一些时间用Java实现了这个想法:

类词典入口{ 公开信; 公共int[]三胞胎; } 类BoggleSolver{ //常数 final int ALPHABET_SIZE=5;//最多2^5=32个字母 最终int板_尺寸=4;//4x4板 final int[]moves={-BOARD_SIZE-1,-BOARD_SIZE,-BOARD_SIZE+1, -1, +1, +线路板尺寸-1,+线路板尺寸,+线路板尺寸+1}; //此处为灵活性计算的技术常数,但应为固定值 DictionaryEntry[]dictionary;//已处理单词列表 int maxWordLength=0; int[]boardTripletinces;//在板坐标中所有三个字母移动的列表 DictionaryEntry[]buildDictionaryString文件名引发IOException{ BufferedReader fileReader=新BufferedReadernew FileReaderfileName; String word=fileReader.readLine; ArrayList结果=新建ArrayList; 而word!=null{ 如果单词长度>=3{ word=word.toUpperCase; 如果word.length>maxWordLength maxWordLength=word.length; DictionaryEntry条目=新DictionaryEntry; entry.letters=新整数[word.length]; entry.triplets=新整数[word.length-2]; int i=0; 对于char字母:word.toCharArray{ entry.letters[i]=字节字母-65;//将ASCII转换为0..25 如果i>=2 条目.三元组[i-2]=条目.字母[i-2]4 返回数学值。absa%BOARD_SIZE-b%BOARD_SIZE>1; } int[]构建三联体{ ArrayList结果=新建ArrayList;
对于int a=0;a=0&&b=0&&c我也用Java解决了这个问题。我的实现有269行长,非常容易使用。首先,你需要创建一个Boggler类的新实例,然后用网格作为参数调用solve函数。在我的计算机上加载50000个单词的字典大约需要100毫秒,然后它会在ab中找到这些单词10-20毫秒。找到的单词存储在ArrayList foundWords中


我用c语言解决了这个问题。在我的机器上运行大约需要48毫秒,大约98%的时间是从磁盘加载字典和创建trie。字典是/usr/share/dict/american english,有62886个单词


我很快就完美地解决了这个问题。我把它放到了一个android应用程序中。在play store链接查看视频,看看它的实际效果

Word Cheats是一款破解任何矩阵式文字游戏的应用程序。该应用程序是由 来帮助我在单词加扰器上作弊。它可以用于单词搜索, ruzzle、words、WordFinder、WordCrack、boggle等等

在这里可以看到

在视频中查看正在运行的应用程序

> P>我在C++中编写了求解器。我实现了一个自定义的树结构。我不确定它可以被看作是一个TIE,但它是相似的。每个节点有26个分支,每个字母的1个分支。我与字典的分支平行地遍历桥牌的分支。如果分支不存在于字典中,我停止搜索。它位于Boggle board上。我将Boggle board上的所有字母转换为int。因此“A”=0。因为它只是数组,所以查找总是O1。每个节点存储是否完成一个单词以及其子节点中存在多少单词。当发现单词时,树会被修剪,以减少对相同单词的重复搜索。我相信修剪也是O1

CPU:奔腾SU2700 1.3GHz 内存:3gb

在<1秒内加载178590个单词的词典。 在4秒内解出100x100 Boggle.txt。找到约44000个单词。 解决4x4难题太快,无法提供有意义的基准:


一个Node.JS JavaScript解决方案。在不到一秒钟的时间内计算所有100个唯一单词,包括读取字典文件MBA 2012

输出: [家庭,家庭,家庭,家庭,家庭,家庭,家庭,家庭,家庭,家庭,家庭,家庭,家庭,waa,waa,waa,waa,waa,am,海,SEW,SEW,SEW,AES,wa,waa,waa,混合,MIL,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,WAS,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆,盆麦斯,梅萨,喵喵叫,车轴,名声,亚欧会议,迈尔,阿米尔,SEAX、SEAM、SEMI、SWAM、AMBO、AMLI、AXILE、AMBLE、SWAMI、AWEST、AWEST、LIMAX、LIMES、LIMBU、LIMBO、EMBOX、SEMBLE、EMBOLE、WAMBLE、FAMBLE]

代码:


给定一个有N行和M列的Boggle板,让我们假设如下:

N*M远远大于可能的字数 N*M远远大于可能的最长单词 在这些假设下,此解决方案的复杂性为*M

我想比较一下 这一示例板在许多方面都没有抓住要点,但为了完整性起见,此解决方案在=count}中完成 终止 终止 定义邻域点,矩阵 i、 j=点 [i-1,0]。最大值。。[i+1,矩阵.row_count-1].min.inject[]do|r,新的_i| [j-1,0]。最大值。。[j+1,矩阵.column_count-1].min.injectr do|r,new_j| 邻居=[new_i,new_j]
邻居。eql?点?r:r所以我想添加另一种PHP方法来解决这个问题,因为每个人都喜欢PHP。 我想做一些重构,比如对字典文件使用regexpression匹配,但现在我只是将整个字典文件加载到一个单词列表中

我是用链表的方法做的。每个节点都有一个字符值、一个位置值和一个下一个指针

位置值是我发现两个节点是否连接的方式

1     2     3     4
11    12    13    14
21    22    23    24
31    32    33    34
因此,使用该网格,我知道如果第一个节点的位置等于第二个节点的位置+/-1(对于同一行),那么两个节点是连接的,对于上面和下面的行,+/-9,10,11

我使用递归进行主搜索。它从单词列表中删除一个单词,找到所有可能的起始点,然后递归地找到下一个可能的连接,记住它不能转到它已经使用的位置,这就是我添加$notInLoc的原因

无论如何,我知道它需要一些重构,我很想听听如何使它更干净,但它会根据我使用的字典文件生成正确的结果。根据黑板上元音和组合的数量,大约需要3到6秒。我知道,一旦我预先匹配字典的结果,这将大大减少

<?php
    ini_set('xdebug.var_display_max_depth', 20);
    ini_set('xdebug.var_display_max_children', 1024);
    ini_set('xdebug.var_display_max_data', 1024);

    class Node {
        var $loc;

        function __construct($value) {
            $this->value = $value;
            $next = null;
        }
    }

    class Boggle {
        var $root;
        var $locList = array (1, 2, 3, 4, 11, 12, 13, 14, 21, 22, 23, 24, 31, 32, 33, 34);
        var $wordList = [];
        var $foundWords = [];

        function __construct($board) {
            // Takes in a board string and creates all the nodes
            $node = new Node($board[0]);
            $node->loc = $this->locList[0];
            $this->root = $node;
            for ($i = 1; $i < strlen($board); $i++) {
                    $node->next = new Node($board[$i]);
                    $node->next->loc = $this->locList[$i];
                    $node = $node->next;
            }
            // Load in a dictionary file
            // Use regexp to elimate all the words that could never appear and load the 
            // rest of the words into wordList
            $handle = fopen("dict.txt", "r");
            if ($handle) {
                while (($line = fgets($handle)) !== false) {
                    // process the line read.
                    $line = trim($line);
                    if (strlen($line) > 2) {
                        $this->wordList[] = trim($line);
                    }
                }
                fclose($handle);
            } else {
                // error opening the file.
                echo "Problem with the file.";
            } 
        }

        function isConnected($node1, $node2) {
        // Determines if 2 nodes are connected on the boggle board

            return (($node1->loc == $node2->loc + 1) || ($node1->loc == $node2->loc - 1) ||
               ($node1->loc == $node2->loc - 9) || ($node1->loc == $node2->loc - 10) || ($node1->loc == $node2->loc - 11) ||
               ($node1->loc == $node2->loc + 9) || ($node1->loc == $node2->loc + 10) || ($node1->loc == $node2->loc + 11)) ? true : false;

        }

        function find($value, $notInLoc = []) {
            // Returns a node with the value that isn't in a location
            $current = $this->root;
            while($current) {
                if ($current->value == $value && !in_array($current->loc, $notInLoc)) {
                    return $current;
                }
                if (isset($current->next)) {
                    $current = $current->next;
                } else {
                    break;
                }
            }
            return false;
        }

        function findAll($value) {
            // Returns an array of nodes with a specific value
            $current = $this->root;
            $foundNodes = [];
            while ($current) {
                if ($current->value == $value) {
                    $foundNodes[] = $current;
                }
                if (isset($current->next)) {
                    $current = $current->next;
                } else {
                    break;
                }
            }
            return (empty($foundNodes)) ? false : $foundNodes;
        }

        function findAllConnectedTo($node, $value, $notInLoc = []) {
            // Returns an array of nodes that are connected to a specific node and 
            // contain a specific value and are not in a certain location
            $nodeList = $this->findAll($value);
            $newList = [];
            if ($nodeList) {
                foreach ($nodeList as $node2) {
                    if (!in_array($node2->loc, $notInLoc) && $this->isConnected($node, $node2)) {
                        $newList[] = $node2;
                    }
                }
            }
            return (empty($newList)) ? false : $newList;
        }



        function inner($word, $list, $i = 0, $notInLoc = []) {
            $i++;
            foreach($list as $node) {
                $notInLoc[] = $node->loc;
                if ($list2 = $this->findAllConnectedTo($node, $word[$i], $notInLoc)) {
                    if ($i == (strlen($word) - 1)) {
                        return true;
                    } else {
                        return $this->inner($word, $list2, $i, $notInLoc);
                    }
                }
            }
            return false;
        }

        function findWord($word) {
            if ($list = $this->findAll($word[0])) {
                return $this->inner($word, $list);
            }
            return false;
        }

        function findAllWords() {
            foreach($this->wordList as $word) {
                if ($this->findWord($word)) {
                    $this->foundWords[] = $word;
                }
            }
        }

        function displayBoard() {
            $current = $this->root;
            for ($i=0; $i < 4; $i++) {
                echo $current->value . " " . $current->next->value . " " . $current->next->next->value . " " . $current->next->next->next->value . "<br />";
                if ($i < 3) {
                    $current = $current->next->next->next->next;
                }
            }
        }

    }

    function randomBoardString() {
        return substr(str_shuffle(str_repeat("abcdefghijklmnopqrstuvwxyz", 16)), 0, 16);
    }

    $myBoggle = new Boggle(randomBoardString());
    $myBoggle->displayBoard();
    $x = microtime(true);
    $myBoggle->findAllWords();
    $y = microtime(true);
    echo ($y-$x);
    var_dump($myBoggle->foundWords);

    ?>

<>我知道我在晚会上真的迟到了,但是我已经实现了一个编码练习,在几个编程语言中,一个笨拙的解决方案,C++,java,Go,C,Python,Ruby,JavaScript,朱丽亚,Lua,PHP,Perl和我认为有人可能对这些感兴趣,所以我把链接放在这里:

以下是使用NLTK工具包中预定义单词的解决方案 NLTK有NLTK.corpus包,因为我们有一个名为words的包,它包含了超过2个LAKHS的英语单词,您可以简单地将所有单词都用到您的程序中

创建矩阵后,将其转换为字符数组并执行以下代码

import nltk
from nltk.corpus import words
from collections import Counter

def possibleWords(input, charSet):
    for word in input:
        dict = Counter(word)
        flag = 1
        for key in dict.keys():
            if key not in charSet:
                flag = 0
        if flag == 1 and len(word)>5: #its depends if you want only length more than 5 use this otherwise remove that one. 
            print(word)


nltk.download('words')
word_list = words.words()
# prints 236736
print(len(word_list))
charSet = ['h', 'e', 'l', 'o', 'n', 'v', 't']
possibleWords(word_list, charSet)
输出:

eleven
eleventh
elevon
entente
entone
ethene
ethenol
evolve
evolvent
hellhole
helvell
hooven
letten
looten
nettle
nonene
nonent
nonlevel
notelet
novelet
novelette
novene
teenet
teethe
teevee
telethon
tellee
tenent
tentlet
theelol
toetoe
tonlet
toothlet
tootle
tottle
vellon
velvet
velveteen
venene
vennel
venthole
voeten
volent
volvelle
volvent
voteen
Found "pic" directions from (4,0)(p) go  → →
Found "pick" directions from (4,0)(p) go  → → ↑
Found "pickman" directions from (4,0)(p) go  → → ↑ ↑ ↖ ↑
Found "picket" directions from (4,0)(p) go  → → ↑ ↗ ↖
Found "picked" directions from (4,0)(p) go  → → ↑ ↗ ↘
Found "pickle" directions from (4,0)(p) go  → → ↑ ↘ →

我希望您能理解。

此解决方案还提供了在给定板中搜索的方向

算法:

输出:

eleven
eleventh
elevon
entente
entone
ethene
ethenol
evolve
evolvent
hellhole
helvell
hooven
letten
looten
nettle
nonene
nonent
nonlevel
notelet
novelet
novelette
novene
teenet
teethe
teevee
telethon
tellee
tenent
tentlet
theelol
toetoe
tonlet
toothlet
tootle
tottle
vellon
velvet
velveteen
venene
vennel
venthole
voeten
volent
volvelle
volvent
voteen
Found "pic" directions from (4,0)(p) go  → →
Found "pick" directions from (4,0)(p) go  → → ↑
Found "pickman" directions from (4,0)(p) go  → → ↑ ↑ ↖ ↑
Found "picket" directions from (4,0)(p) go  → → ↑ ↗ ↖
Found "picked" directions from (4,0)(p) go  → → ↑ ↗ ↘
Found "pickle" directions from (4,0)(p) go  → → ↑ ↘ →
代码:


哦。我很高兴有人上台。虽然这样做有效,但它不记得已经使用过的字母,并且想出了需要使用同一个字母两次的单词,这是不允许的。因为我是个白痴,我该如何解决这个问题呢?没错,它不记得访问了哪些字母,但在您的规范中没有指定。要解决这个问题,您必须在每个队列数据中添加一个所有访问位置的列表,然后在添加下一个字符之前检查该列表。不,在BoggleWords中。不存储四元组x,y,s,n,而是存储五元组x,y,s,n,l,其中l是迄今为止访问过的x,y的列表。然后检查每个x2,y2和l,只有当它不在l中时才接受它。然后你把它添加到新的l。当我厌倦玩攀爬游戏时,我也这么做了。我认为递归DFS而不是BFS解决方案更吸引人,因为您可以只保留一组活动单元,这样您就不会访问同一个单元两次。保持一堆列表会更整洁。这不应该成为一个无限循环吗?我的意思是,对于x,y,一个可能的跟随者是x+1,y+1。然后x+1,y+1被推送到队列中。然而,x,y也将是x+1,y+1的追随者,所以这不会导致他们之间无休止的反弹吗?我不知道它的流行名称是boggle,但我确实在谷歌上找到了一些东西,我只是好奇地想看看人们会想出什么来很不错的!也很快。我会等着看是否还有其他人上台,但到目前为止你的答案看起来不错。我不明白为什么embole是你唯一的6个字母的单词,我有10个不同的单词。看起来你禁止访问同一个节点两次,正如OP所说,这是公平的游戏。好吧,他仍然可能有一个错误,因为他丢弃了不共享角色的FAMBLE WAMBLE和SEMBLE。很好的发现!错误在于前缀集的创建:我需要使用rangelenw+1而不是rangelenw。我声称这句话帮助我了解了DFS的工作原理以及如何实现DFS。除了发表评论之外,我不确定是否有其他方式来表达对此的感激之情。谢谢我知道我没有通过优化俱乐部,但在我开始真正的代码工作之前,我遇到了速度问题,将输入时间从2秒减少到1.2秒对我来说意义重大。/我注意到奇怪的是,现在使用正则表达式和跳过条目的时间比向散列添加键的时间要少。很好,一个Perl实现!我现在就去运行它。Blerg,在我的Web服务器上安装Tree::Trie时遇到了困难您是如何生成上一份报告arch/执行信息的?看起来很有用

关于时间的问题:在我的解决方案中,几乎所有的时间都花在了构建trie上。一旦构建了trie,就可以多次重用它。如果只解决一个难题,那么使用一个更简单的数据结构(如一组所有单词和前缀)会更有效。此外,Adam的字典更大,他的解决方案使用的较长单词数量就证明了这一点。它们都应该根据一本普通字典进行测试。我想没有人玩得太多了吧?Qu是一个字母,我不确定有多少解决方案抓住了这个小细节。看起来他们中的一些人会允许你独立使用u,还有其他一些问题。我最近在面试中问了这个问题,很好地陷入了细节中。我把它当作一个图形问题来处理,这很好,但是这里的解决方案占用的空间要少得多。我现在正在编写自己的解决方案。为所有做出贡献的人做得好!程序只给了我一个字。为什么?我不想被输出淹没。查看底部的注释。或者获取没有路径的所有单词:print“”。joinsortedsetword for word,path in solve大部分时间都用于解析字典。我将其预解析为一个wordlines.py文件,该文件只是一个列表,每个单词都是一个元素。因为它是一个.py文件,所以将转换为.pyc文件。然后我导入它,而不是read.splitlines。有了它,在我的盒子上,我将在大约十分之一秒内解决它。@shellscape,这是Python 2代码。就我所见,Python 3毫无意义地放弃了解构参数的能力,比如def neighborsx,y。它还需要在参数周围加上括号才能打印。我假设您在这里使用了a-p系统而不是[x][y],因为后者在VB中相当复杂?有一次,我花了一天的时间试图得到一个双向动态数组,即:array array 1,hello,1,hello,array,但仍然不知道如何做到这一点:PIn PHP和Perl 2 dim数组很有趣。这可以在VB中完成,但我不认为这是一个有趣的过程。Dim Arr,作为整数={{1,1},{0,0}。A-P过程源于我把自己放在网格上,然后问‘我能从这里走到哪里?’我知道这是一个严格的解决方案,但它在这里工作。哦,我喜欢VB.NET。。。我尝试了URL,但它不起作用。我必须自己重新构建你的代码作为Windows窗体,它可以工作。谢谢。+1@而且我无法理解到底发生了什么,这一点都没有帮助。哈哈,我喜欢诚实!我不懂PHP,但我要尝试的第一件事是将“/[”.内爆,$alphabet.]{3,}$/”从循环中提升出来。也就是说,将一个变量设置为该值,并在循环中使用该变量。我很确定PHP保留了编译正则表达式的每线程全局缓存,但无论如何我都会尝试。@Daniel:显然这是我的web服务器。我在本地运行时不会发生这种情况。耸肩不要真的想猎杀它。什么应该设置为7。最后find_words函数中的参数?我将我的输出与其他StackOverflowers的输出进行比较,似乎Adam、John和rvarcher的输出缺少一些单词。例如,Mwa在字典里是的!,但它不会从Adam、John和rvarcher的输出中返回。它在Paolo的PHP链接中返回了两次。我通过复制粘贴尝试了这一次。上面写着阅读。。。读完……之后什么也没有出现。没有显示匹配项。您的分析很有趣,但您的结果在技术上并不令人困惑。5x5 boggle游戏包括一个包含faces BJKQXZ的骰子,您的实现明确排除了所有这些字母,因此在真正的boggle游戏中,棋盘位置实际上是不可能的。这很好,但此处发布的所有时间都涉及到将字典加载到内存的任何启动时间,因此,将0.35与其他时间进行比较是非常不准确的。还有,你用的是另一本字典吗?你漏掉了一些单词。无论采用哪种方式,+1启动时间都需要6毫秒,因此您需要6.35毫秒才能完成整个运行。我正在使用我的本地/usr/share/dict字典,其中一些单词确实丢失了,比如EMBOLE。回答得好。我想看看你的Java实现。@MikkoP完成了!:花了大约3个小时和220行代码。度过一个下午的好方法。如果您对其工作原理有任何疑问,请告诉我…:谢谢你发布代码!在我添加了缺少的导入内容后,我用自己的字典尝试了一下。我在ok=possibleTriplets[entry.triplets[t]]行上得到一个ArrayIndexOutOfBoundException;。嗯?@MikkoP目前编写这段代码的目的是假设字典只包含大写字母A-Z。关键点在第34行:entry.letters[i]=字节字母-65;它只需获取ASCII值并减去65 A。如果字典中有Umlauts或小写字母,则会给出大于31的值,而第9行中的字母表大小设置不需要这些值。要支持其他字母,您必须展开此行以将它们映射到中
到字母表大小允许的范围。@AlexanderN您可能正确理解了逻辑。我复制字母网格时出错了。。。很抱歉已修复词典的位置是否已更改?我试图查找字典中的单词,因为我想将我的解决方案与每个人进行比较,但在/usr/share/dict的给定链接上找不到。我知道这是一条很老的线,但如果你能指给我看,那就太棒了。提前谢谢你的帮助。现在我的Mac电脑不在手边。你只需要一个英文单词文件,一行一个,用换行符隔开。你可以在网上找到这样的文件。一个在这里:但是可能有更多的单词列表。非常感谢你的回复。我在ubuntu文件中发现了这一点。
hello (6, 3, 8, 8, 10):
42 (from 6 + 3x12), 99, 104, 128
-> "hello" will be stored as 42, 99, 104, 128 in the dictionary
(0,0) (0,1) -> here: h, b : [6][0]
(0,0) (1,0) -> here: h, e : [6][3]
(0,0) (1,1) -> here: h, e : [6][3]
(0,1) (0,0) -> here: b, h : [0][6]
(0,1) (0,2) -> here: b, c : [0][1]
.
:
Precomputation finished in 239.59ms:
  Words in the dictionary: 178590
  Longest word:            15 letters
  Number of triplet-moves: 408

Initialization finished in 0.22ms

Board solved in 3.70ms:
  Number of candidates: 230
  Number of actual words: 163 

Words found:
  eek, eel, eely, eld, elhi, elk, ern, erupt, erupts, euro
  eye, eyer, ghi, ghis, glee, gley, glue, gluer, gluey, glut
  gluts, hip, hiply, hips, his, hist, kelp, kelps, kep, kepi
  kepis, keps, kept, kern, key, kye, lee, lek, lept, leu
  ley, lunt, lunts, lure, lush, lust, lustre, lye, nus, nut
  nuts, ore, ort, orts, ouph, ouphs, our, oust, out, outre
  outs, oyer, pee, per, pert, phi, phis, pis, pish, plus
  plush, ply, plyer, psi, pst, pul, pule, puler, pun, punt
  punts, pur, pure, puree, purely, pus, push, put, puts, ree
  rely, rep, reply, reps, roe, roue, roup, roups, roust, rout
  routs, rue, rule, ruly, run, runt, runts, rupee, rush, rust
  rut, ruts, ship, shlep, sip, sipe, spue, spun, spur, spurn
  spurt, strep, stroy, stun, stupe, sue, suer, sulk, sulker, sulky
  sun, sup, supe, super, sure, surely, tree, trek, trey, troupe
  troy, true, truly, tule, tun, tup, tups, turn, tush, ups
  urn, uts, yeld, yelk, yelp, yelps, yep, yeps, yore, you
  your, yourn, yous
Precomputation finished in 239.68ms:
  Words in the dictionary: 178590
  Longest word:            15 letters
  Number of triplet-moves: 408

Initialization finished in 0.21ms

Board solved in 3.69ms:
  Number of candidates: 87
  Number of actual words: 76

Words found:
  amble, ambo, ami, amie, asea, awa, awe, awes, awl, axil
  axile, axle, boil, bole, box, but, buts, east, elm, emboli
  fame, fames, fax, lei, lie, lima, limb, limbo, limbs, lime
  limes, lob, lobs, lox, mae, maes, maw, maws, max, maxi
  mesa, mew, mewl, mews, mil, mile, milo, mix, oil, ole
  sae, saw, sea, seam, semi, sew, stub, swam, swami, tub
  tubs, tux, twa, twae, twaes, twas, uts, wae, waes, wamble
  wame, wames, was, wast, wax, west
R P R I T
A H H L N
I E T E P
Z R Y S G
O G W E Y
Precomputation finished in 240.39ms:
  Words in the dictionary: 178590
  Longest word:            15 letters
  Number of triplet-moves: 768

Initialization finished in 0.23ms

Board solved in 3.85ms:
  Number of candidates: 331
  Number of actual words: 240

Words found:
  aero, aery, ahi, air, airt, airth, airts, airy, ear, egest
  elhi, elint, erg, ergo, ester, eth, ether, eye, eyen, eyer
  eyes, eyre, eyrie, gel, gelt, gelts, gen, gent, gentil, gest
  geste, get, gets, gey, gor, gore, gory, grey, greyest, greys
  gyre, gyri, gyro, hae, haet, haets, hair, hairy, hap, harp
  heap, hear, heh, heir, help, helps, hen, hent, hep, her
  hero, hes, hest, het, hetero, heth, hets, hey, hie, hilt
  hilts, hin, hint, hire, hit, inlet, inlets, ire, leg, leges
  legs, lehr, lent, les, lest, let, lethe, lets, ley, leys
  lin, line, lines, liney, lint, lit, neg, negs, nest, nester
  net, nether, nets, nil, nit, ogre, ore, orgy, ort, orts
  pah, pair, par, peg, pegs, peh, pelt, pelter, peltry, pelts
  pen, pent, pes, pest, pester, pesty, pet, peter, pets, phi
  philter, philtre, phiz, pht, print, pst, rah, rai, rap, raphe
  raphes, reap, rear, rei, ret, rete, rets, rhaphe, rhaphes, rhea
  ria, rile, riles, riley, rin, rye, ryes, seg, sel, sen
  sent, senti, set, sew, spelt, spelter, spent, splent, spline, splint
  split, stent, step, stey, stria, striae, sty, stye, tea, tear
  teg, tegs, tel, ten, tent, thae, the, their, then, these
  thesp, they, thin, thine, thir, thirl, til, tile, tiles, tilt
  tilter, tilth, tilts, tin, tine, tines, tirl, trey, treys, trog
  try, tye, tyer, tyes, tyre, tyro, west, wester, wry, wryest
  wye, wyes, wyte, wytes, yea, yeah, year, yeh, yelp, yelps
  yen, yep, yeps, yes, yester, yet, yew, yews, zero, zori
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;

public class Boggler {
    private ArrayList<String> words = new ArrayList<String>();      
    private ArrayList<String> roundWords = new ArrayList<String>(); 
    private ArrayList<Word> foundWords = new ArrayList<Word>();     
    private char[][] letterGrid = new char[4][4];                   
    private String letters;                                         

    public Boggler() throws FileNotFoundException, IOException, URISyntaxException {
        long startTime = System.currentTimeMillis();

        URL path = GUI.class.getResource("words.txt");
        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(new File(path.toURI()).getAbsolutePath()), "iso-8859-1"));
        String line;
        while((line = br.readLine()) != null) {
            if(line.length() < 3 || line.length() > 10) {
                continue;
            }

            this.words.add(line);
        }
    }

    public ArrayList<Word> getWords() {
        return this.foundWords;
    }

    public void solve(String letters) {
        this.letters = "";
        this.foundWords = new ArrayList<Word>();

        for(int i = 0; i < letters.length(); i++) {
            if(!this.letters.contains(letters.substring(i, i + 1))) {
                this.letters += letters.substring(i, i + 1);
            }
        }

        for(int i = 0; i < 4; i++) {
            for(int j = 0; j < 4; j++) {
                this.letterGrid[i][j] = letters.charAt(i * 4 + j);
            }
        }

        System.out.println(Arrays.deepToString(this.letterGrid));               

        this.roundWords = new ArrayList<String>();      
        String pattern = "[" + this.letters + "]+";     

        for(int i = 0; i < this.words.size(); i++) {

            if(this.words.get(i).matches(pattern)) {
                this.roundWords.add(this.words.get(i));
            }
        }

        for(int i = 0; i < this.roundWords.size(); i++) {
            Word word = checkForWord(this.roundWords.get(i));

            if(word != null) {
                System.out.println(word);
                this.foundWords.add(word);
            }
        }       
    }

    private Word checkForWord(String word) {
        char initial = word.charAt(0);
        ArrayList<LetterCoord> startPoints = new ArrayList<LetterCoord>();

        int x = 0;  
        int y = 0;
        for(char[] row: this.letterGrid) {
            x = 0;

            for(char letter: row) {
                if(initial == letter) {
                    startPoints.add(new LetterCoord(x, y));
                }

                x++;
            }

            y++;
        }

        ArrayList<LetterCoord> letterCoords = null;
        for(int initialTry = 0; initialTry < startPoints.size(); initialTry++) {
            letterCoords = new ArrayList<LetterCoord>();    

            x = startPoints.get(initialTry).getX(); 
            y = startPoints.get(initialTry).getY();

            LetterCoord initialCoord = new LetterCoord(x, y);
            letterCoords.add(initialCoord);

            letterLoop: for(int letterIndex = 1; letterIndex < word.length(); letterIndex++) {
                LetterCoord lastCoord = letterCoords.get(letterCoords.size() - 1);  
                char currentChar = word.charAt(letterIndex);                        

                ArrayList<LetterCoord> letterLocations = getNeighbours(currentChar, lastCoord.getX(), lastCoord.getY());

                if(letterLocations == null) {
                    return null;    
                }       

                for(int foundIndex = 0; foundIndex < letterLocations.size(); foundIndex++) {
                    if(letterIndex != word.length() - 1 && true == false) {
                        char nextChar = word.charAt(letterIndex + 1);
                        int lastX = letterCoords.get(letterCoords.size() - 1).getX();
                        int lastY = letterCoords.get(letterCoords.size() - 1).getY();

                        ArrayList<LetterCoord> possibleIndex = getNeighbours(nextChar, lastX, lastY);
                        if(possibleIndex != null) {
                            if(!letterCoords.contains(letterLocations.get(foundIndex))) {
                                letterCoords.add(letterLocations.get(foundIndex));
                            }
                            continue letterLoop;
                        } else {
                            return null;
                        }
                    } else {
                        if(!letterCoords.contains(letterLocations.get(foundIndex))) {
                            letterCoords.add(letterLocations.get(foundIndex));

                            continue letterLoop;
                        }
                    }
                }
            }

            if(letterCoords != null) {
                if(letterCoords.size() == word.length()) {
                    Word w = new Word(word);
                    w.addList(letterCoords);
                    return w;
                } else {
                    return null;
                }
            }
        }

        if(letterCoords != null) {
            Word foundWord = new Word(word);
            foundWord.addList(letterCoords);

            return foundWord;
        }

        return null;
    }

    public ArrayList<LetterCoord> getNeighbours(char letterToSearch, int x, int y) {
        ArrayList<LetterCoord> neighbours = new ArrayList<LetterCoord>();

        for(int _y = y - 1; _y <= y + 1; _y++) {
            for(int _x = x - 1; _x <= x + 1; _x++) {
                if(_x < 0 || _y < 0 || (_x == x && _y == y) || _y > 3 || _x > 3) {
                    continue;
                }

                if(this.letterGrid[_y][_x] == letterToSearch && !neighbours.contains(new LetterCoord(_x, _y))) {
                    neighbours.add(new LetterCoord(_x, _y));
                }
            }
        }

        if(neighbours.isEmpty()) {
            return null;
        } else {
            return neighbours;
        }
    }
}

class Word {
    private String word;    
    private ArrayList<LetterCoord> letterCoords = new ArrayList<LetterCoord>();

    public Word(String word) {
        this.word = word;
    }

    public boolean addCoords(int x, int y) {
        LetterCoord lc = new LetterCoord(x, y);

        if(!this.letterCoords.contains(lc)) {
            this.letterCoords.add(lc);

            return true;
        }

        return false;
    }

    public void addList(ArrayList<LetterCoord> letterCoords) {
        this.letterCoords = letterCoords;
    } 

    @Override
    public String toString() {
        String outputString = this.word + " ";
        for(int i = 0; i < letterCoords.size(); i++) {
            outputString += "(" + letterCoords.get(i).getX() + ", " + letterCoords.get(i).getY() + ") ";
        }

        return outputString;
    }

    public String getWord() {
        return this.word;
    }

    public ArrayList<LetterCoord> getList() {
        return this.letterCoords;
    }
}

class LetterCoord extends ArrayList {
    private int x;          
    private int y;          

    public LetterCoord(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return this.x;
    }

    public int getY() {
        return this.y;
    }

    @Override
    public boolean equals(Object o) {
        if(!(o instanceof LetterCoord)) {
            return false;
        }

        LetterCoord lc = (LetterCoord) o;

        if(this.x == lc.getX() &&
                this.y == lc.getY()) {
            return true;
        }

        return false;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 29 * hash + this.x;
        hash = 24 * hash + this.y;
        return hash;
    }
}
var fs = require('fs')

var Node = function(value, row, col) {
    this.value = value
    this.row = row
    this.col = col
}

var Path = function() {
    this.nodes = []
}

Path.prototype.push = function(node) {
    this.nodes.push(node)
    return this
}

Path.prototype.contains = function(node) {
    for (var i = 0, ii = this.nodes.length; i < ii; i++) {
        if (this.nodes[i] === node) {
            return true
        }
    }

    return false
}

Path.prototype.clone = function() {
    var path = new Path()
    path.nodes = this.nodes.slice(0)
    return path
}

Path.prototype.to_word = function() {
    var word = ''

    for (var i = 0, ii = this.nodes.length; i < ii; ++i) {
        word += this.nodes[i].value
    }

    return word
}

var Board = function(nodes, dict) {
    // Expects n x m array.
    this.nodes = nodes
    this.words = []
    this.row_count = nodes.length
    this.col_count = nodes[0].length
    this.dict = dict
}

Board.from_raw = function(board, dict) {
    var ROW_COUNT = board.length
      , COL_COUNT = board[0].length

    var nodes = []

    // Replace board with Nodes
    for (var i = 0, ii = ROW_COUNT; i < ii; ++i) {
        nodes.push([])
        for (var j = 0, jj = COL_COUNT; j < jj; ++j) {
            nodes[i].push(new Node(board[i][j], i, j))
        }
    }

    return new Board(nodes, dict)
}

Board.prototype.toString = function() {
    return JSON.stringify(this.nodes)
}

Board.prototype.update_potential_words = function(dict) {
    for (var i = 0, ii = this.row_count; i < ii; ++i) {
        for (var j = 0, jj = this.col_count; j < jj; ++j) {
            var node = this.nodes[i][j]
              , path = new Path()

            path.push(node)

            this.dfs_search(path)
        }
    }
}

Board.prototype.on_board = function(row, col) {
    return 0 <= row && row < this.row_count && 0 <= col && col < this.col_count
}

Board.prototype.get_unsearched_neighbours = function(path) {
    var last_node = path.nodes[path.nodes.length - 1]

    var offsets = [
        [-1, -1], [-1,  0], [-1, +1]
      , [ 0, -1],           [ 0, +1]
      , [+1, -1], [+1,  0], [+1, +1]
    ]

    var neighbours = []

    for (var i = 0, ii = offsets.length; i < ii; ++i) {
        var offset = offsets[i]
        if (this.on_board(last_node.row + offset[0], last_node.col + offset[1])) {

            var potential_node = this.nodes[last_node.row + offset[0]][last_node.col + offset[1]]
            if (!path.contains(potential_node)) {
                // Create a new path if on board and we haven't visited this node yet.
                neighbours.push(potential_node)
            }
        }
    }

    return neighbours
}

Board.prototype.dfs_search = function(path) {
    var path_word = path.to_word()

    if (this.dict.contains_exact(path_word) && path_word.length >= 3) {
        this.words.push(path_word)
    }

    var neighbours = this.get_unsearched_neighbours(path)

    for (var i = 0, ii = neighbours.length; i < ii; ++i) {
        var neighbour = neighbours[i]
        var new_path = path.clone()
        new_path.push(neighbour)

        if (this.dict.contains_prefix(new_path.to_word())) {
            this.dfs_search(new_path)
        }
    }
}

var Dict = function() {
    this.dict_array = []

    var dict_data = fs.readFileSync('./web2', 'utf8')
    var dict_array = dict_data.split('\n')

    for (var i = 0, ii = dict_array.length; i < ii; ++i) {
        dict_array[i] = dict_array[i].toUpperCase()
    }

    this.dict_array = dict_array.sort()
}

Dict.prototype.contains_prefix = function(prefix) {
    // Binary search
    return this.search_prefix(prefix, 0, this.dict_array.length)
}

Dict.prototype.contains_exact = function(exact) {
    // Binary search
    return this.search_exact(exact, 0, this.dict_array.length)
}

Dict.prototype.search_prefix = function(prefix, start, end) {
    if (start >= end) {
        // If no more place to search, return no matter what.
        return this.dict_array[start].indexOf(prefix) > -1
    }

    var middle = Math.floor((start + end)/2)

    if (this.dict_array[middle].indexOf(prefix) > -1) {
        // If we prefix exists, return true.
        return true
    } else {
        // Recurse
        if (prefix <= this.dict_array[middle]) {
            return this.search_prefix(prefix, start, middle - 1)
        } else {
            return this.search_prefix(prefix, middle + 1, end)
        }
    }
}

Dict.prototype.search_exact = function(exact, start, end) {
    if (start >= end) {
        // If no more place to search, return no matter what.
        return this.dict_array[start] === exact
    }

    var middle = Math.floor((start + end)/2)

    if (this.dict_array[middle] === exact) {
        // If we prefix exists, return true.
        return true
    } else {
        // Recurse
        if (exact <= this.dict_array[middle]) {
            return this.search_exact(exact, start, middle - 1)
        } else {
            return this.search_exact(exact, middle + 1, end)
        }
    }
}

var board = [
    ['F', 'X', 'I', 'E']
  , ['A', 'M', 'L', 'O']
  , ['E', 'W', 'B', 'X']
  , ['A', 'S', 'T', 'U']
]

var dict = new Dict()

var b = Board.from_raw(board, dict)
b.update_potential_words()
console.log(JSON.stringify(b.words.sort(function(a, b) {
    return a.length - b.length
})))
1     2     3     4
11    12    13    14
21    22    23    24
31    32    33    34
<?php
    ini_set('xdebug.var_display_max_depth', 20);
    ini_set('xdebug.var_display_max_children', 1024);
    ini_set('xdebug.var_display_max_data', 1024);

    class Node {
        var $loc;

        function __construct($value) {
            $this->value = $value;
            $next = null;
        }
    }

    class Boggle {
        var $root;
        var $locList = array (1, 2, 3, 4, 11, 12, 13, 14, 21, 22, 23, 24, 31, 32, 33, 34);
        var $wordList = [];
        var $foundWords = [];

        function __construct($board) {
            // Takes in a board string and creates all the nodes
            $node = new Node($board[0]);
            $node->loc = $this->locList[0];
            $this->root = $node;
            for ($i = 1; $i < strlen($board); $i++) {
                    $node->next = new Node($board[$i]);
                    $node->next->loc = $this->locList[$i];
                    $node = $node->next;
            }
            // Load in a dictionary file
            // Use regexp to elimate all the words that could never appear and load the 
            // rest of the words into wordList
            $handle = fopen("dict.txt", "r");
            if ($handle) {
                while (($line = fgets($handle)) !== false) {
                    // process the line read.
                    $line = trim($line);
                    if (strlen($line) > 2) {
                        $this->wordList[] = trim($line);
                    }
                }
                fclose($handle);
            } else {
                // error opening the file.
                echo "Problem with the file.";
            } 
        }

        function isConnected($node1, $node2) {
        // Determines if 2 nodes are connected on the boggle board

            return (($node1->loc == $node2->loc + 1) || ($node1->loc == $node2->loc - 1) ||
               ($node1->loc == $node2->loc - 9) || ($node1->loc == $node2->loc - 10) || ($node1->loc == $node2->loc - 11) ||
               ($node1->loc == $node2->loc + 9) || ($node1->loc == $node2->loc + 10) || ($node1->loc == $node2->loc + 11)) ? true : false;

        }

        function find($value, $notInLoc = []) {
            // Returns a node with the value that isn't in a location
            $current = $this->root;
            while($current) {
                if ($current->value == $value && !in_array($current->loc, $notInLoc)) {
                    return $current;
                }
                if (isset($current->next)) {
                    $current = $current->next;
                } else {
                    break;
                }
            }
            return false;
        }

        function findAll($value) {
            // Returns an array of nodes with a specific value
            $current = $this->root;
            $foundNodes = [];
            while ($current) {
                if ($current->value == $value) {
                    $foundNodes[] = $current;
                }
                if (isset($current->next)) {
                    $current = $current->next;
                } else {
                    break;
                }
            }
            return (empty($foundNodes)) ? false : $foundNodes;
        }

        function findAllConnectedTo($node, $value, $notInLoc = []) {
            // Returns an array of nodes that are connected to a specific node and 
            // contain a specific value and are not in a certain location
            $nodeList = $this->findAll($value);
            $newList = [];
            if ($nodeList) {
                foreach ($nodeList as $node2) {
                    if (!in_array($node2->loc, $notInLoc) && $this->isConnected($node, $node2)) {
                        $newList[] = $node2;
                    }
                }
            }
            return (empty($newList)) ? false : $newList;
        }



        function inner($word, $list, $i = 0, $notInLoc = []) {
            $i++;
            foreach($list as $node) {
                $notInLoc[] = $node->loc;
                if ($list2 = $this->findAllConnectedTo($node, $word[$i], $notInLoc)) {
                    if ($i == (strlen($word) - 1)) {
                        return true;
                    } else {
                        return $this->inner($word, $list2, $i, $notInLoc);
                    }
                }
            }
            return false;
        }

        function findWord($word) {
            if ($list = $this->findAll($word[0])) {
                return $this->inner($word, $list);
            }
            return false;
        }

        function findAllWords() {
            foreach($this->wordList as $word) {
                if ($this->findWord($word)) {
                    $this->foundWords[] = $word;
                }
            }
        }

        function displayBoard() {
            $current = $this->root;
            for ($i=0; $i < 4; $i++) {
                echo $current->value . " " . $current->next->value . " " . $current->next->next->value . " " . $current->next->next->next->value . "<br />";
                if ($i < 3) {
                    $current = $current->next->next->next->next;
                }
            }
        }

    }

    function randomBoardString() {
        return substr(str_shuffle(str_repeat("abcdefghijklmnopqrstuvwxyz", 16)), 0, 16);
    }

    $myBoggle = new Boggle(randomBoardString());
    $myBoggle->displayBoard();
    $x = microtime(true);
    $myBoggle->findAllWords();
    $y = microtime(true);
    echo ($y-$x);
    var_dump($myBoggle->foundWords);

    ?>
import nltk
from nltk.corpus import words
from collections import Counter

def possibleWords(input, charSet):
    for word in input:
        dict = Counter(word)
        flag = 1
        for key in dict.keys():
            if key not in charSet:
                flag = 0
        if flag == 1 and len(word)>5: #its depends if you want only length more than 5 use this otherwise remove that one. 
            print(word)


nltk.download('words')
word_list = words.words()
# prints 236736
print(len(word_list))
charSet = ['h', 'e', 'l', 'o', 'n', 'v', 't']
possibleWords(word_list, charSet)
eleven
eleventh
elevon
entente
entone
ethene
ethenol
evolve
evolvent
hellhole
helvell
hooven
letten
looten
nettle
nonene
nonent
nonlevel
notelet
novelet
novelette
novene
teenet
teethe
teevee
telethon
tellee
tenent
tentlet
theelol
toetoe
tonlet
toothlet
tootle
tottle
vellon
velvet
velveteen
venene
vennel
venthole
voeten
volent
volvelle
volvent
voteen
1. Uses trie to save all the word in the english to fasten the search
2. The uses DFS to search the words in Boggle
Found "pic" directions from (4,0)(p) go  → →
Found "pick" directions from (4,0)(p) go  → → ↑
Found "pickman" directions from (4,0)(p) go  → → ↑ ↑ ↖ ↑
Found "picket" directions from (4,0)(p) go  → → ↑ ↗ ↖
Found "picked" directions from (4,0)(p) go  → → ↑ ↗ ↘
Found "pickle" directions from (4,0)(p) go  → → ↑ ↘ →
from collections import defaultdict
from nltk.corpus import words
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize

english_words = words.words()

# If you wan to remove stop words
# stop_words = set(stopwords.words('english'))
# english_words = [w for w in english_words if w not in stop_words]

boggle = [
    ['c', 'n', 't', 's', 's'],
    ['d', 'a', 't', 'i', 'n'],
    ['o', 'o', 'm', 'e', 'l'],
    ['s', 'i', 'k', 'n', 'd'],
    ['p', 'i', 'c', 'l', 'e']
]

# Instead of X and Y co-ordinates
# better to use Row and column
lenc = len(boggle[0])
lenr = len(boggle)

# Initialize trie datastructure
trie_node = {'valid': False, 'next': {}}

# lets get the delta to find all the nighbors
neighbors_delta = [
    (-1,-1, "↖"),
    (-1, 0, "↑"),
    (-1, 1, "↗"),
    (0, -1, "←"),
    (0,  1, "→"),
    (1, -1, "↙"),
    (1,  0, "↓"),
    (1,  1, "↘"),
]


def gen_trie(word, node):
    """udpates the trie datastructure using the given word"""
    if not word:
        return

    if word[0] not in node:
        node[word[0]] = {'valid': len(word) == 1, 'next': {}}

    # recursively build trie
    gen_trie(word[1:], node[word[0]])


def build_trie(words, trie):
    """Builds trie data structure from the list of words given"""
    for word in words:
        gen_trie(word, trie)
    return trie


def get_neighbors(r, c):
    """Returns the neighbors for a given co-ordinates"""
    n = []
    for neigh in neighbors_delta:
        new_r = r + neigh[0]
        new_c = c + neigh[1]

        if (new_r >= lenr) or (new_c >= lenc) or (new_r < 0) or (new_c < 0):
            continue
        n.append((new_r, new_c, neigh[2]))
    return n


def dfs(r, c, visited, trie, now_word, direction):
    """Scan the graph using DFS"""
    if (r, c) in visited:
        return

    letter = boggle[r][c]
    visited.append((r, c))

    if letter in trie:
        now_word += letter

        if trie[letter]['valid']:
            print('Found "{}" {}'.format(now_word, direction))

        neighbors = get_neighbors(r, c)
        for n in neighbors:
            dfs(n[0], n[1], visited[::], trie[letter], now_word, direction + " " + n[2])


def main(trie_node):
    """Initiate the search for words in boggle"""
    trie_node = build_trie(english_words, trie_node)

    # print the board
    print("Given board")
    for i in range(lenr):print (boggle[i])
    print ('\n')

    for r in range(lenr):
        for c in range(lenc):
            letter = boggle[r][c]
            dfs(r, c, [], trie_node, '', 'directions from ({},{})({}) go '.format(r, c, letter))


if __name__ == '__main__':
    main(trie_node)