Bir harf matrisinden olası kelimelerin listesi nasıl bulunur [Boggle Solver]


376

Son zamanlarda iPhone'umda Scramble adlı bir oyun oynuyorum. Bazılarınız bu oyunu Boggle olarak biliyor olabilir. Esasen, oyun başladığında şöyle bir harf matrisi elde edersiniz:

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

Oyunun amacı, harfleri bir araya getirerek oluşturulabildiğince çok kelime bulmaktır. Herhangi bir harfle başlayabilirsiniz ve onu çevreleyen tüm harfler adil bir oyundur ve bir sonraki harfe geçtiğinizde, daha önce kullanılan harfler hariç , bu harfi çevreleyen tüm harfler adil oyundur . Yani yukarıdaki çizelgede, örneğin, ben sözlerle gelebilir LOB, TUX, SEA, FAME, vb Kelimeler bu oyunda 16 olurdu ama bazı uygulamalarda değişebilen artık NxN karakterden daha az 3 karakter ve olmalıdır . Bu oyun eğlenceli ve bağımlılık yapıcı olsa da, görünüşe göre çok iyi değilim ve bana mümkün olan en iyi kelimeleri verecek bir program yaparak biraz hile yapmak istedim (kelime ne kadar uzun olursa o kadar fazla puan alırsınız).

Örnek Bağlama
(kaynak: boggled.org )

Ne yazık ki, algoritmalar veya verimlilikleri vb.Ile pek iyi değilim. İlk denemem böyle bir sözlük kullanıyor (~ 2.3MB) ve kombinasyonları sözlük girişleriyle eşleştirmeye çalışan doğrusal bir arama yapıyor. Bu , olası kelimeleri bulmak için çok uzun zaman alır ve her turda sadece 2 dakika aldığınız için, bu yeterli değildir.

Herhangi bir Stackoverflower'ın daha verimli çözümler üretip getiremeyeceğini görmek istiyorum. Çoğunlukla Big 3 Ps: Python, PHP ve Perl kullanan çözümler arıyorum, ancak Java veya C ++ ile her şey çok önemlidir, çünkü hız önemlidir.

GÜNCEL ÇÖZÜMLER :

  • Adam Rosenfield, Python, ~ 20'ler
  • John Fouhy, Python, ~ 3'ler
  • Kent Fredric, Perl, ~ 1'ler
  • Darius Bacon, Python, ~ 1'ler
  • rvarcher, VB.NET (canlı bağlantı) , ~ 1s
  • Paolo Bergantino, PHP (canlı bağlantı) , ~ 5s (yerel olarak ~ 2s)

18
özellik isteği MOAR PUZZLES
Kent Fredric

6
Zamanlamalar ile ilgili olarak: benim çözümümde, pratikte her zaman trie'yi inşa etmek için harcanıyor. Üçlü inşa edildikten sonra, birçok kez tekrar kullanılabilir. Sadece bir bulmacayı çözerseniz, daha basit bir veri yapısı kullanmak daha verimli olacaktır (tüm sözcükler ve tüm önekler gibi).
Adam Rosenfield

3
Ayrıca, Adam'ın çözümünün kullandığı daha uzun kelime sayısıyla kanıtlanmış daha büyük bir sözlüğü var. Hepsi ortak bir sözlüğe göre test edilmelidir.
Rich Bradshaw

2
Sanırım kimse Boggle'ı fazla oynamıyor mu? "Qu" bir "harf" tir ve kaç çözümün bu kadar az ayrıntı yakaladığından emin değilim. Bazıları "u" yu diğer problemlerin yanı sıra bağımsız olarak kullanmanıza izin verecek gibi görünüyor.
Qsario

2
Son zamanlarda bunu bir röportaj sorusu olarak aldım ve ayrıntılarda güzelce sıkıştım. Bunu bir grafik problemi olarak ele alıyordum, bu iyi, ama buradaki çözümler çok daha az yer kaplıyor. Şimdi kendi çözümümü kodluyorum. Katkıda bulunan herkese aferin!
Peter Friend

Yanıtlar:


143

Cevabım burada diğerleri gibi çalışıyor, ancak bunu daha hızlı göndereceğim, çünkü diğer Python çözümlerinden biraz daha hızlı görünüyor, sözlüğü daha hızlı kuruyor. (Bunu John Fouhy'nin çözümüne karşı kontrol ettim.) Kurulumdan sonra, çözme süresi gürültüde.

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)

Örnek kullanım:

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

Düzenleme: 3 harften kısa kelimeleri filtreleyin.

Edit 2: Kent Fredric'in Perl çözümünün neden daha hızlı olduğunu merak ettim; bir karakter kümesi yerine normal ifade eşleşmesi kullanıldığı ortaya çıkıyor. Aynı şeyi Python'da yapmak hızı iki katına çıkarır.


Program bana sadece 1 kelime veriyor. Nasıl olur?
Paolo Bergantino

Çıktıda boğulmak istemedim. En alttaki açıklamaya bakın.
Darius Bacon

6
Veya yolları olmayan tüm kelimeleri alın: print '' .join (sıralanmış (set (sözcük (yol, sözcük) için sözcük (yol) (),)))
Darius Bacon

2
Çoğu zaman sadece sözlüğü ayrıştırmak için harcanır. Ben her kelime bir öğe olmak sadece bir liste olan bir "wordlines.py" dosyası içine önceden parsed. Bu bir .py dosyası olduğundan, bu bir .pyc dosyasına dönüştürülür. Sonra read (). Splitlines () yerine bunun bir ithalat yapmak. Bununla kutumda, saniyenin onda biri kadar çözüyorum.
Sean Reifschneider

1
@shellscape, bu Python 2 kodudur. Python 3 gibi argümanları çözme yeteneğini düşürdü def neighbors((x, y))(anlamsızca görebildiğim kadarıyla). Ayrıca, argüman etrafında parantez gerektirir print.
Darius Bacon

116

Elde edeceğiniz en hızlı çözüm muhtemelen sözlüğünüzü bir üçlü olarak saklamayı içerir . Ardından, sıradaki her öğenin ( x , y ) konumunda biten, kılavuzda yazılabilen bir sözcüğün önekine ( s) karşılık geldiği bir üçlüler sırası ( x , y , s ) oluşturun. Kuyruğu , ızgaradaki her kare için bir öğe olan N x N öğeleri (burada N , ızgara boyutudur) ile başlatın. Ardından, algoritma aşağıdaki gibi ilerler:

Kuyruk boş olmasa da:
  Üçlü ayıp (x, y, s)
  (X, y) bitişiğinde c harfiyle her kare (x ', y') için:
    Eğer s + c bir kelimeyse, çıkış s + c
    S + c bir sözcüğün önekiyse, kuyruğa (x ', y', s + c) ekleyin

Sözlüğünüzü bir üçlü olarak saklarsanız, s + c'nin bir kelime mi yoksa bir kelimenin öneki mi olduğunu test etmek sabit zamanda yapılabilir (ayrıca, geçerli kuyruğa ait işaretçi gibi her bir kuyruk datumunda fazladan meta veriler tutmanız şartıyla) yani bu algoritmanın çalışma süresi O (yazılabilecek kelime sayısı) 'dır.

[Düzenle] İşte ben de tam kodlu Python dilinde bir uygulama var:

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

Örnek kullanım:

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

Çıktı:

['fa', 'xi', 'yani', 'io', 'el', 'am', 'balta', 'ae', 'aw', 'mi', 'ma', 'ben', ' lo ',' li ',' oe ',' öküz ',' em ',' ea ',' ea ',' es ',' wa ',' biz ',' wa ',' bo ',' bu ' , ',' aw ',' ae ',' st ',' se ',' sa ',' tu ',' ut ',' fam ',' fae ',' imi ',' eli ',' elm ',' dir ',' ami ',' ama ',' ame ',' aes ',' bız ',' awa ',' awe ',' awa ',' mix ',' mim ',' mil ' , 'mam', 'max', 'mae', 'maw', 'mew', 'mem', 'mes', 'lob', 'lox', 'lei ',' leo ',' yalan ',' lim ',' yağ ',' olm ',' koyun ',' eme ',' balmumu ',' waf ',' wae ',' waw ',' wem ' , 'wea', 'wea', 'oldu', 'waw', 'wae', 'bob', 'blo', 'bub', 'ama', 'ast', 'ase', 'asa', ' tığ ',' awa ',' huşu ',' awa ',' aes ',' swa ',' swa ',' dikmek ',' deniz ',' deniz ',' testere ',' smok ',' küvet ' , 'tut', 'twa', 'twa', 'tst', 'utu', 'fama', 'şöhret', 'ixil', 'imam', 'amli', 'amil', 'ambo', ' aksil ',' aks ',' mimi ',' mima ',' mime ',' milo ','mil ',' mewl ',' mese ',' mesa ',' lolo ',' lobo ',' lima ',' kireç ',' uzuv ',' lile ',' oime ',' oleo ',' olio ' , 'obua', 'obol', 'emim', 'emil', 'doğu', 'kolaylık', 'wame', 'wawa', 'wawa', 'weam', 'batı', 'wese', ' wast ',' wase ',' wawa ',' wawa ',' kaynat ',' bolo ',' bole ',' bobo ',' damla ',' bleo ',' bubo ',' asem ',' saplama ' , 'stut', 'swam', 'yarı', 'seme', 'dikiş', 'seax', 'sasa', 'sawt', 'tutu', 'tuts', 'twae', 'twas', ' twae ',' ilima ',' amble ',' axile ', 'batı', '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 ']

Notlar: Bu program 1 harfli sözcükler çıkarmaz veya sözcük uzunluğuna göre filtrelemez. Bu eklemek kolaydır, ancak sorunla gerçekten alakalı değildir. Ayrıca, birden çok şekilde hecelenebiliyorsa, bazı sözcükleri birden çok kez verir. Belirli bir kelime çok farklı şekillerde hecelenebiliyorsa (en kötü durum: ızgaradaki her harf aynıdır (örneğin 'A') ve sözlüğünüzde 'aaaaaaaaaa' gibi bir kelime), çalışma süresi korkunç bir şekilde katlanır . Yinelenenlerin filtrelenmesi ve sıralama, algoritma bittikten sonra yapılması gereken önemsizdir.


14
Ooo. Birinin tabağa çıkmasına sevindim. Bu işe yarıyor olsa da, daha önce kullandığı mektubu "hatırlamıyor" ve aynı mektubun iki kez kullanılmasına izin verilmeyen kelimeler içeriyor. Ben bir aptal olduğum için, bunu nasıl düzeltebilirim?
Paolo Bergantino

3
Doğru, hangi harflerin ziyaret edildiğini hatırlamıyor, ancak spec =) değerinde belirtilmedi. Bunu düzeltmek için, her kuyruğa ziyaret edilen tüm konumların bir listesini eklemeniz ve ardından bir sonraki karakteri eklemeden önce bu listeyi kontrol etmeniz gerekir.
Adam Rosenfield

Hayır, BoggleWords () içinde. Bir dörtlü (x, y, s, n) saklamak yerine, şu ana kadar ziyaret edilen (x, y) 'nin listesi olan bir dörtlü (x, y, s, n, l) depolarsınız. Sonra her birini (x2, y2) l'e karşı kontrol edersiniz ve yalnızca l'de değilse kabul edersiniz. Sonra yeni l.
Adam Rosenfield

2
Bunu Scramble oynamaktan bıktığımda da yaptım. Ben sadece bir dizi aktif hücre tutabilirsiniz gibi özyinelemeli (BFS yerine DFS) çözüm daha seksi olduğunu düşünüyorum (böylece aynı hücreyi iki kez ziyaret etmeyin). Çok daha temiz sonra listeleri bir sürü tutmak.
Justin Scheiner

2
Bu sonsuz bir döngüye düşmemeli mi? Demek istediğim, (x,y)olası bir takipçi (x+1,y+1). Sonra (x+1,y+1)sıraya itilir. Ancak (x,y)onun da takipçisi olacak (x+1,y+1), bu yüzden aralarında bitmeyen bir geri dönüşe yol açmayacak mı?
SexyBeast

39

Bir sözlük hızlandırması için, sözlük karşılaştırmalarını önceden azaltmak için yapabileceğiniz bir genel dönüşüm / işlem vardır.

Yukarıdaki ızgarada, bazıları yinelenen yalnızca 16 karakter olduğu göz önüne alındığında, elde edilemeyen karakterler içeren girişleri filtreleyerek sözlüğünüzdeki toplam anahtar sayısını büyük ölçüde azaltabilirsiniz.

Bunun bariz bir optimizasyon olduğunu düşündüm ama kimsenin yapmadığını görüyorum.

Beni 200.000 anahtarlık bir sözlükten sadece giriş geçişinde sadece 2.000 anahtarına indirdi. Bu en azından bellek yükünü azaltır ve bellek sonsuz hızlı olmadığından bir yerde bir hız artışıyla eşleştireceğinizden emin olabilirsiniz.

Perl Uygulaması

Uygulamam biraz ağırdır, çünkü sadece içindeki geçerliliği değil, çıkarılan her dizenin tam yolunu bilmeye önem verdim.

Ayrıca teorik olarak içinde delikleri olan bir ızgarayı ve farklı boyutlu çizgileri olan ızgaraları (girişin doğru olduğunu ve bir şekilde hizalandığını varsayarak) çalışmasına izin verecek birkaç uyarlama var.

Erken filtre, daha önce şüphelenildiği gibi, başvurumdaki en önemli darboğazdır, bu hattın 1.5s'den 7.5s'e kadar şiştiğini söylüyor.

Yürütme sırasında tüm tek basamakların kendi geçerli sözcükleri olduğunu düşünüyor gibi görünüyor, ancak sözlük dosyasının nasıl çalıştığından eminim.

Biraz şişirilmiş, ama en azından Ağacı yeniden kullanıyorum :: Trie cpan

Bazıları mevcut uygulamalardan kısmen ilham aldı, bazıları zaten aklımdaydı.

Yapıcı Eleştiri ve iyileştirilebilecek yollar hoş geldiniz (/ bana CPAN'ı bir boggle çözücü için hiç aramadığını söylüyor , ancak bu daha eğlenceli bir işti)

yeni kriterler için güncellendi

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

Karşılaştırma için kemer / yürütme bilgileri:

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

Bu Regex Optimizasyonu hakkında daha fazla mumblings

Kullandığım regex optimizasyonu çoklu çözülmüş sözlükler için işe yaramaz ve çoklu çözme için önceden kesilmiş değil tam bir sözlük isteyeceksiniz.

Bununla birlikte, bir kerelik çözmeler için gerçekten hızlı olduğunu söyledi. (Perl regex C cinsindendir! :))

İşte bazı değişen kod eklemeleri:

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');
      }
  });
}
           filtre uygulanmamış filtreler
filtresiz 8.16 - -94%
filtrelenmiş 0.464 1658% -

ps: 8.16 * 200 = 27 dakika.


2
Optimizasyon kulübünde başarısız olduğumu biliyorum, ancak kodun gerçek işine girmeden önce hız sorunları yaşadım ve giriş süresini 2s'den 1.2s'ye düşürmek benim için çok şey ifade ediyor.
Kent Fredric

/ me şimdi regex ve girişleri atlamak için bir karma anahtar eklemek için daha az zaman aldı garip kaydetti .
Kent Fredric

Güzel, bir Perl uygulaması! Şimdi koşacağım.
Paolo Bergantino

Blerg, web sunucumda Tree :: Trie'yi kurmakta zorlanıyor. :(
Paolo Bergantino

3
Son raporu nasıl oluşturdunuz (arch / execution info)? Yararlı görünüyor.
jmanning2k

33

Sorunu iki parçaya bölebilirsiniz:

  1. Izgaradaki olası dizeleri numaralandıracak bir tür arama algoritması.
  2. Bir dizenin geçerli bir kelime olup olmadığını test etmenin bir yolu.

İdeal olarak, (2) bir dizenin geçerli bir sözcüğün öneki olup olmadığını test etmenin bir yolunu da içermelidir - bu, aramanızı budamanıza ve tüm zamandan tasarruf etmenize olanak tanır.

Adam Rosenfield'ın Trie'si (2) için bir çözümdür. Zarif ve muhtemelen algoritma uzmanınızın tercih ettiği şey, ancak modern diller ve modern bilgisayarlar ile biraz daha tembel olabiliriz. Ayrıca Kent'in önerdiği gibi, ızgarada bulunmayan harfleri içeren kelimeleri atarak sözlük boyutumuzu azaltabiliriz. İşte bazı pitonlar:

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

Vay; sabit zamanlı önek testi. Bağladığınız sözlüğü yüklemek birkaç saniye sürer, ancak yalnızca birkaç :-) (fark edin words <= prefixes)

Şimdi, bölüm (1) için, grafikler açısından düşünmeye meyilliyim. Ben de şuna benzer bir sözlük oluşturacağım:

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

yani graph[(x, y)]konumdan ulaşabileceğiniz koordinat kümesidir (x, y). Ayrıca Noneher şeye bağlanacak bir kukla düğüm ekleyeceğim .

Bina biraz beceriksiz, çünkü 8 olası pozisyon var ve sınır kontrolü yapmanız gerekiyor. İşte buna göre beceriksiz python kodu:

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

Bu kod ayrıca (x,y)karşılık gelen karakterle bir sözlük eşlemesi oluşturur . Bu, bir konum listesini bir kelimeye dönüştürmemi sağlar:

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

Son olarak, önce derinlemesine bir arama yapıyoruz. Temel prosedür:

  1. Arama belirli bir düğüme gelir.
  2. Yolun bir kelimenin parçası olup olmadığını kontrol edin. Değilse, bu dalı daha fazla araştırmayın.
  3. Yolun bir kelime olup olmadığını kontrol edin. Öyleyse, sonuç listesine ekleyin.
  4. Yolun bir parçası olmayan tüm çocukları keşfedin.

Python:

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

    if word not in prefixes:
        return

    if word in words:
        results.add(word)

    for child in graph[position]:
        if child not in prefix:
            find_words(graph, chardict, child, prefix+[child], results, words, prefixes)

Kodu şu şekilde çalıştırın:

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)

ve rescevapları görmek için inceleyin . Örneğiniz için bulunan ve boyuta göre sıralanmış kelimelerin listesi:

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

Kod, sözlüğü yüklemek için (tam anlamıyla) birkaç saniye sürer, ancak gerisi makinemde anındadır.


Çok hoş! Çok hızlı. Başkasının plakaya çıkıp çıkmadığını görmek için etrafta bekleyeceğim, ama cevabınız şimdiye kadar iyi görünüyor.
Paolo Bergantino

Neden "embole" ın tek 6 harfli kelimeniz olduğu konusunda kafam karıştı, bunun için 10 farklı kelimem var. Aynı düğümü iki kez ziyaret etmeyi yasakladığınız anlaşılıyor ve OP'nin belirttiği gibi, bu adil bir oyun.
Kent Fredric

1
tamam, hes hala "FAMBLE", "WAMBLE" ve "SEMBLE" karakterlerini atmayan bir hata aldı.
Kent Fredric

İyi tespit! Hata kümesi önekleri oluşturma oldu: Bunun range(len(w)+1)yerine kullanmak gerekiyordu range(len(w)). Bunu iddia ettim words <= prefixesama görünüşe göre bunu test etmedim: - /
John Fouhy

1
Bu, bir DFS'nin nasıl çalıştığını ve bir DFS'nin nasıl uygulanacağını öğrenmeme yardımcı oldu. Bir yorum dışında bunun için takdir göstermek için hiçbir şekilde emin değildi. Teşekkürler!
Graham Smith

23

Java denemem. Dosya okumak ve trie oluşturmak yaklaşık 2 s sürer ve bulmacayı çözmek için yaklaşık 50 ms. Soruda bağlantılı sözlüğü kullandım (İngilizce'de fae, ima gibi var olduğunu bilmediğim birkaç kelime var)

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

Kaynak kodu 6 sınıftan oluşmaktadır. Onları aşağıya göndereceğim (eğer bu StackOverflow'da doğru uygulama değilse, lütfen bana bildirin).

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
Çıktımı diğer StackOverflowers çıktılarıyla karşılaştırıyordum ve Adam, John ve rvarcher'ın çıktılarında bazı kelimeler eksik görünüyor. Örneğin, "Mwa" sözlükte (evet!), Ancak Adam, John ve rvarcher çıktılarında döndürülmez. Paolo'nun PHP bağlantısında iki kez döndürülür.
gineer

1
Bunu kopyalayıp yapıştırarak denedim. "Okuyor ..." ve "Okumayı bitir ..." yazıyor ama bundan sonra hiçbir şey görünmüyor. Hiçbir eşleşme görüntülenmiyor.
MikkoP

23

Sanırım muhtemelen zamanınızın çoğunu harf kılavuzunuz tarafından oluşturulamayacak kelimelerle eşleştirmeye çalışacaksınız. Yani, ilk yapacağım şey bu adımı hızlandırmaya çalışmak ve bu sizi oraya en çok götürecek.

Bunun için ızgarayı, baktığınız harf geçişiyle indekslediğiniz olası "hareketlerin" tablosu olarak yeniden ifade edeceğim.

Her harfe tüm alfabenizden bir sayı atayarak başlayın (A = 0, B = 1, C = 2, ... vb.).

Şimdi bu örneği ele alalım:

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

Şimdilik, sahip olduğumuz harflerin alfabesini kullanalım (genellikle her seferinde aynı tüm alfabeyi kullanmak istersiniz):

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

Ardından, belirli bir harf geçişinizin olup olmadığını söyleyen bir 2D boolean dizisi yaparsınız:

     |  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

Şimdi kelime listenizi inceleyin ve kelimeleri geçişlere dönüştürün:

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

Ardından, tablolarınıza bakarak bu geçişlere izin verilip verilmediğini kontrol edin:

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

Hepsine izin verilirse, bu kelimenin bulunma ihtimali vardır.

Örneğin tablonuzdaki bu giriş yanlış olduğundan "kask" kelimesi 4. geçişte (m'den e: helMEt'e) göz ardı edilebilir.

Ve ilk (h'den a'ya) geçişe izin verilmediğinden (tablonuzda bile mevcut değil) ve hamster kelimesi dışlanabilir.

Şimdi, muhtemelen ortadan kaldırmamanız gereken çok az kelime için, bunları şimdi yaptığınız gibi veya buradaki diğer cevapların bazılarında önerildiği gibi ızgarada bulmaya çalışın. Bu, ızgaranızdaki aynı harfler arasındaki sıçramalardan kaynaklanan yanlış pozitifleri önlemek içindir. Örneğin, "yardım" kelimesine tablo tarafından izin verilir, ancak ızgara tarafından izin verilmez.

Bu fikirle ilgili bazı performans iyileştirme ipuçları:

  1. Bir 2D dizisi kullanmak yerine, 1D dizisi kullanın ve ikinci harfin dizinini kendiniz hesaplayın. Yani, yukarıdaki gibi 12x12 bir dizi yerine, 144 uzunluğunda bir 1D dizisi yapın. Daha sonra her zaman aynı alfabeyi (yani standart İngilizce alfabe için bir 26x26 = 676x1 dizisi) kullanırsanız, ızgarada tüm harfler görünmese bile , endeksleri sözlük kelimelerinizle eşleşmesi için test etmeniz gereken bu 1D dizisine önceden hesaplayabilirsiniz. Örneğin, yukarıdaki örnekte 'merhaba' endeksleri

    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. Fikri 3B tabloya (1D dizisi olarak ifade edilir), yani izin verilen tüm 3 harfli kombinasyonlara uzatın. Bu şekilde daha fazla kelimeyi hemen ortadan kaldırabilir ve her kelime için dizi arama sayısını 1 azaltabilirsiniz: 'Merhaba' için sadece 3 dizi aramasına ihtiyacınız vardır: hel, ell, llo. Bu tabloyu oluşturmak çok hızlı olacak, çünkü ızgarada sadece 400 olası 3 harfli hareket var.

  3. Izgaradaki tablonuza eklemeniz gereken hareketlerin indekslerini önceden hesaplayın. Yukarıdaki örnek için aşağıdaki girişleri 'Doğru' olarak ayarlamanız gerekir:

    (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. Ayrıca, oyun kılavuzunuzu 16 girişli bir 1-D dizide temsil edin ve tablo 3'te önceden hesaplanmış olarak dizinleri bu diziye dahil edin.

Eminim bu yaklaşımı kullanırsanız, önceden hesaplanmış ve zaten belleğe yüklenmişse, kodunuzu inanılmaz hızlı çalıştırabilirsiniz.

BTW: Yapacak başka bir güzel şey, eğer bir oyun inşa ediyorsanız, bu tür şeyleri hemen arka planda çalıştırmaktır. Kullanıcı hala uygulamanızdaki başlık ekranına bakarken ve "Oynat" a basmak için parmağını yerine getirirken ilk oyunu oluşturmaya ve çözmeye başlayın. Ardından kullanıcı bir öncekini oynadıkça bir sonraki oyunu oluşturun ve çözün. Bu, kodunuzu çalıştırmak için size çok zaman vermelidir.

(Bu sorunu seviyorum, bu yüzden muhtemelen nasıl bir performans sergilediğini görmek için önümüzdeki günlerde Java teklifimi uygulamak için cazip olacak ... Ben bir kez kod burada göndereceğiz.)

GÜNCELLEME:

Tamam, bugün biraz zaman geçirdim ve bu fikri Java'da uyguladım:

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

İşte bazı sonuçlar:

Orijinal sorudaki (DGHI ...) yayınlanan resimdeki ızgara için:

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

Orijinal soruda (FXIE ...) örnek olarak gönderilen mektuplar için

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

Aşağıdaki 5x5 ızgara için:

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

bunu verir:

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

Bunun için TWL06 Turnuvası Scrabble Kelime Listesini kullandım , çünkü orijinal sorudaki bağlantı artık çalışmıyor. Bu dosya 1.85MB, bu yüzden biraz daha kısa. Ve buildDictionaryfonksiyon 3 kelimeden az olan tüm kelimeleri atar.

İşte bunun performansı hakkında birkaç gözlem:

  • Victor Nicollet'in OCaml uygulamasının rapor edilen performansından yaklaşık 10 kat daha yavaş. Bunun nedeni farklı algoritma, daha kısa sözlük olması, kodunun derlenmesi ve benim bir Java sanal makinesinde çalışması ya da bilgisayarlarımızın performansı (benimki WinXP çalıştıran Intel Q6600 @ 2.4MHz'dir), Bilmiyorum. Ancak orijinal sorunun sonunda alıntılanan diğer uygulamaların sonuçlarından çok daha hızlıdır. Yani, bu algoritma trie sözlüğünden daha üstün olsun ya da olmasın, bu noktada bilmiyorum.

  • Kullanılan tablo yöntemi checkWordTriplets(), gerçek cevaplara çok iyi bir yaklaşım vermektedir. Sadece 3-05 Ocak kelimeler başarısız olur geçti checkWords()testi (Bkz adayların sayısını vs fiili sözcük sayısı üzerinde).

  • Yukarıda göremediğiniz bir şey: checkWordTriplets()İşlev yaklaşık 3,65 ms sürer ve bu nedenle arama sürecinde tamamen baskındır. checkWords()Fonksiyon hemen hemen kalan 0,05-0,20 ms kaplıyor.

  • checkWordTriplets()İşlevin yürütme süresi doğrusal olarak sözlük boyutuna bağlıdır ve neredeyse pano boyutundan bağımsızdır!

  • Uygulama süresi checkWords(), pano boyutuna ve hariç bırakılmayan kelime sayısına bağlıdır checkWordTriplets().

  • Yukarıdaki checkWords()uygulama ben geldi en aptal ilk sürümüdür. Temel olarak hiç optimize edilmemiştir. Ancak bununla karşılaştırıldığında checkWordTriplets()uygulamanın toplam performansı için önemsizdir, bu yüzden endişelenmedim. Ancak , tahta boyutu büyürse, bu işlev yavaşlar ve yavaşlar ve sonunda önemli olmaya başlar. Ardından, optimize edilmesi de gerekir.

  • Bu kodla ilgili güzel bir şey esnekliğidir:

    • Pano boyutunu kolayca değiştirebilirsiniz: Satır 10'u ve Dize dizisini güncelleyin initializeBoard().
    • Daha büyük / farklı alfabeleri destekleyebilir ve herhangi bir performans yükü olmadan 'Qu' harfini tek harf olarak işlemek gibi işlemleri yapabilir. Bunu yapmak için, satır 9'u ve karakterlerin sayılara dönüştürüldüğü birkaç yeri güncellemek gerekir (şu anda sadece ASCII değerinden 65 çıkarılarak)

Tamam, ama şimdi bu yazının yeterince uzun olduğunu düşünüyorum. Sorularınızı kesinlikle yanıtlayabilirim, ama bunu yorumlara taşıyalım.


Güzel cevap. Uygulamanızı Java'da görmek istiyorum.
MikkoP

@MikkoP Tamamlandı! :) Yaklaşık 3 saat 220 satır kod aldı. Bir öğleden sonra geçmek için iyi bir yol. Nasıl çalıştığı hakkında herhangi bir sorunuz varsa bize bildirin ... :)
Markus A.

Kodu gönderdiğiniz için teşekkürler! Eksik ithalatları ekledikten sonra kendi sözlüğümle denedim. Hatta bir ArrayIndexOutOfBoundException alıyorum ok = possibleTriplets[entry.triplets[t]];. hmm?
MikkoP

@MikkoP Bu kod, sözlüğün yalnızca AZ büyük harflerini içerdiğini varsaymak için yazılmıştır. Crux 34. sıradadır: entry.letters[i] = (byte) letter - 65;Sadece ASCII değerini alır ve 65'i ("A") çıkarır. Sözlüğünüzde Büyük Harfler veya küçük harfler varsa, bu, satır 9'daki alfabe boyutunun ayarlanmasıyla planlanmayan 31'den büyük değerler verecektir. Diğer harfleri desteklemek için bu satırı genişletmeniz gerekir bunları alfabe boyutunun izin verdiği aralığa eşlemek için.
Markus

1
@AlexanderN Muhtemelen mantığı doğru anlıyorsunuz. Harf ızgarasını kopyalarken bir hata yaptım ... Üzgünüm ... (sabit)
Markus A.

19

Şaşırtıcı bir şekilde, hiç kimse bunun bir PHP sürümünü denemedi.

Bu John Fouhy'nin Python çözümünün çalışan bir PHP sürümüdür.

Herkesin cevabından bazı göstergeler almama rağmen, bu çoğunlukla John'dan kopyalandı.

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

İşte olan canlı bağlantı senin de denemek istiyorum. Yerel makinemde ~ 2s olsa da, web sunucumda ~ 5s sürer. Her iki durumda da, çok hızlı değil. Yine de, oldukça iğrenç, bu yüzden zamanın önemli ölçüde azaltılabileceğini hayal edebiliyorum. Bunu nasıl başaracağına dair herhangi bir işaret takdir edilecektir. PHP'nin tuples eksikliği, koordinatları çalışmak için garip hale getirdi ve cehennemin neler olduğunu anlayamıyorum hiç yardımcı olmadı.

DÜZENLEME : Birkaç düzeltme yerel olarak 1 saniyeden az sürmesini sağlar.


+1 @ "ve cehennemin neler olduğunu anlayamama hiç yardımcı olmadı." lol. Dürüstlüğü seviyorum!
dna123

PHP bilmiyorum, ama ilk denediğim şey '/ ['. implode ('', $ alfabe). '] {3,} $ /' döngüden çıktı. Yani, bir değişken ayarlayın ve bu değişkeni döngü içinde kullanın.
Darius Bacon

PHP derlenmiş düzenli ifadeler iş parçacığı başına global bir önbellek tutar eminim, ama yine de deneyeceğim.
Paolo Bergantino

1
@Daniel: Görünüşe göre bu benim web sunucum. Yerel olarak koştuğumda olmaz. Omuz silkme. Onu avlamak gibi hissetme.
Paolo Bergantino

2
Sonunda find_words işlevinde 7. parametresi olarak ne ayarlanmalıdır?
MikkoP

16

VB ile ilgilenmiyor musunuz? :) Direnemedim. Bunu burada sunulan birçok çözümden farklı bir şekilde çözdüm.

Zamanlarım:

  • Sözlük ve sözcük öneklerini bir hashtable'a yükleme: .5 ila 1 saniye.
  • Kelimeleri bulmak: ortalama 10 milisaniyenin altında.

DÜZENLEME: Web barındırma sunucusundaki sözlük yükleme süreleri ev bilgisayarımdan yaklaşık 1,5 saniye daha uzun çalışıyor.

Sunucuda bir yük ile zamanların ne kadar kötü bozulacağını bilmiyorum.

Çözümümü .Net'te bir web sayfası olarak yazdım. myvrad.com/boggle

Orijinal soruda belirtilen sözlüğü kullanıyorum.

Harfler bir sözcük içinde yeniden kullanılmaz. Yalnızca 3 karakter veya daha uzun kelimeler bulunur.

Bir trie yerine tüm benzersiz kelime öneklerini ve kelimelerini içeren bir hashtable kullanıyorum. Trie'nin ne olduğunu bilmiyordum, o yüzden orada bir şey öğrendim. Tam kelimelere ek olarak kelimelerin öneklerinin bir listesini oluşturma fikri, sonunda zamanlarımı saygın bir sayıya indirdi.

Ek ayrıntılar için kod yorumlarını okuyun.

İşte kod:

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

Burada [x] [y] yerine ap sistemini kullandığınızı varsayacağım çünkü ikincisi VB'de oldukça karmaşık mı? Ben bir kez, yani: dizi (dizi (1, "merhaba"), 1, "merhaba", dizi ()), 2 yönlü dinamik dizi almaya çalışırken bir gün geçirdim, hala nasıl yapacağımı bilmiyorum that: P
Kent Fredric

PHP ve Perl 2'de dim dizileri eğlencelidir. VB'de yapılabilir, ancak buna eğlenceli bir süreç demezdim. Dim Arr (,) Tamsayı Olarak = {{1,1}, {0,0}}. AP süreci benden büyüdü ve kendimi şebekeye koyup 'buradan nereye gidebilirim?' Diye sordu. Bunun katı bir çözüm olduğunu biliyorum ama burada çalışıyor.
rvarcher

Ohh VB.NET'i beğendim ... URL'yi denedim ama işe yaramadı. Kodunuzu Windows Forms olarak kendim yeniden oluşturmak zorunda kaldım ve işe yarıyor. Teşekkürler.
Ahmed Eissa

11

Sorun ifadesini görür görmez "Trie" yi düşündüm. Fakat diğer posterlerin bu yaklaşımı kullandığını görünce, farklı olmak için başka bir yaklaşım aradım. Ne yazık ki, Trie yaklaşımı daha iyi performans gösteriyor. Kent'in Perl çözümünü makinemde çalıştırdım ve sözlük dosyamı kullanmak için uyarladıktan sonra çalışması 0.31 saniye sürdü . Kendi perl uygulamamın çalışması için 0,54 saniye gerekiyordu.

Bu benim yaklaşımımdı:

  1. Yasal geçişleri modellemek için bir geçiş karması oluşturun.

  2. Tüm 16 ^ 3 olası üç harfli kombinasyonları yineleyin.

    • Döngüde, yasadışı geçişleri hariç tutun ve aynı kareye ziyaretleri tekrarlayın. Tüm yasal 3 harfli dizileri oluşturun ve bunları bir karma içinde saklayın.
  3. Ardından sözlükteki tüm kelimeler arasında dolaşın.

    • Çok uzun veya kısa kelimeleri hariç tutun
    • Her sözcüğün üzerinde 3 harfli bir pencere kaydırın ve 2. adımdaki 3 harfli kombinasyonlar arasında olup olmadığını görün. Başarısız olan kelimeleri hariç tutun. Bu, çoğu eşleşmeyi ortadan kaldırır.
    • Hala elimine edilmemişse, bulmacanın içinden geçerek yolun oluşturulup oluşturulmadığını görmek için özyinelemeli bir algoritma kullanın. (Bu kısım yavaş fakat seyrek olarak adlandırılıyor.)
  4. Bulduğum kelimeleri yazdırın.

    3 harfli ve 4 harfli dizileri denedim, ancak 4 harfli diziler programı yavaşlattı.

Kodumda, sözlüğüm için / usr / share / dict / words kullanıyorum. MAC OS X ve birçok Unix sisteminde standart olarak gelir. İsterseniz başka bir dosya kullanabilirsiniz. Farklı bir bulmacayı kırmak için, @puzzle değişkenini değiştirmeniz yeterlidir. Bu, daha büyük matrislere uyum sağlamak için kolay olacaktır. Sadece% geçişler karmasını ve% legalTransitions karmasını değiştirmeniz gerekir.

Bu çözümün gücü, kodun kısa olması ve veri yapılarının basit olmasıdır.

İşte Perl kodu (çok fazla global değişken kullanan, biliyorum):

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

Sözlüğün yeri değişti mi? Çözümümü herkesle karşılaştırmak istediğim için sözlük kelimelerini bulmaya çalıştım ama / usr / share / dict adresindeki bağlantıda bulamadım. Oldukça eski bir iplik olduğunu biliyorum ama bana işaret edebilirseniz harika olur. Yardımın için şimdiden teşekkür ederim.
Naman

Mac bilgisayarımı şu anda elinizde bulundurmayın. İhtiyacınız olan tek şey, bir satırdan diğerine, yeni satırlarla ayrılmış İngilizce kelimeler içeren bir dosya. İnternette böyle bir dosya bulabilirsiniz. Biri burada: mieliestronk.com/corncob_lowercase.txt ama muhtemelen bundan daha fazla kelime içeren listeler var.
Paul Chernoch

Cevabınız için çok teşekkürler. Bunu ubuntu dosyalarında buldum.
Naman

9

Süper geç olduğumu biliyorum ama bir süre önce PHP'de yaptım - sadece eğlence için ...

http://www.lostsockdesign.com.au/sandbox/boggle/index.php?letters=fxieamloewbxastu 0.90108 saniyede 75 kelime (133 puan) bulundu

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

Programın gerçekte ne yaptığını gösteren bazı göstergeler sunar - her harf, her bir '.' izlemeye çalıştığı yolu gösterir. Daha fazla '.' daha fazla arama var.

Eğer kodu istiyorsanız bana bildirin ... PHP ve HTML korkunç bir karışımı asla gün ışığına görmek için tasarlanmıştı bu yüzden burada göndermek cesaret edemiyorum: P


9

En iyi 10 yoğun 5x5 Boggle pano sorununa bir çözüm üzerinde çalışarak 3 ay geçirdim.

Sorun şimdi çözülmüş ve 5 web sayfasında tam açıklama ile çözülmüştür. Lütfen sorular ile bana ulaşın.

Pano analiz algoritması, doğrudan alt bilgiler ve bir zaman damgası izleme mekanizması ile Yönlendirilmiş Bir Çevrimsel Kelime Grafiği üzerinden tahta kareleri yalancı özyinelemeli olarak geçmek için açık bir yığın kullanır. Bu, dünyanın en gelişmiş sözlük veri yapısı olabilir.

Şema, dört çekirdekli bir saniyede yaklaşık 10.000 çok iyi tahta değerlendiriyor. (9500+ puan)

Ana Web Sayfası:

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

Bileşen Web Sayfaları:

Optimal Skor Tablosu - http://www.pathcom.com/~vadco/binary.html

Gelişmiş Sözlük Yapısı - http://www.pathcom.com/~vadco/adtdawg.html

Yönetim Kurulu Analiz Algoritması - http://www.pathcom.com/~vadco/guns.html

Paralel Toplu İşleme - http://www.pathcom.com/~vadco/parallel.html

- Bu kapsamlı çalışma, sadece en iyisini isteyen birini ilgilendirir.


4
Analiziniz ilginç, ancak sonuçlarınız teknik olarak Boggle panoları değil. 5x5 boggle oyunu, BJKQXZ yüzlerini içeren bir kalıp içerir, uygulamanız açıkça bu harflerin tümünü hariç tutar ve böylece tahta pozisyonu gerçek bir Boggle oyununda aslında mümkün değildir.
MarkPflug

4

Arama algoritmanız, aramanız devam ederken kelime listesini sürekli olarak düşürüyor mu?

Örneğin, yukarıdaki aramada, kelimelerinizin başlayabileceği yalnızca 13 harf vardır (etkili olarak başlangıç ​​harflerinin yarısına kadar azalır).

Daha fazla harf permütasyonu ekledikçe, kullanılabilir kelime kümelerini daha da azaltarak gerekli aramayı azaltacaktır.

Oradan başlardım.


4

Tam bir çözüme daha fazla düşünmek zorundayım, ancak kullanışlı bir optimizasyon olarak, tüm diyagramlara göre diyagram ve trigramların (2- ve 3 harfli kombinasyonlar) frekans tablolarını önceden hesaplamaya değip değmeyeceğini merak ediyorum. kelimelerinize ekleyin ve aramanıza öncelik vermek için bunu kullanın. Kelimelerin başlangıç ​​harfleri ile giderdim. Dolayısıyla sözlüğünüz "Hindistan", "Su", "Aşırı" ve "Olağanüstü" kelimelerini içeriyorsa, önceden hesaplanmış tablonuz şöyle olabilir:

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

Sonra bu digramları ortak sıraya göre arayın (önce EX, sonra WA / IN)


4

İlk olarak, C # dil tasarımcılarından birinin ilgili bir sorunu nasıl çözdüğünü okuyun: http://blogs.msdn.com/ericlippert/archive/2009/02/04/a-nasality-talisman-for-the-sultana-analyst.aspx .

Onun gibi, bir sözlükle başlayabilir ve alfabetik olarak sıralanmış bir harf dizisinden bu harflerden hecelenebilecek sözcüklerin listesine bir sözlük oluşturarak kelimeleri canonacalize edebilirsiniz.

Ardından, tahtadan olası kelimeleri oluşturmaya ve aramaya başlayın. Bunun sizi oldukça uzağa götüreceğinden şüpheleniyorum, ama kesinlikle işleri hızlandıracak daha fazla numara var.


4

Kelimelere dayalı bir harf ağacı yapmayı öneririm. Ağaç, aşağıdaki gibi bir harf yapısından oluşacaktır:

letter: char
isWord: boolean

Daha sonra, her derinlik yeni bir harf ekleyerek ağacı inşa edersiniz. Başka bir deyişle, birinci seviyede alfabe olurdu; daha sonra bu ağaçların her birinden, 26 kelimeden başka bir giriş daha olurdu ve bu şekilde tüm kelimeleri heceleyene kadar. Bu ayrıştırılmış ağaca asın ve olası tüm cevapları aramak daha hızlı olacaktır.

Bu ayrıştırılmış ağaç ile çok hızlı bir şekilde çözümler bulabilirsiniz. İşte sözde kod:

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.

Bu biraz dinamik programlama ile hızlandırılabilir. Örneğin, örneğinizde, iki 'A'nın her ikisi de' E 've' W'nin yanındadır (onlara vurdukları noktadan itibaren) aynı olacaktır. Gerçekten bunun kodunu hecelemek için yeterli zamanım yok, ama fikri toplayabileceğini düşünüyorum.

Ayrıca, Google için "Boggle çözücü" için başka çözümler bulacaksınız eminim.



3

Neşeli. Neredeyse aynı soruyu birkaç gün önce aynı lanet oyun yüzünden yayınladım! Ancak yaptım çünkü sadece google için boggle çözücü python aradı ve istediğim tüm cevapları aldım.


Popüler adının "boggle" olduğunu bilmiyordum, ama google'da bazı şeyler buldum, sadece insanların SO ile ne arayacağını merak ediyordum. :)
Paolo Bergantino

3

Bu sorunun zamanının geldiğini ve gittiğini anlıyorum, ama kendim bir çözücü üzerinde çalıştığım ve bu konuda dolaşırken tökezlediğimden, diğerlerinden biraz farklı göründüğü için benim bir referans göndermem gerektiğini düşündüm.

Oyun tahtası için düz bir dizi ile gitmeyi ve tahtadaki her harften tekrarlayan avlar yapmayı seçtim, geçerli komşustan geçerli komşuya geçerek, geçerli bir önek varsa dizinin geçerli bir listesi varsa avı genişletirim. Geçerli sözcüğün kavramını geçerken, sözcükleri oluşturan harflerin değil, dizinlerin listesi haline getirilir. Dizini kontrol ederken, dizinler harflere çevrilir ve kontrol yapılır.

Dizin, bir trie benzeri bir kaba kuvvet sözlüğüdür, ancak dizinin Pythonic sorgularına izin verir. Listede 'kedi' ve 'cater' kelimeleri varsa, bunu sözlükte alırsınız:

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

Geçerli_word 'ca' ise, 'ca' in dTrue döndürdüğü için geçerli bir önek olduğunu bilirsiniz (bu nedenle tahta geçişine devam edin). Current_word 'cat' ise geçerli bir kelime olduğunu biliyorsunuz çünkü geçerli bir önek ve'cat' in d['cat'] True de döndürür.

Bunun gibi hissedilirse, çok yavaş görünmeyen bazı okunabilir kodlara izin verilir. Herkes gibi bu sistemdeki gider de endeksi okuyor / oluşturuyor. Tahtayı çözmek oldukça fazla gürültü.

Kod http://gist.github.com/268079 adresindedir . Çok sayıda açık geçerlilik kontrolü ile kasıtlı olarak dikey ve naiftir çünkü problemi bir sürü sihir veya müstehcenlikle karıştırmadan anlamak istedim.


3

Çözücümü C ++ 'da yazdım. Özel bir ağaç yapısı uyguladım. Bir üçlü olarak kabul edilebileceğinden emin değilim ama benzer. Her düğümün 26 dalı vardır, alfabenin her harfi için 1. Sözlük tahtasının dallarını sözlüğümün dallarına paralel olarak geçiyorum. Şube sözlükte yoksa, Boggle panosunda aramayı bırakıyorum. Tahtadaki tüm harfleri ints'e dönüştürüyorum. Yani 'A' = 0. Sadece diziler olduğundan, arama daima O (1) 'dir. Her düğüm bir kelimeyi tamamlarsa ve çocuklarında kaç kelime varsa saklar. Aynı sözcükleri tekrar tekrar aramayı azaltan kelimelerin bulunmasıyla ağaç budanır. Budamanın da O (1) olduğuna inanıyorum.

İşlemci: Pentium SU2700 1.3GHz
RAM: 3gb

<1 saniyede 178,590 kelimelik sözlük yükler.
100x100 Boggle'ı (boggle.txt) 4 saniyede çözer. ~ 44.000 kelime bulundu.
4x4 Boggle'ı çözmek anlamlı bir karşılaştırma yapmak için çok hızlı. :)

Hızlı Boggle Çözücü GitHub Repo


2

N sıralı ve M sütunlu bir Boggle tahtası verildiğinde, aşağıdakileri varsayalım:

  • N * M olası sözcük sayısından önemli ölçüde fazla
  • N * M mümkün olan en uzun kelimeden çok daha büyük

Bu varsayımlar altında, bu çözeltinin karmaşıklığı O (N * M) dir.

Sanırım bu bir örnek kart için çalışma sürelerini birçok yönden karşılaştırmak noktayı kaçırıyor, ancak tamlık uğruna, bu çözüm modern MacBook Pro'mda <0.2s içinde tamamlanıyor.

Bu çözüm, topluluktaki her kelime için tüm olası yolları bulacaktır.

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

Bu çözüm aynı zamanda verilen tahtada arama yapmak için yön verir

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

Çıktı:

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

Kod:

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

Ben var OCaml bir çözüm uygulamaya . Bir sözlüğü bir üçlü olarak önceden derler ve işlemeyi daha da hızlandırmak için bir sözcükte asla görünmeyen kenarları ortadan kaldırmak için iki harfli sıra frekansları kullanır.

Örnek kartınızı 0.35ms'de çözer (çoğunlukla trie'nin belleğe yüklenmesi ile ilgili olan 6ms'lik ek bir başlatma süresi ile).

Bulunan çözümler:

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

Bu güzel, ama burada yayınlanan tüm zamanlar sözlüğü belleğe yüklemek için herhangi bir "başlangıç" süresi içerir, bu nedenle 0.35 diğer zamanlar ile karşılaştırmak doğru olmaktan oldukça uzaktır. Ayrıca, farklı bir sözlük mü kullanıyorsunuz? Bazı kelimeleri kaçırıyorsun. Her iki durumda da +1
Paolo Bergantino

Başlangıç ​​süresi 6 ms sürer, bu nedenle tam bir çalışma için 6.35 ms'ye bakarsınız. Yerel /usr/share/dictsözlüğümü kullanıyorum ve bazı kelimeler gerçekten eksik (EMBOLE gibi).
Victor Nicollet

1

Bir Node.JS JavaScript çözümü. Sözlük dosyasının okunmasını içeren bir saniyeden daha kısa sürede 100 benzersiz kelimeyi hesaplar (MBA 2012).

Çıktı:
["FAM", "TUX", "TUB", "FAE", "ELI", "ELM", "ELB", "TWA", "TWA", "SAW", "AMI", "SWA" "swa", "AME", "DENİZ", "SEW", "AES", "AWL", "AWE", "DENİZ", "AWA", "MIX", "MIL", "AST"," ASE", "MAKS", "MAE", "MAW", "MEW", "AWE", "MES", "AWL", "YALAN", "LIM", "AWA", "AES", "AMA" "BLO", "WAS", "WAE", "WEA", "LEI", "LEO", "LOB", "LOX", "WEM", "YAĞ", "OLM", "WEA"," WAE", "WAX", "WAF","MILO", "DOĞU", "WAME", "TWAS", "TWAE", "EMİL", "WEAM", "OIME", "AXIL", "BATI", "TWAE", "LIMB", "WASE ", "idin", "Bleo", "STUB", "BOIL", "BOLE", "KİREÇ", "Sawt", "LİMA", "MESA", "mewl", "AKS", "FAME", "ASEM", "MİL", "AMIL", "SEAX", "DİKİŞ", "YARI", "swam", "AMBO", "AMLI", "axile", "amble", "Swami", "AWEST ", "AWEST", "Limax", "LIMES", "LIMBU", "LİMBO", "Em", "harfini andıran bir hal", "embol", "WAMBLE", "FAMBLE"]DOĞU", "WAME", "TWAS", "TWAE", "EMİL", "WEAM", "OIME", "AXIL", "BATI", "TWAE", "LIMB", "WASE", "idin" "Bleo", "STUB", "BOIL", "BOLE", "KİREÇ", "Sawt", "LİMA", "MESA", "mewl", "AKS", "FAME", "ASEM"," MİL", "AMIL", "SEAX", "DİKİŞ", "YARI", "swam", "AMBO", "AMLI", "axile", "amble", "Swami", "AWEST", "AWEST" "Limax", "LIMES", "LIMBU", "LİMBO", "Em", "harfini andıran bir hal", "embol", "WAMBLE", "FAMBLE"]DOĞU", "WAME", "TWAS", "TWAE", "EMİL", "WEAM", "OIME", "AXIL", "BATI", "TWAE", "LIMB", "WASE", "idin" "Bleo", "STUB", "BOIL", "BOLE", "KİREÇ", "Sawt", "LİMA", "MESA", "mewl", "AKS", "FAME", "ASEM"," MİL", "AMIL", "SEAX", "DİKİŞ", "YARI", "swam", "AMBO", "AMLI", "axile", "amble", "Swami", "AWEST", "AWEST" "Limax", "LIMES", "LIMBU", "LİMBO", "Em", "harfini andıran bir hal", "embol", "WAMBLE", "FAMBLE"]"TWAE", "EMİL", "WEAM", "OIME", "AXIL", "BATI", "TWAE", "LIMB", "WASE", "idin", "Bleo", "STUB", "BOIL ", "BOLE", "KİREÇ", "Sawt", "LİMA", "MESA", "mewl", "AKS", "FAME", "ASEM", "MİL", "AMIL", "SEAX", "DİKİŞ", "YARI", "swam", "AMBO", "AMLI", "axile", "amble", "Swami", "AWEST", "AWEST", "Limax", "LIMES", "LIMBU ", "LİMBO", "Em", "harfini andıran bir hal", "embol", "WAMBLE", "FAMBLE"]"TWAE", "EMİL", "WEAM", "OIME", "AXIL", "BATI", "TWAE", "LIMB", "WASE", "idin", "Bleo", "STUB", "BOIL ", "BOLE", "KİREÇ", "Sawt", "LİMA", "MESA", "mewl", "AKS", "FAME", "ASEM", "MİL", "AMIL", "SEAX", "DİKİŞ", "YARI", "swam", "AMBO", "AMLI", "axile", "amble", "Swami", "AWEST", "AWEST", "Limax", "LIMES", "LIMBU ", "LİMBO", "Em", "harfini andıran bir hal", "embol", "WAMBLE", "FAMBLE"]"BATI", "TWAE", "LIMB", "WASE", "idin", "Bleo", "STUB", "BOIL", "BOLE", "KİREÇ", "Sawt", "LİMA", "MESA ", "mewl", "AKS", "FAME", "ASEM", "MİL", "AMIL", "SEAX", "DİKİŞ", "YARI", "swam", "AMBO", "AMLI", "axile", "amble", "Swami", "AWEST", "AWEST", "Limax", "LIMES", "LIMBU", "LİMBO", "Em Kutusu", "harfini andıran bir hal", "emboli", "WAMBLE ", "FAMBLE"]"BATI", "TWAE", "LIMB", "WASE", "idin", "Bleo", "STUB", "BOIL", "BOLE", "KİREÇ", "Sawt", "LİMA", "MESA ", "mewl", "AKS", "FAME", "ASEM", "MİL", "AMIL", "SEAX", "DİKİŞ", "YARI", "swam", "AMBO", "AMLI", "axile", "amble", "Swami", "AWEST", "AWEST", "Limax", "LIMES", "LIMBU", "LİMBO", "Em Kutusu", "harfini andıran bir hal", "emboli", "WAMBLE ", "FAMBLE"]Sawt", "LİMA", "MESA", "mewl", "AKS", "FAME", "ASEM", "MİL", "AMIL", "SEAX", "DİKİŞ", "YARI", "swam" "AMBO", "AMLI", "axile", "amble", "Swami", "AWEST", "AWEST", "Limax", "LIMES", "LIMBU", "LİMBO", "Em Kutusu"," ekiplerimiz", "emboli", "WAMBLE", "FAMBLE"]Sawt", "LİMA", "MESA", "mewl", "AKS", "FAME", "ASEM", "MİL", "AMIL", "SEAX", "DİKİŞ", "YARI", "swam" "AMBO", "AMLI", "axile", "amble", "Swami", "AWEST", "AWEST", "Limax", "LIMES", "LIMBU", "LİMBO", "Em Kutusu"," ekiplerimiz", "emboli", "WAMBLE", "FAMBLE"]Limax", "LIMES", "LIMBU", "LİMBO", "Em", "harfini andıran bir hal", "embol", "WAMBLE", "FAMBLE"]Limax", "LIMES", "LIMBU", "LİMBO", "Em", "harfini andıran bir hal", "embol", "WAMBLE", "FAMBLE"]

Kod:

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

Bu yüzden herkes PHP'yi sevdiği için bunu çözmenin başka bir PHP yolunu eklemek istedim. Sözlük dosyasına karşı bir regexpression maçı kullanmak gibi yapmak istediğim biraz yeniden düzenleme var, ama şu anda sadece tüm sözlük dosyasını bir wordList'e yüklüyorum.

Bunu bağlantılı bir liste fikri kullanarak yaptım. Her Düğüm bir karakter değerine, bir konum değerine ve bir sonraki işaretçiye sahiptir.

Konum değeri iki düğüm bağlı olup olmadığını nasıl öğrendim.

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

Bu ızgarayı kullanarak, eğer ilk düğümün konumu ikinci düğümlerin konumu aynı satır için +/- 1, yukarıdaki ve aşağıda satır için +/- 9, 10, 11'e eşitse iki düğümün bağlı olduğunu biliyorum.

Ana arama için özyineleme kullanıyorum. WordList kelimesinden bir kelime çıkarır, tüm olası başlangıç ​​noktalarını bulur ve daha sonra bir sonraki olası bağlantıyı tekrar tekrar bulur ve zaten kullanmakta olduğu bir konuma gidemeyeceğini unutmayın (bu yüzden $ notInLoc ekliyorum).

Her neyse, bazı yeniden düzenleme gerektirdiğini biliyorum ve nasıl daha temiz hale getirileceğine dair düşünceleri duymak isterim, ancak kullandığım sözlük dosyasına göre doğru sonuçları üretir. Tahtadaki sesli harflerin ve kombinasyonların sayısına bağlı olarak, yaklaşık 3 ila 6 saniye sürer. Ben sözlük sonuçları preg_match kez, bu önemli ölçüde azalacağını biliyorum.

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

Partiye gerçekten geç kaldığımı biliyorum ama bir kodlama alıştırması olarak çeşitli programlama dillerinde (C ++, Java, Go, C #, Python, Ruby, JavaScript, Julia, Lua, PHP, Perl) ve Birinin bunlarla ilgilenebileceğini düşündüm, bu yüzden bağlantıyı burada bırakıyorum: https://github.com/AmokHuginnsson/boggle-solvers


1

İşte çözüm NLTK araç setinde Önceden Tanımlı sözcükleri kullanma NLTK, nltk.corpus paketine sahiptir, çünkü sözcükler adında bir pakete sahibiz ve sadece programınızda kullanabileceğiniz 2'den fazla İngilizce kelime içerir.

Matrisinizi oluşturduktan sonra onu bir karakter dizisine dönüştürün ve bu kodu gerçekleştirin

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)

Çıktı:

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

Umarım alırsın.


0

İşte benim java uygulaması: https://github.com/zouzhile/interview/blob/master/src/com/interview/algorithms/tree/BoggleSolver.java

Trie oluşturma işlemi 0 saat, 0 dakika, 1 saniye, 532 milisaniye
aldı Kelime arama 0 saat, 0 dakika, 0 saniye, 92 milisaniye aldı

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 

Not: Bu iş parçacığının başında sözlük ve karakter matrisini kullandım. Kod MacBookPro'mda çalıştırıldı, aşağıda makine hakkında bazı bilgiler var.

Model Adı: MacBook Pro
Model Tanıtıcısı: MacBookPro8,1
İşlemci Adı: Intel Core i5
İşlemci Hızı: 2.3 GHz
İşlemci Sayısı: 1
Toplam Çekirdek Sayısı: 2
L2 Önbellek (çekirdek başına): 256 KB
L3 Önbellek: 3 MB
Bellek: 4 GB
Boot ROM Sürümü: MBP81.0047.B0E
SMC Sürümü (sistem): 1.68f96


0

Bunu Java ile de çözdüm. Uygulamam 269 satır uzunluğunda ve kullanımı oldukça kolay. Öncelikle Boggler sınıfının yeni bir örneğini oluşturmanız ve ardından grid ile birlikte çözme işlevini parametre olarak çağırmanız gerekir. Bilgisayarıma 50 000 kelimenin sözlüğünü yüklemek yaklaşık 100 ms sürüyor ve yaklaşık 10-20 ms'lik kelimeleri buluyor. Bulunan kelimeler bir ArrayList içinde saklanır 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

Bunu c. Makinemde çalıştırmak yaklaşık 48 ms sürüyor (yaklaşık% 98'i sözlüğü diskten yüklemek ve trie oluşturmak için harcanan zamanın yaklaşık% 98'i ile). Sözlük / usr / share / dict / amerikan-ingilizce olan 62886 kelime.

Kaynak kodu


0

Bunu mükemmel ve çok hızlı çözdüm. Bir android uygulamasına koydum. Eylem halinde görmek için videoyu oynatma deposu bağlantısında görüntüleyin.

Kelime Hileleri herhangi bir matris tarzı kelime oyunu "çatlamak" bir uygulama. Bu uygulama bana kelime karıştırıcı hile yardımcı olmak için inşa edilmiştir. Kelime arama, ruzzle, kelimeler, kelime bulucu, kelime çatlak, boggle ve daha fazlası için kullanılabilir!

Burada https://play.google.com/store/apps/details?id=com.harris.wordcracker görülebilir

Uygulamayı videoda https://www.youtube.com/watch?v=DL2974WmNAI içinde görüntüleyin

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.