385

最近はiPhoneでスクランブルというゲームをやっています。このゲームを Boggle として知っている人もいるかもしれません。基本的に、ゲームが開始すると、次のような文字のマトリックスが表示されます。

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

このゲームの目的は、文字をつなげてできる単語をできるだけ多く見つけることです。任意の文字から始めることができ、それを囲むすべての文字は公正なゲームであり、次の文字に移ると、その文字を囲むすべての文字は、以前に使用された文字を除いて公正なゲームです. たとえば、上のグリッドでは、、、、、などの単語を思いつくことができます。単語LOBは、少なくとも 3 文字で、NxN 文字以下である必要があります。このゲームでは 16 文字ですが、実装によっては異なる場合があります。 . このゲームは楽しくて中毒性がありますが、私はどうやらそれが得意ではないようで、可能な限り最高の単語 (単語が長いほど、より多くのポイントを獲得できる) を提供するプログラムを作成して、少しごまかしたかったのです。TUXSEAFAME

サンプルボーグル
(出典:boggled.org

残念ながら、私はアルゴリズムやその効率などについてあまり得意ではありません。私の最初の試みは、このような辞書(~2.3MB) を使用して、組み合わせを辞書エントリと一致させようとする線形検索を行います。これは、可能な単語を見つけるのに非常に長い時間がかかります。また、ラウンドごとに 2 分しか与えられないため、単純に不十分です。

Stackoverflowers がより効率的なソリューションを思い付くことができるかどうかを確認することに興味があります。Python、PHP、Perl のビッグ 3 P を使用したソリューションを主に探していますが、速度が不可欠であるため、Java や C++ を使用するものもクールです。

現在のソリューション:

  • Adam Rosenfield、パイソン、20 代まで
  • John Fouhy、Python、~3 秒
  • Kent Fredric、Perl、~1 秒
  • Darius Bacon、パイソン、~1 秒
  • rvarcher、VB.NET、~1 秒
  • Paolo Bergantino、PHP (ライブ リンク)、~5 秒 (ローカルで~2 秒)
4

35 に答える 35

147

私の答えはここにある他の答えと同じように機能しますが、辞書の設定が速くなるため、他の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:ケントフレデリックのPerlソリューションがなぜ速いのか興味がありました。文字のセットの代わりに正規表現の一致を使用することが判明しました。Pythonで同じことを行うと、速度が約2倍になります。

于 2009-04-15T02:00:37.647 に答える
117

あなたが得ようとしている最速の解決策は、おそらくあなたの辞書をトライに保存することを含むでしょう。次に、トリプレット(xys)のキューを作成します。キュー内の各要素は、グリッドで綴ることができる単語の接頭辞sに対応し、場所( xy)で終わります。N x N要素(Nはグリッドのサイズ)でキューを初期化します。グリッド内の正方形ごとに1つの要素です。次に、アルゴリズムは次のように進行します。

キューが空でない場合:
  トリプル(x、y、s)をデキューします
  (x、y)に隣接する文字cを持つ各正方形(x'、y')について:
    s + cが単語の場合、s+cを出力します
    s + cが単語の接頭辞である場合は、(x'、y'、s + c)をキューに挿入します

辞書をトライに保存する場合、s + cが単語であるか単語のプレフィックスであるかを一定時間でテストできます(現在のノードへのポインターなど、各キューデータに追加のメタデータも保持している場合)したがって、このアルゴリズムの実行時間は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'、 ' 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'、'was'、'waw'、'wae'、'bob'、'blo'、'bub'、'but'、'ast'、'ase'、'asa'、 ' awl'、' awa'、' awe'、' awa'、' aes'、' swa'、' swa'、' sew'、' sea'、' sea'、' saw'、' tux'、' tab ' 、'tut'、'twa'、'twa'、'tst'、'utu'、'fama'、'fame'、'ixil'、'imam'、'amli'、'amil'、'ambo'、 ' axil'、' axle'、' 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'、' wase'、' wawa'、' wawa'、' boil'、' bolo'、' bole'、' bobo'、' blob'、' bleo'、' bubo'、' asem'、' stub ' 、'stut'、'swam'、'semi'、'seme'、'seam'、'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'、' famble'、' mimble'、' maxima'、' embolo'、' embole'、' wamble'、' semese'、' semble'、' sawbwa'、' sawbwa ' ]sawbwa']sawbwa']

注:このプログラムは、1文字の単語を出力したり、単語の長さでフィルタリングしたりすることはありません。これは簡単に追加できますが、問題とはあまり関係がありません。また、複数の方法で綴ることができる場合は、いくつかの単語を複数回出力します。特定の単語をさまざまな方法で綴ることができる場合(最悪の場合:グリッド内のすべての文字が同じで(たとえば「A」)、辞書に「aaaaaaaaaa」のような単語がある場合)、実行時間はひどく指数関数的になります。アルゴリズムが終了した後は、重複を除外して並べ替えるのは簡単です。

于 2009-04-14T02:19:18.437 に答える
39

ディクショナリの高速化のために、事前にディクショナリの比較を大幅に削減するために実行できる一般的な変換/プロセスが 1 つあります。

上記のグリッドには 16 文字しか含まれておらず、一部は重複しているため、取得できない文字を含むエントリを除外するだけで、辞書内のキーの総数を大幅に減らすことができます。

これは明らかな最適化だと思いましたが、誰もそれをしなかったのを見て、私はそれについて言及しています。

入力パスの間だけで、200,000 個のキーの辞書から 2,000 個のキーだけに減りました。これにより、少なくともメモリのオーバーヘッドが削減されます。メモリは無限に高速ではないため、どこかで速度が向上することは間違いありません。

Perl の実装

有効性だけでなく、抽出されたすべての文字列の正確なパスを知ることができることを重視したため、私の実装は少し重くなっています。

また、理論的には穴の開いたグリッドが機能することを可能にするいくつかの適応と、異なるサイズの線を持つグリッドがあります (入力が正しく、それが何らかの形で整列すると仮定します)。

前に疑ったように、アーリー フィルターは、私のアプリケーションで最も重大なボトルネックです。

実行すると、すべての 1 桁が有効な単語であると考えられるように見えますが、それは辞書ファイルの仕組みによるものだと確信しています。

少し肥大化していますが、少なくともcpan のTree::Trieを再利用します

一部は既存の実装に部分的に影響を受けており、一部はすでに頭に浮かんでいました。

建設的批判とそれを改善する方法は歓迎されます (/me は、CPAN でボーグル ソルバーを検索したことはないと述べていますが、これは解決するのがより楽しいものでした)。

新しい基準のために更新されました

#!/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 ) ;
};

比較のためのアーキテクチャ/実行情報:

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% ==============================

その正規表現の最適化に関するより多くのつぶやき

私が使用する正規表現の最適化は、マルチソルブ辞書には役に立ちません。マルチソルブの場合、事前にトリミングされた辞書ではなく、完全な辞書が必要になります。

ただし、1 回限りのソルバの場合は、非常に高速です。(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 フィルタなし フィルタあり
フィルタなし 8.16 -- -94%
フィルタリングされた 0.464 1658% --

ps: 8.16 * 200 = 27 分。

于 2009-04-14T10:02:13.240 に答える
33

問題を 2 つの部分に分割できます。

  1. グリッド内の可能な文字列を列挙するある種の検索アルゴリズム。
  2. 文字列が有効な単語かどうかをテストする方法。

理想的には、(2) には、文字列が有効な単語のプレフィックスであるかどうかをテストする方法も含める必要があります。これにより、検索を削減し、時間を大幅に節約できます。

Adam Rosenfield の Trie は (2) に対する解決策です。これは洗練されており、おそらくアルゴリズムの専門家が好むものですが、最新の言語と最新のコンピューターを使用すると、少し怠惰になる可能性があります. また、ケントが示唆するように、グリッドに存在しない文字を含む単語を破棄することで、辞書のサイズを減らすことができます。ここにいくつかのpythonがあります:

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

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

    return words, prefixes

わお; 一定時間のプレフィックス テスト。リンクした辞書をロードするのに数秒かかりますが、ほんの数秒です :-) (注意してくださいwords <= prefixes)

さて、パート (1) については、グラフの観点から考える傾向があります。そこで、次のような辞書を作成します。

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

つまりgraph[(x, y)]、 position から到達できる座標のセットです(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. これまでパスに含まれていなかったすべての子を調べます。

パイソン:

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']

コードが辞書をロードするのに (文字通り) 数秒かかりますが、残りはマシン上で瞬時に実行されます。

于 2009-04-14T04:27:46.070 に答える
23

文字グリッドでは構築できない単語を一致させるために、おそらくほとんどの時間を費やすことになると思います。したがって、私が最初に行うことは、そのステップをスピードアップすることです。

このために、グリッドを可能な「動き」のテーブルとして再表現し、見ている文字遷移によってインデックスを付けます。

まず、アルファベット全体から各文字に数字を割り当てます (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

それらがすべて許可されている場合、この単語が見つかる可能性があります。

たとえば、「ヘルメット」という単語は、4 番目のトランジション (m から e: helMEt) で除外できます。これは、テーブルのエントリが false であるためです。

最初の (h から a への) 遷移は許可されていないため (テーブルには存在しません)、ハムスターという単語は除外できます。

さて、おそらく削除しなかった残りの単語については、現在行っている方法で、またはここの他の回答のいくつかで提案されているように、実際にグリッドでそれらを見つけてみてください. これは、グリッド内の同一文字間のジャンプに起因する誤検知を回避するためです。たとえば、「ヘルプ」という単語はテーブルでは許可されていますが、グリッドでは許可されていません。

このアイデアに関するさらなるパフォーマンス改善のヒント:

  1. 2D 配列を使用する代わりに、1D 配列を使用して、2 番目の文字のインデックスを自分で計算するだけです。したがって、上記のような 12x12 配列の代わりに、長さ 144 の 1D 配列を作成します。その後、すべての文字がグリッドに表示されなくても、常に同じアルファベットを使用する場合 (つまり、標準的な英語のアルファベットでは 26x26 = 676x1 配列)。 、辞書の単語と一致するかどうかをテストする必要があるこの 1D 配列のインデックスを事前に計算できます。たとえば、上記の例の「hello」のインデックスは次のようになります。

    hello (6, 3, 8, 8, 10):
    42 (from 6 + 3x12), 99, 104, 128
    -> "hello" will be stored as 42, 99, 104, 128 in the dictionary
    
  2. アイデアを 3D テーブル (1D 配列として表される) に拡張します。つまり、すべて 3 文字の組み合わせが許可されます。そうすれば、さらに多くの単語をすぐに削除でき、各単語の配列ルックアップの数を 1 減らすことができます。「hello」の場合、必要な配列ルックアップは 3 つだけです: hel、ell、llo。ちなみに、このテーブルを作成するのは非常に簡単です。グリッドには 400 の可能な 3 文字の動きしかないからです。

  3. テーブルに含める必要があるグリッド内の動きのインデックスを事前に計算します。上記の例では、次のエントリを「True」に設定する必要があります。

    (0,0) (0,1) -> here: h, b : [6][0]
    (0,0) (1,0) -> here: h, e : [6][3]
    (0,0) (1,1) -> here: h, e : [6][3]
    (0,1) (0,0) -> here: b, h : [0][6]
    (0,1) (0,2) -> here: b, c : [0][1]
    .
    :
    
  4. また、ゲーム グリッドを 16 エントリの 1-D 配列で表し、テーブルを 3. で事前に計算します。この配列にインデックスを含めます。

このアプローチを使用すると、辞書が事前に計算され、既にメモリにロードされている場合、コードを非常に高速に実行できると確信しています。

ところで: ゲームを構築している場合、もう 1 つの良い方法は、これらの種類のものをバックグラウンドですぐに実行することです。ユーザーがまだアプリのタイトル画面を見て、指を所定の位置に置いて [再生] を押している間に、最初のゲームの生成と解決を開始します。次に、ユーザーが前のゲームをプレイするときに、次のゲームを生成して解決します。これにより、コードを実行するための多くの時間を得ることができます。

(私はこの問題が好きなので、実際にどのように機能するかを確認するために、数日中に私の提案を 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 仮想マシンで実行されるという事実、または私たちのコンピューターのパフォーマンス (私のコンピューターは Intel Q6600 @ 2.4MHz で WinXP を実行している) によって引き起こされているかどうかにかかわらず、知らない。しかし、元の質問の最後に引用されている他の実装の結果よりもはるかに高速です。したがって、このアルゴリズムがトライ辞書よりも優れているかどうかは、現時点ではわかりません。

  • で使用されているテーブル メソッドはcheckWordTriplets()、実際の回答に対して非常に優れた近似値を生成します。渡された 3 ~ 5 単語のうち 1 つだけがcheckWords()テストに失敗します (上記の候補の数と実際の単語の数を参照してください)。

  • 上に表示されていないもの: このcheckWordTriplets()関数は約 3.65 ミリ秒かかるため、検索プロセスで完全に支配的です。このcheckWords()関数は、残りの 0.05 ~ 0.20 ミリ秒のほとんどを占めています。

  • 関数の実行時間はcheckWordTriplets()ディクショナリのサイズに直線的に依存し、ボードのサイズには実質的に依存しません!

  • の実行時間はcheckWords()、ボードのサイズと によって除外されない単語の数によって異なりますcheckWordTriplets()

  • 上記のcheckWords()実装は、私が思いついた最もばかげた最初のバージョンです。基本的に最適化されていません。しかし、checkWordTriplets()それはアプリケーションの全体的なパフォーマンスとは無関係なので、私は気にしませんでした。しかし、ボードのサイズが大きくなると、この機能はますます遅くなり、最終的には問題になり始めます。次に、それも最適化する必要があります。

  • このコードの優れた点の 1 つは、その柔軟性です。

    • ボード サイズは簡単に変更できます。10 行目と に渡される文字列配列を更新しinitializeBoard()ます。
    • より大きな/異なるアルファベットをサポートでき、パフォーマンスのオーバーヘッドなしで「Qu」を 1 文字として処理することができます。これを行うには、9 行目と、文字が数字に変換されるいくつかの場所を更新する必要があります (現在は、ASCII 値から 65 を引くだけです)。

わかりましたが、今ではこの投稿は十分に長いと思います。どんな質問にも確実にお答えできますが、それはコメントに移しましょう。

于 2012-11-12T05:42:29.577 に答える
19

驚いたことに、誰もこれのPHPバージョンを試みませんでした。

これは、JohnFouhyのPythonソリューションの動作するPHPバージョンです。

私は他のみんなの答えからいくつかの指針を取りましたが、これはほとんどジョンからコピーされています。

$boggle = "fxie
           amlo
           ewbx
           astu";

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

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

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

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

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

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

$solution = array();
find_words($graph, $chardict, 'None', array(), $prefixes, $solution);
print_r($solution);

試してみたい場合は、こちらのライブリンクをご覧ください。ローカルマシンでは約2秒かかりますが、Webサーバーでは約5秒かかります。どちらの場合も、それほど速くはありません。それでも、それはかなり恐ろしいので、時間を大幅に短縮できると想像できます。それを達成する方法についてのポインタをいただければ幸いです。PHPにはタプルがないため、座標の操作がおかしくなり、何が起こっているのかを理解できなかったため、まったく役に立ちませんでした。

編集:いくつかの修正により、ローカルで1秒未満かかります。

于 2009-04-16T17:20:12.153 に答える
11

問題文を見た瞬間「トライ」と思いました。しかし、他のいくつかのポスターがそのアプローチを利用しているのを見て、私は別のアプローチを探しました。残念ながら、Trie アプローチの方がパフォーマンスが優れています。Kent の Perl ソリューションを自分のマシンで実行したところ、辞書ファイルを使用するように調整した後、実行に0.31秒かかりました。私自身の perl 実装では、実行に0.54秒かかりました。

これが私のアプローチでした:

  1. 遷移ハッシュを作成して、正当な遷移をモデル化します。

  2. 16^3 の可能な 3 文字の組み合わせすべてを反復処理します。

    • ループ内で、不正な遷移を除外し、同じ広場への訪問を繰り返します。すべての正当な 3 文字シーケンスを形成し、それらをハッシュに格納します。
  3. 次に、辞書内のすべての単語をループします。

    • 長すぎるまたは短すぎる単語を除外する
    • 各単語の上に 3 文字のウィンドウをスライドさせ、ステップ 2 の 3 文字の組み合わせの中にあるかどうかを確認します。失敗した単語を除外します。これにより、ほとんどの不一致が排除されます。
    • それでも解消されない場合は、再帰アルゴリズムを使用して、パズルを通るパスを作成することで単語を形成できるかどうかを確認します。(この部分は遅いですが、あまり呼び出されません。)
  4. 見つけた単語を印刷します。

    3 文字と 4 文字のシーケンスを試しましたが、4 文字のシーケンスではプログラムの速度が低下しました。

私のコードでは、辞書に /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.
}
于 2009-06-26T05:37:53.167 に答える
9

私は3か月かけて、密集した5x5ボグルボードのベスト10の問題の解決策に取り組みました。

この問題は現在解決されており、5つのWebページに完全に開示されています。ご不明な点がございましたらご連絡ください。

ボード分析アルゴリズムは、明示的なスタックを使用して、直接の子情報とタイムスタンプ追跡メカニズムを備えた有向非巡回ワードグラフを介してボードの正方形を疑似再帰的にトラバースします。これは、世界で最も高度なレキシコンデータ構造である可能性があります。

このスキームは、クアッドコアで1秒あたり約10,000の非常に優れたボードを評価します。(9500以上のポイント)

親Webページ:

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

コンポーネントWebページ:

最適なスコアボード-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

-この包括的な一連の作業は、最高のものを要求する人にのみ関心があります。

于 2009-11-19T07:06:12.983 に答える
9

私は非常に遅れていることを知っていますが、PHPで少し前にこれらの1つを作成しました-ただの楽しみでもあります...

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

于 2009-10-07T12:59:10.623 に答える
4

完全な解決策についてもっと考えなければなりませんが、便利な最適化として、すべての辞書からの単語、そしてこれを使用して検索に優先順位を付けます。私は単語の最初の文字で行きます。したがって、辞書に「India」、「Water」、「Extreme」、および「Extraordinary」という単語が含まれている場合、事前に計算されたテーブルは次のようになります。

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

次に、これらの図を共通の順序で検索します(最初にEX、次にWA / IN)

于 2009-04-14T02:20:32.523 に答える
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.

これは、少し動的計画法でスピードアップすることができます。たとえば、サンプルでは、​​2つの「A」は両方とも「E」と「W」の隣にあり、(それらが当たった時点から)同一になります。このためのコードを実際に説明するのに十分な時間がありませんが、アイデアを集めることができると思います。

また、「Boggleソルバー」をグーグルで検索すれば、他の解決策も見つかると思います。

于 2009-04-14T02:25:19.470 に答える
4

まず、C# 言語設計者の 1 人が関連する問題をどのように解決したかを読んでください: http://blogs.msdn.com/ericlippert/archive/2009/02/04/a-nasality-talisman-for-the-sultana-analyst.aspx .

彼のように、辞書から始めて、アルファベット順に並べ替えられた文字の配列から、それらの文字からつづることができる単語のリストまで辞書を作成することで、単語を正規化できます。

次に、ボードから可能な単語を作成し、それらを調べ始めます。これでかなりうまくいくと思いますが、物事をスピードアップするためのトリックが他にもあることは確かです。

于 2009-04-14T02:15:05.967 に答える
4

検索アルゴリズムは、検索を続けるにつれて単語リストを継続的に減らしますか?

たとえば、上記の検索では、単語を開始できる文字は 13 文字しかありません (事実上、開始文字数を半分に減らすことができます)。

文字の順列を追加すると、使用可能な単語セットがさらに減少し、必要な検索が減少します。

私はそこから始めます。

于 2009-04-14T02:16:28.133 に答える
4

楽しみのために、bash に 1 つ実装しました。超高速ではありませんが、妥当です。

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

于 2010-08-24T04:12:00.127 に答える
3

私はソルバーを C++ で書きました。カスタムツリー構造を実装しました。それがトライと見なされるかどうかはわかりませんが、似ています。各ノードには、アルファベットの各文字に 1 つずつ、合計 26 のブランチがあります。辞書の枝と並行してボグルボードの枝を横断します。ブランチが辞書にない場合は、Boggle ボードでの検索を停止します。ボード上のすべての文字を int に変換します。したがって、'A' = 0 です。単なる配列なので、ルックアップは常に O(1) です。各ノードは、単語を完成させるかどうか、およびその子に存在する単語の数を格納します。同じ単語を繰り返し検索することを減らす単語が見つかると、ツリーは刈り込まれます。刈り込みもO(1)だと思います。

CPU: Pentium SU2700 1.3GHz
RAM: 3GB

1 秒未満で 178,590 語の辞書を読み込みます。
100x100 Boggle (boggle.txt) を 4 秒で解決します。~44,000 語が見つかりました。
4x4 Boggle を解くのは速すぎて、意味のあるベンチマークを提供できません。:)

高速ボーグル ソルバー GitHub リポジトリ

于 2013-04-08T00:19:13.950 に答える
3

陽気な。同じクソゲームが原因で、数日前にほぼ同じ質問を投稿しました。しかし、Googleでboggle solver pythonを検索しただけで、必要なすべての回答が得られたので、そうしませんでした。

于 2009-04-14T03:53:51.207 に答える
3

この質問の時期が過ぎ去ったことに気づきましたが、私は自分でソルバーに取り組んでいて、グーグルで調べているときにこれに出くわしたので、他のいくつかとは少し違うように見えるので、私の参照を投稿する必要があると思いました.

私はゲーム ボードにフラット配列を使用し、ボード上の各文字から再帰的なハントを行い、有効な隣人から有効な隣人へとトラバースし、現在の文字のリストがインデックス内の有効なプレフィックスである場合はハントを拡張することにしました。現在の単語の概念をたどると、ボードへのインデックスのリストであり、単語を構成する文字ではありません。インデックスをチェックすると、インデックスが文字に変換され、チェックが完了します。

インデックスは、少しトライに似たブルート フォース ディクショナリですが、インデックスの Python クエリが可能です。「cat」と「cater」という単語がリストにある場合、辞書には次のように表示されます。

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

したがって、current_word が 'ca' の場合、'ca' in dTrue が返されるため、それが有効なプレフィックスであることがわかります (したがって、ボードのトラバーサルを続けます)。'cat' in d['cat']current_word が 'cat' の場合、これは有効なプレフィックスであり、 True も返すため、有効な単語であることがわかります。

これにより、遅すぎないように見える読み取り可能なコードが可能になると感じた場合。他のすべての人と同様に、このシステムの費用はインデックスの読み取り/構築です。ボードを解決することはかなりのノイズです。

コードはhttp://gist.github.com/268079にあります。たくさんの魔法やあいまいさで問題を作り上げることなく問題を理解したかったので、それは意図的に垂直的で単純であり、多くの明示的な有効性チェックを備えています。

于 2010-01-03T19:04:29.473 に答える
2

N 行 M 列の Boggle ボードがある場合、次のように仮定します。

  • N*M は可能な単語の数よりも大幅に大きい
  • N*M は、可能な最長の単語よりも大幅に大きい

これらの仮定の下で、このソリューションの複雑さは O(N*M) です。

この 1 つのサンプル ボードの実行時間をさまざまな点で比較するのは的外れだと思いますが、完全を期すために、このソリューションは最新の MacBook Pro で 0.2 秒未満で完了します。

このソリューションは、コーパス内の各単語のすべての可能なパスを見つけます。

#!/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}"
于 2015-03-24T06:54:00.210 に答える
1

パーティーに遅刻してしまいましたが、コーディングの練習として、いくつかのプログラミング言語 (C++、Java、Go、C#、Python、Ruby、JavaScript、Julia、Lua、PHP、Perl) でボーグル ソルバーを実装しました。誰かがそれらに興味を持っているかもしれないと思ったので、ここにリンクを残します: https://github.com/AmokHuginnsson/boggle-solvers

于 2016-05-03T17:48:27.117 に答える
1

Node.JS JavaScript ソリューション。辞書ファイルの読み取りを含め、100 個の一意の単語すべてを 1 秒未満で計算します (MBA 2012)。

出力:
["FAM"、"TUX"、"TUB"、"FAE"、"ELI"、"ELM"、"ELB"、"TWA"、"TWA"、"SAW"、"AMI"、"SWA" ,"SWA","AME","SEA","SEW","AES","AWL","AWE","SEA","AWA","MIX","MIL","AST"," ASE","MAX","MAE","MAW","MEW","AWE","MES","AWL","LIE","LIM","AWA","AES","BUT" ,"BLO","WAS","WAE","WEA","LEI","LEO","LOB","LOX","WEM","OIL","OLM","WEA"," WAE","WAX","WAF",「MILO」、「EAST」、「WAME」、「TWAS」、「TWAE」、「EMIL」、「WEAM」、「OIME」、「AXIL」、「WEST」、「TWAE」、「LIMB」、「WASE」 ","WAST","BLEO","STUB","BOIL","BOLE","LIME","SAWT","LIMA","MESA","MEWL","AXLE","FAME", 「ASEM」、「MILE」、「AMIL」、「SEAX」、「SEAM」、「SEMI」、「SWAM」、「AMBO」、「AMLI」、「AXILE」、「AMBLE」、「SWAMI」、「AWEST」 ","AWEST","LIMAX","LIMES","LIMBU","LIMBO","EMBOX","SEMBLE","EMBOLE","WAMBLE","FAMBLE"]EAST","WAME","TWAS","TWAE","EMIL","WEAM","OIME","AXIL","WEST","TWAE","LIMB","WASE","WAST" ,"BLEO","STUB","BOIL","BOLE","LIME","SAWT","LIMA","MESA","MEWL","AXLE","FAME","ASEM"," MILE","AMIL","SEAX","SEAM","SEMI","SWAM","AMBO","AMLI","AXILE","AMBLE","SWAMI","AWEST","AWEST" ,"LIMAX","LIMES","LIMBU","LIMBO","EMBOX","SEMBLE","EMBOLE","WAMBLE","FAMBLE"]EAST","WAME","TWAS","TWAE","EMIL","WEAM","OIME","AXIL","WEST","TWAE","LIMB","WASE","WAST" ,"BLEO","STUB","BOIL","BOLE","LIME","SAWT","LIMA","MESA","MEWL","AXLE","FAME","ASEM"," MILE","AMIL","SEAX","SEAM","SEMI","SWAM","AMBO","AMLI","AXILE","AMBLE","SWAMI","AWEST","AWEST" ,"LIMAX","LIMES","LIMBU","LIMBO","EMBOX","SEMBLE","EMBOLE","WAMBLE","FAMBLE"]"TWAE"、"EMIL"、"WEAM"、"OIME"、"AXIL"、"WEST"、"TWAE"、"LIMB"、"WASE"、"WAST"、"BLEO"、"STUB"、"BOIL ","BOLE","LIME","SAWT","LIMA","MESA","MEWL","AXLE","FAME","ASEM","MILE","AMIL","SEAX", 「SEAM」、「SEMI」、「SWAM」、「AMBO」、「AMLI」、「AXILE」、「AMBLE」、「SWAMI」、「AWEST」、「AWEST」、「LIMAX」、「LIMES」、「LIMBU」 ","LIMBO","EMBOX","SEMBLE","EMBOLE","WAMBLE","FAMBLE"]"TWAE"、"EMIL"、"WEAM"、"OIME"、"AXIL"、"WEST"、"TWAE"、"LIMB"、"WASE"、"WAST"、"BLEO"、"STUB"、"BOIL ","BOLE","LIME","SAWT","LIMA","MESA","MEWL","AXLE","FAME","ASEM","MILE","AMIL","SEAX", 「SEAM」、「SEMI」、「SWAM」、「AMBO」、「AMLI」、「AXILE」、「AMBLE」、「SWAMI」、「AWEST」、「AWEST」、「LIMAX」、「LIMES」、「LIMBU」 ","LIMBO","EMBOX","SEMBLE","EMBOLE","WAMBLE","FAMBLE"]「WEST」、「TWAE」、「LIMB」、「WASE」、「WAST」、「BLEO」、「STUB」、「BOIL」、「BOLE」、「LIME」、「SAWT」、「LIMA」、「MESA」 ","MEWL","AXLE","FAME","ASEM","MILE","AMIL","SEAX","SEAM","SEMI","SWAM","AMBO","AMLI", 「AXILE」、「AMBLE」、「SWAMI」、「AWEST」、「AWEST」、「LIMAX」、「LIMES」、「LIMBU」、「LIMBO」、「EMBOX」、「SEMBLE」、「EMBOLE」、「WAMBLE」 ","ファンブル"]「WEST」、「TWAE」、「LIMB」、「WASE」、「WAST」、「BLEO」、「STUB」、「BOIL」、「BOLE」、「LIME」、「SAWT」、「LIMA」、「MESA」 ","MEWL","AXLE","FAME","ASEM","MILE","AMIL","SEAX","SEAM","SEMI","SWAM","AMBO","AMLI", 「AXILE」、「AMBLE」、「SWAMI」、「AWEST」、「AWEST」、「LIMAX」、「LIMES」、「LIMBU」、「LIMBO」、「EMBOX」、「SEMBLE」、「EMBOLE」、「WAMBLE」 ","ファンブル"]SAWT"、"LIMA"、"MESA"、"MEWL"、"AXLE"、"FAME"、"ASEM"、"MILE"、"AMIL"、"SEAX"、"SEAM"、"SEMI"、"SWAM" ,"AMBO","AMLI","AXILE","AMBLE","SWAMI","AWEST","AWEST","LIMAX","LIMES","LIMBU","LIMBO","EMBOX"," SEMBLE","EMBOLE","WAMBLE","FAMBLE"]SAWT"、"LIMA"、"MESA"、"MEWL"、"AXLE"、"FAME"、"ASEM"、"MILE"、"AMIL"、"SEAX"、"SEAM"、"SEMI"、"SWAM" ,"AMBO","AMLI","AXILE","AMBLE","SWAMI","AWEST","AWEST","LIMAX","LIMES","LIMBU","LIMBO","EMBOX"," SEMBLE","EMBOLE","WAMBLE","FAMBLE"]LIMAX","LIMES","LIMBU","LIMBO","EMBOX","SEMBLE","EMBOLE","WAMBLE","FAMBLE"]LIMAX","LIMES","LIMBU","LIMBO","EMBOX","SEMBLE","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
})))
于 2014-10-16T07:06:26.720 に答える
1

OCaml でソリューションを実装しました。辞書をトライとして事前にコンパイルし、2 文字シーケンスの頻度を使用して、単語に決して現れないエッジを排除して、処理をさらに高速化します。

サンプル ボードを 0.35 ミリ秒で解決します (トライをメモリにロードするために 6 ミリ秒の追加の起動時間がかかります)。

見つかった解決策:

["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"]
于 2012-05-13T12:01:07.627 に答える
1

NLTK ツールキットで定義済みの単語を使用するソリューションは次のとおりです。

マトリックスを作成したら、それを文字配列に変換し、このコードを実行します

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

私はあなたがそれを得る願っています。

于 2018-06-19T12:21:41.077 に答える
1

誰もが PHP を愛しているので、これを解決する別の PHP の方法を追加したかったのです。辞書ファイルに対して正規表現の一致を使用するなど、やりたいリファクタリングが少しありますが、今は辞書ファイル全体を wordList にロードしているだけです。

リンクされたリストのアイデアを使用してこれを行いました。各ノードには、文字値、位置値、および次のポインターがあります。

場所の値は、2 つのノードが接続されているかどうかを確認する方法です。

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

したがって、そのグリッドを使用すると、最初のノードの位置が 2 番目のノードの位置と同じ行の +/- 1、上下の行の +/- 9、10、11 に等しい場合、2 つのノードが接続されていることがわかります。

主な検索には再帰を使用します。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);

    ?>
于 2015-08-20T00:11:07.957 に答える
0

私はこれを完全かつ非常に迅速に解決しました。Androidアプリに入れました。Play ストアのリンクでビデオを表示して、実際の動作を確認してください。

Word Cheats は、マトリックス スタイルの単語ゲームを「クラック」するアプリです。このアプリは、単語スクランブラーでごまかすのに役立つように作成されました。単語検索、ラズル、単語、単語ファインダー、単語クラック、ボグルなどに使用できます。

ここで見ることができます https://play.google.com/store/apps/details?id=com.harris.wordcracker

動画でアプリの動作を ご覧ください https://www.youtube.com/watch?v=DL2974WmNAI

于 2013-03-08T03:17:34.593 に答える
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;
    }
}
于 2012-11-19T13:36:29.557 に答える
0

私はこれをcで解決しました。私のマシンでの実行には約 48 ミリ秒かかります (約 98% の時間は、ディスクからの辞書のロードとトライの作成に費やされます)。辞書は /usr/share/dict/american-english で、62886 語あります。

ソースコード

于 2012-12-10T22:56:06.300 に答える
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)]
于 2014-01-06T19:30:23.473 に答える
0

DFA アルゴリズムを使用して、C# でこれを解決しました。あなたはで私のコードをチェックアウトすることができます

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

マトリックス内の単語を見つけることに加えて、私のアルゴリズムは単語の実際のパスを保存するため、単語検索ゲームを設計するために、実際のパスに単語があるかどうかを確認できます。

于 2013-09-23T07:49:19.377 に答える
0

これが私のJava実装です: https://github.com/zouzhile/interview/blob/master/src/com/interview/algorithms/tree/BoggleSolver.java

Trie ビルドにかかった時間は 0 時間 0 分 1 秒 532 ミリ秒
単語検索にかかった時間は 0 時間 0 分 0 秒 92 ミリ秒

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

注: このスレッドの冒頭で辞書と文字マトリックスを使用しました。コードは私の MacBookPro で実行されました。以下はマシンに関する情報です。

モデル名: MacBook Pro
モデル識別子: MacBookPro8,1
プロセッサ名: Intel Core i5
プロセッサ速度: 2.3 GHz
プロセッサの数: 1
コアの総数: 2
L2 キャッシュ (コアあたり): 256 KB
L3 キャッシュ: 3 MB
メモリ: 4 GB
ブート ROM バージョン: MBP81.0047.B0E
SMC バージョン (システム): 1.68f96

于 2012-07-28T06:34:07.797 に答える