Come trovare un elenco di parole possibili da una matrice di lettere [Risolutore di Boggle]


376

Ultimamente sto giocando a un gioco sul mio iPhone chiamato Scramble. Alcuni di voi potrebbero conoscere questo gioco come Boggle. In sostanza, quando il gioco inizia ottieni una matrice di lettere in questo modo:

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

L'obiettivo del gioco è trovare quante più parole possibili che possono essere formate concatenando le lettere insieme. Puoi iniziare con qualsiasi lettera e tutte le lettere che la circondano sono un gioco leale, quindi una volta che passi alla lettera successiva, tutte le lettere che circondano quella lettera sono un gioco leale, ad eccezione delle lettere utilizzate in precedenza . Così nella griglia sopra, per esempio, ho potuto venire con le parole LOB, TUX, SEA, FAME, ecc parole devono essere almeno 3 caratteri e non più di caratteri NxN, che sarebbe 16 in questo gioco, ma può variare in alcune implementazioni . Mentre questo gioco è divertente e avvincente, apparentemente non sono molto bravo in questo e volevo imbrogliare un po 'creando un programma che mi avrebbe dato le migliori parole possibili (più lunga è la parola più punti ottieni).

Campione Boggle
(fonte: boggled.org )

Purtroppo, non sono molto bravo con gli algoritmi o le loro efficienze e così via. Il mio primo tentativo usa un dizionario come questo (~ 2.3 MB) e fa una ricerca lineare cercando di abbinare le combinazioni con le voci del dizionario. Questo richiede molto tempo per trovare le parole possibili e dato che ottieni solo 2 minuti per round, semplicemente non è adeguato.

Sono interessato a vedere se qualche Stackoverflower può trovare soluzioni più efficienti. Cerco principalmente soluzioni con Big 3 Ps: Python, PHP e Perl, anche se qualsiasi cosa con Java o C ++ è interessante, poiché la velocità è essenziale.

SOLUZIONI ATTUALI :

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

18
richiesta di funzione MOAR PUZZLES
Kent Fredric

6
Per quanto riguarda i tempi: nella mia soluzione, praticamente tutto il tempo è dedicato alla costruzione del trie. Una volta creato, il trie può essere riutilizzato più volte. Se solo risolvendo un puzzle, sarebbe più efficiente utilizzare una struttura di dati più semplice (come un insieme di tutte le parole e tutti i prefissi).
Adam Rosenfield,

3
Inoltre, Adam ha un dizionario più ampio, evidenziato dal numero di parole più lunghe utilizzate dalla sua soluzione. Dovrebbero essere tutti testati sulla base di un dizionario comune.
Rich Bradshaw,

2
Immagino che nessuno suoni molto Boggle? "Qu" è una "lettera" e non sono sicuro di quante soluzioni abbiano colto quel piccolo dettaglio. Sembra che alcuni di loro ti permetterebbero di usare la "u" in modo indipendente, tra gli altri problemi.
Qsario,

2
Recentemente ho avuto questo come una domanda di intervista e sono rimasto ben bloccato nei dettagli. Lo stavo trattando come un problema grafico, il che va bene, ma le soluzioni qui usano molto meno spazio. Sto codificando la mia soluzione ora. Complimenti a tutti coloro che hanno contribuito!
Peter Friend

Risposte:


143

La mia risposta funziona come le altre qui, ma la posterò perché sembra un po 'più veloce rispetto alle altre soluzioni Python, dall'impostazione del dizionario più veloce. (Ho verificato questo contro la soluzione di John Fouhy.) Dopo l'installazione, il tempo per risolvere è basso nel rumore.

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)

Esempio di utilizzo:

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

Modifica: filtra le parole lunghe meno di 3 lettere.

Modifica 2: Ero curioso di sapere perché la soluzione Perl di Kent Fredric fosse più veloce; si scopre di usare la corrispondenza di espressioni regolari invece di un insieme di caratteri. Fare lo stesso in Python per raddoppiare la velocità.


Il programma mi sta dando solo 1 parola. Come mai?
Paolo Bergantino,

Non volevo affogare in uscita. Vedi il commento in fondo.
Darius Bacon,

6
O ottieni tutte le parole senza i percorsi: stampa '' .join (ordinato (imposta (parola per (parola, percorso) in risolve ())))
Darius Bacon

2
Gran parte del tempo viene impiegato per analizzare il dizionario. L'ho pre-analizzato in un file "wordlines.py" che è solo un elenco in cui ogni parola è un elemento. Poiché è un file .py, verrà trasformato in un file .pyc. Quindi faccio un'importazione di quello invece di read (). Splitlines (). Detto questo, sulla mia scatola, lo risolverò in circa un decimo di secondo.
Sean Reifschneider,

1
@shellscape, è il codice Python 2. Python 3 ha abbandonato la capacità di decostruire argomenti, come def neighbors((x, y))(inutilmente, per quanto posso vedere). Inoltre richiede parentesi attorno all'argomento print.
Darius Bacon,

116

La soluzione più veloce che otterrai sarà probabilmente la memorizzazione del tuo dizionario in un trie . Quindi, crea una coda di terzine ( x , y , s ), in cui ciascun elemento nella coda corrisponde a un prefisso s di una parola che può essere scritto nella griglia, terminando nella posizione ( x , y ). Inizializza la coda con N x N elementi (dove N è la dimensione della griglia), un elemento per ogni quadrato nella griglia. Quindi, l'algoritmo procede come segue:

Mentre la coda non è vuota:
  Dequeue una tripla (x, y, s)
  Per ogni quadrato (x ', y') con la lettera c adiacente a (x, y):
    Se s + c è una parola, genera s + c
    Se s + c è un prefisso di una parola, inserire (x ', y', s + c) nella coda

Se si memorizza il dizionario in un trie, è possibile verificare se s + c è una parola o un prefisso di una parola in tempo costante (purché si mantengano anche alcuni metadati extra in ogni dato della coda, come un puntatore al nodo corrente nel trie), quindi il tempo di esecuzione di questo algoritmo è O (numero di parole che possono essere scritte).

[Modifica] Ecco un'implementazione in Python che ho appena codificato:

#!/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

Esempio di utilizzo:

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

Produzione:

['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 ',' wait ',' soggezione ',' awa ',' mix ',' mim ',' mil ' , "mamma", "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", "ma", "ast", "ase", "asa", " awl ',' wait ',' awe ',' wait ',' aes ',' swa ',' swa ',' sew ',' sea ',' sea ',' saw ',' tux ',' tub ' , "tut", "twa", "twa", "tst", "utu", "fama", "fama", "ixil", "imam", "amli", "amil", "ambo", " axil "," axle "," mimi "," mima "," mime "," milo ","miglio ',' mewl ',' mese ',' mesa ',' lolo ',' lobo ',' lima ',' lime ',' arto ',' lile ',' oime ',' oleo ',' olio ' , "oboe", "obol", "emim", "emil", "est", "facilità", "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 ']

Note: Questo programma non genera parole di 1 lettera, né filtra per lunghezza di parole. È facile da aggiungere ma non molto rilevante per il problema. Emette anche alcune parole più volte se possono essere scritte in più modi. Se una determinata parola può essere scritta in molti modi diversi (caso peggiore: ogni lettera nella griglia è la stessa (ad es. 'A') e una parola come 'aaaaaaaaaa' è nel tuo dizionario), il tempo di esecuzione diventerà orribilmente esponenziale . Filtrare i duplicati e l'ordinamento è banale a causa della fine dell'algoritmo.


14
Ooo. Sono contento che qualcuno sia salito sul piatto. Sebbene funzioni, non "ricorda" la lettera che ha già usato e produce parole che richiederebbero l'uso della stessa lettera due volte, cosa non consentita. Dato che sono un idiota, come potrei fare per risolverlo?
Paolo Bergantino,

3
È vero, non ricorda quali lettere sono state visitate, ma ciò non è stato specificato nella tua specifica =). Per risolvere questo problema, dovresti aggiungere a ciascun dato di coda un elenco di tutte le posizioni visitate, quindi controllare tale elenco prima di aggiungere il carattere successivo.
Adam Rosenfield,

No, all'interno di BoggleWords (). Invece di memorizzare un quadruplet (x, y, s, n), memorizzeresti un quintuplet (x, y, s, n, l), dove l è l'elenco di (x, y) visitato finora. Quindi controlli ciascuno (x2, y2) rispetto a l e lo accetti solo se non è in l. Quindi lo aggiungi alla nuova l.
Adam Rosenfield,

2
L'ho fatto anche quando mi sono stancato di giocare a Scramble. Penso che la soluzione ricorsiva (DFS anziché BFS) sia più sexy, in quanto puoi semplicemente mantenere un set di celle attive (quindi non visiti la stessa cella due volte). Molto più ordinato quindi mantenendo un sacco di elenchi.
Justin Scheiner,

2
Non dovrebbe cadere in un ciclo infinito? Voglio dire, supponiamo (x,y), un possibile seguace è (x+1,y+1). Quindi (x+1,y+1)viene inserito nella coda. Tuttavia (x,y)sarà anche un seguace (x+1,y+1), quindi questo non porterà a un rimbalzo senza fine tra loro?
SexyBeast,

39

Per accelerare il dizionario, esiste una trasformazione / processo generale che è possibile eseguire per ridurre notevolmente i confronti del dizionario in anticipo.

Dato che la griglia sopra contiene solo 16 caratteri, alcuni dei quali duplicati, puoi ridurre notevolmente il numero di chiavi totali nel dizionario semplicemente filtrando le voci con caratteri irraggiungibili.

Ho pensato che questa fosse l'ovvia ottimizzazione, ma non vedendo nessuno, l'ho menzionato.

Mi ha ridotto da un dizionario di 200.000 chiavi a solo 2.000 chiavi semplicemente durante il passaggio di input. Questo almeno riduce l'overhead di memoria, e questo è sicuro di mappare su un aumento di velocità da qualche parte poiché la memoria non è infinitamente veloce.

Implementazione Perl

La mia implementazione è un po 'pesante perché ho dato importanza alla possibilità di conoscere il percorso esatto di ogni stringa estratta, non solo la validità in essa.

Ho anche alcuni adattamenti che teoricamente permetterebbero a una griglia con buchi di funzionare, e griglie con linee di dimensioni diverse (supponendo che tu abbia l'input giusto e che si allinea in qualche modo).

Il filtro precoce è di gran lunga il collo di bottiglia più significativo nella mia applicazione, come si sospettava in precedenza, commentando che la linea lo gonfia da 1,5 a 7,5 secondi.

Al momento dell'esecuzione sembra pensare che tutte le singole cifre siano composte da parole valide, ma sono abbastanza sicuro che sia dovuto al modo in cui funziona il file del dizionario.

È un po 'gonfio, ma almeno riutilizzo Tree :: Trie da cpan

Alcuni di essi sono stati in parte ispirati dalle implementazioni esistenti, altri ne avevo già in mente.

Critiche costruttive e modi in cui potrebbe essere migliorato benvenuto (/ io nota che non ha mai cercato CPAN per un risolutore di boggle , ma è stato più divertente da capire)

aggiornato per nuovi criteri

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

Informazioni sull'arco / esecuzione per il confronto:

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

Altri borbottii su quell'ottimizzazione Regex

L'ottimizzazione regex che uso è inutile per dizionari multi-risoluzione, e per multi-risoluzione avrai bisogno di un dizionario completo, non di un prefisso.

Tuttavia, ciò detto, per le soluzioni una tantum, è davvero veloce. (Perl regex sono in C! :))

Ecco alcune aggiunte di codice variabili:

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');
      }
  });
}
           filtrato non filtrato
non filtrato 8.16 - -94%
filtrata 0,464 1658% -

ps: 8.16 * 200 = 27 minuti.


2
So che sto fallendo nel club di ottimizzazione, ma ho avuto problemi di velocità prima di arrivare al vero lavoro del codice, e ridurre i tempi di input da 2s a 1,2s significa molto per me.
Kent Fredric,

/ me ho notato che adesso è strano che ci sia voluto meno tempo per regex e saltare le voci di quanto ci sia voluto per aggiungere le chiavi a un hash.
Kent Fredric,

Bello, un'implementazione del Perl! Ora vado a correre.
Paolo Bergantino,

Blerg, non riesco a installare Tree :: Trie sul mio server web. :(
Paolo Bergantino,

3
Come hai generato l'ultimo rapporto (informazioni sull'arch / esecuzione)? Sembra utile
jmanning2k,

33

Potresti dividere il problema in due parti:

  1. Un qualche tipo di algoritmo di ricerca che enumera le possibili stringhe nella griglia.
  2. Un modo per verificare se una stringa è una parola valida.

Idealmente, (2) dovrebbe includere anche un modo per testare se una stringa è un prefisso di una parola valida - questo ti permetterà di potare la tua ricerca e risparmiare un sacco di tempo.

Trie di Adam Rosenfield è una soluzione per (2). È elegante e probabilmente ciò che il tuo specialista di algoritmi preferirebbe, ma con linguaggi moderni e computer moderni, possiamo essere un po 'più pigri. Inoltre, come suggerisce Kent, possiamo ridurre le dimensioni del nostro dizionario scartando le parole che hanno lettere non presenti nella griglia. Ecco un po 'di pitone:

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

Wow; test dei prefissi a tempo costante. Ci vogliono un paio di secondi per caricare il dizionario che hai collegato, ma solo un paio :-) (nota che words <= prefixes)

Ora, per la parte (1), sono propenso a pensare in termini di grafici. Quindi costruirò un dizionario che assomiglia a questo:

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

cioè graph[(x, y)]è l'insieme di coordinate che puoi raggiungere dalla posizione (x, y). Aggiungerò anche un nodo fittizio Noneche si collegherà a tutto.

Costruire è un po 'goffo, perché ci sono 8 posizioni possibili e devi fare il controllo dei limiti. Ecco un po 'di codice python corrispondentemente goffo:

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

Questo codice crea anche una mappatura del dizionario (x,y)sul carattere corrispondente. Questo mi permette di trasformare un elenco di posizioni in una parola:

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

Infine, facciamo una ricerca approfondita. La procedura di base è:

  1. La ricerca arriva a un nodo particolare.
  2. Controlla se il percorso finora potrebbe far parte di una parola. In caso contrario, non esplorare più questo ramo.
  3. Controlla se il percorso finora è una parola. In tal caso, aggiungi all'elenco dei risultati.
  4. Esplora tutti i bambini che non fanno parte del percorso finora.

Pitone:

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)

Esegui il codice come:

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)

e ispeziona resper vedere le risposte. Ecco un elenco di parole trovate per il tuo esempio, ordinate per dimensione:

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

Il caricamento del dizionario richiede (letteralmente) un paio di secondi, ma il resto è istantaneo sulla mia macchina.


Molto bella! Anche molto veloce. Aspetterò in giro per vedere se qualcun altro si avvicina al piatto, ma la tua risposta sembra buona finora.
Paolo Bergantino,

Sono confuso perché "embole" è la tua unica parola di 6 lettere, ho ricevuto 10 parole diverse per quello. Sembra che tu proibisca di visitare due volte lo stesso nodo e, come affermato dall'OP, questo è un gioco equo.
Kent Fredric,

1
ok, probabilmente ha ancora un bug mentre sta scartando "FAMBLE" "WAMBLE" e "SEMBLE", che non condividono i personaggi.
Kent Fredric,

Ben individuato! Il bug era nella creazione del set di prefissi: dovevo usare range(len(w)+1)invece di range(len(w)). L'ho affermato words <= prefixesma a quanto pare non l'ho provato: - /
John Fouhy,

1
Questo mi ha aiutato a imparare come funziona un DFS e come implementarlo. Non ero sicuro di alcun modo per mostrare apprezzamento per questo se non con un commento. Grazie!
Graham Smith,

23

Il mio tentativo in Java. Sono necessari circa 2 secondi per leggere il file e creare trie e circa 50 ms per risolvere il puzzle. Ho usato il dizionario collegato alla domanda (ha alcune parole che non sapevo esistessero in inglese come 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

Il codice sorgente è composto da 6 classi. Li posterò di seguito (se questa non è la pratica giusta su StackOverflow, per favore dimmelo).

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
Stavo confrontando la mia uscita con le uscite di altri StackOverflower, e sembra che le uscite di Adam, John e rvarcher mancassero alcune parole. Ad esempio, "Mwa" è nel dizionario (sì!), Ma non viene restituito negli output di Adam, John e Rvarcher. Viene restituito due volte nel link PHP di Paolo.
Gineer,

1
Ho provato questo copiandolo e incollandolo. Dice "Lettura ..." e "Fine lettura ...", ma dopo non appare nulla. Nessuna corrispondenza visualizzata.
MikkoP,

23

Penso che probabilmente passerai la maggior parte del tuo tempo a cercare di abbinare parole che non possono essere costruite con la tua griglia di lettere. Quindi, la prima cosa che vorrei fare è cercare di accelerare quel passo e che dovrebbe portarti quasi ovunque.

Per questo, vorrei ri-esprimere la griglia come una tabella di possibili "mosse" che indicizzi in base alla transizione di lettere che stai osservando.

Inizia assegnando a ogni lettera un numero dell'intero alfabeto (A = 0, B = 1, C = 2, ... e così via).

Facciamo questo esempio:

h b c d
e e g h
l l k l
m o f p

E per ora, usiamo l'alfabeto delle lettere che abbiamo (di solito probabilmente vorrai usare lo stesso intero alfabeto ogni volta):

 b | c | d | e | f | g | h | k | l | m |  o |  p
---+---+---+---+---+---+---+---+---+---+----+----
 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11

Quindi si crea un array booleano 2D che indica se è disponibile una determinata transizione di lettere:

     |  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

Ora passa attraverso l'elenco delle parole e converti le parole in transizioni:

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

Quindi controlla se queste transizioni sono consentite cercandole nella tabella:

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

Se sono tutti consentiti, è possibile che questa parola venga trovata.

Ad esempio, la parola "casco" può essere esclusa nella quarta transizione (da m a e: helMEt), poiché quella voce nella tabella è falsa.

E la parola criceto può essere esclusa, poiché la prima transizione (da h a a) non è consentita (non esiste nemmeno nella tua tabella).

Ora, per le poche parole rimanenti che probabilmente non hai eliminato, prova a trovarle nella griglia nel modo in cui lo stai facendo ora o come suggerito in alcune delle altre risposte qui. Questo per evitare falsi positivi che derivano da salti tra lettere identiche nella griglia. Ad esempio, la parola "aiuto" è consentita dalla tabella, ma non dalla griglia.

Alcuni ulteriori suggerimenti per migliorare le prestazioni su questa idea:

  1. Invece di usare un array 2D, usa un array 1D e calcola semplicemente tu stesso l'indice della seconda lettera. Quindi, invece di un array 12x12 come sopra, crea un array 1D di lunghezza 144. Se poi usi sempre lo stesso alfabeto (cioè un array 26x26 = 676x1 per l'alfabeto inglese standard), anche se non tutte le lettere vengono visualizzate nella tua griglia , è possibile pre-calcolare gli indici in questo array 1D che è necessario testare per abbinare le parole del dizionario. Ad esempio, gli indici per "ciao" nell'esempio sopra sarebbero

    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. Estendi l'idea a una tabella 3D (espressa come un array 1D), ovvero tutte le combinazioni di 3 lettere consentite. In questo modo puoi eliminare immediatamente ancora più parole e ridurre il numero di ricerche di array per ogni parola di 1: Per "ciao", hai bisogno solo di 3 ricerche di array: hel, ell, llo. A proposito, sarà molto veloce costruire questa tabella, poiché nella griglia ci sono solo 400 mosse possibili di 3 lettere.

  3. Calcola in anticipo gli indici delle mosse nella griglia che devi includere nella tabella. Per l'esempio sopra, è necessario impostare le seguenti voci su "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. Rappresenta anche la griglia di gioco in un array 1-D con 16 voci e la tabella pre-calcolata in 3. contiene gli indici in questo array.

Sono sicuro che se usi questo approccio puoi far funzionare il tuo codice follemente veloce, se hai il dizionario pre-calcolato e già caricato in memoria.

A proposito: Un'altra cosa carina da fare, se stai costruendo un gioco, è eseguire questo tipo di cose immediatamente in background. Inizia a generare e risolvere il primo gioco mentre l'utente sta ancora guardando la schermata del titolo sulla tua app e posiziona il dito per premere "Gioca". Quindi genera e risolvi il gioco successivo mentre l'utente gioca a quello precedente. Questo dovrebbe darti molto tempo per eseguire il tuo codice.

(Mi piace questo problema, quindi probabilmente sarò tentato di implementare la mia proposta in Java nei prossimi giorni per vedere come potrebbe effettivamente funzionare ... Pubblicherò il codice qui una volta che lo farò.)

AGGIORNARE:

Ok, ho avuto un po 'di tempo oggi e ho implementato questa idea in 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();
  }
}

Ecco alcuni risultati:

Per la griglia dall'immagine pubblicata nella domanda originale (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

Per le lettere pubblicate come esempio nella domanda originale (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

Per la seguente griglia 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

dà questo:

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

Per questo ho usato la lista di parole scrabble del torneo TWL06 , poiché il link nella domanda originale non funziona più. Questo file è 1,85 MB, quindi è un po 'più corto. E ilbuildDictionary funzione elimina tutte le parole con meno di 3 lettere.

Ecco un paio di osservazioni sull'esecuzione di questo:

  • È circa 10 volte più lento delle prestazioni riportate dell'implementazione OCaml di Victor Nicollet. Se questo è causato dal diverso algoritmo, dal dizionario più breve che ha usato, dal fatto che il suo codice è compilato e il mio viene eseguito in una macchina virtuale Java, o dalle prestazioni dei nostri computer (il mio è un Intel Q6600 a 2,4 MHz che esegue WinXP), Non lo so. Ma è molto più veloce dei risultati per le altre implementazioni citate alla fine della domanda originale. Quindi, se questo algoritmo sia superiore o meno al dizionario trie, non lo so a questo punto.

  • Il metodo di tabella utilizzato in checkWordTriplets()fornisce un'ottima approssimazione alle risposte effettive. Solo 1 su 3-5 parole superate non supererà il checkWords()test (vedere il numero di candidati rispetto al numero di parole effettive sopra).

  • Qualcosa che non puoi vedere sopra: la checkWordTriplets()funzione richiede circa 3,65 ms ed è quindi completamente dominante nel processo di ricerca. La checkWords()funzione occupa praticamente i rimanenti 0,05-0,20 ms.

  • Il tempo di esecuzione della checkWordTriplets()funzione dipende linearmente dalla dimensione del dizionario ed è praticamente indipendente dalla dimensione della scheda!

  • Il tempo di esecuzione checkWords()dipende dalle dimensioni della scheda e dal numero di parole non escluse checkWordTriplets().

  • L' checkWords()implementazione sopra è la prima versione più stupida che mi è venuta in mente. Fondamentalmente non è affatto ottimizzato. Ma rispetto ad checkWordTriplets()esso è irrilevante per le prestazioni totali dell'applicazione, quindi non me ne sono preoccupato. Ma se le dimensioni della scheda aumentano, questa funzione diventerà sempre più lenta e alla fine inizierà a importare. Quindi, dovrebbe anche essere ottimizzato.

  • Una cosa bella di questo codice è la sua flessibilità:

    • È possibile modificare facilmente le dimensioni della scheda: aggiornare la riga 10 e l'array String passato a initializeBoard().
    • Può supportare alfabeti più grandi / diversi e può gestire cose come il trattamento di "Qu" come una sola lettera senza alcun sovraccarico di prestazioni. Per fare ciò, bisognerebbe aggiornare la riga 9 e la coppia di posizioni in cui i caratteri vengono convertiti in numeri (attualmente sottraendo semplicemente 65 dal valore ASCII)

Ok, ma penso che ormai questo post sia abbastanza lungo. Posso sicuramente rispondere a qualsiasi domanda tu possa avere, ma passiamo ai commenti.


Bella risposta. Mi piacerebbe vedere la tua implementazione in Java.
MikkoP,

@MikkoP Fatto! :) Ho impiegato circa 3 ore e 220 righe di codice. Un buon modo per passare un pomeriggio. Fammi sapere se hai domande su come funziona ... :)
Markus A.

Grazie per aver pubblicato il codice! L'ho provato con il mio dizionario dopo aver aggiunto le importazioni mancanti. Ottengo un ArrayIndexOutOfBoundException sulla linea ok = possibleTriplets[entry.triplets[t]];. hmm?
MikkoP,

@MikkoP Questo codice è attualmente scritto per assumere che il dizionario contenga solo lettere maiuscole AZ. Il punto cruciale è nella riga 34: entry.letters[i] = (byte) letter - 65;prende semplicemente il valore ASCII e sottrae 65 ("A"). Se hai Umlaut o lettere minuscole nel tuo dizionario, questo fornirà valori maggiori di 31, che non sono previsti dall'impostazione della dimensione dell'alfabeto nella riga 9. Per supportare altre lettere, dovresti espandere questa riga per mapparli nell'intervallo consentito dalla dimensione dell'alfabeto.
Markus A.

1
@AlexanderN Probabilmente stai capendo correttamente la logica. Ho fatto un errore copiando la griglia delle lettere ... Mi dispiace ... (risolto)
Markus A.

19

Sorprendentemente, nessuno ha tentato una versione PHP di questo.

Questa è una versione PHP funzionante della soluzione Python di John Fouhy.

Anche se ho preso alcuni suggerimenti dalle risposte di tutti, questo è per lo più copiato da John.

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

Ecco un link live se vuoi provarlo. Anche se ci vogliono ~ 2s nella mia macchina locale, ci vogliono ~ 5s sul mio server web. In entrambi i casi, non è molto veloce. Tuttavia, è abbastanza orribile, quindi posso immaginare che il tempo possa essere ridotto in modo significativo. Qualche suggerimento su come realizzare ciò sarebbe apprezzato. La mancanza di tuple di PHP ha reso strane le coordinate con cui lavorare e la mia incapacità di comprendere esattamente cosa diavolo sta succedendo non ha aiutato affatto.

EDIT : alcune correzioni richiedono meno di 1 s localmente.


+1 @ "e la mia incapacità di comprendere esattamente cosa diavolo sta succedendo non ha aiutato affatto." lol. Adoro l'onestà!
dna123,

Non conosco PHP, ma la prima cosa che proverei è sollevare '/ ['. implode ('', $ alfabeto). '] {3,} $ /' fuori dal ciclo. Cioè, imposta una variabile su quella e usa invece la variabile all'interno del ciclo.
Darius Bacon,

Sono abbastanza sicuro che PHP mantenga una cache globale per thread di espressioni regolari compilate, ma ci proverò comunque.
Paolo Bergantino,

1
@Daniel: Apparentemente è il mio web server. Non succede quando corro localmente. Scrollata di spalle. Non mi va di cacciarlo.
Paolo Bergantino,

2
Cosa dovrebbe essere impostato come parametro 7. nella funzione find_words alla fine?
MikkoP,

16

Non ti interessa VB? :) Non ho resistito. L'ho risolto in modo diverso rispetto a molte delle soluzioni presentate qui.

I miei tempi sono:

  • Caricamento del dizionario e dei prefissi di parole in una tabella hash: da 0,5 a 1 secondi.
  • Trovare le parole: media inferiore a 10 millisecondi.

EDIT: i tempi di caricamento del dizionario sul server host web durano da 1 a 1,5 secondi in più rispetto al mio computer di casa.

Non so quanto i tempi si deterioreranno con un carico sul server.

Ho scritto la mia soluzione come pagina Web in .Net. myvrad.com/boggle

Sto usando il dizionario a cui fa riferimento la domanda originale.

Le lettere non vengono riutilizzate in una parola. Sono state trovate solo parole di 3 caratteri o più.

Sto usando una tabella hash di tutti i prefissi e parole univoci anziché un trie. Non sapevo di Trie, quindi ho imparato qualcosa lì. L'idea di creare un elenco di prefissi di parole oltre alle parole complete è ciò che alla fine ha ridotto i miei tempi a un numero rispettabile.

Leggi i commenti sul codice per ulteriori dettagli.

Ecco il codice:

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

Presumo che qui hai usato il sistema ap invece di [x] [y] perché quest'ultimo è piuttosto complesso in VB? Ho trascorso una giornata cercando di ottenere un array dinamico a 2 vie in quella volta, ovvero: array (array (1, "hello"), 1, "hello", array ()), ancora non so come fare che: P
Kent Fredric,

In PHP e Perl 2 dim array sono divertenti. Può essere fatto in VB ma non lo definirei un processo divertente. Dim Arr (,) As Integer = {{1,1}, {0,0}}. Il processo AP è nato da me mettendomi sulla griglia e chiedendo: "dove posso andare da qui?" So che è una soluzione rigida ma funziona qui.
rvarcher,

Ohh mi piace VB.NET ... Ho provato l'URL ma non ha funzionato. Ho dovuto ricostruire il tuo codice da solo come Windows Form e funziona. Grazie.
Ahmed Eissa,

11

Non appena ho visto la dichiarazione del problema, ho pensato "Trie". Ma visto che molti altri poster hanno utilizzato questo approccio, ho cercato un altro approccio solo per essere diverso. Purtroppo, l'approccio Trie funziona meglio. Ho eseguito la soluzione Perl di Kent sul mio computer e ci sono voluti 0,31 secondi per essere eseguito, dopo averlo adattato per utilizzare il mio file di dizionario. La mia implementazione perl ha richiesto 0,54 secondi per l'esecuzione.

Questo era il mio approccio:

  1. Crea un hash di transizione per modellare le transizioni legali.

  2. Scorrere tutte le 16 ^ 3 possibili combinazioni di tre lettere.

    • Nel ciclo, escludi le transizioni illegali e ripeti le visite nella stessa casella. Forma tutte le sequenze legali di 3 lettere e memorizzale in un hash.
  3. Quindi scorrere tutte le parole nel dizionario.

    • Escludere parole troppo lunghe o brevi
    • Fai scorrere una finestra di 3 lettere su ogni parola e vedi se è tra le combinazioni di 3 lettere del passaggio 2. Escludi le parole che non riescono. Questo elimina la maggior parte delle non partite.
    • Se ancora non eliminato, usa un algoritmo ricorsivo per vedere se la parola può essere formata facendo percorsi attraverso il puzzle. (Questa parte è lenta, ma chiamata di rado.)
  4. Stampa le parole che ho trovato.

    Ho provato sequenze di 3 e 4 lettere, ma sequenze di 4 lettere hanno rallentato il programma.

Nel mio codice, uso / usr / share / dict / words per il mio dizionario. Viene fornito di serie su MAC OS X e molti sistemi Unix. Puoi usare un altro file se vuoi. Per rompere un puzzle diverso, basta cambiare la variabile @puzzle. Questo sarebbe facile da adattare per matrici più grandi. Dovresti solo cambiare l'hash% transitions e l'hash% legalTransitions.

Il punto di forza di questa soluzione è che il codice è breve e le strutture di dati semplici.

Ecco il codice Perl (che usa troppe variabili globali, lo so):

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

La posizione del dizionario è cambiata? Ho cercato di trovare le parole del dizionario poiché volevo confrontare la mia soluzione con tutti, ma non sono riuscito a trovarla sul link indicato in / usr / share / dict. So che è un filo piuttosto vecchio ma sarebbe fantastico se puoi indicarmi. Grazie in anticipo per il vostro aiuto.
Naman,

Al momento non ho il mio Mac a portata di mano. Tutto ciò che serve è un file con parole inglesi, da una a una riga, separate da nuove righe. È possibile trovare un file del genere su Internet. Uno è qui: mieliestronk.com/corncob_lowercase.txt ma probabilmente ci sono elenchi con più parole di così.
Paul Chernoch,

Grazie mille per la risposta L'avevo trovato nei file Ubuntu.
Naman,

9

So di essere in ritardo, ma ne ho fatto uno poco fa in PHP - anche solo per divertimento ...

http://www.lostsockdesign.com.au/sandbox/boggle/index.php?letters=fxieamloewbxastu Trovato 75 parole (133 punti) in 0,90108 secondi

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

Fornisce alcune indicazioni su ciò che il programma sta effettivamente facendo: ogni lettera è il punto in cui inizia a guardare attraverso gli schemi mentre ciascuna "." mostra un percorso che ha tentato di prendere. Più '.' ci sono più ha cercato.

Fammi sapere se vuoi il codice ... è un orribile mix di PHP e HTML che non è mai stato pensato per vedere la luce del giorno, quindi non oso pubblicarlo qui: P


9

Ho trascorso 3 mesi a lavorare su una soluzione al problema delle 10 migliori schede Dive 5x5 a punti densi.

Il problema è ora risolto e esposto con una completa divulgazione su 5 pagine Web. Vi prego di contattarmi per domande.

L'algoritmo di analisi della scheda utilizza uno stack esplicito per attraversare pseudo-ricorsivamente i quadrati della scheda attraverso un grafico a parole aciclico diretto con informazioni figlio dirette e un meccanismo di tracciamento del timestamp. Questa potrebbe benissimo essere la struttura di dati lessicali più avanzata al mondo.

Lo schema valuta circa 10.000 ottime schede al secondo su un quad core. (9500+ punti)

Pagina Web principale:

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

Pagine Web dei componenti:

Quadro di valutazione ottimale - http://www.pathcom.com/~vadco/binary.html

Advanced Lexicon Structure - http://www.pathcom.com/~vadco/adtdawg.html

Algoritmo di analisi della scheda - http://www.pathcom.com/~vadco/guns.html

Elaborazione batch parallela - http://www.pathcom.com/~vadco/parallel.html

- Questo corpus completo di lavori interesserà solo una persona che richiede il meglio.


4
La tua analisi è interessante, ma i tuoi risultati non sono tecnicamente Boggle board. Il gioco boggle 5x5 include un dado che contiene le facce BJKQXZ, la tua implementazione esclude esplicitamente tutte queste lettere e quindi la posizione del tabellone non è effettivamente possibile in un vero gioco Boggle.
MarkPflug

4

Il tuo algoritmo di ricerca riduce continuamente l'elenco di parole mentre la tua ricerca continua?

Ad esempio, nella ricerca sopra ci sono solo 13 lettere con cui le tue parole possono iniziare (riducendo in modo efficace alla metà del numero di lettere iniziali).

Quando si aggiungono più permutazioni di lettere, si ridurrebbe ulteriormente le serie di parole disponibili diminuendo la ricerca necessaria.

Vorrei iniziare da lì.


4

Dovrei pensare di più a una soluzione completa, ma come utile ottimizzazione, mi chiedo se valga la pena pre-calcolare una tabella di frequenze di digram e trigrammi (combinazioni di 2 e 3 lettere) basate su tutti i parole dal tuo dizionario e utilizzale per dare priorità alla tua ricerca. Vorrei andare con le lettere iniziali delle parole. Quindi se il tuo dizionario conteneva le parole "India", "Acqua", "Estremo" e "Straordinario", la tua tabella pre-calcolata potrebbe essere:

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

Quindi cerca questi digram nell'ordine di comunanza (prima EX, poi WA / IN)


4

Innanzitutto, leggi come uno dei designer del linguaggio C # ha risolto un problema correlato: http://blogs.msdn.com/ericlippert/archive/2009/02/04/a-nasality-talisman-for-the-sultana-analyst.aspx .

Come lui, puoi iniziare con un dizionario e le parole canonacalizzate creando un dizionario da una matrice di lettere ordinate alfabeticamente in un elenco di parole che possono essere scritte da quelle lettere.

Quindi, inizia a creare le possibili parole dalla lavagna e cercandole. Sospetto che ti porterà abbastanza lontano, ma ci sono sicuramente altri trucchi che potrebbero accelerare le cose.


4

Suggerisco di creare un albero di lettere basato sulle parole. L'albero sarebbe composto da una lettera strutturata, in questo modo:

letter: char
isWord: boolean

Quindi costruisci l'albero, con ogni profondità aggiungendo una nuova lettera. In altre parole, al primo livello ci sarebbe l'alfabeto; poi da ciascuno di quegli alberi, ci sarebbero state altre 26 voci, e così via, fino a quando non avrai scritto tutte le parole. Aggrappati a questo albero analizzato e renderà più veloci tutte le risposte possibili per cercare.

Con questo albero analizzato, puoi trovare rapidamente soluzioni. Ecco lo pseudo-codice:

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.

Questo potrebbe essere accelerato con un po 'di programmazione dinamica. Ad esempio, nel tuo campione, le due "A" sono entrambe accanto a una "E" e una "W", che (dal punto in cui le hanno colpite) sarebbero identiche. Non ho abbastanza tempo per scrivere il codice per questo, ma penso che tu possa raccogliere l'idea.

Inoltre, sono sicuro che troverai altre soluzioni se Google per "Risolutore di Boggle".



3

Divertente. Ho quasi pubblicato la stessa domanda qualche giorno fa a causa dello stesso dannato gioco! Non l'ho fatto perché ho appena cercato su Google il pitone risolutore di boggle e ho ottenuto tutte le risposte che avrei potuto desiderare.


Non sapevo che il nome popolare fosse "boggle", ma ho trovato alcune cose su google, ero solo curioso di vedere cosa sarebbe successo a SO. :)
Paolo Bergantino,

3

Mi rendo conto che il tempo di questa domanda è arrivato e finito, ma dato che stavo lavorando su un risolutore e inciampato su questo mentre cercavo su Google, ho pensato di dover pubblicare un riferimento al mio in quanto sembra un po 'diverso da alcuni degli altri.

Ho scelto di scegliere un array piatto per il tabellone di gioco e di fare cacce ricorsive da ogni lettera sul tabellone, passando da un vicino valido a un vicino valido, estendendo la caccia se l'attuale elenco di lettere è un prefisso valido in un indice. Mentre si attraversa la nozione della parola corrente è un elenco di indici in lavagna, non lettere che compongono una parola. Quando si controlla l'indice, gli indici vengono tradotti in lettere e il controllo viene eseguito.

L'indice è un dizionario di forza bruta che è un po 'come un trie, ma consente query Pythonic dell'indice. Se le parole "cat" e "cater" sono presenti nell'elenco, otterrai questo nel dizionario:

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

Quindi se current_word è 'ca', sai che è un prefisso valido perché 'ca' in drestituisce True (quindi continua l'attraversamento della scheda). E se current_word è 'cat', allora sai che è una parola valida perché è un prefisso valido e 'cat' in d['cat']restituisce anche True.

Se ritenuto così, è consentito un codice leggibile che non sembra troppo lento. Come tutti gli altri, la spesa in questo sistema è leggere / costruire l'indice. Risolvere la scheda è praticamente un rumore.

Il codice è su http://gist.github.com/268079 . È intenzionalmente verticale e ingenuo con un sacco di controllo esplicito della validità perché volevo capire il problema senza rimuoverlo con un mucchio di magia o oscurità.


3

Ho scritto il mio risolutore in C ++. Ho implementato una struttura ad albero personalizzata. Non sono sicuro che possa essere considerato un trie ma è simile. Ogni nodo ha 26 rami, 1 per ogni lettera dell'alfabeto. Attraverso i rami del tabellone in parallelo con i rami del mio dizionario. Se il ramo non esiste nel dizionario, smetto di cercarlo sulla scheda Boggle. Converto tutte le lettere sulla lavagna in ints. Quindi 'A' = 0. Dato che sono solo array, la ricerca è sempre O (1). Ogni nodo memorizza se completa una parola e quante parole esistono nei suoi figli. L'albero viene potato quando le parole vengono trovate per ridurre ripetutamente la ricerca delle stesse parole. Credo che anche la potatura sia O (1).

CPU: Pentium SU2700 1.3GHz
RAM: 3

Carica dizionario di 178.590 parole in <1 secondo.
Risolve 100x100 Boggle (boggle.txt) in 4 secondi. Sono state trovate circa 44.000 parole.
Risolvere un Boggle 4x4 è troppo veloce per fornire un benchmark significativo. :)

Risolutore rapido di Boggle GitHub Repo


2

Data una scheda Boggle con N righe e colonne M, supponiamo che:

  • N * M è sostanzialmente maggiore del numero di parole possibili
  • N * M è sostanzialmente maggiore della parola più lunga possibile

In base a questi presupposti, la complessità di questa soluzione è O (N * M).

Penso che il confronto dei tempi di esecuzione per questa scheda di esempio in molti modi manchi il punto ma, per completezza, questa soluzione si completa in <0,2 secondi sul mio moderno MacBook Pro.

Questa soluzione troverà tutti i percorsi possibili per ogni parola nel corpus.

#!/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

Questa soluzione fornisce anche la direzione per la ricerca nella scheda data

Algo:

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

Produzione:

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  → → ↑ ↘ →

Codice:

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

Ho implementato una soluzione in OCaml . Precompila un dizionario come trie e utilizza frequenze di sequenza di due lettere per eliminare i bordi che non potrebbero mai apparire in una parola per accelerare ulteriormente l'elaborazione.

Risolve la scheda di esempio in 0,35 ms (con un tempo di avvio aggiuntivo di 6 ms che è principalmente correlato al caricamento del trie in memoria).

Le soluzioni trovate:

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

Questo è carino, ma tutte le volte pubblicate qui implicano qualsiasi tempo di "avvio" per caricare il dizionario in memoria, quindi confrontare lo 0,35 con le altre volte è piuttosto lontano dall'accuratezza. Inoltre, stai usando un dizionario diverso? Ti mancano alcune parole. Ad ogni modo, +1
Paolo Bergantino,

Il tempo di avvio richiede 6ms, quindi stai cercando 6,35ms per una corsa completa. Sto usando il mio /usr/share/dictdizionario locale e alcune parole mancano davvero (come EMBOLE).
Victor Nicollet,

1

Una soluzione JavaScript Node.JS. Calcola tutte le 100 parole univoche in meno di un secondo che include la lettura del file di dizionario (MBA 2012).

Uscita:
["FAM", "TUX", "TUB", "FAE", "ELI", "ELM", "ELB", "TWA", "TWA", "SAW", "AMI", "SWA" , "SWA", "AME", "mare", "SEW", "AES", "AWL", "AWE", "mare", "AWA", "MIX", "mil", "AST"," ASE", "MAX", "MAE", "MAW", "MEW", "AWE", "MES", "AWL", "LIE", "LIM", "AWA", "AES", "MA" , "BLO", "era", "WAE", "WEA", "lei", "LEO", "LOB", "LOX", "WEM", "OIL", "OLM", "WEA"," WAE", "CERA", "WAF","MILO", "Oriente", "WAME", "TWAS", "TWAE", "Emil", "WEAM", "OIME", "AXIL", "Occidente", "TWAE", "ARTI", "WASE ", "WAST", "Bleo", "stub", "bollire", "BOLE", "LIME", "SAWT", "LIMA", "Mesa", "miagolare", "ASSE", "Fame", "ASEM", "miglio", "AMIL", "Seax", "SEAM", "SEMI", "SWAM", "AMBO", "AMLI", "assile", "AMBLE", "Swami", "AWEST ", "AWEST", "Limax", "Limes", "Limbu", "limbo", "eMBox", "semble", "EMBOLE", "Wamble", "FAMBLE"]EST", "WAME", "TWAS", "TWAE", "Emil", "WEAM", "OIME", "AXIL", "Occidente", "TWAE", "ARTI", "WASE", "WAST" , "Bleo", "stub", "bollire", "BOLE", "LIME", "SAWT", "LIMA", "Mesa", "miagolare", "ASSE", "Fame", "ASEM"," MILE", "AMIL", "Seax", "SEAM", "SEMI", "SWAM", "AMBO", "AMLI", "assile", "AMBLE", "Swami", "AWEST", "AWEST" , "Limax", "Limes", "Limbu", "limbo", "eMBox", "semble", "EMBOLE", "Wamble", "FAMBLE"]EST", "WAME", "TWAS", "TWAE", "Emil", "WEAM", "OIME", "AXIL", "Occidente", "TWAE", "ARTI", "WASE", "WAST" , "Bleo", "stub", "bollire", "BOLE", "LIME", "SAWT", "LIMA", "Mesa", "miagolare", "ASSE", "Fame", "ASEM"," MILE", "AMIL", "Seax", "SEAM", "SEMI", "SWAM", "AMBO", "AMLI", "assile", "AMBLE", "Swami", "AWEST", "AWEST" , "Limax", "Limes", "Limbu", "limbo", "eMBox", "semble", "EMBOLE", "Wamble", "FAMBLE"]"TWAE", "Emil", "WEAM", "OIME", "AXIL", "Occidente", "TWAE", "ARTI", "WASE", "WAST", "Bleo", "stub", "bollire ", "BOLE", "LIME", "SAWT", "LIMA", "Mesa", "miagolare", "ASSE", "Fame", "ASEM", "miglio", "AMIL", "Seax", "SEAM", "SEMI", "SWAM", "AMBO", "AMLI", "assile", "AMBLE", "Swami", "AWEST", "AWEST", "Limax", "Limes", "Limbu ", "limbo", "eMBox", "semble", "EMBOLE", "Wamble", "FAMBLE"]"TWAE", "Emil", "WEAM", "OIME", "AXIL", "Occidente", "TWAE", "ARTI", "WASE", "WAST", "Bleo", "stub", "bollire ", "BOLE", "LIME", "SAWT", "LIMA", "Mesa", "miagolare", "ASSE", "Fame", "ASEM", "miglio", "AMIL", "Seax", "SEAM", "SEMI", "SWAM", "AMBO", "AMLI", "assile", "AMBLE", "Swami", "AWEST", "AWEST", "Limax", "Limes", "Limbu ", "limbo", "eMBox", "semble", "EMBOLE", "Wamble", "FAMBLE"]"WEST", "TWAE", "ARTI", "WASE", "WAST", "Bleo", "stub", "bollire", "BOLE", "LIME", "SAWT", "LIMA", "MESA ", "miagolare", "ASSE", "Fame", "ASEM", "miglio", "AMIL", "Seax", "SEAM", "SEMI", "SWAM", "AMBO", "AMLI", "assile", "AMBLE", "Swami", "AWEST", "AWEST", "Limax", "Limes", "Limbu", "limbo", "eMBox", "semble", "EMBOLE", "Wamble ", "FAMBLE"]"WEST", "TWAE", "ARTI", "WASE", "WAST", "Bleo", "stub", "bollire", "BOLE", "LIME", "SAWT", "LIMA", "MESA ", "miagolare", "ASSE", "Fame", "ASEM", "miglio", "AMIL", "Seax", "SEAM", "SEMI", "SWAM", "AMBO", "AMLI", "assile", "AMBLE", "Swami", "AWEST", "AWEST", "Limax", "Limes", "Limbu", "limbo", "eMBox", "semble", "EMBOLE", "Wamble ", "FAMBLE"]SAWT", "LIMA", "Mesa", "miagolare", "ASSE", "Fame", "ASEM", "miglio", "AMIL", "Seax", "SEAM", "SEMI", "SWAM" , "AMBO", "AMLI", "assile", "AMBLE", "Swami", "AWEST", "AWEST", "Limax", "Limes", "Limbu", "limbo", "eMBox"," semble", "EMBOLE", "Wamble", "FAMBLE"]SAWT", "LIMA", "Mesa", "miagolare", "ASSE", "Fame", "ASEM", "miglio", "AMIL", "Seax", "SEAM", "SEMI", "SWAM" , "AMBO", "AMLI", "assile", "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"]

Codice:

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

Quindi volevo aggiungere un altro modo PHP per risolvere questo, dal momento che tutti amano PHP. Vorrei fare un po 'di refactoring, come usare una corrispondenza regexpression con il file del dizionario, ma in questo momento sto solo caricando l'intero file del dizionario in un elenco di parole.

L'ho fatto usando un'idea dell'elenco collegato. Ogni nodo ha un valore di carattere, un valore di posizione e un puntatore successivo.

Il valore della posizione è come ho scoperto se sono collegati due nodi.

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

Quindi, usando quella griglia, so che due nodi sono collegati se la posizione del primo nodo è uguale alla posizione del secondo nodo +/- 1 per la stessa riga, +/- 9, 10, 11 per la riga sopra e sotto.

Uso la ricorsione per la ricerca principale. Elimina una parola dall'elenco di parole, trova tutti i possibili punti di partenza e quindi ricorsivamente trova la prossima connessione possibile, tenendo presente che non può andare in una posizione che sta già utilizzando (motivo per cui aggiungo $ notInLoc).

Ad ogni modo, so che ha bisogno di un po 'di refactoring e mi piacerebbe sentire pensieri su come renderlo più pulito, ma produce i risultati corretti in base al file del dizionario che sto usando. A seconda del numero di vocali e combinazioni sul tabellone, sono necessari dai 3 ai 6 secondi. So che, una volta ottenuto il risultato, i risultati del dizionario si ridurranno in modo significativo.

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

So di essere molto in ritardo alla festa ma ho implementato, come esercizio di programmazione, un risolutore di boggle in diversi linguaggi di programmazione (C ++, Java, Go, C #, Python, Ruby, JavaScript, Julia, Lua, PHP, Perl) e Ho pensato che qualcuno potesse essere interessato a quelli, quindi lascio il link qui: https://github.com/AmokHuginnsson/boggle-solvers


1

Ecco la soluzione Utilizzo di parole predefinite nel toolkit NLTK NLTK ha un pacchetto nltk.corpus in quanto abbiamo un pacchetto chiamato parole e contiene più di 2Lakh parole inglesi che puoi semplicemente usare tutte nel tuo programma.

Una volta creata la matrice, convertila in una matrice di caratteri ed esegui questo codice

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)

Produzione:

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

Spero che tu lo ottenga.


0

Ecco la mia implementazione java: https://github.com/zouzhile/interview/blob/master/src/com/interview/algorithms/tree/BoggleSolver.java

La creazione di Trie ha richiesto 0 ore, 0 minuti, 1 secondi, 532 millisecondi
La ricerca delle parole ha impiegato 0 ore, 0 minuti, 0 secondi, 92 millisecondi

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 

Nota: ho usato il dizionario e la matrice di caratteri all'inizio di questa discussione. Il codice è stato eseguito sul mio MacBookPro, di seguito sono riportate alcune informazioni sulla macchina.

Nome modello: MacBook Pro
Identificatore modello: MacBookPro8,1
Nome processore: Intel Core i5
Velocità del processore: 2,3 GHz
Numero di processori: 1
Numero totale di core: 2
L2 Cache (per core): 256 KB
L3 Cache: 3 MB
Memoria: 4
Versione ROM di avvio GB : MBP81.0047.B0E
Versione SMC (sistema): 1.68f96


0

Ho risolto anche questo, con Java. La mia implementazione è lunga 269 linee e abbastanza facile da usare. Innanzitutto è necessario creare una nuova istanza della classe Boggler e quindi chiamare la funzione di risoluzione con la griglia come parametro. Ci vogliono circa 100 ms per caricare il dizionario di 50.000 parole sul mio computer e trova le parole in circa 10-20 ms. Le parole trovate vengono memorizzate in un 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

Ho risolto questo problema in c. Sono necessari circa 48 ms per l'esecuzione sul mio computer (con circa il 98% del tempo impiegato per caricare il dizionario dal disco e creare il trie). Il dizionario è / usr / share / dict / american-english che ha 62886 parole.

Codice sorgente


0

L'ho risolto perfettamente e molto velocemente. L'ho inserito in un'app Android. Guarda il video sul link del Play Store per vederlo in azione.

Word Cheats è un'app che "incrina" qualsiasi gioco di parole in stile matrice. Questa app è stata creata per aiutarmi a imbrogliare nello scrambler di parole. Può essere utilizzato per ricerche di parole, ruzzle, parole, word finder, word crack, boggle e molto altro!

Può essere visto qui https://play.google.com/store/apps/details?id=com.harris.wordcracker

Guarda l'app in azione nel video https://www.youtube.com/watch?v=DL2974WmNAI

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.