文字マトリックスから可能な単語のリストを見つける方法[Boggle Solver]


376

最近、iPhoneでScrambleと呼ばれるゲームをプレイしています。このゲームをBoggleと知っている方もいるかもしれません。基本的に、ゲームが開始すると、次のような文字のマトリックスが得られます。

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

ゲームの目標は、文字をつなげて形成できる単語をできるだけ多く見つけることです。任意の文字で開始でき、その文字を囲むすべての文字は公平なゲームです。次の文字に移動すると、以前に使用された文字を除いて、その文字を囲むすべての文字が公平なゲームになります。したがって、上記のグリッドでは、例えば、私は言葉を思い付くことができLOBTUXSEAFAME、などの言葉は、このゲームでは16になるだろうが、いくつかの実装が異なることができ、それ以上のN×Nの文字より少なくとも3つの文字、あってはなりません。このゲームは楽しさと中毒性がありますが、私は明らかにそれがあまり得意ではないので、可能な限り最高の単語を与えるプログラム(単語が長いほどポイントが多くなります)を作成することで少し浮気したいと思いました。

サンプルボグル
(ソース:boggled.org

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

Stackoverflowersがより効率的なソリューションを考え出せるかどうかに興味があります。私は主にBig 3 Ps:Python、PHP、Perlを使用したソリューションを探していますが、JavaまたはC ++を使用するものもクールです。

現在のソリューション

  • アダム・ローゼンフィールド、パイソン、〜20代
  • ジョン・フーイ、パイソン、〜3秒
  • ケントフレドリック、Perl、〜1秒
  • ダライアスベーコン、パイソン、〜1秒
  • rvarcher、VB.NET (ライブリンク)、〜1s
  • Paolo Bergantino、PHP (ライブリンク)、〜5秒(ローカルでは〜2秒)

18
機能リクエストMOAR PUZZLES
ケントフレドリック

6
タイミングに関して:私の解決策では、ほとんどすべての時間がトライの構築に費やされています。トライを構築すると、何度でも再利用できます。パズルを1つだけ解く場合は、より単純なデータ構造(すべての単語とすべての接頭辞のセットなど)を使用する方が効率的です。
Adam Rosenfield、

3
また、Adam'sにはより大きな辞書があり、彼のソリューションが使用するより長い単語の数によって証明されています。これらはすべて、共通の辞書に基づいてテストする必要があります。
リッチブラッドショー、

2
ボグルをたくさん演じる人はいないと思いますか?"Qu"は "手紙"の1つであり、ソリューションがその細部をどれだけ捉えたかはわかりません。それらのいくつかは、他の問題の中でも特に、「u」を独立して使用できるように見えます。
Qsario 2012

2
私は最近これをインタビューの質問として持っていて、細部にうまく行き詰まりました。私はそれをグラフの問題として扱っていましたが、問題ありませんが、ここでの解決策ははるかに少ないスペースを使用します。私は今、独自のソリューションをコーディングしています。貢献したすべての人によくできました!
Peter Friend

回答:


143

私の答えはここでは他の人と同じように機能しますが、辞書の設定が速くなるため、他の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で同じことを行うと、速度が約2倍になります。


プログラムは私に1ワードだけを与えています。どうして?
Paolo Bergantino、

出力に溺れたくありませんでした。下部のコメントを参照してください。
ダライアスベーコン

6
またはパスなしですべての単語を取得します。print「」.join(解決()))で(ワード、パス)について(ソートセット(単語)
ダリウス・ベーコン

2
多くの時間は、辞書の解析に費やされます。これを事前に解析して、各単語が要素となっている単なるリストである「wordlines.py」ファイルを作成しました。これは.pyファイルであるため、.pycファイルに変換されます。それで、read()。splitlines()の代わりにそれをインポートします。それで、私の箱で、私はそれを約10分の1秒で解決しています。
Sean Reifschneider、2010年

1
@shellscape、それはPython 2コードです。Python 3では、def neighbors((x, y))(無意味に、私の知る限りでは)のように、引数を分解する機能が削除されました。また、への引数を括弧で囲む必要がありますprint
ダライアスベーコン

116

あなたが得ようとしている最速の解決策はおそらくあなたの辞書をトライに保存することを含むでしょう。次に、トリプレット(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」、「tub」 、「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」のような単語が辞書にある場合)、実行時間は恐ろしく指数関数的になります。 。重複が除外され、並べ替えられるのは、アルゴリズムが終了した後のことです。


14
ああ。誰かがお皿に上がってよかった。これは機能しますが、すでに使用されている文字を「記憶」しておらず、同じ文字を2回使用する必要があるという言葉は考えられません。私はばかなので、どうすれば修正できますか?
Paolo Bergantino、

3
確かに、アクセスされた文字は記憶されていませんが、仕様では指定されていません=)。これを修正するには、訪問したすべての場所のリストを各キューデータに追加し、次の文字を追加する前にそのリストを確認する必要があります。
アダム・ローゼンフィールド、

いいえ、BoggleWords()の中では。4つ組(x、y、s、n)を保存する代わりに、5つ組(x、y、s、n、l)を保存します。ここで、lはこれまでにアクセスした(x、y)のリストです。次に、各(x2、y2)をlに対してチェックし、lにない場合にのみ受け入れます。次に、それを新しいlに追加します。
アダム・ローゼンフィールド

2
私もスクランブルをプレイするのに飽きたときにこれをしました。アクティブセルのセットを保持できるため(同じセルに2回アクセスしないように)、再帰(BFSではなくDFS)ソリューションの方がセクシーだと思います。その後、多くのリストを保持することにより、よりきちんと整理されます。
Justin Scheiner、

2
これは無限ループに陥るべきではありませんか?つまり、たとえば(x,y)、可能なフォロワーは(x+1,y+1)です。その後(x+1,y+1)、キューにプッシュされます。しかし、(x,y)あまりにものフォロワーに(x+1,y+1)なるので、それらの間で終わりのない跳ね返りにつながるのではないでしょうか?
SexyBeast

39

辞書の高速化のために、事前に辞書の比較を大幅に削減するために実行できる一般的な変換/プロセスがあります。

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

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

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

Perlの実装

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

また、理論的には穴のあるグリッドが機能するようにするいくつかの適応策と、さまざまなサイズのラインを持つグリッドがあります(入力が正しくなり、何らかの方法でラインアップすると仮定します)。

以前に疑われたように、早期のフィルターは、アプリケーションでこれまでで最も重要なボトルネックであり、1.5行から7.5秒にラインが膨らむとコメントしています。

実行すると、すべての1桁が独自の有効な単語上にあると思われますが、辞書ファイルの機能により、これはかなり確かです。

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

その一部は既存の実装に部分的に影響を受けており、一部はすでに考えていました。

建設的な批判と、それは歓迎を改善することができた方法(/私は、彼は決してノート尻込みソルバのために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分。


2
最適化クラブで失敗していることはわかっていますが、コードの実際の作業に入る前に速度の問題があり、入力時間を2秒から1.2秒に短縮することは私にとって大きな意味があります。
ケントフレドリック

/ meは奇妙なことに、ハッシュにキーを追加するよりも、正規表現とエントリのスキップにかかる時間が少なくなりました。
ケントフレドリック

Perlの実装です。すぐに実行します。
Paolo Bergantino、

Blerg、私のWebサーバーにTree :: Trieをインストールするのに苦労しました。:(
パオロベルガンティーノ

3
最後のレポート(arch / execution info)をどのように生成しましたか?便利そうです。
jmanning2k 2009

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)]、位置から到達できる座標のセットです(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. これまでにパスの一部ではないすべての子供を探索します。

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

コードは(文字通り)ディクショナリをロードするのに数秒かかりますが、残りは私のマシンで即座に実行されます。


非常に素晴らしい!とても速い。他の誰かがプレートに足を踏み入れるかどうかを確認するまで待ちますが、あなたの答えは今のところ見事です。
Paolo Bergantino、

「embole」があなたの唯一の6文字の単語である理由がわかりません。そのために10種類の単語を用意しました。同じノードに2回アクセスすることを禁止しているようです。OPが述べたように、それは公正なゲームです。
ケントフレドリック

1
わかりました。キャラクターを共有しない「FAMBLE」、「WAMBLE」、「SEMBLE」を破棄しているため、バグが発生する可能性があります。
ケントフレドリック

よくわかりました!バグは接頭辞セットの作成にありました:のrange(len(w)+1)代わりに使用する必要がありましたrange(len(w))。私はそれを主張しましたwords <= prefixesが、どうやら私はそれをテストしませんでした:-/
John Fouhy

1
これは、DFSのしくみとDFSの実装方法を学ぶのに役立ちました。コメント以外に感謝の気持ちを表す方法がわからなかった。ありがとう!
Graham Smith

23

私のJavaでの試み。ファイルを読み取ってトライを構築するのに約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;
    }
}

1
私の出力を他のStackOverflowersの出力と比較していましたが、Adam、John、およびrvarcherの出力にはいくつかの単語が欠けているようです。たとえば、「Mwa」は辞書にあります(ええ!)が、Adam、John、およびrvarcherからの出力では返されません。PaoloのPHPリンクで2回返されます。
gineer 2009

1
コピー貼り付けしてみました。「読み中...」「読み終わり...」と書いてありますが、その後は何も表示されません。一致するものは表示されません。
MikkoP

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

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

たとえば、「helmet」という単語は、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つ減らすことができます。ちなみに、グリッドには3文字の移動が400回しかないため、このテーブルを作成するのは非常に迅速です。

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

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

この方法を使用すると、辞書が事前に計算され、メモリに既に読み込まれている場合、コードを非常に高速に実行できます。

ところで、ゲームを構築している場合に行うもう1つの良いことは、このようなことをバックグラウンドですぐに実行することです。ユーザーがアプリのタイトル画面を見ている間に、最初のゲームの生成と解決を開始し、指で「再生」を押す位置に移動します。次に、ユーザーが前のゲームをプレイするときに、次のゲームを生成して解決します。これにより、コードを実行するのに多くの時間が与えられます。

(私はこの問題が好きなので、実際にどのように機能するかを確認するために、今後数日のうちにJavaで自分の提案を実装したいと思うでしょう...実行したら、ここにコードを投稿します。)

更新:

OK、今日は少し時間があり、このアイデアを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倍遅いです。これが異なるアルゴリズム、彼が使用したより短い辞書、彼のコードがコンパイルされて私の仮想マシンで実行されるという事実、または私たちのコンピューターのパフォーマンス(私のものはWinXPを実行するIntel Q6600 @ 2.4MHz)が原因であるかどうかに関係なく、知りません。しかし、元の質問の最後に引用した他の実装の結果よりもはるかに高速です。したがって、このアルゴリズムがトライ辞書より優れているかどうかは、現時点ではわかりません。

  • で使用されているテーブル方式checkWordTriplets()は、実際の回答を非常によく近似します。わずか1 3-5の言葉は、それが失敗しますから渡されたcheckWords()テスト(参照の候補の数実際のワード数以上を)。

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

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

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

  • 上記のcheckWords()実装は、私が思いついた最も馬鹿げた最初のバージョンです。基本的にはまったく最適化されていません。しかし、それと比較するcheckWordTriplets()と、アプリケーションの全体的なパフォーマンスには無関係なので、心配する必要はありませんでした。しかし、ボードのサイズが大きくなると、この関数はどんどん遅くなり、やがて重要になります。次に、それも最適化する必要があります。

  • このコードのすばらしい点の1つは、その柔軟性です。

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

わかりましたが、この投稿はもう十分長いと思います。私は間違いなくあなたの質問に答えることができますが、それをコメントに移しましょう。


いい答えだ。Javaでの実装を確認してください。
MikkoP

@MikkoP完了!:)約3時間と220行のコードを要しました。午後を通過する良い方法。それがどのように機能するかについて質問がある場合は私に知らせてください... :)
Markus A.

コードを投稿していただきありがとうございます。足りないインポートを追加した後、自分の辞書で試してみました。行にArrayIndexOutOfBoundExceptionが表示されok = possibleTriplets[entry.triplets[t]];ます。うーん?
MikkoP

@MikkoPこのコードは現在、辞書に大文字のAZのみが含まれていることを前提としています。cruxは34行目にentry.letters[i] = (byte) letter - 65;あります。ASCII値を取り、65( "A")を減算します。辞書にウムラウトまたは小文字がある場合、これは31より大きい値になります。これは、9行目のアルファベットサイズの設定では計画されていません。他の文字をサポートするには、この行を展開する必要がありますそれらをアルファベットサイズで許可された範囲にマップします。
Markus A.

1
@AlexanderNロジックを正しく理解していると思います。レターグリッドのコピーを間違えました...すみません...(修正済み)

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

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

編集:いくつかの修正により、ローカルでの所要時間が1秒未満になりました。


+1 @「そして、地獄が何が起こっているのかを理解することができなかったので、まったく役に立ちませんでした。」笑。私は正直が大好きです!
dna123 2009

PHPはわかりませんが、最初に試してみたいのは '/ ['を巻き上げることです。implode( ''、$ alphabet)。'] {3、} $ /'はループの外です。つまり、変数をそれに設定し、代わりにループ内で変数を使用します。
ダライアスベーコン

PHPはコンパイルされた正規表現のスレッドごとのグローバルキャッシュを保持していると確信していますが、とにかくそれを試します。
Paolo Bergantino、

1
@ダニエル:どうやらそれは私のウェブサーバーです。ローカルで実行する場合は発生しません。肩をすくめる。それを追い詰めるような気がしないでください。
Paolo Bergantino、

2
最後にfind_words関数の7.パラメータとして何を設定する必要がありますか?
MikkoP

16

VBに興味がない?:)私は抵抗することができませんでした。これは、ここで紹介する多くのソリューションとは異なる方法で解決しました。

私の時間は:

  • 辞書と単語の接頭辞をハッシュテーブルに読み込む:.5〜1秒。
  • 単語の検索:10ミリ秒未満の平均。

編集:Webホストサーバーでの辞書の読み込み時間は、自宅のコンピューターよりも1〜1.5秒長く実行されています。

サーバーの負荷によって時間がどれほどひどく悪化するのかはわかりません。

ソリューションを.NetのWebページとして記述しました。myvrad.com/boggle

元の質問で参照されている辞書を使用しています。

つまり、文字は再利用されません。3文字以上の単語のみが見つかります。

トライの代わりに、すべての一意の単語の接頭辞と単語のハッシュテーブルを使用しています。トライについては知らなかったので、そこで何かを学びました。完全な単語に加えて単語の接頭辞のリストを作成するという考えは、ようやく私の時間をかなりの数にしたものです。

詳細については、コードのコメントを参照してください。

コードは次のとおりです。

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

ここでは、[x] [y]の代わりにapシステムを使用したと仮定します。後者はVBではかなり複雑だからです。私はその日に双方向動的配列を取得しようと1日を費やしました、すなわち:array(array(1、 "hello")、1、 "hello"、array())、まだ方法がわかりませんthat:P
ケントフレドリック

PHPとPerl 2では、dim配列は楽しいです。これはVBで実行できますが、楽しいプロセスとは言えません。Dim Arr(、)As Integer = {{1,1}、{0,0}}。APプロセスは、自分自身をグリッドに配置して、「ここからどこに行けばいいのですか?」私はそれが厳密な解決策であることを知っていますが、それはここで機能します。
rvarcher 2009

ああ、私はVB.NETが好きです...私はURLを試しましたが、うまくいきませんでした。私は自分のコードをWindowsフォームとして自分で再構築する必要があり、それが機能しました。ありがとう。
Ahmed Eissa

11

問題の記述を見るとすぐに、「トリー」と思った。しかし、他のいくつかのポスターがそのアプローチを利用しているのを見て、私は違うだけの別のアプローチを探しました。悲しいかな、トライのアプローチの方がパフォーマンスが優れています。私は自分のマシンで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.
}

辞書の場所は変わりましたか?私のソリューションをすべての人と比較したいので、辞書の単語を見つけようとしましたが、/ usr / share / dictにあるリンクでそれを見つけることができませんでした。私はそれがかなり古いスレッドであることを知っていますが、あなたが私を指すことができればそれは素晴らしいです。よろしくお願いします。
ナマン

現在、Macを手元に置いておきません。必要なのは、改行で区切られた、1行に1つの英語の単語を含むファイルです。あなたはインターネットでそのようなファイルを見つけるかもしれません。1つはここにあります:mieliestronk.com/corncob_lowercase.txt ですが、おそらくそれ以上の単語のリストがあります。
Paul Chernoch

返信ありがとうございます。私はそれをubuntuファイルで見つけました。
ナマン

9

私は非常に遅いことを知っていますが、PHPでしばらく前にこれらの1つを作成しました-ちょうど楽しみのためにも...

http://www.lostsockdesign.com.au/sandbox/boggle/index.php?letters=fxieamloewbxastu 0.90108秒で 75ワード(133 pts)見つかりました

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

プログラムが実際に何をしているかを示します。各文字は、各「。」の間にパターンを調べ始める場所です。試行したパスを示します。もっと '。' さらに検索しました。

コードが必要な場合はお知らせください... PHPとHTMLの恐ろしい組み合わせであり、日の目を見ることを意図したものではなかったので、ここでは投稿しません:P


9

私は3か月かけて、10ポイントの高密度5x5ボグルボードの問題の解決に取り組んだ。

この問題は解決され、5つのWebページに完全に開示されます。質問があれば私に連絡してください。

ボード分析アルゴリズムは、明示的なスタックを使用して、直接の子情報を含む有向非循環ワードグラフとタイムスタンプ追跡メカニズムを介して、ボードの正方形を疑似再帰的にトラバースします。これは非常によく、世界で最も先進的な辞書データ構造です。

このスキームは、クアッドコアで毎秒約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

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


4
あなたの分析は興味深いですが、結果は技術的にはBoggleボードではありません。5x5ボグルゲームには、フェースBJKQXZを含む1つのダイが含まれています。実装では、これらの文字をすべて明示的に除外しているため、実際のボグルゲームではボードの位置は実際には不可能です。
MarkPflug 2012年

4

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

たとえば、上記の検索では、単語の先頭に使用できるのは13文字だけです(事実上、開始文字の半分の数に削減されます)。

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

そこから始めます。


4

完全な解決策についてもっと考えなければなりませんが、便利な最適化として、すべてに基づいてダイグラムとトリグラム(2文字と3文字の組み合わせ)の頻度の表を事前に計算する価値があるかどうか疑問に思います辞書から単語を抽出し、これを使用して検索に優先順位を付けます。私は言葉の最初の文字で行きます。したがって、辞書に「インド」、「水」、「極端」、および「異常」という単語が含まれている場合、事前計算されたテーブルは次のようになります。

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

次に、これらのダイグラムを共通性の順序で検索します(最初のEX、次にWA / IN)。


4

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

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

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


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'の隣にあり、(それらがヒットした点から)同一です。このためのコードを実際に説明する時間はありませんが、アイデアを集めることはできます。

また、Googleで「Boggle solver」を検索すれば、他の解決策も見つかるはずです。



3

陽気な、こっけいな。同じゲームが原因で、数日前に同じ質問を投稿しました!ただし、Googleでboggle solver pythonを検索して、必要なすべての回答を得ただけでした。


「boggle」という通称は知らなかったが、グーグルで何か見つけたので、SOで人々が何を思いつくか知りたくてたまらなかった。:)
パオロベルガンティーノ

3

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

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

インデックスは、ちょっとしたトライのようなブルー​​トフォース辞書ですが、インデックスのPythonicクエリを可能にします。「cat」と「cater」という単語がリストにある場合、辞書でこれを取得します。

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

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

このように感じられた場合、あまり遅くないと思われるいくつかの読み取り可能なコードが許可されました。他の皆と同じように、このシステムの費用はインデックスの読み取り/構築です。ボードを解くことはかなりのノイズです。

コードはhttp://gist.github.com/268079にあります。それは意図的に垂直で、多くの明示的な有効性チェックでナイーブです。なぜなら、私はたくさんの魔法や不明瞭さで問題を解決することなく問題を理解したかったからです。


3

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

CPU:Pentium SU2700 1.3GHz
RAM:3GB

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

Fast Boggle Solver GitHubリポジトリ


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

2

このソリューションは、指定されたボードで検索する方向も提供します

アルゴ:

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)

1

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

それはあなたのサンプルボードを0.35msで解決します(トライをメモリにロードすることに主に関連する追加の6msの起動時間を伴います)。

見つかったソリューション:

["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"]

これはすばらしいことですが、ここに掲載されているすべての時間には、辞書をメモリにロードするための「起動」時間が含まれているため、0.35を他の時間と比較することは正確とはほど遠いものです。また、別の辞書を使用していますか?いくつかの単語が欠けています。いずれにしても、+ 1
パオロベルガンティーノ

起動時間は6ミリ秒かかるため、完全な実行には6.35ミリ秒と表示されます。私は自分のローカル/usr/share/dict辞書を使用していますが、一部の単語が実際にありません(EMBOLEなど)。
Victor Nicollet、2012年

1

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

出力:
["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」 "、" 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」 "、" 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 "]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
})))

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

    ?>

1

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


1

これは、NLTKツールキットで事前定義された単語を使用するソリューションです。NLTKにはnltk.corpusパッケージがあり、wordsというパッケージがあり、2Lakhsを超える英語の単語が含まれているため、すべてをプログラムで簡単に使用できます。

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

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

よろしくお願いします。


0

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

トライの作成にかかった時間は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


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

0

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

ソースコード


0

私はこれを完全かつ非常に速く解決しました。私はそれをアンドロイドアプリに入れました。実際の動作を確認するには、Playストアのリンクでビデオをご覧ください。

Word Cheatsは、マトリックススタイルの単語ゲームを「クラック」するアプリです。このアプリは、単語スクランブラーを騙すのを助けるために作られました。単語検索、パズル、単語、単語ファインダー、単語クラック、ボグルなどに使用できます!

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

動作中のアプリをビデオで見る https://www.youtube.com/watch?v=DL2974WmNAI

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.