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

时间:2009-04-14 02:11:33

标签: algorithm puzzle boggle

最近我一直在我的iPhone上玩一款名为Scramble的游戏。有些人可能认为这个游戏是Boggle。基本上,当游戏开始时你会得到一个像这样的字母矩阵:

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

游戏的目标是尽可能多地找到可以通过将字母链接在一起形成的单词。你可以从任何字母开始,并且它周围的所有字母都是公平的游戏,然后一旦你继续下一个字母,围绕那个字母的所有字母都是公平的游戏,除了之前使用的任何字母< / strong>即可。例如,在上面的网格中,我可以提出单词LOBTUXSEAFAME等。单词必须至少为3个字符,并且不超过NxN个字符,在这个游戏中为16,但在某些实现中可能会有所不同。虽然这个游戏很有趣且令人上瘾,但我显然不是很擅长它而且我想通过制作一个可以给我最好的单词的程序来作弊(单词越长,得分就越多)。

Sample Boggle
(来源:boggled.org

不幸的是,我对算法或效率等方面的表现并不是很好。我的第一次尝试使用字典such as this one(~2.3MB)并进行线性搜索,尝试将组合与字典条目匹配。这需要非常很长时间才能找到可能的单词,因为每轮只有2分钟,所以这是不够的。

我很想知道Stackoverflowers是否可以提供更有效的解决方案。我主要是在寻找使用Big 3 Ps的解决方案:Python,PHP和Perl,尽管Java或C ++也很酷,因为速度是必不可少的。

当前解决方案

  • Adam Rosenfield,Python,〜20s
  • John Fouhy,Python,~3s
  • Kent Fredric,Perl,~1s
  • Darius Bacon,Python,~1s
  • rvarcher,VB.NET (live link),~1s
  • Paolo Bergantino,PHP (live link),~5s(本地约2s)

BOUNTY

我正在为这个问题增添一笔赏金,这是我向所有投入他们计划的人表示感谢的方式。不幸的是,我只能向你们中的一个人提供接受的答案,所以我将测量从现在起7天内谁拥有最快的晃动解算器,并奖励获胜者赏金。

赏金奖励。感谢所有参与的人。

35 个答案:

答案 0 :(得分:141)

我的回答与其他人一样,但我会发布它,因为它看起来比其他Python解决方案更快,从更快地设置字典。 (我对John Fouhy的解决方案进行了检查。)设置完成后,解决的时间就会降低。

grid = "fxie amlo ewbx astu".split()
nrows, ncols = len(grid), len(grid[0])

# A dictionary word that could be a solution must use only the grid's
# letters and have length >= 3. (With a case-insensitive match.)
import re
alphabet = ''.join(set(''.join(grid)))
bogglable = re.compile('[' + alphabet + ']{3,}$', re.I).match

words = set(word.rstrip('\n') for word in open('words') if bogglable(word))
prefixes = set(word[:i] for word in words
               for i in range(2, len(word)+1))

def solve():
    for y, row in enumerate(grid):
        for x, letter in enumerate(row):
            for result in extending(letter, ((x, y),)):
                yield result

def extending(prefix, path):
    if prefix in words:
        yield (prefix, path)
    for (nx, ny) in neighbors(path[-1]):
        if (nx, ny) not in path:
            prefix1 = prefix + grid[ny][nx]
            if prefix1 in prefixes:
                for result in extending(prefix1, path + ((nx, ny),)):
                    yield result

def neighbors((x, y)):
    for nx in range(max(0, x-1), min(x+2, ncols)):
        for ny in range(max(0, y-1), min(y+2, nrows)):
            yield (nx, ny)

样本用法:

# Print a maximal-length word and its path:
print max(solve(), key=lambda (word, path): len(word))

修改:过滤少于3个字母的字词。

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

答案 1 :(得分:116)

您将获得的最快解决方案可能涉及将您的字典存储在trie中。然后,创建一个三元组队列( 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

如果您将字典存储在trie中,则测试 s + c 是单词还是单词的前缀可以在固定时间内完成(前提是你也是在每个队列数据中保留一些额外的元数据,例如指向trie中当前节点的指针,因此该算法的运行时间为O(可以拼写的单词数)。

[编辑] 以下是我编写的Python实现:

#!/usr/bin/python

class TrieNode:
    def __init__(self, parent, value):
        self.parent = parent
        self.children = [None] * 26
        self.isWord = False
        if parent is not None:
            parent.children[ord(value) - 97] = self

def MakeTrie(dictfile):
    dict = open(dictfile)
    root = TrieNode(None, '')
    for word in dict:
        curNode = root
        for letter in word.lower():
            if 97 <= ord(letter) < 123:
                nextNode = curNode.children[ord(letter) - 97]
                if nextNode is None:
                    nextNode = TrieNode(curNode, letter)
                curNode = nextNode
        curNode.isWord = True
    return root

def BoggleWords(grid, dict):
    rows = len(grid)
    cols = len(grid[0])
    queue = []
    words = []
    for y in range(cols):
        for x in range(rows):
            c = grid[y][x]
            node = dict.children[ord(c) - 97]
            if node is not None:
                queue.append((x, y, c, node))
    while queue:
        x, y, s, node = queue[0]
        del queue[0]
        for dx, dy in ((1, 0), (1, -1), (0, -1), (-1, -1), (-1, 0), (-1, 1), (0, 1), (1, 1)):
            x2, y2 = x + dx, y + dy
            if 0 <= x2 < cols and 0 <= y2 < rows:
                s2 = s + grid[y2][x2]
                node2 = node.children[ord(grid[y2][x2]) - 97]
                if node2 is not None:
                    if node2.isWord:
                        words.append(s2)
                    queue.append((x2, y2, s2, node2))

    return words

使用示例:

d = MakeTrie('/usr/share/dict/words')
print(BoggleWords(['fxie','amlo','ewbx','astu'], d))

输出:

  

['fa','xi','ie','io','el','am','ax','ae','aw','mi','ma','me me ','lo','li','oe','ox','em','ea','ea','es','wa','we','wa','bo', 'bu','as','aw','ae','st','se','sa','tu','ut','fam','fae','imi','eli ','elm','elb','ami','ama','ame','aes','awl','awa','awe','awa','mix','mim', 'mil','mam','max','mae','maw','mew','mem','mes','lob','lox','lei','leo','lie ','lim','oil','olm','ewe','eme','wax','waf','wae','waw','wem','wea','wea', '是','waw','wa','bob','blo','bub','但','ast','ase','asa','awl','awa','敬畏','awa','aes','swa','swa','sew','sea','sea','saw','tux','tub','tut','twa', 'twa','tst','utu','fama','fame','ixil','imam','amli','amil','ambo','axil','axe','mimi ','mima','mime','milo','mile','mewl','mese','mesa','lolo','lobo','lima','lime','limb', 'lile','oime','oleo','olio','oboe','obol','emim','emil','east','ease','wame','wawa','wawa ','weam','west','wese','wast','wa se','wawa','wawa','boil','bolo','bole','bobo','blob','bleo','bubo','asem','stub','stut' ,'游泳','半','seme','缝','seax','sasa','sawt','tutu','tuts','twae','twas','twae',' ilima','amble','axile','awest','mamie','mambo','maxim','mease','mesem','limax','limes','limbo','limbu' ,'obole','emesa','embox','awest','swami','famable','mimble','maxima','embolo','embole','wamble','semese',' semble','sawbwa','sawbwa']

注意:此程序不输出1个字母的单词,或者根据单词长度进行过滤。这很容易添加,但与问题无关。如果它们可以以多种方式拼写,它还会多次输出一些单词。如果一个给定的单词可以用许多不同的方式拼写(最坏的情况:网格中的每个字母都是相同的(例如'A'),并且像你的字典中的'aaaaaaaaa'这样的单词),那么运行时间将会非常指数。算法完成后,过滤掉重复数据和排序是微不足道的。

答案 2 :(得分:39)

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

鉴于上面的网格只包含16个字符,其中一些是重复的,只需过滤掉具有无法实现的字符的条目,就可以大大减少字典中的总键数。

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

在输入传递期间,它将我从200,000键的字典缩减到仅2,000键。这至少可以减少内存开销,并且由于内存不是无限快,因此肯定会映射到某个地方的速度增加。

Perl实施

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

我在那里也有一些适应性理论上允许带有孔的网格起作用,并且网格具有不同大小的线(假设你输入正确并且它以某种方式排列)。

早期过滤器是我的应用程序中迄今为止最重要的重要瓶颈,正如之前所怀疑的那样,评论该行从1.5秒膨胀到7.5秒。

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

它有点臃肿,但至少我从cpan

重用Tree::Trie

其中一些部分受到现有实现的启发,其中一些已经考虑过了。

建设性批评及其可以改进的方式受到欢迎(/我注意到他从来没有searched CPAN for a boggle solver,但这对于锻炼来说更有趣)

针对新条件进行了更新

#!/usr/bin/perl 

use strict;
use warnings;

{

  # this package manages a given path through the grid.
  # Its an array of matrix-nodes in-order with
  # Convenience functions for pretty-printing the paths
  # and for extending paths as new paths.

  # Usage:
  # my $p = Prefix->new(path=>[ $startnode ]);
  # my $c = $p->child( $extensionNode );
  # print $c->current_word ;

  package Prefix;
  use Moose;

  has path => (
      isa     => 'ArrayRef[MatrixNode]',
      is      => 'rw',
      default => sub { [] },
  );
  has current_word => (
      isa        => 'Str',
      is         => 'rw',
      lazy_build => 1,
  );

  # Create a clone of this object
  # with a longer path

  # $o->child( $successive-node-on-graph );

  sub child {
      my $self    = shift;
      my $newNode = shift;
      my $f       = Prefix->new();

      # Have to do this manually or other recorded paths get modified
      push @{ $f->{path} }, @{ $self->{path} }, $newNode;
      return $f;
  }

  # Traverses $o->path left-to-right to get the string it represents.

  sub _build_current_word {
      my $self = shift;
      return join q{}, map { $_->{value} } @{ $self->{path} };
  }

  # Returns  the rightmost node on this path

  sub tail {
      my $self = shift;
      return $self->{path}->[-1];
  }

  # pretty-format $o->path

  sub pp_path {
      my $self = shift;
      my @path =
        map { '[' . $_->{x_position} . ',' . $_->{y_position} . ']' }
        @{ $self->{path} };
      return "[" . join( ",", @path ) . "]";
  }

  # pretty-format $o
  sub pp {
      my $self = shift;
      return $self->current_word . ' => ' . $self->pp_path;
  }

  __PACKAGE__->meta->make_immutable;
}

{

  # Basic package for tracking node data
  # without having to look on the grid.
  # I could have just used an array or a hash, but that got ugly.

# Once the matrix is up and running it doesn't really care so much about rows/columns,
# Its just a sea of points and each point has adjacent points.
# Relative positioning is only really useful to map it back to userspace

  package MatrixNode;
  use Moose;

  has x_position => ( isa => 'Int', is => 'rw', required => 1 );
  has y_position => ( isa => 'Int', is => 'rw', required => 1 );
  has value      => ( isa => 'Str', is => 'rw', required => 1 );
  has siblings   => (
      isa     => 'ArrayRef[MatrixNode]',
      is      => 'rw',
      default => sub { [] }
  );

# Its not implicitly uni-directional joins. It would be more effient in therory
# to make the link go both ways at the same time, but thats too hard to program around.
# and besides, this isn't slow enough to bother caring about.

  sub add_sibling {
      my $self    = shift;
      my $sibling = shift;
      push @{ $self->siblings }, $sibling;
  }

  # Convenience method to derive a path starting at this node

  sub to_path {
      my $self = shift;
      return Prefix->new( path => [$self] );
  }
  __PACKAGE__->meta->make_immutable;

}

{

  package Matrix;
  use Moose;

  has rows => (
      isa     => 'ArrayRef',
      is      => 'rw',
      default => sub { [] },
  );

  has regex => (
      isa        => 'Regexp',
      is         => 'rw',
      lazy_build => 1,
  );

  has cells => (
      isa        => 'ArrayRef',
      is         => 'rw',
      lazy_build => 1,
  );

  sub add_row {
      my $self = shift;
      push @{ $self->rows }, [@_];
  }

  # Most of these functions from here down are just builder functions,
  # or utilities to help build things.
  # Some just broken out to make it easier for me to process.
  # All thats really useful is add_row
  # The rest will generally be computed, stored, and ready to go
  # from ->cells by the time either ->cells or ->regex are called.

  # traverse all cells and make a regex that covers them.
  sub _build_regex {
      my $self  = shift;
      my $chars = q{};
      for my $cell ( @{ $self->cells } ) {
          $chars .= $cell->value();
      }
      $chars = "[^$chars]";
      return qr/$chars/i;
  }

  # convert a plain cell ( ie: [x][y] = 0 )
  # to an intelligent cell ie: [x][y] = object( x, y )
  # we only really keep them in this format temporarily
  # so we can go through and tie in neighbouring information.
  # after the neigbouring is done, the grid should be considered inoperative.

  sub _convert {
      my $self = shift;
      my $x    = shift;
      my $y    = shift;
      my $v    = $self->_read( $x, $y );
      my $n    = MatrixNode->new(
          x_position => $x,
          y_position => $y,
          value      => $v,
      );
      $self->_write( $x, $y, $n );
      return $n;
  }

# go through the rows/collums presently available and freeze them into objects.

  sub _build_cells {
      my $self = shift;
      my @out  = ();
      my @rows = @{ $self->{rows} };
      for my $x ( 0 .. $#rows ) {
          next unless defined $self->{rows}->[$x];
          my @col = @{ $self->{rows}->[$x] };
          for my $y ( 0 .. $#col ) {
              next unless defined $self->{rows}->[$x]->[$y];
              push @out, $self->_convert( $x, $y );
          }
      }
      for my $c (@out) {
          for my $n ( $self->_neighbours( $c->x_position, $c->y_position ) ) {
              $c->add_sibling( $self->{rows}->[ $n->[0] ]->[ $n->[1] ] );
          }
      }
      return \@out;
  }

  # given x,y , return array of points that refer to valid neighbours.
  sub _neighbours {
      my $self = shift;
      my $x    = shift;
      my $y    = shift;
      my @out  = ();
      for my $sx ( -1, 0, 1 ) {
          next if $sx + $x < 0;
          next if not defined $self->{rows}->[ $sx + $x ];
          for my $sy ( -1, 0, 1 ) {
              next if $sx == 0 && $sy == 0;
              next if $sy + $y < 0;
              next if not defined $self->{rows}->[ $sx + $x ]->[ $sy + $y ];
              push @out, [ $sx + $x, $sy + $y ];
          }
      }
      return @out;
  }

  sub _has_row {
      my $self = shift;
      my $x    = shift;
      return defined $self->{rows}->[$x];
  }

  sub _has_cell {
      my $self = shift;
      my $x    = shift;
      my $y    = shift;
      return defined $self->{rows}->[$x]->[$y];
  }

  sub _read {
      my $self = shift;
      my $x    = shift;
      my $y    = shift;
      return $self->{rows}->[$x]->[$y];
  }

  sub _write {
      my $self = shift;
      my $x    = shift;
      my $y    = shift;
      my $v    = shift;
      $self->{rows}->[$x]->[$y] = $v;
      return $v;
  }

  __PACKAGE__->meta->make_immutable;
}

use Tree::Trie;

sub readDict {
  my $fn = shift;
  my $re = shift;
  my $d  = Tree::Trie->new();

  # Dictionary Loading
  open my $fh, '<', $fn;
  while ( my $line = <$fh> ) {
      chomp($line);

 # Commenting the next line makes it go from 1.5 seconds to 7.5 seconds. EPIC.
      next if $line =~ $re;    # Early Filter
      $d->add( uc($line) );
  }
  return $d;
}

sub traverseGraph {
  my $d     = shift;
  my $m     = shift;
  my $min   = shift;
  my $max   = shift;
  my @words = ();

  # Inject all grid nodes into the processing queue.

  my @queue =
    grep { $d->lookup( $_->current_word ) }
    map  { $_->to_path } @{ $m->cells };

  while (@queue) {
      my $item = shift @queue;

      # put the dictionary into "exact match" mode.

      $d->deepsearch('exact');

      my $cword = $item->current_word;
      my $l     = length($cword);

      if ( $l >= $min && $d->lookup($cword) ) {
          push @words,
            $item;    # push current path into "words" if it exactly matches.
      }
      next if $l > $max;

      # put the dictionary into "is-a-prefix" mode.
      $d->deepsearch('boolean');

    siblingloop: foreach my $sibling ( @{ $item->tail->siblings } ) {
          foreach my $visited ( @{ $item->{path} } ) {
              next siblingloop if $sibling == $visited;
          }

          # given path y , iterate for all its end points
          my $subpath = $item->child($sibling);

          # create a new path for each end-point
          if ( $d->lookup( $subpath->current_word ) ) {

             # if the new path is a prefix, add it to the bottom of the queue.
              push @queue, $subpath;
          }
      }
  }
  return \@words;
}

sub setup_predetermined { 
  my $m = shift; 
  my $gameNo = shift;
  if( $gameNo == 0 ){
      $m->add_row(qw( F X I E ));
      $m->add_row(qw( A M L O ));
      $m->add_row(qw( E W B X ));
      $m->add_row(qw( A S T U ));
      return $m;
  }
  if( $gameNo == 1 ){
      $m->add_row(qw( D G H I ));
      $m->add_row(qw( K L P S ));
      $m->add_row(qw( Y E U T ));
      $m->add_row(qw( E O R N ));
      return $m;
  }
}
sub setup_random { 
  my $m = shift; 
  my $seed = shift;
  srand $seed;
  my @letters = 'A' .. 'Z' ; 
  for( 1 .. 4 ){ 
      my @r = ();
      for( 1 .. 4 ){
          push @r , $letters[int(rand(25))];
      }
      $m->add_row( @r );
  }
}

# Here is where the real work starts.

my $m = Matrix->new();
setup_predetermined( $m, 0 );
#setup_random( $m, 5 );

my $d = readDict( 'dict.txt', $m->regex );
my $c = scalar @{ $m->cells };    # get the max, as per spec

print join ",\n", map { $_->pp } @{
  traverseGraph( $d, $m, 3, $c ) ;
};

用于比较的Arch /执行信息:

model name      : Intel(R) Core(TM)2 Duo CPU     T9300  @ 2.50GHz
cache size      : 6144 KB
Memory usage summary: heap total: 77057577, heap peak: 11446200, stack peak: 26448
       total calls   total memory   failed calls
 malloc|     947212       68763684              0
realloc|      11191        1045641              0  (nomove:9063, dec:4731, free:0)
 calloc|     121001        7248252              0
   free|     973159       65854762

Histogram for block sizes:
  0-15         392633  36% ==================================================
 16-31          43530   4% =====
 32-47          50048   4% ======
 48-63          70701   6% =========
 64-79          18831   1% ==
 80-95          19271   1% ==
 96-111        238398  22% ==============================
112-127          3007  <1% 
128-143        236727  21% ==============================

关于正则表达式优化的更多信息

我使用的正则表达式优化对于多解析字典无用,而对于多解析,你需要一个完整的字典,而不是预先修剪的字典。

然而,那说,一次性解决,它真的很快。 (Perl正则表达式在C!:))

以下是一些不同的代码添加:

sub readDict_nofilter {
  my $fn = shift;
  my $re = shift;
  my $d  = Tree::Trie->new();

  # Dictionary Loading
  open my $fh, '<', $fn;
  while ( my $line = <$fh> ) {
      chomp($line);
      $d->add( uc($line) );
  }
  return $d;
}

sub benchmark_io { 
  use Benchmark qw( cmpthese :hireswallclock );
   # generate a random 16 character string 
   # to simulate there being an input grid. 
  my $regexen = sub { 
      my @letters = 'A' .. 'Z' ; 
      my @lo = ();
      for( 1..16 ){ 
          push @lo , $_ ; 
      }
      my $c  = join '', @lo;
      $c = "[^$c]";
      return qr/$c/i;
  };
  cmpthese( 200 , { 
      filtered => sub { 
          readDict('dict.txt', $regexen->() );
      }, 
      unfiltered => sub {
          readDict_nofilter('dict.txt');
      }
  });
}
           s/iter unfiltered   filtered
unfiltered   8.16         --       -94%
filtered    0.464      1658%         --

ps:8.16 * 200 = 27分钟。

答案 3 :(得分:33)

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

  1. 某种搜索算法,它将枚举网格中可能的字符串。
  2. 一种测试字符串是否为有效字的方法。
  3. 理想情况下,(2)还应该包括一种测试字符串是否是有效字的前缀的方法 - 这将允许您修剪搜索并节省一大堆时间。

    Adam Rosenfield的Trie是(2)的解决方案。它很优雅,可能是你的算法专家所喜欢的,但是对于现代语言和现代计算机,我们可能会有点懒散。此外,正如Kent建议的那样,我们可以通过丢弃网格中不存在字母的单词来减少字典大小。这是一些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
    

    哇;恒定时间前缀测试。加载你链接的字典需要几秒钟,但只有几个:-)(请注意words <= prefixes

    现在,对于第(1)部分,我倾向于用图表来思考。所以我将构建一个类似于这样的字典:

    graph = { (x, y):set([(x0,y0), (x1,y1), (x2,y2)]), }
    

    即。 graph[(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))
    

    此代码还会构建一个将(x,y)映射到相应字符的字典。这让我可以将一个位置列表转换成一个单词:

    def to_word(chardict, pos_list):
        return ''.join(chardict[x] for x in pos_list)
    

    最后,我们进行深度优先搜索。基本程序是:

    1. 搜索到达特定节点。
    2. 检查到目前为止的路径是否可以成为单词的一部分。如果没有,请不要再探索这个分支。
    3. 检查目前为止的路径是否为单词。如果是,请添加到结果列表中。
    4. 探索目前为止不属于路径的所有儿童。
    5. 的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']
      

      代码(字面上)需要几秒钟来加载字典,但其余部分在我的机器上是即时的。

答案 4 :(得分:23)

我认为您可能会花费大部分时间来尝试匹配可能由您的字母网格构建的单词。所以,我要做的第一件事是尝试加快这一步,这应该可以让你在那里大部分时间。

为此,我会将网格重新表达为可能的&#34;移动&#34;您按照正在查看的字母转换进行索引。

首先为每个字母分配整个字母表中的数字(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

然后你创建一个2D布尔数组,告诉你是否有一个可用的字母转换:

     |  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

现在浏览你的单词列表并将单词转换为过渡:

hello (6, 3, 8, 8, 10):
6 -> 3, 3 -> 8, 8 -> 8, 8 -> 10

然后在表格中查看是否允许这些转换:

[6][ 3] : T
[3][ 8] : T
[8][ 8] : T
[8][10] : T

如果他们都被允许,那么可能会找到这个词。

例如单词&#34; helmet&#34;可以排除第4次转换(m到e:helMEt),因为表中的条目是假的。

可以排除仓鼠这个词,因为不允许第一个(h到a)转换(甚至不存在于你的表中)。

现在,对于你可能没有消除的极少数剩余单词,尝试在网格中实际找到它们的方式,就像你现在正在做的那样,或者在这里的其他一些答案中建议的那样。这是为了避免因网格中相同字母之间的跳转而导致误报。例如,&#34; help&#34;表格允许,但不允许通过网格。

有关此想法的一些进一步的绩效改进提示:

  1. 不使用2D数组,而是使用1D数组并自行计算第二个字母的索引。因此,不是像上面这样的12x12数组,而是制作一个长度为144的一维数组。如果你总是使用相同的字母表(即标准英文字母表中的26x26 = 676x1数组),即使并非所有字母都显示在你的网格中,您可以将索引预先计算到此1D数组中,您需要测试该数组以匹配您的字典单词。例如,&#39;你好&#39;的指数在上面的例子中将是

    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
    
  2. 将想法扩展到3D表格(表示为1D数组),即所有允许的3个字母组合。这样你就可以立即消除更多的单词并将每个单词的数组查找次数减少1:对于&#39; hello&#39;,你只需要3个数组查找:hel,ell,llo。顺便说一下,构建此表会非常快,因为网格中只有400个可能的3个字母移动。

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

    (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]
    .
    :
    
  4. 还表示您的游戏网格在一个包含16个条目的一维数组中,并且在3中预先计算的表包含此数组中的索引。
  5. 我确定如果你使用这种方法,你可以让你的代码疯狂地运行,如果你有预先计算好的字典并且已经加载到内存中。

    BTW:另外一件好事,如果你正在建立一个游戏,就是在后台立即运行这些东西。当用户仍在查看应用程序上的标题屏幕并让他的手指进入按下&#34; Play&#34;时,开始生成并解决第一个游戏。然后在用户播放上一个游戏时生成并解决下一个游戏。这应该会给你很多时间来运行你的代码。

    (我喜欢这个问题,所以我很可能会在接下来的几天里用Java来实现我的提议,看看它实际上会如何执行...我会在这里发布代码做。)

    <强>更新

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

    class DictionaryEntry {
      public int[] letters;
      public int[] triplets;
    }
    
    class BoggleSolver {
    
      // Constants
      final int ALPHABET_SIZE = 5;  // up to 2^5 = 32 letters
      final int BOARD_SIZE    = 4;  // 4x4 board
      final int[] moves = {-BOARD_SIZE-1, -BOARD_SIZE, -BOARD_SIZE+1, 
                                      -1,                         +1,
                           +BOARD_SIZE-1, +BOARD_SIZE, +BOARD_SIZE+1};
    
    
      // Technically constant (calculated here for flexibility, but should be fixed)
      DictionaryEntry[] dictionary; // Processed word list
      int maxWordLength = 0;
      int[] boardTripletIndices; // List of all 3-letter moves in board coordinates
    
      DictionaryEntry[] buildDictionary(String fileName) throws IOException {
        BufferedReader fileReader = new BufferedReader(new FileReader(fileName));
        String word = fileReader.readLine();
        ArrayList<DictionaryEntry> result = new ArrayList<DictionaryEntry>();
        while (word!=null) {
          if (word.length()>=3) {
            word = word.toUpperCase();
            if (word.length()>maxWordLength) maxWordLength = word.length();
            DictionaryEntry entry = new DictionaryEntry();
            entry.letters  = new int[word.length()  ];
            entry.triplets = new int[word.length()-2];
            int i=0;
            for (char letter: word.toCharArray()) {
              entry.letters[i] = (byte) letter - 65; // Convert ASCII to 0..25
              if (i>=2)
                entry.triplets[i-2] = (((entry.letters[i-2]  << ALPHABET_SIZE) +
                                         entry.letters[i-1]) << ALPHABET_SIZE) +
                                         entry.letters[i];
              i++;
            }
            result.add(entry);
          }
          word = fileReader.readLine();
        }
        return result.toArray(new DictionaryEntry[result.size()]);
      }
    
      boolean isWrap(int a, int b) { // Checks if move a->b wraps board edge (like 3->4)
        return Math.abs(a%BOARD_SIZE-b%BOARD_SIZE)>1;
      }
    
      int[] buildTripletIndices() {
        ArrayList<Integer> result = new ArrayList<Integer>();
        for (int a=0; a<BOARD_SIZE*BOARD_SIZE; a++)
          for (int bm: moves) {
            int b=a+bm;
            if ((b>=0) && (b<board.length) && !isWrap(a, b))
              for (int cm: moves) {
                int c=b+cm;
                if ((c>=0) && (c<board.length) && (c!=a) && !isWrap(b, c)) {
                  result.add(a);
                  result.add(b);
                  result.add(c);
                }
              }
          }
        int[] result2 = new int[result.size()];
        int i=0;
        for (Integer r: result) result2[i++] = r;
        return result2;
      }
    
    
      // Variables that depend on the actual game layout
      int[] board = new int[BOARD_SIZE*BOARD_SIZE]; // Letters in board
      boolean[] possibleTriplets = new boolean[1 << (ALPHABET_SIZE*3)];
    
      DictionaryEntry[] candidateWords;
      int candidateCount;
    
      int[] usedBoardPositions;
    
      DictionaryEntry[] foundWords;
      int foundCount;
    
      void initializeBoard(String[] letters) {
        for (int row=0; row<BOARD_SIZE; row++)
          for (int col=0; col<BOARD_SIZE; col++)
            board[row*BOARD_SIZE + col] = (byte) letters[row].charAt(col) - 65;
      }
    
      void setPossibleTriplets() {
        Arrays.fill(possibleTriplets, false); // Reset list
        int i=0;
        while (i<boardTripletIndices.length) {
          int triplet = (((board[boardTripletIndices[i++]]  << ALPHABET_SIZE) +
                           board[boardTripletIndices[i++]]) << ALPHABET_SIZE) +
                           board[boardTripletIndices[i++]];
          possibleTriplets[triplet] = true; 
        }
      }
    
      void checkWordTriplets() {
        candidateCount = 0;
        for (DictionaryEntry entry: dictionary) {
          boolean ok = true;
          int len = entry.triplets.length;
          for (int t=0; (t<len) && ok; t++)
            ok = possibleTriplets[entry.triplets[t]];
          if (ok) candidateWords[candidateCount++] = entry;
        }
      }
    
      void checkWords() { // Can probably be optimized a lot
        foundCount = 0;
        for (int i=0; i<candidateCount; i++) {
          DictionaryEntry candidate = candidateWords[i];
          for (int j=0; j<board.length; j++)
            if (board[j]==candidate.letters[0]) { 
              usedBoardPositions[0] = j;
              if (checkNextLetters(candidate, 1, j)) {
                foundWords[foundCount++] = candidate;
                break;
              }
            }
        }
      }
    
      boolean checkNextLetters(DictionaryEntry candidate, int letter, int pos) {
        if (letter==candidate.letters.length) return true;
        int match = candidate.letters[letter];
        for (int move: moves) {
          int next=pos+move;
          if ((next>=0) && (next<board.length) && (board[next]==match) && !isWrap(pos, next)) {
            boolean ok = true;
            for (int i=0; (i<letter) && ok; i++)
              ok = usedBoardPositions[i]!=next;
            if (ok) {
              usedBoardPositions[letter] = next;
              if (checkNextLetters(candidate, letter+1, next)) return true;
            }
          }
        }   
        return false;
      }
    
    
      // Just some helper functions
      String formatTime(long start, long end, long repetitions) {
        long time = (end-start)/repetitions;
        return time/1000000 + "." + (time/100000) % 10 + "" + (time/10000) % 10 + "ms";
      }
    
      String getWord(DictionaryEntry entry) {
        char[] result = new char[entry.letters.length];
        int i=0;
        for (int letter: entry.letters)
          result[i++] = (char) (letter+97);
        return new String(result);
      }
    
      void run() throws IOException {
        long start = System.nanoTime();
    
        // The following can be pre-computed and should be replaced by constants
        dictionary = buildDictionary("C:/TWL06.txt");
        boardTripletIndices = buildTripletIndices();
        long precomputed = System.nanoTime();
    
    
        // The following only needs to run once at the beginning of the program
        candidateWords     = new DictionaryEntry[dictionary.length]; // WAAAY too generous
        foundWords         = new DictionaryEntry[dictionary.length]; // WAAAY too generous
        usedBoardPositions = new int[maxWordLength];
        long initialized = System.nanoTime(); 
    
        for (int n=1; n<=100; n++) {
          // The following needs to run again for every new board
          initializeBoard(new String[] {"DGHI",
                                        "KLPS",
                                        "YEUT",
                                        "EORN"});
          setPossibleTriplets();
          checkWordTriplets();
          checkWords();
        }
        long solved = System.nanoTime();
    
    
        // Print out result and statistics
        System.out.println("Precomputation finished in " + formatTime(start, precomputed, 1)+":");
        System.out.println("  Words in the dictionary: "+dictionary.length);
        System.out.println("  Longest word:            "+maxWordLength+" letters");
        System.out.println("  Number of triplet-moves: "+boardTripletIndices.length/3);
        System.out.println();
    
        System.out.println("Initialization finished in " + formatTime(precomputed, initialized, 1));
        System.out.println();
    
        System.out.println("Board solved in "+formatTime(initialized, solved, 100)+":");
        System.out.println("  Number of candidates: "+candidateCount);
        System.out.println("  Number of actual words: "+foundCount);
        System.out.println();
    
        System.out.println("Words found:");
        int w=0;
        System.out.print("  ");
        for (int i=0; i<foundCount; i++) {
          System.out.print(getWord(foundWords[i]));
          w++;
          if (w==10) {
            w=0;
            System.out.println(); System.out.print("  ");
          } else
            if (i<foundCount-1) System.out.print(", ");
        }
        System.out.println();
      }
    
      public static void main(String[] args) throws IOException {
        new BoggleSolver().run();
      }
    }
    

    以下是一些结果:

    对于原始问题(DGHI ...)中张贴的图片中的网格:

    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
    

    对于在原始问题(FXIE ......)中作为示例发布的字母

    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
    

    对于以下5x5网格:

    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
    

    为此,我使用了TWL06 Tournament Scrabble Word List,因为原始问题中的链接不再有效。这个文件是1.85MB,所以它有点短。并且buildDictionary函数抛出少于3个字母的所有单词。

    以下是对此表现的一些观察:

    • 它比Victor Nicollet的OCaml实施报告的性能慢约10倍。这是否是由不同的算法,他使用的较短的字典,他的代码编译和我在Java虚拟机中运行的事实,或我们的计算机的性能(我的是运行WinXP的英特尔Q6600 @ 2.4MHz),我不知道。但它比原始问题末尾引用的其他实现的结果快得多。那么,这个算法是否优于trie词典,我现在还不知道。

    • checkWordTriplets()中使用的表格方法可以很好地逼近实际答案。通过它的3-5个单词中只有1个会通过checkWords()测试失败(请参阅上面的候选人数实际单词的数量)。

    • 您可以在上面看到的内容:checkWordTriplets()函数大约需要3.65毫秒,因此在搜索过程中占据主导地位。 checkWords()函数占用了剩余的0.05-0.20毫秒。

    • checkWordTriplets()函数的执行时间线性地取决于字典大小,实际上与电路板大小无关!

    • checkWords()的执行时间取决于主板尺寸和checkWordTriplets()未排除的字数。

    • 上面的checkWords()实现是我想出的最愚蠢的第一个版本。它基本上没有优化。但与checkWordTriplets()相比,它与应用程序的总体性能无关,所以我并不担心它。 但是,如果电路板尺寸变大,这个功能会越来越慢,最终会变得很重要。然后,它也需要进行优化。

    • 这段代码的一个好处是它的灵活性:

      • 您可以轻松更改电路板尺寸:更新第10行和传递给initializeBoard()的字符串数组。
      • 它可以支持更大/不同的字母表,并且可以处理诸如处理&#39; Qu&#39;作为一个没有任何性能开销的字母。要做到这一点,需要更新第9行和将字符转换为数字的几个地方(目前只需从ASCII值中减去65)

    好的,但我觉得到目前为止这篇文章已经足够长了。我绝对可以回答你可能遇到的任何问题,但请将其移至评论中。

答案 5 :(得分:23)

我在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上的正确做法,请告诉我)。

gineer.bogglesolver.Main

package gineer.bogglesolver;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;

public class Main
{
    private final static Logger logger = Logger.getLogger(Main.class);

    public static void main(String[] args)
    {
        BasicConfigurator.configure();

        Solver solver = new Solver(4,
                        "FXIE" +
                        "AMLO" +
                        "EWBX" +
                        "ASTU");
        solver.solve();

    }
}

gineer.bogglesolver.Solver

package gineer.bogglesolver;

import gineer.bogglesolver.trie.Trie;
import gineer.bogglesolver.util.Constants;
import gineer.bogglesolver.util.Util;
import org.apache.log4j.Logger;

public class Solver
{
    private char[] puzzle;
    private int maxSize;

    private boolean[] used;
    private StringBuilder stringSoFar;

    private boolean[][] matrix;
    private Trie trie;

    private final static Logger logger = Logger.getLogger(Solver.class);

    public Solver(int size, String puzzle)
    {
        trie = Util.getTrie(size);
        matrix = Util.connectivityMatrix(size);

        maxSize = size * size;
        stringSoFar = new StringBuilder(maxSize);
        used = new boolean[maxSize];

        if (puzzle.length() == maxSize)
        {
            this.puzzle = puzzle.toCharArray();
        }
        else
        {
            logger.error("The puzzle size does not match the size specified: " + puzzle.length());
            this.puzzle = puzzle.substring(0, maxSize).toCharArray();
        }
    }

    public void solve()
    {
        for (int i = 0; i < maxSize; i++)
        {
            traverseAt(i);
        }
    }

    private void traverseAt(int origin)
    {
        stringSoFar.append(puzzle[origin]);
        used[origin] = true;

        //Check if we have a valid word
        if ((stringSoFar.length() >= Constants.MINIMUM_WORD_LENGTH) && (trie.containKey(stringSoFar.toString())))
        {
            logger.info("Found: " + stringSoFar.toString());
        }

        //Find where to go next
        for (int destination = 0; destination < maxSize; destination++)
        {
            if (matrix[origin][destination] && !used[destination] && trie.containPrefix(stringSoFar.toString() + puzzle[destination]))
            {
                traverseAt(destination);
            }
        }

        used[origin] = false;
        stringSoFar.deleteCharAt(stringSoFar.length() - 1);
    }

}

gineer.bogglesolver.trie.Node

package gineer.bogglesolver.trie;

import gineer.bogglesolver.util.Constants;

class Node
{
    Node[] children;
    boolean isKey;

    public Node()
    {
        isKey = false;
        children = new Node[Constants.NUMBER_LETTERS_IN_ALPHABET];
    }

    public Node(boolean key)
    {
        isKey = key;
        children = new Node[Constants.NUMBER_LETTERS_IN_ALPHABET];
    }

    /**
     Method to insert a string to Node and its children

     @param key the string to insert (the string is assumed to be uppercase)
     @return true if the node or one of its children is changed, false otherwise
     */
    public boolean insert(String key)
    {
        //If the key is empty, this node is a key
        if (key.length() == 0)
        {
            if (isKey)
                return false;
            else
            {
                isKey = true;
                return true;
            }
        }
        else
        {//otherwise, insert in one of its child

            int childNodePosition = key.charAt(0) - Constants.LETTER_A;
            if (children[childNodePosition] == null)
            {
                children[childNodePosition] = new Node();
                children[childNodePosition].insert(key.substring(1));
                return true;
            }
            else
            {
                return children[childNodePosition].insert(key.substring(1));
            }
        }
    }

    /**
     Returns whether key is a valid prefix for certain key in this trie.
     For example: if key "hello" is in this trie, tests with all prefixes "hel", "hell", "hello" return true

     @param prefix the prefix to check
     @return true if the prefix is valid, false otherwise
     */
    public boolean containPrefix(String prefix)
    {
        //If the prefix is empty, return true
        if (prefix.length() == 0)
        {
            return true;
        }
        else
        {//otherwise, check in one of its child
            int childNodePosition = prefix.charAt(0) - Constants.LETTER_A;
            return children[childNodePosition] != null && children[childNodePosition].containPrefix(prefix.substring(1));
        }
    }

    /**
     Returns whether key is a valid key in this trie.
     For example: if key "hello" is in this trie, tests with all prefixes "hel", "hell" return false

     @param key the key to check
     @return true if the key is valid, false otherwise
     */
    public boolean containKey(String key)
    {
        //If the prefix is empty, return true
        if (key.length() == 0)
        {
            return isKey;
        }
        else
        {//otherwise, check in one of its child
            int childNodePosition = key.charAt(0) - Constants.LETTER_A;
            return children[childNodePosition] != null && children[childNodePosition].containKey(key.substring(1));
        }
    }

    public boolean isKey()
    {
        return isKey;
    }

    public void setKey(boolean key)
    {
        isKey = key;
    }
}

gineer.bogglesolver.trie.Trie

package gineer.bogglesolver.trie;

public class Trie
{
    Node root;

    public Trie()
    {
        this.root = new Node();
    }

    /**
     Method to insert a string to Node and its children

     @param key the string to insert (the string is assumed to be uppercase)
     @return true if the node or one of its children is changed, false otherwise
     */
    public boolean insert(String key)
    {
        return root.insert(key.toUpperCase());
    }

    /**
     Returns whether key is a valid prefix for certain key in this trie.
     For example: if key "hello" is in this trie, tests with all prefixes "hel", "hell", "hello" return true

     @param prefix the prefix to check
     @return true if the prefix is valid, false otherwise
     */
    public boolean containPrefix(String prefix)
    {
        return root.containPrefix(prefix.toUpperCase());
    }

    /**
     Returns whether key is a valid key in this trie.
     For example: if key "hello" is in this trie, tests with all prefixes "hel", "hell" return false

     @param key the key to check
     @return true if the key is valid, false otherwise
     */
    public boolean containKey(String key)
    {
        return root.containKey(key.toUpperCase());
    }


}

gineer.bogglesolver.util.Constants

package gineer.bogglesolver.util;

public class Constants
{

    public static final int NUMBER_LETTERS_IN_ALPHABET = 26;
    public static final char LETTER_A = 'A';
    public static final int MINIMUM_WORD_LENGTH = 3;
    public static final int DEFAULT_PUZZLE_SIZE = 4;
}

gineer.bogglesolver.util.Util

package gineer.bogglesolver.util;

import gineer.bogglesolver.trie.Trie;
import org.apache.log4j.Logger;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class Util
{
    private final static Logger logger = Logger.getLogger(Util.class);
    private static Trie trie;
    private static int size = Constants.DEFAULT_PUZZLE_SIZE;

    /**
     Returns the trie built from the dictionary.  The size is used to eliminate words that are too long.

     @param size the size of puzzle.  The maximum lenght of words in the returned trie is (size * size)
     @return the trie that can be used for puzzle of that size
     */
    public static Trie getTrie(int size)
    {
        if ((trie != null) && size == Util.size)
            return trie;

        trie = new Trie();
        Util.size = size;

        logger.info("Reading the dictionary");
        final File file = new File("dictionary.txt");
        try
        {
            Scanner scanner = new Scanner(file);
            final int maxSize = size * size;
            while (scanner.hasNext())
            {
                String line = scanner.nextLine().replaceAll("[^\\p{Alpha}]", "");

                if (line.length() <= maxSize)
                    trie.insert(line);
            }
        }
        catch (FileNotFoundException e)
        {
            logger.error("Cannot open file", e);
        }

        logger.info("Finish reading the dictionary");
        return trie;
    }

    static boolean[] connectivityRow(int x, int y, int size)
    {
        boolean[] squares = new boolean[size * size];
        for (int offsetX = -1; offsetX <= 1; offsetX++)
        {
            for (int offsetY = -1; offsetY <= 1; offsetY++)
            {
                final int calX = x + offsetX;
                final int calY = y + offsetY;
                if ((calX >= 0) && (calX < size) && (calY >= 0) && (calY < size))
                    squares[calY * size + calX] = true;
            }
        }

        squares[y * size + x] = false;//the current x, y is false

        return squares;
    }

    /**
     Returns the matrix of connectivity between two points.  Point i can go to point j iff matrix[i][j] is true
     Square (x, y) is equivalent to point (size * y + x).  For example, square (1,1) is point 5 in a puzzle of size 4

     @param size the size of the puzzle
     @return the connectivity matrix
     */
    public static boolean[][] connectivityMatrix(int size)
    {
        boolean[][] matrix = new boolean[size * size][];
        for (int x = 0; x < size; x++)
        {
            for (int y = 0; y < size; y++)
            {
                matrix[y * size + x] = connectivityRow(x, y, size);
            }
        }
        return matrix;
    }
}

答案 6 :(得分:19)

令人惊讶的是,没有人尝试过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);

如果你想尝试一下,这是一个live link。虽然我的本地机器需要大约2秒,但我的网络服务器需要大约5秒。在任何一种情况下,它都不是很快。尽管如此,它仍然很可怕,所以我可以想象时间可以大大减少。关于如何实现这一点的任何指示将不胜感激。 PHP缺乏元组使坐标变得奇怪,而我无法理解到底发生什么事情根本没有帮助。

编辑:一些修复使其在本地播放时间不到1秒。

答案 7 :(得分:16)

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

我的时代是:

  • 将字典和字词前缀加载到哈希表中:.5到1秒。
  • 找到单词:平均不到10毫秒。

编辑:网络主机服务器上的字典加载时间比我的家用电脑长1到1.5秒。

我不知道服务器上的负载会对时间造成多大的影响。

我在.Net中将我的解决方案写成了一个网页。 myvrad.com/boggle

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

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

我正在使用所有唯一字词前缀和单词的散列表而不是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

答案 8 :(得分:11)

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

这是我的方法:

  1. 创建转换哈希以模拟合法转换。

  2. 迭代所有16 ^ 3个可能的三个字母组合。

    • 在循环中,排除非法转换并重复访问 同方。形成所有合法的3个字母序列并将它们存储在哈希中。
  3. 然后遍历字典中的所有单词。

    • 排除太长或太短的字词
    • 在每个单词上滑动一个3个字母的窗口,查看它是否属于步骤2中的3个字母组合。排除失败的单词。这消除了大多数不匹配。
    • 如果仍未消除,请使用递归算法查看是否可以通过拼图创建路径来形成单词。 (这部分很慢,但很少被称为。)
  4. 打印出我找到的字样。

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

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

    这个解决方案的优势在于代码简短,数据结构简单。

    这是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.
    }
    

答案 9 :(得分:9)

我花了3个月的时间研究解决10个最佳点密集5x5博格板问题。

问题现已解决,并在5个网页上完整披露。如有任何问题,请与我联系。

电路板分析算法使用显式堆栈通过带有直接子信息的定向非循环字图以及时间戳跟踪机制伪递归地遍历电路板方块。这可能是世界上最先进的词典数据结构。

该方案在四核上每秒评估大约10,000个非常好的电路板。 (9500+分)

父网页:

DeepSearch.c - http://www.pathcom.com/~vadco/deep.html

组件网页:

最佳记分牌 - http://www.pathcom.com/~vadco/binary.html

高级词典结构 - http://www.pathcom.com/~vadco/adtdawg.html

电路板分析算法 - http://www.pathcom.com/~vadco/guns.html

并行批处理 - http://www.pathcom.com/~vadco/parallel.html

- 这种全面的工作只会引起最需要的人的兴趣。

答案 10 :(得分:9)

我知道我已经很晚了,但我刚刚在 PHP 中制作了其中一个 - 只是为了好玩......

http://www.lostsockdesign.com.au/sandbox/boggle/index.php?letters=fxieamloewbxastu 0.90108秒

中找到75个字(133个点)

F.........X..I..............E............... A......................................M..............................L............................O............................... E....................W............................B..........................X A..................S..................................................T.................U....

给出一些关于程序实际执行情况的指示 - 每个字母都是在每个'''开始查看模式的地方。显示它尝试采取的路径。越多'。'它进一步搜索了。

如果您想要代码,请告诉我......这是PHP和HTML的可怕混合,从来没有想过看到光明的一天,所以我不敢在这里发布:P

答案 11 :(得分:4)

您的搜索算法会在搜索继续时不断减少单词列表吗?

例如,在上面的搜索中,只有13个字母可以从你的单词开始(有效地减少到一半的起始字母)。

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

我会从那里开始。

答案 12 :(得分:4)

首先,阅读一位C#语言设计师如何解决相关问题: http://blogs.msdn.com/ericlippert/archive/2009/02/04/a-nasality-talisman-for-the-sultana-analyst.aspx

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

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

答案 13 :(得分:4)

为了好玩,我在bash中实现了一个。 它不是超级快,但合理。

http://dev.xkyle.com/bashboggle/

答案 14 :(得分:4)

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

letter: char
isWord: boolean

然后你建立树,每个深度添加一个新的字母。换句话说,在第一级有字母表;然后从这些树的每一个,还有另外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'旁边,(从它们点击它们的那一点)将是相同的。我没有足够的时间为此准备拼写代码,但我认为你可以收集这个想法。

此外,如果您使用Google“Boggle solver”,我相信您会找到其他解决方案。

答案 15 :(得分:4)

我必须更多地考虑一个完整的解决方案,但作为一个方便的优化,我想知道是否值得预先计算一个基于digrams和trigrams(2和3个字母组合)的频率表在您的词典中的所有单词,并使用此来优先搜索。我会用起始字母来表达。因此,如果您的词典包含“印度”,“水”,“极端”和“非凡”等字样,那么您预先计算的表格可能是:

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

然后按共性(第一个EX,然后是WA / IN)

的顺序搜索这些数字

答案 16 :(得分:3)

热闹。由于同样该死的游戏,我几天前几乎发布了同样的问题!然而,我并没有因为只是搜索谷歌boggle solver python而得到了我想要的所有答案。

答案 17 :(得分:3)

我意识到这个问题的时间已经过去了,但是因为我自己正在研究一个求解器,并且在谷歌搜索时偶然发现了这个问题,我想我应该发布一个对我的引用,因为它看起来有些不同于其他

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

索引是一个强力字典,有点像trie,但允许对索引进行Pythonic查询。如果列表中包含“cat”和“cater”字样,您将在字典中获得:

   d = { 'c': ['cat','cater'],
     'ca': ['cat','cater'],
     'cat': ['cat','cater'],
     'cate': ['cater'],
     'cater': ['cater'],
   }

因此,如果current_word为'ca',您知道它是一个有效的前缀,因为'ca' in d返回True(所以继续遍历板)。如果current_word是'cat',那么你知道它是一个有效的单词,因为它是一个有效的前缀,'cat' in d['cat']也返回True。

如果觉得这样做允许一些看起来不太慢的可读代码。像其他人一样,这个系统的费用是阅读/建立索引。解决电路板几乎就是噪音。

代码位于http://gist.github.com/268079。它是故意垂直和天真的,有很多明确的有效性检查,因为我想要理解这个问题,而不是用一堆魔法或默默无闻的方式来解决它。

答案 18 :(得分:3)

我用C ++编写了我的求解器。我实现了自定义树结构。我不确定它是否可以被视为特里但它是相似的。每个节点有26个分支,每个字母对应一个字母。我跟着我的字典的分支并行遍历了boggle board的分支。如果字典中不存在分支,我将停止在Boggle板上搜索它。我将电路板上的所有字母转换为整数。所以'A'= 0.因为它只是数组,所以查找总是O(1)。每个节点存储它是否完成一个单词以及其子节点中存在多少单词。当发现单词减少重复搜索相同的单词时,树被修剪。我相信修剪也是O(1)。

CPU:Pentium SU2700 1.3GHz
RAM:3gb

在&lt;中加载178,590个单词的词典1秒。
在4秒内解决100x100 Boggle(boggle.txt)问题。找到~44,000个单词。
解决4x4 Boggle的速度太快,无法提供有意义的基准测试。 :)

Fast Boggle Solver GitHub Repo

答案 19 :(得分:2)

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

  • N * M远大于可能的单词数
  • N * M远远大于最长的单词

在这些假设下,该解决方案的复杂性为O(N * M)。

我认为在很多方面比较这个示例板的运行时间会忽略这一点,但为了完整起见,此解决方案在我的现代MacBook Pro上以<0.2s完成。

此解决方案将为语料库中的每个单词找到所有可能的路径。

#!/usr/bin/env ruby
# Example usage: ./boggle-solver --board "fxie amlo ewbx astu"

autoload :Matrix, 'matrix'
autoload :OptionParser, 'optparse'

DEFAULT_CORPUS_PATH = '/usr/share/dict/words'.freeze

# Functions

def filter_corpus(matrix, corpus, min_word_length)
  board_char_counts = Hash.new(0)
  matrix.each { |c| board_char_counts[c] += 1 }

  max_word_length = matrix.row_count * matrix.column_count
  boggleable_regex = /^[#{board_char_counts.keys.reduce(:+)}]{#{min_word_length},#{max_word_length}}$/
  corpus.select{ |w| w.match boggleable_regex }.select do |w|
    word_char_counts = Hash.new(0)
    w.each_char { |c| word_char_counts[c] += 1 }
    word_char_counts.all? { |c, count| board_char_counts[c] >= count }
  end
end

def neighbors(point, matrix)
  i, j = point
  ([i-1, 0].max .. [i+1, matrix.row_count-1].min).inject([]) do |r, new_i|
    ([j-1, 0].max .. [j+1, matrix.column_count-1].min).inject(r) do |r, new_j|
      neighbor = [new_i, new_j]
      neighbor.eql?(point) ? r : r << neighbor
    end
  end
end

def expand_path(path, word, matrix)
  return [path] if path.length == word.length

  next_char = word[path.length]
  viable_neighbors = neighbors(path[-1], matrix).select do |point|
    !path.include?(point) && matrix.element(*point).eql?(next_char)
  end

  viable_neighbors.inject([]) do |result, point|
    result + expand_path(path.dup << point, word, matrix)
  end
end

def find_paths(word, matrix)
  result = []
  matrix.each_with_index do |c, i, j|
    result += expand_path([[i, j]], word, matrix) if c.eql?(word[0])
  end
  result
end

def solve(matrix, corpus, min_word_length: 3)
  boggleable_corpus = filter_corpus(matrix, corpus, min_word_length)
  boggleable_corpus.inject({}) do |result, w|
    paths = find_paths(w, matrix)
    result[w] = paths unless paths.empty?
    result
  end
end

# Script

options = { corpus_path: DEFAULT_CORPUS_PATH }
option_parser = OptionParser.new do |opts|
  opts.banner = 'Usage: boggle-solver --board <value> [--corpus <value>]'

  opts.on('--board BOARD', String, 'The board (e.g. "fxi aml ewb ast")') do |b|
    options[:board] = b
  end

  opts.on('--corpus CORPUS_PATH', String, 'Corpus file path') do |c|
    options[:corpus_path] = c
  end

  opts.on_tail('-h', '--help', 'Shows usage') do
    STDOUT.puts opts
    exit
  end
end
option_parser.parse!

unless options[:board]
  STDERR.puts option_parser
  exit false
end

unless File.file? options[:corpus_path]
  STDERR.puts "No corpus exists - #{options[:corpus_path]}"
  exit false
end

rows = options[:board].downcase.scan(/\S+/).map{ |row| row.scan(/./) }

raw_corpus = File.readlines(options[:corpus_path])
corpus = raw_corpus.map{ |w| w.downcase.rstrip }.uniq.sort

solution = solve(Matrix.rows(rows), corpus)
solution.each_pair do |w, paths|
  STDOUT.puts w
  paths.each do |path|
    STDOUT.puts "\t" + path.map{ |point| point.inspect }.join(', ')
  end
end
STDOUT.puts "TOTAL: #{solution.count}"

答案 20 :(得分:1)

这是在NLTK工具箱中使用预定义单词的解决方案 NLTK具有nltk.corpus软件包,其中有名为word的软件包,其中包含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

我希望你能得到它。

答案 21 :(得分:1)

我有implemented a solution in OCaml。它将字典预编译为trie,并使用双字母序列频率来消除可能永远不会出现在单词中的边缘,以进一步加快处理速度。

它在0.35ms内解决了你的示例板(另外6ms的启动时间主要与将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"]

答案 22 :(得分:1)

我知道我在聚会上已经很晚了,但作为一个编码练习,我已经实现了几种编程语言的摇摆解决方案(C ++,Java,Go,C#,Python,Ruby,JavaScript,Julia,Lua,PHP, Perl)我觉得有人可能对这些感兴趣,所以我在这里留下链接: https://github.com/AmokHuginnsson/boggle-solvers

答案 23 :(得分:1)

所以我想添加另一种PHP解决方法,因为每个人都喜欢PHP。 我想做一些重构,比如对字典文件使用regexpression匹配,但是现在我只是将整个字典文件加载到wordList中。

我使用链接列表的想法做到了这一点。每个节点都有一个字符值,一个位置值和一个下一个指针。

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

1     2     3     4
11    12    13    14
21    22    23    24
31    32    33    34

因此,使用该网格,我知道如果第一个节点的位置等于第二个节点位置+/- 1用于同一行,则连接两个节点,+ / - 9,10,11用于上面的行,并且下方。

我使用递归进行主搜索。它从wordList中取出一个字,找到所有可能的起点,然后递归地找到下一个可能的连接,记住它不能到达它已经使用的位置(这就是为什么我添加$ notInLoc)。

无论如何,我知道它需要一些重构,并且很想听到关于如何使它更清洁的想法,但它会根据我使用的字典文件产生正确的结果。根据电路板上元音和组合的数量,大约需要3至6秒。我知道,一旦我preg_match字典结果,这将显着减少。

<?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);

    ?>

答案 24 :(得分:1)

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

输出:
[ “FAM”, “TUX”, “TUB”, “FAE”, “ELI”, “ELM”, “ELB”, “TWA”, “TWA”, “SAW”, “AMI”, “SWA”,” SWA”, “AME”, “海”, “SEW”, “AES”, “AWL”, “博览馆”, “海”, “AWA”, “MIX”, “MIL”, “AST”, “ASE” , “MAX”, “前”, “鱼鳔”, “巢”, “敬畏”, “MES”, “AWL”, “谎言”, “LIM”, “AWA”, “AES”, “但是”,” BLO”, “是”, “WAE”, “WEA”, “雷”, “狮子座”, “LOB”, “液氧”, “WEM”, “油”, “东方龙马”, “WEA”, “WAE” , “蜡”, “WAF”, “MILO”, “东”, “WAME”, “TWAS”, “TWAE”, “EMIL”, “WEAM”, “OIME”, “叶腋”, “西”,” TWAE”, “肢体”, “哇塞”, “WAST”, “BLEO”, “存根”, “熬”, “伯乐”, “LIME”, “之声”, “利马”, “MESA”, “MEWL” “桥” “FAME”, “ASEM”, “里”, “AMIL”, “SEAX”, “SEAM”, “半成品”, “SWAM”, “AMBO”, “AMLI”, “轴上的”,” AMBLE”, “斯瓦米”, “AWEST”, “AWEST”, “LIMAX”, “LIMES”, “LIMBU”, “LIMBO”, “EMBOX”, “桑布勒”, “EMBOLE”, “WAMBLE”, “FAMBLE” ]

代码:

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
})))

答案 25 :(得分:0)

如何简单排序并在字典中使用二进制搜索

0.35秒返回整个列表,可以进一步优化(例如删除带有未使用字母的单词等)。

from bisect import bisect_left

f = open("dict.txt")
D.extend([line.strip() for line in f.readlines()])
D = sorted(D)

def neibs(M,x,y):
    n = len(M)
    for i in xrange(-1,2):
        for j in xrange(-1,2):
            if (i == 0 and j == 0) or (x + i < 0 or x + i >= n or y + j < 0 or y + j >= n):
                continue
            yield (x + i, y + j)

def findWords(M,D,x,y,prefix):
    prefix = prefix + M[x][y]

    # find word in dict by binary search
    found = bisect_left(D,prefix)

    # if found then yield
    if D[found] == prefix: 
        yield prefix

    # if what we found is not even a prefix then return
    # (there is no point in going further)
    if len(D[found]) < len(prefix) or D[found][:len(prefix)] != prefix:
        return

    # recourse
    for neib in neibs(M,x,y):
        for word in findWords(M,D,neib[0], neib[1], prefix):
            yield word

def solve(M,D):
    # check each starting point
    for x in xrange(0,len(M)):
        for y in xrange(0,len(M)):
            for word in findWords(M,D,x,y,""):
                yield word

grid = "fxie amlo ewbx astu".split()
print [x for x in solve(grid,D)]

答案 26 :(得分:0)

我已经使用DFA算法在C#中解决了这个问题。您可以在

查看我的代码

https://github.com/attilabicsko/wordshuffler/

除了在矩阵中查找单词外,我的算法还保存了单词的实际路径,因此在设计单词查找器游戏时,您可以检查实际路径上是否有单词。

答案 27 :(得分:0)

    package ProblemSolving;

import java.util.HashSet;
import java.util.Set;

/**
 * Given a 2-dimensional array of characters and a
 * dictionary in which a word can be searched in O(1) time.
 * Need to print all the words from array which are present
 * in dictionary. Word can be formed in any direction but
 * has to end at any edge of array.
 * (Need not worry much about the dictionary)
 */
public class DictionaryWord {
    private static char[][] matrix = new char[][]{
            {'a', 'f', 'h', 'u', 'n'},
            {'e', 't', 'a', 'i', 'r'},
            {'a', 'e', 'g', 'g', 'o'},
            {'t', 'r', 'm', 'l', 'p'}
    };
    private static int dim_x = matrix.length;
    private static int dim_y = matrix[matrix.length -1].length;
    private static Set<String> wordSet = new HashSet<String>();

    public static void main(String[] args) {
        //dictionary
        wordSet.add("after");
        wordSet.add("hate");
        wordSet.add("hair");
        wordSet.add("air");
        wordSet.add("eat");
        wordSet.add("tea");

        for (int x = 0; x < dim_x; x++) {
            for (int y = 0; y < dim_y; y++) {
                checkAndPrint(matrix[x][y] + "");
                int[][] visitedMap = new int[dim_x][dim_y];
                visitedMap[x][y] = 1;
                recursion(matrix[x][y] + "", visitedMap, x, y);
            }
        }
    }

    private static void checkAndPrint(String word) {
        if (wordSet.contains(word)) {
            System.out.println(word);
        }
    }

    private static void recursion(String word, int[][] visitedMap, int x, int y) {
        for (int i = Math.max(x - 1, 0); i < Math.min(x + 2, dim_x); i++) {
            for (int j = Math.max(y - 1, 0); j < Math.min(y + 2, dim_y); j++) {
                if (visitedMap[i][j] == 1) {
                    continue;
                } else {
                    int[][] newVisitedMap = new int[dim_x][dim_y];
                    for (int p = 0; p < dim_x; p++) {
                        for (int q = 0; q < dim_y; q++) {
                           newVisitedMap[p][q] = visitedMap[p][q];
                        }
                    }
                    newVisitedMap[i][j] = 1;
                    checkAndPrint(word + matrix[i][j]);
                    recursion(word + matrix[i][j], newVisitedMap, i, j);
                }
            }
        }
    }

}

答案 28 :(得分:0)

我完美而快速地解决了这个问题。我把它放入一个Android应用程序。在Play商店链接中查看视频以查看其实际效果。

Word Cheats是一款“破解”任何矩阵式文字游戏的应用程序。这个程序是建立的 帮助我欺骗单词扰乱者。它可以用于单词搜索, ruzzle,words,word finder,word crack,boggle等等!

这里可以看到 https://play.google.com/store/apps/details?id=com.harris.wordcracker

在视频中查看正在使用的应用 https://www.youtube.com/watch?v=DL2974WmNAI

答案 29 :(得分:0)

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

Source code

答案 30 :(得分:0)

我也用Java解决了这个问题。我的实现长269行,非常容易使用。首先,您需要创建Boggler类的新实例,然后使用网格作为参数调用solve函数。在我的计算机上加载50 000个单词的字典大约需要100毫秒,它会在大约10-20毫秒内找到单词。找到的单词存储在ArrayList foundWords

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;
    }
}

答案 31 :(得分:0)

import java.util.HashSet;
import java.util.Set;

/**
 * @author Sujeet Kumar (mrsujeet@gmail.com) It prints out all strings that can
 *         be formed by moving left, right, up, down, or diagonally and exist in
 *         a given dictionary , without repeating any cell. Assumes words are
 *         comprised of lower case letters. Currently prints words as many times
 *         as they appear, not just once. *
 */

public class BoggleGame 
{
  /* A sample 4X4 board/2D matrix */
  private static char[][] board = { { 's', 'a', 's', 'g' },
                                  { 'a', 'u', 't', 'h' }, 
                                  { 'r', 't', 'j', 'e' },
                                  { 'k', 'a', 'h', 'e' }
};

/* A sample dictionary which contains unique collection of words */
private static Set<String> dictionary = new HashSet<String>();

private static boolean[][] visited = new boolean[board.length][board[0].length];

public static void main(String[] arg) {
    dictionary.add("sujeet");
    dictionary.add("sarthak");
    findWords();

}

// show all words, starting from each possible starting place
private static void findWords() {
    for (int i = 0; i < board.length; i++) {
        for (int j = 0; j < board[i].length; j++) {
            StringBuffer buffer = new StringBuffer();
            dfs(i, j, buffer);
        }

    }

}

// run depth first search starting at cell (i, j)
private static void dfs(int i, int j, StringBuffer buffer) {
    /*
     * base case: just return in recursive call when index goes out of the
     * size of matrix dimension
     */
    if (i < 0 || j < 0 || i > board.length - 1 || j > board[i].length - 1) {
        return;
    }

    /*
     * base case: to return in recursive call when given cell is already
     * visited in a given string of word
     */
    if (visited[i][j] == true) { // can't visit a cell more than once
        return;
    }

    // not to allow a cell to reuse
    visited[i][j] = true;

    // combining cell character with other visited cells characters to form
    // word a potential word which may exist in dictionary
    buffer.append(board[i][j]);

    // found a word in dictionary. Print it.
    if (dictionary.contains(buffer.toString())) {
        System.out.println(buffer);
    }

    /*
     * consider all neighbors.For a given cell considering all adjacent
     * cells in horizontal, vertical and diagonal direction
     */
    for (int k = i - 1; k <= i + 1; k++) {
        for (int l = j - 1; l <= j + 1; l++) {
            dfs(k, l, buffer);

        }

    }
    buffer.deleteCharAt(buffer.length() - 1);
    visited[i][j] = false;
  }
}

答案 32 :(得分:0)

这是我提出的解决boggle问题的解决方案。我想这是最“pythonic”的做事方式:

from itertools import combinations
from itertools import izip
from math import fabs

def isAllowedStep(current,step,length,doubleLength):
            # for step == length -1 not to be 0 => trivial solutions are not allowed
    return length > 1 and \
           current + step < doubleLength and current - step > 0 and \
           ( step == 1 or step == -1 or step <= length+1 or step >= length - 1)

def getPairwiseList(someList):
    iterableList = iter(someList)
    return izip(iterableList, iterableList)

def isCombinationAllowed(combination,length,doubleLength):

    for (first,second) in  getPairwiseList(combination):
        _, firstCoordinate = first
        _, secondCoordinate = second
        if not isAllowedStep(firstCoordinate, fabs(secondCoordinate-firstCoordinate),length,doubleLength):
            return False
    return True

def extractSolution(combinations):
    return ["".join([x[0] for x in combinationTuple]) for combinationTuple in combinations]


length = 4
text = tuple("".join("fxie amlo ewbx astu".split()))
textIndices = tuple(range(len(text)))
coordinates = zip(text,textIndices)

validCombinations = [combination for combination in combinations(coordinates,length) if isCombinationAllowed(combination,length,length*length)]
solution = extractSolution(validCombinations)

这部分我建议你不要使用所有可能的匹配,但它实际上可以检查你生成的单词是否实际构成有效的单词:

import mechanize
def checkWord(word):
    url = "https://en.oxforddictionaries.com/search?filter=dictionary&query="+word
    br = mechanize.Browser()
    br.set_handle_robots(False)
    response = br.open(url)
    text = response.read()
    return "no exact matches"  not in text.lower()

print [valid for valid in solution[:10] if checkWord(valid)]

答案 33 :(得分:0)

这是我的java实现:https://github.com/zouzhile/interview/blob/master/src/com/interview/algorithms/tree/BoggleSolver.java

Trie build需要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
  处理器名称:Intel Core i5
  处理器速度:2.3 GHz
  处理器数量:1
  核心总数:2
  L2缓存(每个核心):256 KB
  L3缓存:3 MB
  记忆:4 GB
  Boot ROM版本:MBP81.0047.B0E
  SMC版本(系统):1.68f96

答案 34 :(得分:0)

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

算法:

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)