Cách tìm danh sách các từ có thể có từ ma trận chữ cái [Bộ giải Boggle]


376

Gần đây tôi đã chơi một trò chơi trên iPhone của tôi được gọi là Scramble. Một số bạn có thể biết trò chơi này là Boggle. Về cơ bản, khi trò chơi bắt đầu, bạn nhận được một ma trận các chữ cái như vậy:

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

Mục tiêu của trò chơi là tìm ra càng nhiều từ càng tốt mà bạn có thể hình thành bằng cách xâu chuỗi các chữ cái lại với nhau. Bạn có thể bắt đầu bằng bất kỳ chữ cái nào và tất cả các chữ cái bao quanh nó là trò chơi công bằng, và sau đó khi bạn chuyển sang chữ cái tiếp theo, tất cả các chữ cái bao quanh chữ cái đó là trò chơi công bằng, ngoại trừ mọi chữ cái được sử dụng trước đó . Vì vậy, trong lưới trên, ví dụ, tôi có thể đưa ra những lời LOB, TUX, SEA, FAMEvv Words phải có ít nhất 3 ký tự, và không quá ký tự NxN, đó sẽ là 16 trong trò chơi này, nhưng có thể thay đổi trong một số triển khai . Mặc dù trò chơi này thú vị và gây nghiện, nhưng rõ ràng tôi không giỏi lắm và tôi muốn gian lận một chút bằng cách tạo ra một chương trình mang lại cho tôi những từ tốt nhất có thể (từ càng dài thì bạn càng nhận được nhiều điểm).

Mẫu thử
(nguồn: boggled.org )

Thật không may, tôi không giỏi lắm với các thuật toán hoặc hiệu quả của chúng và vân vân. Lần thử đầu tiên của tôi sử dụng một từ điển như từ điển này (~ 2,3 MB) và thực hiện tìm kiếm tuyến tính cố gắng khớp các kết hợp với các mục từ điển. Điều này mất một thời gian rất dài để tìm các từ có thể, và vì bạn chỉ nhận được 2 phút mỗi vòng, nên đơn giản là không đủ.

Tôi quan tâm xem liệu có bất kỳ Stackoverflowers nào có thể đưa ra các giải pháp hiệu quả hơn không. Tôi chủ yếu tìm kiếm các giải pháp bằng cách sử dụng Big 3 Ps: Python, PHP và Perl, mặc dù mọi thứ với Java hoặc C ++ cũng rất tuyệt, vì tốc độ là điều cần thiết.

GIẢI PHÁP HIỆN TẠI :


18
yêu cầu tính năng MOAR PUZZLES
Kent Fredric

6
Liên quan đến thời gian: trong giải pháp của tôi, thực tế tất cả thời gian được dành để xây dựng bộ ba. Một khi bộ ba được xây dựng, nó có thể được tái sử dụng nhiều lần. Nếu chỉ giải một câu đố, sẽ hiệu quả hơn khi sử dụng cấu trúc dữ liệu đơn giản hơn (chẳng hạn như một tập hợp tất cả các từ và tất cả các tiền tố).
Adam Rosenfield

3
Ngoài ra, Adam's có một từ điển lớn hơn, bằng chứng là số lượng từ dài hơn mà giải pháp của anh ta sử dụng. Tất cả chúng nên được kiểm tra dựa trên một từ điển chung.
Giàu có Bradshaw

2
Tôi đoán không ai chơi nhiều Boggle? "Qu" là một "chữ cái" và tôi không chắc có bao nhiêu giải pháp bắt được chi tiết nhỏ đó. Có vẻ như một số trong số họ sẽ cho phép bạn sử dụng "u" một cách độc lập, trong số các vấn đề khác.
Qsario

2
Gần đây tôi đã có một câu hỏi phỏng vấn và bị mắc kẹt trong các chi tiết. Tôi đã coi nó như là một vấn đề đồ thị, điều này là tốt, nhưng các giải pháp ở đây sử dụng ít không gian hơn. Tôi đang mã hóa giải pháp của riêng tôi bây giờ. Làm tốt cho tất cả những người đóng góp!
Peter Friend

Câu trả lời:


143

Câu trả lời của tôi hoạt động giống như những câu hỏi khác ở đây, nhưng tôi sẽ đăng nó vì nó nhanh hơn một chút so với các giải pháp Python khác, từ việc thiết lập từ điển nhanh hơn. (Tôi đã kiểm tra điều này với giải pháp của John Fouhy.) Sau khi thiết lập, thời gian để giải quyết giảm trong tiếng ồn.

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)

Sử dụng mẫu:

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

Chỉnh sửa: Lọc ra các từ dài dưới 3 chữ cái.

Chỉnh sửa 2: Tôi tò mò tại sao giải pháp Perl của Kent Fredric lại nhanh hơn; hóa ra là sử dụng kết hợp biểu thức chính quy thay vì một bộ ký tự. Làm tương tự trong Python về tăng gấp đôi tốc độ.


Chương trình chỉ cho tôi 1 từ. Làm thế nào mà?
Paolo Bergantino

Tôi không muốn chết đuối trong đầu ra. Xem bình luận ở phía dưới.
Darius Bacon

6
Hoặc nhận tất cả các từ mà không có đường dẫn: print '' .join (được sắp xếp (đặt (từ cho (từ, đường dẫn) trong giải ())))
Darius Bacon

2
Phần lớn thời gian được dành cho việc phân tích từ điển. Tôi đã phân tích cú pháp trước đó thành một tập tin "wordlines.py" chỉ là một danh sách với mỗi từ là một thành phần. Bởi vì đó là tệp .py, tệp đó sẽ được chuyển thành tệp .pyc. Vì vậy, sau đó tôi thực hiện nhập khẩu thay vì read (). Splitlines (). Với điều đó, trên hộp của tôi, tôi đang giải quyết nó trong khoảng một phần mười giây.
Sean Reifschneider

1
@shellscape, đó là mã Python 2. Python 3 đã bỏ khả năng giải cấu trúc các đối số, như def neighbors((x, y))(vô nghĩa, theo như tôi có thể thấy). Ngoài ra, nó đòi hỏi dấu ngoặc đơn xung quanh đối số để print.
Darius Bacon

116

Giải pháp nhanh nhất bạn sẽ nhận được có thể sẽ liên quan đến việc lưu trữ từ điển của bạn trong một Trie . Sau đó, tạo một hàng ba bộ ba ( x , y , s ), trong đó mỗi phần tử trong hàng đợi tương ứng với một tiền tố s của một từ có thể được đánh vần trong lưới, kết thúc tại vị trí ( x , y ). Khởi tạo hàng đợi với N x N yếu tố (trong đó N là kích thước của lưới của bạn), một trong những yếu tố cho mỗi vuông trong lưới. Sau đó, thuật toán tiến hành như sau:

Trong khi hàng đợi không trống:
  Dequeue một bộ ba (x, y, s)
  Với mỗi hình vuông (x ', y') có chữ c liền kề (x, y):
    Nếu s + c là một từ, đầu ra s + c
    Nếu s + c là tiền tố của một từ, hãy chèn (x ', y', s + c) vào hàng đợi

Nếu bạn lưu trữ từ điển của mình trong bộ ba, kiểm tra xem s + c là một từ hoặc tiền tố của một từ có thể được thực hiện trong thời gian không đổi (miễn là bạn cũng giữ một số siêu dữ liệu bổ sung trong mỗi mốc dữ liệu hàng đợi, chẳng hạn như một con trỏ đến nút hiện tại trong bộ ba), vì vậy thời gian chạy của thuật toán này là O (số từ có thể đánh vần).

[Chỉnh sửa] Đây là một triển khai trong Python mà tôi vừa mã hóa:

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

Ví dụ sử dụng:

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

Đầu ra:

['fa', 'xi', 'tức là', 'io', 'el', 'am', 'ax', 'ae', 'aw', 'mi', 'ma', 'tôi', ' lo ',' li ',' oe ',' ox ',' em ',' e ',' e ',' es ',' wa ',' chúng tôi ',' wa ',' bo ',' bu ' , 'như', 'aw', 'ae', 'st', 'se', 'sa', 'tu', 'ut', 'fam', 'fae', 'imi', 'eli', ' elm ',' elb ',' ami ',' ama ',' ame ',' aes ',' awl ',' chờ đợi ',' awe ',' chờ đợi ',' trộn ',' mim ',' mil ' , 'mam', 'max', 'mae', 'maw', 'mew', 'mem', 'mes', 'lob', 'lox', 'lei ',' leo ',' nói dối ',' lim ',' dầu ',' olm ',' ewe ',' eme ',' sáp ',' waf ',' wae ',' waw ',' wem ' , 'dệt', 'dệt', 'là', 'waw', 'wae', 'bob', 'blo', 'bub', 'nhưng', 'ast', 'ase', 'asa', ' awl ',' chờ đợi ',' awe ',' chờ đợi ',' aes ',' swa ',' swa ',' may ',' biển ',' biển ',' cưa ',' tux ',' tub ' , 'tut', 'twa', 'twa', 'tst', 'utu', 'fama', 'fame', 'ixil', 'imam', 'amli', 'amil', 'ambo', ' axil ',' trục ',' mimi ',' mima ',' mime ',' milo ','dặm ',' mewl ',' mese ',' mesa ',' lolo ',' lobo ',' lima ',' vôi ',' limb ',' lile ',' oime ',' oleo ',' olio ' , 'oboe', 'obol', 'emim', 'emil', 'East', 'easy', 'wame', 'wawa', 'wawa', 'weam', 'west', 'wese', ' lãng phí ',' wase ',' wawa ',' wawa ',' boil ',' bolo ',' bole ',' bobo ',' blob ',' bleo ',' bubo ',' asem ',' stub ' , 'stut', 'swam', 'semi', 'seme', 'seam', 'seax', 'sasa', 'sawt', 'tutu', 'tuts', 'twae', 'twas', ' twae ',' ilima ',' amble ',' axile ', 'awest', 'mamie', 'mambo', 'maxim', 'mease', 'mesem', 'limax', 'limes', 'limbo', 'limbu', 'obole', 'emesa', ' embox ',' awest ',' swami ',' famble ',' mimble ',' maxima ',' embolo ',' embole ',' wamble ',' semese ',' semble ',' sawbwa ',' sawbwa ' ]cưa ']cưa ']

Lưu ý: Chương trình này không xuất ra các từ 1 chữ cái hoặc lọc theo độ dài từ. Điều đó dễ dàng để thêm nhưng không thực sự liên quan đến vấn đề. Nó cũng xuất ra một số từ nhiều lần nếu chúng có thể được đánh vần theo nhiều cách. Nếu một từ nhất định có thể được đánh vần theo nhiều cách khác nhau (trường hợp xấu nhất: mọi chữ cái trong lưới đều giống nhau (ví dụ 'A') và một từ như 'aaaaaaaaaa' trong từ điển của bạn), thì thời gian chạy sẽ có số mũ khủng khiếp . Lọc ra các bản sao và sắp xếp là không đáng kể sau khi thuật toán kết thúc.


14
Ôi. Tôi mừng vì có người bước lên đĩa. Mặc dù điều này hoạt động, nhưng nó không "nhớ" chữ cái mà nó đã sử dụng và đưa ra các từ yêu cầu sử dụng cùng một chữ cái hai lần không được phép. Là một thằng ngốc, tôi sẽ sửa nó như thế nào?
Paolo Bergantino

3
Đúng, nó không nhớ những chữ cái nào đã được truy cập, nhưng nó không được chỉ định trong spec =) của bạn. Để khắc phục điều đó, bạn phải thêm vào mỗi cột dữ liệu hàng đợi một danh sách tất cả các vị trí đã truy cập, sau đó kiểm tra danh sách đó trước khi thêm ký tự tiếp theo.
Adam Rosenfield

Không, bên trong Boggleemme (). Thay vì lưu trữ một bộ tứ (x, y, s, n), bạn sẽ lưu trữ một nhóm ngũ phân vị (x, y, s, n, l), trong đó l là danh sách của (x, y) đã truy cập cho đến nay. Sau đó, bạn kiểm tra từng (x2, y2) so với l và chỉ chấp nhận nếu nó không nằm trong l. Sau đó, bạn thêm nó vào l mới.
Adam Rosenfield

2
Tôi cũng đã làm điều này khi tôi phát ốm khi chơi Scramble. Tôi nghĩ rằng giải pháp đệ quy (DFS thay vì BFS) gợi cảm hơn, vì bạn chỉ có thể giữ một tập hợp các ô đang hoạt động (vì vậy bạn không truy cập vào cùng một ô hai lần). Nhiều gọn gàng hơn sau đó giữ một loạt các danh sách.
Justin Scheiner

2
Không nên rơi vào một vòng lặp vô tận? Ý tôi là, nói cho (x,y), một người theo dõi có thể là (x+1,y+1). Sau đó (x+1,y+1)được đẩy đến hàng đợi. Tuy nhiên, (x,y)cũng sẽ là một người theo dõi (x+1,y+1), vì vậy sẽ không dẫn đến một sự nảy lại không hồi kết giữa họ?
SexyBeast

39

Để tăng tốc từ điển, có một quá trình chuyển đổi / quy trình chung mà bạn có thể làm để giảm đáng kể các so sánh từ điển trước thời hạn.

Cho rằng lưới ở trên chỉ chứa 16 ký tự, một số ký tự trùng lặp, bạn có thể giảm đáng kể số lượng khóa trong từ điển của mình bằng cách lọc ra các mục có ký tự không thể đạt được.

Tôi nghĩ rằng đây là tối ưu hóa rõ ràng nhưng không thấy ai làm điều đó, tôi đang đề cập đến nó.

Nó giảm tôi từ một từ điển 200.000 khóa xuống chỉ còn 2.000 khóa chỉ trong quá trình nhập liệu. Điều này ít nhất làm giảm chi phí bộ nhớ và chắc chắn sẽ ánh xạ tới tốc độ tăng ở đâu đó vì bộ nhớ không quá nhanh.

Thực hiện Perl

Việc triển khai của tôi hơi nặng nề vì tôi đặt tầm quan trọng vào việc có thể biết đường dẫn chính xác của mỗi chuỗi được trích xuất, không chỉ tính hợp lệ trong đó.

Tôi cũng có một vài sự thích nghi trong đó về mặt lý thuyết sẽ cho phép một lưới có các lỗ trong đó hoạt động và các lưới có các đường có kích thước khác nhau (giả sử bạn có đầu vào đúng và bằng cách nào đó sẽ xếp hàng).

Bộ lọc sớm cho đến nay là nút cổ chai quan trọng nhất trong ứng dụng của tôi, như nghi ngờ trước đó, nhận xét rằng dòng đó nở rộ từ 1,5 đến 7,5 giây.

Khi thực hiện, có vẻ như tất cả các chữ số đều nằm trên các từ hợp lệ của riêng chúng, nhưng tôi khá chắc chắn đó là do cách hoạt động của tệp từ điển.

Nó hơi bồng bềnh, nhưng ít nhất tôi sử dụng lại Tree :: Trie từ cpan

Một số trong số đó được lấy cảm hứng một phần bởi các triển khai hiện có, một số trong đó tôi đã có trong tâm trí.

Phê bình mang tính xây dựng và các cách có thể được cải thiện chào đón (/ tôi lưu ý rằng anh ấy không bao giờ tìm kiếm CPAN cho một người giải boggle , nhưng điều này thú vị hơn khi làm việc)

cập nhật cho các tiêu chí mới

#!/usr/bin/perl 

use strict;
use warnings;

{

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

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

  package Prefix;
  use Moose;

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

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

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

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

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

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

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

  # Returns  the rightmost node on this path

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

  # pretty-format $o->path

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

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

  __PACKAGE__->meta->make_immutable;
}

{

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

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

  package MatrixNode;
  use Moose;

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

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

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

  # Convenience method to derive a path starting at this node

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

}

{

  package Matrix;
  use Moose;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  __PACKAGE__->meta->make_immutable;
}

use Tree::Trie;

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

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

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

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

  # Inject all grid nodes into the processing queue.

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

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

      # put the dictionary into "exact match" mode.

      $d->deepsearch('exact');

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

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

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

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

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

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

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

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

# Here is where the real work starts.

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

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

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

Arch / thực hiện thông tin để so sánh:

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

Thêm sự lầm bầm về Tối ưu hóa Regex đó

Tối ưu hóa regex tôi sử dụng là vô ích đối với các từ điển đa giải và đối với đa giải sẽ bạn muốn có một từ điển đầy đủ, không phải là một từ điển được cắt xén trước.

Tuy nhiên, điều đó nói rằng, đối với giải quyết một lần, nó thực sự nhanh chóng. (Perl regex đang ở C! :))

Dưới đây là một số bổ sung mã khác nhau:

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

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

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

ps: 8,16 * 200 = 27 phút.


2
Tôi biết tôi đang thất bại trong câu lạc bộ tối ưu hóa, nhưng tôi gặp vấn đề về tốc độ trước khi tôi bắt đầu công việc thực sự của mã và việc giảm thời gian nhập từ 2 giây xuống còn 1,2 giây có ý nghĩa rất lớn đối với tôi.
Kent Fredric

/ tôi lưu ý rằng bây giờ nó mất ít thời gian hơn để regex và bỏ qua các mục so với việc thêm khóa vào hàm băm.
Kent Fredric

Thật tuyệt, một triển khai Perl! Tôi sẽ chạy nó ngay bây giờ.
Paolo Bergantino

Blerg, gặp khó khăn khi cài đặt Tree :: Trie trên máy chủ web của tôi. :(
Paolo Bergantino

3
Làm thế nào bạn tạo báo cáo cuối cùng (thông tin vòm / thực thi)? Có vẻ hữu ích.
jmanning2k

33

Bạn có thể chia vấn đề thành hai phần:

  1. Một số loại thuật toán tìm kiếm sẽ liệt kê các chuỗi có thể có trong lưới.
  2. Một cách để kiểm tra xem một chuỗi có phải là một từ hợp lệ hay không.

Lý tưởng nhất, (2) cũng nên bao gồm một cách kiểm tra xem một chuỗi có phải là tiền tố của một từ hợp lệ hay không - điều này sẽ cho phép bạn cắt tỉa tìm kiếm của mình và tiết kiệm cả đống thời gian.

Trie của Adam Rosenfield là một giải pháp cho (2). Thật thanh lịch và có lẽ là những gì chuyên gia thuật toán của bạn thích, nhưng với ngôn ngữ hiện đại và máy tính hiện đại, chúng ta có thể lười hơn một chút. Ngoài ra, như Kent gợi ý, chúng tôi có thể giảm kích thước từ điển của mình bằng cách loại bỏ các từ có chữ cái không có trong lưới. Đây là một con trăn:

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

Ồ; kiểm tra tiền tố thời gian không đổi. Phải mất vài giây để tải từ điển bạn đã liên kết, nhưng chỉ một vài :-) (lưu ý rằng words <= prefixes)

Bây giờ, đối với phần (1), tôi có xu hướng suy nghĩ về các biểu đồ. Vì vậy, tôi sẽ xây dựng một từ điển trông giống như thế này:

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

tức graph[(x, y)]là tập hợp tọa độ mà bạn có thể đạt được từ vị trí (x, y). Tôi cũng sẽ thêm một nút giả Nonesẽ kết nối với mọi thứ.

Xây dựng nó hơi vụng về, bởi vì có 8 vị trí có thể và bạn phải kiểm tra giới hạn. Đây là một số mã trăn tương ứng vụng về:

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

Mã này cũng xây dựng một ánh xạ từ điển (x,y)đến ký tự tương ứng. Điều này cho phép tôi biến một danh sách các vị trí thành một từ:

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

Cuối cùng, chúng tôi thực hiện tìm kiếm theo chiều sâu. Thủ tục cơ bản là:

  1. Tìm kiếm đến một nút cụ thể.
  2. Kiểm tra xem đường dẫn cho đến nay có thể là một phần của một từ. Nếu không, đừng khám phá chi nhánh này nữa.
  3. Kiểm tra nếu đường dẫn cho đến nay là một từ. Nếu vậy, thêm vào danh sách kết quả.
  4. Khám phá tất cả trẻ em không phải là một phần của con đường cho đến nay.

Con trăn

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)

Chạy mã dưới dạng:

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)

và kiểm tra resđể xem câu trả lời. Dưới đây là danh sách các từ được tìm thấy cho ví dụ của bạn, được sắp xếp theo kích thước:

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

Mã này mất (theo nghĩa đen) một vài giây để tải từ điển, nhưng phần còn lại là ngay lập tức trên máy của tôi.


Rất đẹp! Rất nhanh, quá. Tôi sẽ đợi xung quanh để xem có ai khác bước lên đĩa không, nhưng câu trả lời của bạn có vẻ tốt cho đến nay.
Paolo Bergantino

Tôi bối rối tại sao "embole" là từ 6 chữ cái duy nhất của bạn, tôi có 10 từ khác nhau cho từ đó. Có vẻ như bạn cấm truy cập cùng một nút hai lần và như OP đã nêu, đó là trò chơi công bằng.
Kent Fredric

1
ok, ngần ngại vẫn có thể có một lỗi khi ngần ngại loại bỏ "FAMBLE" "WAMBLE" và "SEMBLE", không chia sẻ các ký tự.
Kent Fredric

Cũng phát hiện ra! Lỗi là trong việc tạo tiền tố được đặt: Tôi cần sử dụng range(len(w)+1)thay vì range(len(w)). Tôi đã tuyên bố điều đó words <= prefixesnhưng dường như tôi đã không kiểm tra rằng: - /
John Fouhy

1
Điều này giúp tôi tìm hiểu cách DFS hoạt động và cách triển khai. Không chắc chắn về bất kỳ cách nào để thể hiện sự đánh giá cao về điều này ngoài việc nhận xét. Cảm ơn!
Graham Smith

23

Nỗ lực của tôi trong Java. Mất khoảng 2 giây để đọc tệp và xây dựng trie, và khoảng 50 ms để giải câu đố. Tôi đã sử dụng từ điển được liên kết trong câu hỏi (nó có một vài từ mà tôi không biết tồn tại trong tiếng Anh như fae, ima)

0 [main] INFO gineer.bogglesolver.util.Util  - Reading the dictionary
2234 [main] INFO gineer.bogglesolver.util.Util  - Finish reading the dictionary
2234 [main] INFO gineer.bogglesolver.Solver  - Found: FAM
2234 [main] INFO gineer.bogglesolver.Solver  - Found: FAME
2234 [main] INFO gineer.bogglesolver.Solver  - Found: FAMBLE
2234 [main] INFO gineer.bogglesolver.Solver  - Found: FAE
2234 [main] INFO gineer.bogglesolver.Solver  - Found: IMA
2234 [main] INFO gineer.bogglesolver.Solver  - Found: ELI
2234 [main] INFO gineer.bogglesolver.Solver  - Found: ELM
2234 [main] INFO gineer.bogglesolver.Solver  - Found: ELB
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AXIL
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AXILE
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AXLE
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AMI
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AMIL
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AMLI
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AME
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AMBLE
2234 [main] INFO gineer.bogglesolver.Solver  - Found: AMBO
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AES
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWL
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWEST
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MIX
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MIL
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MILE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MILO
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MAX
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MAE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MAW
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MEW
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MEWL
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MES
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MESA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: MWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIM
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIMA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIMAX
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIME
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIMES
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIMB
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIMBO
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LIMBU
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LEI
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LEO
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LOB
2250 [main] INFO gineer.bogglesolver.Solver  - Found: LOX
2250 [main] INFO gineer.bogglesolver.Solver  - Found: OIME
2250 [main] INFO gineer.bogglesolver.Solver  - Found: OIL
2250 [main] INFO gineer.bogglesolver.Solver  - Found: OLE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: OLM
2250 [main] INFO gineer.bogglesolver.Solver  - Found: EMIL
2250 [main] INFO gineer.bogglesolver.Solver  - Found: EMBOLE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: EMBOX
2250 [main] INFO gineer.bogglesolver.Solver  - Found: EAST
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAF
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAX
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAME
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAMBLE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WEA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WEAM
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WEM
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WEA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WES
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WEST
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAS
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WASE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: WAST
2250 [main] INFO gineer.bogglesolver.Solver  - Found: BLEO
2250 [main] INFO gineer.bogglesolver.Solver  - Found: BLO
2250 [main] INFO gineer.bogglesolver.Solver  - Found: BOIL
2250 [main] INFO gineer.bogglesolver.Solver  - Found: BOLE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: BUT
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AES
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWL
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AWEST
2250 [main] INFO gineer.bogglesolver.Solver  - Found: ASE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: ASEM
2250 [main] INFO gineer.bogglesolver.Solver  - Found: AST
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SEA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SEAX
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SEAM
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SEMI
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SEMBLE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SEW
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SEA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SWAM
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SWAMI
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SAW
2250 [main] INFO gineer.bogglesolver.Solver  - Found: SAWT
2250 [main] INFO gineer.bogglesolver.Solver  - Found: STU
2250 [main] INFO gineer.bogglesolver.Solver  - Found: STUB
2250 [main] INFO gineer.bogglesolver.Solver  - Found: TWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: TWAE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: TWA
2250 [main] INFO gineer.bogglesolver.Solver  - Found: TWAE
2250 [main] INFO gineer.bogglesolver.Solver  - Found: TWAS
2250 [main] INFO gineer.bogglesolver.Solver  - Found: TUB
2250 [main] INFO gineer.bogglesolver.Solver  - Found: TUX

Mã nguồn bao gồm 6 lớp. Tôi sẽ đăng chúng bên dưới (nếu đây không phải là cách thực hành đúng trên StackOverflow, vui lòng cho tôi biết).

gineer.blassolver. Chính

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.blassolver.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.blassolver.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.blassolver.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.blassolver.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.blassolver.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
Tôi đã so sánh kết quả đầu ra của mình với các kết quả đầu ra từ các StackOverflowers khác và có vẻ như các kết quả đầu ra của Adam, John và rvarcher bị thiếu một số từ. Ví dụ: "Mwa" có trong từ điển (yeah!), Nhưng nó không được trả về trong các kết quả đầu ra từ Adam, John và rvarcher. Nó được trả lại hai lần trong liên kết PHP của Paolo.
gineer

1
Tôi đã thử cái này bằng cách sao chép nó. Nó nói "Đọc ..." và "Đọc xong ...", nhưng không có gì xuất hiện sau đó. Không có trận đấu được hiển thị.
MikkoP

23

Tôi nghĩ rằng bạn có thể sẽ dành phần lớn thời gian của mình để cố gắng ghép các từ mà lưới chữ cái của bạn không thể xây dựng được. Vì vậy, điều đầu tiên tôi sẽ làm là cố gắng tăng tốc bước đó và điều đó sẽ giúp bạn đi gần hết quãng đường đó.

Đối với điều này, tôi sẽ thể hiện lại lưới dưới dạng một bảng "di chuyển" có thể mà bạn lập chỉ mục bằng cách chuyển chữ cái mà bạn đang xem.

Bắt đầu bằng cách gán cho mỗi chữ cái một số từ toàn bộ bảng chữ cái của bạn (A = 0, B = 1, C = 2, ... và vv).

Hãy lấy ví dụ này:

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

Và bây giờ, hãy sử dụng bảng chữ cái của các chữ cái chúng ta có (thông thường bạn có thể muốn sử dụng cùng một bảng chữ cái mỗi lần):

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

Sau đó, bạn tạo một mảng boolean 2D cho biết bạn có sẵn một chuyển đổi chữ cái nào đó không:

     |  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

Bây giờ đi qua danh sách từ của bạn và chuyển đổi các từ thành chuyển tiếp:

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

Sau đó kiểm tra xem các chuyển đổi này có được phép hay không bằng cách tra cứu chúng trong bảng của bạn:

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

Nếu tất cả chúng đều được cho phép, có khả năng từ này có thể được tìm thấy.

Ví dụ, từ "mũ bảo hiểm" có thể được loại trừ trong lần chuyển đổi thứ 4 (m sang e: helMEt), vì mục nhập trong bảng của bạn là sai.

Và từ hamster có thể được loại trừ, vì quá trình chuyển đổi (h sang a) đầu tiên không được phép (thậm chí không tồn tại trong bảng của bạn).

Bây giờ, đối với rất ít từ còn lại mà bạn không loại bỏ, hãy thử thực sự tìm thấy chúng trong lưới theo cách bạn đang làm bây giờ hoặc như được đề xuất trong một số câu trả lời khác ở đây. Điều này là để tránh các kết quả dương tính giả do nhảy giữa các chữ cái giống hệt nhau trong lưới của bạn. Ví dụ, từ "trợ giúp" được cho phép bởi bảng, nhưng không phải bởi lưới.

Một số mẹo cải thiện hiệu suất hơn nữa về ý tưởng này:

  1. Thay vì sử dụng mảng 2D, hãy sử dụng mảng 1D và chỉ cần tự mình tính toán chỉ số của chữ cái thứ hai. Vì vậy, thay vì mảng 12x12 như trên, hãy tạo mảng 1D có độ dài 144. Nếu sau đó bạn luôn sử dụng cùng một bảng chữ cái (tức là mảng 26x26 = 676x1 cho bảng chữ cái tiếng Anh chuẩn), ngay cả khi không phải tất cả các chữ cái đều hiển thị trong lưới của bạn , bạn có thể tính toán trước các chỉ số vào mảng 1D này mà bạn cần kiểm tra để khớp với các từ trong từ điển của bạn. Ví dụ: các chỉ số cho 'xin chào' trong ví dụ trên sẽ là

    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. Mở rộng ý tưởng sang bảng 3D (được biểu thị dưới dạng mảng 1D), tức là tất cả các kết hợp 3 chữ cái được phép. Bằng cách đó, bạn có thể loại bỏ nhiều từ hơn ngay lập tức và bạn giảm số lần tra cứu mảng cho mỗi từ bằng 1: Đối với 'xin chào', bạn chỉ cần 3 lần tra cứu mảng: hel, ell, llo. Nhân tiện, sẽ rất nhanh để xây dựng bảng này, vì chỉ có 400 chuyển động 3 chữ cái có thể có trong lưới của bạn.

  3. Tính toán trước các chỉ số của các di chuyển trong lưới của bạn mà bạn cần đưa vào bảng của mình. Đối với ví dụ trên, bạn cần đặt các mục sau thành 'Đúng':

    (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. Đồng thời biểu diễn lưới trò chơi của bạn trong mảng 1-D với 16 mục và có bảng được tính toán trước trong 3. chứa các chỉ mục vào mảng này.

Tôi chắc chắn nếu bạn sử dụng phương pháp này, bạn có thể khiến mã của mình chạy cực nhanh, nếu bạn có từ điển được tính toán trước và đã được tải vào bộ nhớ.

BTW: Một điều tuyệt vời khác để làm, nếu bạn đang xây dựng một trò chơi, là chạy những thứ này ngay lập tức trong nền. Bắt đầu tạo và giải quyết trò chơi đầu tiên trong khi người dùng vẫn đang nhìn vào màn hình tiêu đề trên ứng dụng của bạn và đưa ngón tay vào vị trí để nhấn "Chơi". Sau đó tạo và giải quyết trò chơi tiếp theo khi người dùng chơi trò chơi trước đó. Điều đó sẽ cho bạn rất nhiều thời gian để chạy mã của bạn.

(Tôi thích vấn đề này, vì vậy có lẽ tôi sẽ bị cám dỗ thực hiện đề xuất của mình trong Java vào một ngày nào đó để xem nó thực sự sẽ hoạt động như thế nào ... Tôi sẽ đăng mã ở đây sau khi tôi làm.)

CẬP NHẬT:

Ok, tôi đã có một chút thời gian hôm nay và thực hiện ý tưởng này trong Java:

class DictionaryEntry {
  public int[] letters;
  public int[] triplets;
}

class BoggleSolver {

  // Constants
  final int ALPHABET_SIZE = 5;  // up to 2^5 = 32 letters
  final int BOARD_SIZE    = 4;  // 4x4 board
  final int[] moves = {-BOARD_SIZE-1, -BOARD_SIZE, -BOARD_SIZE+1, 
                                  -1,                         +1,
                       +BOARD_SIZE-1, +BOARD_SIZE, +BOARD_SIZE+1};


  // Technically constant (calculated here for flexibility, but should be fixed)
  DictionaryEntry[] dictionary; // Processed word list
  int maxWordLength = 0;
  int[] boardTripletIndices; // List of all 3-letter moves in board coordinates

  DictionaryEntry[] buildDictionary(String fileName) throws IOException {
    BufferedReader fileReader = new BufferedReader(new FileReader(fileName));
    String word = fileReader.readLine();
    ArrayList<DictionaryEntry> result = new ArrayList<DictionaryEntry>();
    while (word!=null) {
      if (word.length()>=3) {
        word = word.toUpperCase();
        if (word.length()>maxWordLength) maxWordLength = word.length();
        DictionaryEntry entry = new DictionaryEntry();
        entry.letters  = new int[word.length()  ];
        entry.triplets = new int[word.length()-2];
        int i=0;
        for (char letter: word.toCharArray()) {
          entry.letters[i] = (byte) letter - 65; // Convert ASCII to 0..25
          if (i>=2)
            entry.triplets[i-2] = (((entry.letters[i-2]  << ALPHABET_SIZE) +
                                     entry.letters[i-1]) << ALPHABET_SIZE) +
                                     entry.letters[i];
          i++;
        }
        result.add(entry);
      }
      word = fileReader.readLine();
    }
    return result.toArray(new DictionaryEntry[result.size()]);
  }

  boolean isWrap(int a, int b) { // Checks if move a->b wraps board edge (like 3->4)
    return Math.abs(a%BOARD_SIZE-b%BOARD_SIZE)>1;
  }

  int[] buildTripletIndices() {
    ArrayList<Integer> result = new ArrayList<Integer>();
    for (int a=0; a<BOARD_SIZE*BOARD_SIZE; a++)
      for (int bm: moves) {
        int b=a+bm;
        if ((b>=0) && (b<board.length) && !isWrap(a, b))
          for (int cm: moves) {
            int c=b+cm;
            if ((c>=0) && (c<board.length) && (c!=a) && !isWrap(b, c)) {
              result.add(a);
              result.add(b);
              result.add(c);
            }
          }
      }
    int[] result2 = new int[result.size()];
    int i=0;
    for (Integer r: result) result2[i++] = r;
    return result2;
  }


  // Variables that depend on the actual game layout
  int[] board = new int[BOARD_SIZE*BOARD_SIZE]; // Letters in board
  boolean[] possibleTriplets = new boolean[1 << (ALPHABET_SIZE*3)];

  DictionaryEntry[] candidateWords;
  int candidateCount;

  int[] usedBoardPositions;

  DictionaryEntry[] foundWords;
  int foundCount;

  void initializeBoard(String[] letters) {
    for (int row=0; row<BOARD_SIZE; row++)
      for (int col=0; col<BOARD_SIZE; col++)
        board[row*BOARD_SIZE + col] = (byte) letters[row].charAt(col) - 65;
  }

  void setPossibleTriplets() {
    Arrays.fill(possibleTriplets, false); // Reset list
    int i=0;
    while (i<boardTripletIndices.length) {
      int triplet = (((board[boardTripletIndices[i++]]  << ALPHABET_SIZE) +
                       board[boardTripletIndices[i++]]) << ALPHABET_SIZE) +
                       board[boardTripletIndices[i++]];
      possibleTriplets[triplet] = true; 
    }
  }

  void checkWordTriplets() {
    candidateCount = 0;
    for (DictionaryEntry entry: dictionary) {
      boolean ok = true;
      int len = entry.triplets.length;
      for (int t=0; (t<len) && ok; t++)
        ok = possibleTriplets[entry.triplets[t]];
      if (ok) candidateWords[candidateCount++] = entry;
    }
  }

  void checkWords() { // Can probably be optimized a lot
    foundCount = 0;
    for (int i=0; i<candidateCount; i++) {
      DictionaryEntry candidate = candidateWords[i];
      for (int j=0; j<board.length; j++)
        if (board[j]==candidate.letters[0]) { 
          usedBoardPositions[0] = j;
          if (checkNextLetters(candidate, 1, j)) {
            foundWords[foundCount++] = candidate;
            break;
          }
        }
    }
  }

  boolean checkNextLetters(DictionaryEntry candidate, int letter, int pos) {
    if (letter==candidate.letters.length) return true;
    int match = candidate.letters[letter];
    for (int move: moves) {
      int next=pos+move;
      if ((next>=0) && (next<board.length) && (board[next]==match) && !isWrap(pos, next)) {
        boolean ok = true;
        for (int i=0; (i<letter) && ok; i++)
          ok = usedBoardPositions[i]!=next;
        if (ok) {
          usedBoardPositions[letter] = next;
          if (checkNextLetters(candidate, letter+1, next)) return true;
        }
      }
    }   
    return false;
  }


  // Just some helper functions
  String formatTime(long start, long end, long repetitions) {
    long time = (end-start)/repetitions;
    return time/1000000 + "." + (time/100000) % 10 + "" + (time/10000) % 10 + "ms";
  }

  String getWord(DictionaryEntry entry) {
    char[] result = new char[entry.letters.length];
    int i=0;
    for (int letter: entry.letters)
      result[i++] = (char) (letter+97);
    return new String(result);
  }

  void run() throws IOException {
    long start = System.nanoTime();

    // The following can be pre-computed and should be replaced by constants
    dictionary = buildDictionary("C:/TWL06.txt");
    boardTripletIndices = buildTripletIndices();
    long precomputed = System.nanoTime();


    // The following only needs to run once at the beginning of the program
    candidateWords     = new DictionaryEntry[dictionary.length]; // WAAAY too generous
    foundWords         = new DictionaryEntry[dictionary.length]; // WAAAY too generous
    usedBoardPositions = new int[maxWordLength];
    long initialized = System.nanoTime(); 

    for (int n=1; n<=100; n++) {
      // The following needs to run again for every new board
      initializeBoard(new String[] {"DGHI",
                                    "KLPS",
                                    "YEUT",
                                    "EORN"});
      setPossibleTriplets();
      checkWordTriplets();
      checkWords();
    }
    long solved = System.nanoTime();


    // Print out result and statistics
    System.out.println("Precomputation finished in " + formatTime(start, precomputed, 1)+":");
    System.out.println("  Words in the dictionary: "+dictionary.length);
    System.out.println("  Longest word:            "+maxWordLength+" letters");
    System.out.println("  Number of triplet-moves: "+boardTripletIndices.length/3);
    System.out.println();

    System.out.println("Initialization finished in " + formatTime(precomputed, initialized, 1));
    System.out.println();

    System.out.println("Board solved in "+formatTime(initialized, solved, 100)+":");
    System.out.println("  Number of candidates: "+candidateCount);
    System.out.println("  Number of actual words: "+foundCount);
    System.out.println();

    System.out.println("Words found:");
    int w=0;
    System.out.print("  ");
    for (int i=0; i<foundCount; i++) {
      System.out.print(getWord(foundWords[i]));
      w++;
      if (w==10) {
        w=0;
        System.out.println(); System.out.print("  ");
      } else
        if (i<foundCount-1) System.out.print(", ");
    }
    System.out.println();
  }

  public static void main(String[] args) throws IOException {
    new BoggleSolver().run();
  }
}

Đây là một số kết quả:

Đối với lưới từ hình ảnh được đăng trong câu hỏi ban đầu (DGHI ...):

Precomputation finished in 239.59ms:
  Words in the dictionary: 178590
  Longest word:            15 letters
  Number of triplet-moves: 408

Initialization finished in 0.22ms

Board solved in 3.70ms:
  Number of candidates: 230
  Number of actual words: 163 

Words found:
  eek, eel, eely, eld, elhi, elk, ern, erupt, erupts, euro
  eye, eyer, ghi, ghis, glee, gley, glue, gluer, gluey, glut
  gluts, hip, hiply, hips, his, hist, kelp, kelps, kep, kepi
  kepis, keps, kept, kern, key, kye, lee, lek, lept, leu
  ley, lunt, lunts, lure, lush, lust, lustre, lye, nus, nut
  nuts, ore, ort, orts, ouph, ouphs, our, oust, out, outre
  outs, oyer, pee, per, pert, phi, phis, pis, pish, plus
  plush, ply, plyer, psi, pst, pul, pule, puler, pun, punt
  punts, pur, pure, puree, purely, pus, push, put, puts, ree
  rely, rep, reply, reps, roe, roue, roup, roups, roust, rout
  routs, rue, rule, ruly, run, runt, runts, rupee, rush, rust
  rut, ruts, ship, shlep, sip, sipe, spue, spun, spur, spurn
  spurt, strep, stroy, stun, stupe, sue, suer, sulk, sulker, sulky
  sun, sup, supe, super, sure, surely, tree, trek, trey, troupe
  troy, true, truly, tule, tun, tup, tups, turn, tush, ups
  urn, uts, yeld, yelk, yelp, yelps, yep, yeps, yore, you
  your, yourn, yous

Đối với các chữ cái được đăng làm ví dụ trong câu hỏi ban đầu (FXIE ...)

Precomputation finished in 239.68ms:
  Words in the dictionary: 178590
  Longest word:            15 letters
  Number of triplet-moves: 408

Initialization finished in 0.21ms

Board solved in 3.69ms:
  Number of candidates: 87
  Number of actual words: 76

Words found:
  amble, ambo, ami, amie, asea, awa, awe, awes, awl, axil
  axile, axle, boil, bole, box, but, buts, east, elm, emboli
  fame, fames, fax, lei, lie, lima, limb, limbo, limbs, lime
  limes, lob, lobs, lox, mae, maes, maw, maws, max, maxi
  mesa, mew, mewl, mews, mil, mile, milo, mix, oil, ole
  sae, saw, sea, seam, semi, sew, stub, swam, swami, tub
  tubs, tux, twa, twae, twaes, twas, uts, wae, waes, wamble
  wame, wames, was, wast, wax, west

Đối với lưới 5x5 sau:

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

nó cho cái này:

Precomputation finished in 240.39ms:
  Words in the dictionary: 178590
  Longest word:            15 letters
  Number of triplet-moves: 768

Initialization finished in 0.23ms

Board solved in 3.85ms:
  Number of candidates: 331
  Number of actual words: 240

Words found:
  aero, aery, ahi, air, airt, airth, airts, airy, ear, egest
  elhi, elint, erg, ergo, ester, eth, ether, eye, eyen, eyer
  eyes, eyre, eyrie, gel, gelt, gelts, gen, gent, gentil, gest
  geste, get, gets, gey, gor, gore, gory, grey, greyest, greys
  gyre, gyri, gyro, hae, haet, haets, hair, hairy, hap, harp
  heap, hear, heh, heir, help, helps, hen, hent, hep, her
  hero, hes, hest, het, hetero, heth, hets, hey, hie, hilt
  hilts, hin, hint, hire, hit, inlet, inlets, ire, leg, leges
  legs, lehr, lent, les, lest, let, lethe, lets, ley, leys
  lin, line, lines, liney, lint, lit, neg, negs, nest, nester
  net, nether, nets, nil, nit, ogre, ore, orgy, ort, orts
  pah, pair, par, peg, pegs, peh, pelt, pelter, peltry, pelts
  pen, pent, pes, pest, pester, pesty, pet, peter, pets, phi
  philter, philtre, phiz, pht, print, pst, rah, rai, rap, raphe
  raphes, reap, rear, rei, ret, rete, rets, rhaphe, rhaphes, rhea
  ria, rile, riles, riley, rin, rye, ryes, seg, sel, sen
  sent, senti, set, sew, spelt, spelter, spent, splent, spline, splint
  split, stent, step, stey, stria, striae, sty, stye, tea, tear
  teg, tegs, tel, ten, tent, thae, the, their, then, these
  thesp, they, thin, thine, thir, thirl, til, tile, tiles, tilt
  tilter, tilth, tilts, tin, tine, tines, tirl, trey, treys, trog
  try, tye, tyer, tyes, tyre, tyro, west, wester, wry, wryest
  wye, wyes, wyte, wytes, yea, yeah, year, yeh, yelp, yelps
  yen, yep, yeps, yes, yester, yet, yew, yews, zero, zori

Đối với điều này, tôi đã sử dụng Danh sách từ Scrabble giải đấu TWL06 , vì liên kết trong câu hỏi ban đầu không còn hoạt động. Tệp này là 1,85 MB, vì vậy nó ngắn hơn một chút. Và buildDictionaryhàm ném ra tất cả các từ có ít hơn 3 chữ cái.

Dưới đây là một vài quan sát về hiệu suất của việc này:

  • Nó chậm hơn khoảng 10 lần so với hiệu suất được báo cáo về việc thực hiện OCaml của Victor Nicollet. Cho dù điều này là do thuật toán khác nhau, từ điển ngắn hơn mà anh ta đã sử dụng, thực tế là mã của anh ta được biên dịch và mã của tôi chạy trong máy ảo Java hoặc hiệu suất của các máy tính của chúng tôi (của tôi là Intel Q6600 @ 2.4 MHz chạy WinXP), Tôi không biết. Nhưng nó nhanh hơn nhiều so với kết quả cho các triển khai khác được trích dẫn ở cuối câu hỏi ban đầu. Vì vậy, liệu thuật toán này có vượt trội hơn so với từ điển trie hay không, tôi không biết tại thời điểm này.

  • Phương pháp bảng được sử dụng checkWordTriplets()mang lại một xấp xỉ rất tốt cho các câu trả lời thực tế. Chỉ 1 trong 3-5 từ được thông qua sẽ không vượt qua checkWords()bài kiểm tra (Xem số lượng thí sinh so với số từ thực tế ở trên).

  • Một cái gì đó bạn không thể thấy ở trên: checkWordTriplets()Hàm mất khoảng 3,65ms và do đó hoàn toàn chiếm ưu thế trong quá trình tìm kiếm. Các checkWords()chức năng chiếm khá nhiều 0,05-0,20 ms còn lại.

  • Thời gian thực hiện của checkWordTriplets()chức năng phụ thuộc tuyến tính vào kích thước từ điển và hầu như không phụ thuộc vào kích thước bảng!

  • Thời gian thực hiện checkWords()phụ thuộc vào kích thước bảng và số lượng từ không được loại trừ bởi checkWordTriplets().

  • Việc checkWords()thực hiện ở trên là phiên bản đầu tiên ngu ngốc nhất mà tôi nghĩ ra. Về cơ bản nó không được tối ưu hóa. Nhưng so với checkWordTriplets()nó là không liên quan đến tổng hiệu suất của ứng dụng, vì vậy tôi không lo lắng về nó. Nhưng , nếu kích thước bảng trở nên lớn hơn, chức năng này sẽ ngày càng chậm hơn và cuối cùng sẽ bắt đầu quan trọng. Sau đó, nó sẽ cần phải được tối ưu hóa là tốt.

  • Một điều tốt đẹp về mã này là tính linh hoạt của nó:

    • Bạn có thể dễ dàng thay đổi kích thước bảng: Cập nhật dòng 10 và mảng Chuỗi được truyền vào initializeBoard().
    • Nó có thể hỗ trợ các bảng chữ cái lớn hơn / khác nhau và có thể xử lý những thứ như coi 'Qu' là một chữ cái mà không có bất kỳ chi phí hiệu năng nào. Để làm điều này, người ta sẽ cần cập nhật dòng 9 và một vài vị trí nơi các ký tự được chuyển đổi thành số (hiện chỉ đơn giản bằng cách trừ 65 từ giá trị ASCII)

Ok, nhưng tôi nghĩ rằng bây giờ bài viết này là waaaay đủ lâu. Tôi chắc chắn có thể trả lời bất kỳ câu hỏi nào bạn có thể có, nhưng hãy chuyển câu hỏi đó sang các bình luận.


Câu trả lời tốt đẹp. Tôi muốn thấy triển khai của bạn trong Java.
MikkoP

@MikkoP Xong! :) Mất khoảng 3 giờ và 220 dòng mã. Cách tốt để vượt qua một buổi chiều. Hãy cho tôi biết nếu bạn có bất kỳ câu hỏi nào về cách thức hoạt động của nó ... :)
Markus A.

Cảm ơn đã đăng mã! Tôi đã thử nó với từ điển của riêng tôi sau khi tôi đã thêm các mục nhập bị thiếu. Tôi nhận được một ArrayIndexOutOfBoundException trên dòng ok = possibleTriplets[entry.triplets[t]];. hmm
MikkoP

@MikkoP Mã này hiện được viết để giả sử từ điển chỉ chứa chữ in hoa AZ. Điểm mấu chốt nằm ở dòng 34: entry.letters[i] = (byte) letter - 65;Nó chỉ đơn giản lấy giá trị ASCII và trừ 65 ("A"). Nếu bạn có Umlauts hoặc chữ thường trong từ điển của bạn, điều này sẽ cung cấp các giá trị lớn hơn 31, không được lên kế hoạch cho cài đặt kích thước bảng chữ cái trong dòng 9. Để hỗ trợ các chữ cái khác, bạn sẽ phải mở rộng dòng này để ánh xạ chúng vào phạm vi cho phép theo kích thước bảng chữ cái.
Markus A.

1
@AlexanderN Có lẽ bạn đang hiểu logic một cách chính xác. Tôi đã mắc lỗi sao chép lưới thư ... Xin lỗi ... (đã sửa)
Markus A.

19

Đáng ngạc nhiên, không ai cố gắng một phiên bản PHP của điều này.

Đây là phiên bản PHP đang hoạt động của giải pháp Python của John Fouhy.

Mặc dù tôi đã lấy một số gợi ý từ câu trả lời của người khác, nhưng điều này chủ yếu được sao chép từ John.

$boggle = "fxie
           amlo
           ewbx
           astu";

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

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

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

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

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

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

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

Đây là một liên kết trực tiếp nếu bạn muốn dùng thử. Mặc dù phải mất ~ 2 giây trong máy cục bộ của tôi, nhưng phải mất ~ 5s trên máy chủ web của tôi. Trong cả hai trường hợp, nó không phải là rất nhanh. Tuy nhiên, mặc dù, nó khá gớm ghiếc nên tôi có thể tưởng tượng thời gian có thể giảm đáng kể. Bất kỳ con trỏ về cách thực hiện sẽ được đánh giá cao. Việc thiếu các bộ dữ liệu của PHP làm cho các tọa độ trở nên kỳ lạ khi làm việc và tôi không thể hiểu được những gì đang xảy ra không giúp ích gì cả.

EDIT : Một vài sửa chữa làm cho nó mất ít hơn 1 giây cục bộ.


+1 @ "và tôi không thể hiểu được những gì đang xảy ra không giúp được gì cả." cười lớn. Tôi yêu sự trung thực!
dna123

Tôi không biết PHP, nhưng điều đầu tiên tôi thử là tích trữ '/ ['. nổ ('', $ bảng chữ cái). '] {3,} $ /' ngoài vòng lặp. Đó là, đặt một biến cho đó và sử dụng biến thay thế bên trong vòng lặp.
Darius Bacon

Tôi khá chắc chắn rằng PHP giữ bộ đệm toàn cục cho mỗi luồng của các biểu thức chính quy được biên dịch, nhưng tôi sẽ thử cách đó.
Paolo Bergantino

1
@Daniel: Rõ ràng đó là máy chủ web của tôi. Nó không xảy ra khi tôi chạy cục bộ. Nhún vai. Đừng thực sự cảm thấy muốn săn lùng nó.
Paolo Bergantino

2
Điều gì nên được đặt làm tham số 7. trong hàm find_words cuối cùng?
MikkoP

16

Không quan tâm đến VB? :) Tôi không thể cưỡng lại. Tôi đã giải quyết vấn đề này khác với nhiều giải pháp được trình bày ở đây.

Thời của tôi là:

  • Tải từ điển và tiền tố từ vào một hashtable: .5 đến 1 giây.
  • Tìm các từ: trung bình dưới 10 mili giây.

EDIT: Thời gian tải từ điển trên máy chủ lưu trữ web đang chạy lâu hơn khoảng 1 đến 1,5 giây so với máy tính ở nhà của tôi.

Tôi không biết thời gian sẽ xấu đi như thế nào với tải trên máy chủ.

Tôi đã viết giải pháp của mình dưới dạng một trang web bằng .Net. myvrad.com/boggle

Tôi đang sử dụng từ điển được tham chiếu trong câu hỏi ban đầu.

Thư không được sử dụng lại trong một từ. Chỉ có từ 3 ký tự trở lên được tìm thấy.

Tôi đang sử dụng một hashtable của tất cả các tiền tố và từ duy nhất thay vì một trie. Tôi không biết về trie nên tôi đã học được điều gì đó ở đó. Ý tưởng tạo ra một danh sách các tiền tố của các từ ngoài các từ hoàn chỉnh là điều cuối cùng đã khiến thời gian của tôi giảm xuống một con số đáng nể.

Đọc các bình luận mã để biết thêm chi tiết.

Đây là mã:

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

Tôi sẽ giả sử ở đây bạn đã sử dụng hệ thống ap thay vì [x] [y] bởi vì cái sau khá phức tạp trong VB? Tôi đã dành một ngày cố gắng để có được một mảng động 2 chiều trong đó, ví dụ: mảng (mảng (1, "xin chào"), 1, "xin chào", mảng ()), vẫn không biết cách làm đó: P
Kent Fredric

Trong các mảng mờ PHP và Perl 2 rất thú vị. Nó có thể được thực hiện trong VB nhưng tôi sẽ không gọi đó là một quá trình thú vị. Dim Arr (,) Là Số nguyên = {{1,1}, {0,0}}. Quá trình AP phát triển từ việc tôi đặt mình lên lưới và hỏi, 'tôi có thể đi đâu từ đây?' Tôi biết đó là một giải pháp cứng nhắc nhưng nó hoạt động ở đây.
rvarcher

Ồ tôi thích VB.NET ... Tôi đã thử URL nhưng nó không hoạt động. Tôi đã phải tự xây dựng lại mã của bạn dưới dạng Windows Forms và nó hoạt động. Cảm ơn.
Ahmed Eissa

11

Ngay khi nhìn thấy tuyên bố vấn đề, tôi đã nghĩ "Trie". Nhưng khi thấy một số áp phích khác sử dụng cách tiếp cận đó, tôi tìm kiếm một cách tiếp cận khác để khác biệt. Than ôi, phương pháp Trie thực hiện tốt hơn. Tôi đã chạy giải pháp Perl của Kent trên máy của mình và phải mất 0,31 giây để chạy, sau khi điều chỉnh nó để sử dụng tệp từ điển của tôi. Việc thực hiện perl của riêng tôi cần 0,54 giây để chạy.

Đây là cách tiếp cận của tôi:

  1. Tạo một hàm băm chuyển tiếp để mô hình hóa các chuyển đổi hợp pháp.

  2. Lặp lại qua tất cả 16 ^ 3 kết hợp ba chữ cái có thể.

    • Trong vòng lặp, loại trừ các chuyển đổi bất hợp pháp và lặp lại các lượt truy cập vào cùng một hình vuông. Hình thành tất cả các chuỗi 3 chữ cái hợp pháp và lưu trữ chúng trong một hàm băm.
  3. Sau đó lặp qua tất cả các từ trong từ điển.

    • Không bao gồm các từ quá dài hoặc ngắn
    • Trượt một cửa sổ 3 chữ cái trên mỗi từ và xem liệu nó có nằm trong số các tổ hợp 3 chữ cái từ bước 2. Loại trừ các từ không thành công. Điều này giúp loại bỏ hầu hết các trận đấu.
    • Nếu vẫn không được loại bỏ, sử dụng thuật toán đệ quy để xem liệu từ đó có thể được hình thành bằng cách thực hiện các đường dẫn thông qua câu đố hay không. (Phần này chậm, nhưng được gọi không thường xuyên.)
  4. In ra những từ tôi tìm thấy.

    Tôi đã thử các chuỗi 3 chữ cái và 4 chữ cái, nhưng các chuỗi 4 chữ cái làm chậm chương trình.

Trong mã của tôi, tôi sử dụng / usr / share / dict / words cho từ điển của mình. Nó có tiêu chuẩn trên MAC OS X và nhiều hệ thống Unix. Bạn có thể sử dụng một tập tin khác nếu bạn muốn. Để bẻ khóa một câu đố khác, chỉ cần thay đổi biến @puheads. Điều này sẽ dễ dàng thích ứng cho ma trận lớn hơn. Bạn chỉ cần thay đổi hàm băm chuyển tiếp% và băm% LegalTransitions.

Điểm mạnh của giải pháp này là mã ngắn và cấu trúc dữ liệu đơn giản.

Đây là mã Perl (tôi sử dụng quá nhiều biến toàn cục, tôi biết):

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

Vị trí của từ điển đã thay đổi? Tôi đã cố gắng tìm các từ trong từ điển vì tôi muốn so sánh giải pháp của mình với mọi người nhưng tôi không thể tìm thấy nó trên liên kết đã cho tại / usr / share / dict. Tôi biết đó là chủ đề khá cũ nhưng thật tuyệt nếu bạn có thể chỉ cho tôi. Cảm ơn trước sự giúp đỡ của bạn.
Naman

Đừng có máy Mac của tôi lúc này. Tất cả những gì bạn cần là một tệp có các từ tiếng Anh, từ một đến một dòng, được phân tách bằng các dòng mới. Bạn có thể tìm thấy một tập tin như vậy trên internet. Một ở đây: mieliestronk.com/corncob_lowercase.txt nhưng có lẽ có nhiều danh sách có nhiều từ hơn thế.
Paul Chernoch

Cám ơn đã trả lời. Tôi đã tìm thấy rằng trong các tập tin Ubuntu.
Naman

9

Tôi biết tôi siêu muộn nhưng tôi đã tạo một trong những thứ này trước đây trong PHP - chỉ để cho vui thôi ...

http://www.lostsockdesign.com.au/sandbox/boggle/index.php?letters=fxieamloewbxastu Tìm thấy 75 từ (133 điểm) trong 0,90108 giây

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

Cung cấp một số dấu hiệu về những gì chương trình đang thực sự làm - mỗi chữ cái là nơi nó bắt đầu xem qua các mẫu trong khi mỗi '.' cho thấy một con đường mà nó đã cố gắng đi. Nhiều hơn '.' có nhiều hơn nó đã tìm kiếm.

Hãy cho tôi biết nếu bạn muốn mã ... đó là sự pha trộn khủng khiếp giữa PHP và HTML không bao giờ có nghĩa là nhìn thấy ánh sáng trong ngày nên tôi không dám đăng nó ở đây: P


9

Tôi đã dành 3 tháng để nghiên cứu một giải pháp cho 10 vấn đề về ván bài 5x5 dày đặc nhất.

Vấn đề hiện đã được giải quyết và đưa ra với công bố đầy đủ trên 5 trang web. Hãy liên hệ với tôi với câu hỏi.

Thuật toán phân tích bảng sử dụng một ngăn xếp rõ ràng để giả mạo đệ quy qua các ô vuông thông qua Biểu đồ từ được điều hướng có hướng với thông tin con trực tiếp và cơ chế theo dõi dấu thời gian. Đây rất có thể là cấu trúc dữ liệu từ vựng tiên tiến nhất thế giới.

Đề án đánh giá khoảng 10.000 bảng rất tốt mỗi giây trên lõi tứ. (9500 điểm)

Trang web phụ huynh:

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

Trang web thành phần:

Bảng điểm tối ưu - http://www.pathcom.com/~vadco/binary.html

Cấu trúc Lexicon nâng cao - http://www.pathcom.com/~vadco/adtdawg.html

Thuật toán phân tích bảng - http://www.pathcom.com/~vadco/guns.html

Xử lý hàng loạt song song - http://www.pathcom.com/~vadco/abul.html

- Cơ quan làm việc toàn diện này sẽ chỉ quan tâm đến một người đòi hỏi những điều tốt nhất.


4
Phân tích của bạn rất thú vị, nhưng kết quả của bạn không phải là bảng kỹ thuật. Trò chơi boggle 5x5 bao gồm một khuôn có chứa các khuôn mặt BJKQXZ, việc triển khai của bạn rõ ràng không bao gồm tất cả các chữ cái này và do đó, vị trí bảng không thực sự có thể có trong một trò chơi Boggle thực sự.
MarkPflug

4

Có phải thuật toán tìm kiếm của bạn liên tục giảm danh sách từ khi tìm kiếm của bạn tiếp tục?

Chẳng hạn, trong tìm kiếm ở trên chỉ có 13 chữ cái mà từ của bạn có thể bắt đầu bằng (giảm hiệu quả xuống còn một nửa số chữ cái bắt đầu).

Khi bạn thêm nhiều hoán vị chữ cái, nó sẽ làm giảm thêm các bộ từ có sẵn làm giảm việc tìm kiếm cần thiết.

Tôi sẽ bắt đầu ở đó.


4

Tôi phải suy nghĩ nhiều hơn về một giải pháp hoàn chỉnh, nhưng như một sự tối ưu hóa tiện dụng, tôi tự hỏi liệu nó có đáng để tính toán trước một bảng tần số digram và trigram (kết hợp 2 và 3 chữ cái) dựa trên tất cả từ trong từ điển của bạn và sử dụng từ này để ưu tiên tìm kiếm của bạn. Tôi sẽ đi với các chữ cái bắt đầu của từ. Vì vậy, nếu từ điển của bạn chứa các từ "Ấn Độ", "Nước", "Cực kỳ" và "Bất thường", thì bảng tính toán trước của bạn có thể là:

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

Sau đó tìm kiếm các digram này theo thứ tự phổ biến (đầu tiên là EX, sau đó là WA / IN)


4

Trước tiên, hãy đọc cách một trong những nhà thiết kế ngôn ngữ C # giải quyết một vấn đề liên quan: http://bloss.msdn.com/ericlippert/archive/2009/02/04/a-nasality-talisman-for-the-sultana-analyst.aspx .

Giống như anh ấy, bạn có thể bắt đầu với một từ điển và các từ canonacalize bằng cách tạo một từ điển từ một mảng các chữ cái được sắp xếp theo thứ tự abc đến một danh sách các từ có thể được đánh vần từ các chữ cái đó.

Tiếp theo, bắt đầu tạo các từ có thể từ bảng và tìm kiếm chúng. Tôi nghi ngờ rằng điều đó sẽ giúp bạn đi khá xa, nhưng chắc chắn có nhiều thủ thuật có thể tăng tốc mọi thứ.


4

Tôi đề nghị làm một cây chữ dựa trên các từ. Cây sẽ bao gồm một cấu trúc chữ cái, như thế này:

letter: char
isWord: boolean

Sau đó, bạn xây dựng cây, với mỗi độ sâu thêm một chữ cái mới. Nói cách khác, ở cấp độ đầu tiên sẽ có bảng chữ cái; sau đó từ mỗi cây đó, sẽ có thêm 26 mục nữa, và cứ thế, cho đến khi bạn đánh vần tất cả các từ. Treo lên cây được phân tích cú pháp này và nó sẽ làm cho tất cả các câu trả lời có thể nhanh hơn để tra cứu.

Với cây được phân tích cú pháp này, bạn có thể rất nhanh chóng tìm ra giải pháp. Đây là mã giả:

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.

Điều này có thể được tăng tốc với một chút lập trình động. Ví dụ: trong mẫu của bạn, hai chữ 'A đều nằm cạnh' E 'và' W ', mà (từ điểm chúng chạm vào chúng) sẽ giống hệt nhau. Tôi không có đủ thời gian để thực sự đánh vần mã cho việc này, nhưng tôi nghĩ bạn có thể thu thập ý tưởng.

Ngoài ra, tôi chắc chắn bạn sẽ tìm thấy các giải pháp khác nếu bạn Google cho "Người giải quyết vấn đề".



3

Vui nhộn. Tôi gần như đã đăng cùng một câu hỏi vài ngày trước vì cùng một trò chơi chết tiệt! Tuy nhiên, tôi đã không tìm kiếm google cho python boggle solver và có tất cả các câu trả lời tôi muốn.


Tôi không biết tên phổ biến của nó là "boggle", nhưng tôi đã tìm thấy một số thứ trên google, tôi chỉ tò mò muốn xem mọi người sẽ nghĩ gì về SO. :)
Paolo Bergantino

3

Tôi nhận ra thời gian của câu hỏi này đã đến và biến mất, nhưng vì tôi đang tự mình làm một người giải quyết, và tình cờ gặp vấn đề này trong khi đang loay hoay, tôi nghĩ tôi nên đăng một tài liệu tham khảo cho tôi vì nó có vẻ hơi khác với một số người khác.

Tôi đã chọn đi với một mảng phẳng cho bảng trò chơi và thực hiện các cuộc săn đệ quy từ mỗi chữ cái trên bảng, đi qua hàng xóm hợp lệ đến hàng xóm hợp lệ, mở rộng săn nếu danh sách các chữ cái hiện tại nếu có tiền tố hợp lệ trong một chỉ mục. Trong khi đi qua khái niệm của từ hiện tại là danh sách các chỉ mục vào bảng, không phải các chữ cái tạo thành một từ. Khi kiểm tra chỉ mục, các chỉ mục được dịch sang các chữ cái và kiểm tra được thực hiện.

Chỉ mục này là một từ điển vũ phu hơi giống một trie, nhưng cho phép truy vấn Pythonic của chỉ mục. Nếu các từ 'mèo' và 'phục vụ' có trong danh sách, bạn sẽ nhận được từ này trong từ điển:

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

Vì vậy, nếu current_word là 'ca', bạn biết rằng đó là tiền tố hợp lệ vì 'ca' in dtrả về True (vì vậy hãy tiếp tục duyệt bảng). Và nếu current_word là 'cat' thì bạn biết rằng đó là một từ hợp lệ vì nó là tiền tố hợp lệ và cũng 'cat' in d['cat']trả về True.

Nếu cảm thấy như thế này cho phép một số mã có thể đọc được mà dường như không quá chậm. Giống như mọi người khác, chi phí trong hệ thống này là đọc / xây dựng chỉ mục. Giải quyết bảng là khá nhiều tiếng ồn.

Mã này có tại http://gist.github.com/268079 . Nó có chủ ý thẳng đứng và ngây thơ với rất nhiều kiểm tra tính hợp lệ rõ ràng bởi vì tôi muốn hiểu vấn đề mà không vượt qua nó bằng một loạt các phép thuật hoặc che khuất.


3

Tôi đã viết bộ giải của tôi trong C ++. Tôi thực hiện một cấu trúc cây tùy chỉnh. Tôi không chắc nó có thể được coi là một trie nhưng nó tương tự nhau. Mỗi nút có 26 nhánh, 1 cho mỗi chữ cái của bảng chữ cái. Tôi đi qua các nhánh của bảng boggle song song với các nhánh trong từ điển của tôi. Nếu nhánh không tồn tại trong từ điển, tôi dừng tìm kiếm nó trên bảng Boggle. Tôi chuyển đổi tất cả các chữ cái trên bảng thành ints. Vậy 'A' = 0. Vì nó chỉ là mảng, tra cứu luôn là O (1). Mỗi nút lưu trữ nếu nó hoàn thành một từ và có bao nhiêu từ tồn tại trong con của nó. Cây được cắt tỉa vì các từ được tìm thấy để giảm nhiều lần tìm kiếm cùng một từ. Tôi tin rằng cắt tỉa cũng là O (1).

CPU: Pentium SU2700 1.3GHz
RAM 1,3 GHz: 3gb

Tải từ điển 178.590 từ trong <1 giây.
Giải quyết 100x100 Boggle (boggle.txt) trong 4 giây. ~ 44.000 từ được tìm thấy.
Việc giải quyết một Boggle 4x4 là quá nhanh để cung cấp một điểm chuẩn có ý nghĩa. :)

Bộ giải nhanh GgleHub Repo


2

Đưa ra một bảng Boggle với N hàng và cột M, hãy giả sử như sau:

  • N * M lớn hơn số lượng từ có thể
  • N * M lớn hơn nhiều so với từ dài nhất có thể

Theo các giả định này, độ phức tạp của giải pháp này là O (N * M).

Tôi nghĩ rằng việc so sánh thời gian chạy cho bảng một ví dụ này theo nhiều cách sẽ bỏ lỡ vấn đề, nhưng để hoàn thiện, giải pháp này hoàn thành trong <0,2 giây trên MacBook Pro hiện đại của tôi.

Giải pháp này sẽ tìm thấy tất cả các đường dẫn có thể cho mỗi từ trong kho văn bản.

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

Giải pháp này cũng đưa ra hướng tìm kiếm trong bảng đã cho

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

Đầu ra:

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

Mã số:

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

Tôi đã thực hiện một giải pháp trong OCaml . Nó biên dịch trước một từ điển dưới dạng trie và sử dụng tần số chuỗi hai chữ cái để loại bỏ các cạnh không bao giờ xuất hiện trong một từ để tăng tốc độ xử lý.

Nó giải quyết bảng ví dụ của bạn trong 0,35ms (với thời gian khởi động thêm 6ms, chủ yếu liên quan đến việc tải bộ ba vào bộ nhớ).

Các giải pháp tìm thấy:

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

Điều này là tốt, nhưng tất cả các lần được đăng ở đây liên quan đến bất kỳ thời gian "khởi động" nào để tải từ điển vào bộ nhớ, vì vậy so sánh 0,35 với các thời điểm khác là khá chính xác. Ngoài ra, bạn đang sử dụng một từ điển khác nhau? Bạn đang thiếu một số từ. Dù bằng cách nào, +1
Paolo Bergantino

Thời gian khởi động mất 6ms, vì vậy bạn đang xem 6,35ms để chạy hoàn chỉnh. Tôi đang sử dụng /usr/share/dicttừ điển địa phương của mình và một số từ thực sự bị thiếu (chẳng hạn như EMBOLE).
Victor Nicollet

1

Một giải pháp JavaScript Node.JS. Tính toán tất cả 100 từ duy nhất trong chưa đầy một giây bao gồm đọc tệp từ điển (MBA 2012).

Đầu ra:
["FAM", "TUX", "TUB", "FAE", "ELI", "ELM", "ELB", "TWA", "TWA", "SAW", "AMI", "SWA" , "SWA", "AME", "BIỂN", "SEW", "AES", "AWL", "AWE", "BIỂN", "AWA", "MIX", "SỮA", "AST", " ASE "," MAX "," MAE "," MAW "," MEW "," AWE "," MES "," AWL "," LIE "," LIM "," AWA "," AES "," NHƯNG " , "BLO", "WAS", "WAE", "WEA", "LEI", "LEO", "LOB", "LOX", "WEM", "DẦU", "OLM", "WEA", " WAE "," THUẾ "," WAF ","SỮA", "ĐÔNG", "WAME", "TWAS", "TWAE", "EMIL", "WEAM", "OIME", "AXIL", "TÂY", "TWAE", "LIMB", "WASE "," WAST "," BLEO "," STUB "," BOIL "," BOLE "," LIME "," SAWT "," LIMA "," MESA "," MEWL "," AXLE "," FAME ", "ASEM", "MILE", "AMIL", "SEAX", "SEAM", "SEMI", "SWAM", "AMBO", "AMLI", "AXILE", "AMBLE", "SWAMI", "TUYỆT VỜI" "," TUYỆT VỜI "," LIMAX "," LIMES "," LIMBU "," LIMBO "," EMBOX "," SEMBLE "," EMBOLE "," WAMBLE "," FAMBLE "]ĐÔNG "," WAME "," TWAS "," TWAE "," EMIL "," WEAM "," OIME "," AXIL "," TÂY "," TWAE "," LIMB "," WASE "," WAST " , "BLEO", "STUB", "BOIL", "BOLE", "LIME", "SAWT", "LIMA", "MESA", "MEWL", "AXLE", "FAME", "ASEM", " MILE "," AMIL "," SEAX "," SEAM "," SEMI "," SWAM "," AMBO "," AMLI "," AXILE "," AMBLE "," SWAMI "," AWEST "," AWEST " , "LIMAX", "LIMES", "LIMBU", "LIMBO", "EMBOX", "SEMBLE", "EMBOLE", "WAMBLE", "FAMBLE"]ĐÔNG "," WAME "," TWAS "," TWAE "," EMIL "," WEAM "," OIME "," AXIL "," TÂY "," TWAE "," LIMB "," WASE "," WAST " , "BLEO", "STUB", "BOIL", "BOLE", "LIME", "SAWT", "LIMA", "MESA", "MEWL", "AXLE", "FAME", "ASEM", " MILE "," AMIL "," SEAX "," SEAM "," SEMI "," SWAM "," AMBO "," AMLI "," AXILE "," AMBLE "," SWAMI "," AWEST "," AWEST " , "LIMAX", "LIMES", "LIMBU", "LIMBO", "EMBOX", "SEMBLE", "EMBOLE", "WAMBLE", "FAMBLE"]"TWAE", "EMIL", "WEAM", "OIME", "AXIL", "TÂY", "TWAE", "LIMB", "WASE", "WAST", "BLEO", "STUB", "BOIL" "," BOLE "," LIME "," SAWT "," LIMA "," MESA "," MEWL "," AXLE "," FAME "," ASEM "," MILE "," AMIL "," SEAX "," "SEAM", "SEMI", "SWAM", "AMBO", "AMLI", "AXILE", "AMBLE", "SWAMI", "AWEST", "AWEST", "LIMAX", "LIMES", "LIMBU" "," LIMBO "," EMBOX "," SEMBLE "," EMBOLE "," WAMBLE "," FAMBLE "]"TWAE", "EMIL", "WEAM", "OIME", "AXIL", "TÂY", "TWAE", "LIMB", "WASE", "WAST", "BLEO", "STUB", "BOIL" "," BOLE "," LIME "," SAWT "," LIMA "," MESA "," MEWL "," AXLE "," FAME "," ASEM "," MILE "," AMIL "," SEAX "," "SEAM", "SEMI", "SWAM", "AMBO", "AMLI", "AXILE", "AMBLE", "SWAMI", "AWEST", "AWEST", "LIMAX", "LIMES", "LIMBU" "," LIMBO "," EMBOX "," SEMBLE "," EMBOLE "," WAMBLE "," FAMBLE "]"TÂY", "TWAE", "LIMB", "WASE", "WAST", "BLEO", "STUB", "BOIL", "BOLE", "LIME", "SAWT", "LIMA", "MESA "," MEWL "," AXLE "," FAME "," ASEM "," MILE "," AMIL "," SEAX "," SEAM "," SEMI "," SWAM "," AMBO "," AMLI ", "AXILE", "AMBLE", "SWAMI", "AWEST", "AWEST", "LIMAX", "LIMES", "LIMBU", "LIMBO", "EMBOX", "SEMBLE", "EMBOLE", "WAMBLE "," GIA ĐÌNH "]"TÂY", "TWAE", "LIMB", "WASE", "WAST", "BLEO", "STUB", "BOIL", "BOLE", "LIME", "SAWT", "LIMA", "MESA "," MEWL "," AXLE "," FAME "," ASEM "," MILE "," AMIL "," SEAX "," SEAM "," SEMI "," SWAM "," AMBO "," AMLI ", "AXILE", "AMBLE", "SWAMI", "AWEST", "AWEST", "LIMAX", "LIMES", "LIMBU", "LIMBO", "EMBOX", "SEMBLE", "EMBOLE", "WAMBLE "," GIA ĐÌNH "]SAWT "," LIMA "," MESA "," MEWL "," AXLE "," FAME "," ASEM "," MILE "," AMIL "," SEAX "," SEAM "," SEMI "," SWAM " , "AMBO", "AMLI", "AXILE", "AMBLE", "SWAMI", "TUYỆT VỜI", "TUYỆT VỜI", "LIMAX", "LIMES", "LIMBU", "LIMBO", "EMBOX", " SEMBLE "," EMBOLE "," WAMBLE "," FAMBLE "]SAWT "," LIMA "," MESA "," MEWL "," AXLE "," FAME "," ASEM "," MILE "," AMIL "," SEAX "," SEAM "," SEMI "," SWAM " , "AMBO", "AMLI", "AXILE", "AMBLE", "SWAMI", "TUYỆT VỜI", "TUYỆT VỜI", "LIMAX", "LIMES", "LIMBU", "LIMBO", "EMBOX", " SEMBLE "," EMBOLE "," WAMBLE "," FAMBLE "]LIMAX "," LIMES "," LIMBU "," LIMBO "," EMBOX "," SEMBLE "," EMBOLE "," WAMBLE "," FAMBLE "]LIMAX "," LIMES "," LIMBU "," LIMBO "," EMBOX "," SEMBLE "," EMBOLE "," WAMBLE "," FAMBLE "]

Mã số:

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

Vì vậy, tôi muốn thêm một cách khác để giải quyết vấn đề này vì mọi người đều yêu thích PHP. Có một chút tái cấu trúc mà tôi muốn thực hiện, như sử dụng một kết hợp regexpression với tệp từ điển, nhưng ngay bây giờ tôi chỉ tải toàn bộ tệp từ điển vào một wordList.

Tôi đã làm điều này bằng cách sử dụng một ý tưởng danh sách liên kết. Mỗi nút có một giá trị ký tự, giá trị vị trí và con trỏ tiếp theo.

Giá trị vị trí là cách tôi tìm ra nếu hai nút được kết nối.

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

Vì vậy, bằng cách sử dụng lưới đó, tôi biết hai nút được kết nối nếu vị trí của nút thứ nhất bằng với vị trí nút thứ hai +/- 1 cho cùng một hàng, +/- 9, 10, 11 cho hàng trên và dưới.

Tôi sử dụng đệ quy cho tìm kiếm chính. Nó loại bỏ wordList, tìm tất cả các điểm bắt đầu có thể và sau đó tìm đệ quy kết nối có thể tiếp theo, lưu ý rằng nó không thể đi đến một vị trí mà nó đã sử dụng (đó là lý do tại sao tôi thêm $ notInLoc).

Dù sao, tôi biết nó cần một số cấu trúc lại, và rất thích nghe suy nghĩ về cách làm cho nó sạch hơn, nhưng nó tạo ra kết quả chính xác dựa trên tệp từ điển tôi đang sử dụng. Tùy thuộc vào số lượng nguyên âm và kết hợp trên bảng, mất khoảng 3 đến 6 giây. Tôi biết rằng một khi tôi preg_match kết quả từ điển, điều đó sẽ giảm đáng kể.

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

Tôi biết tôi thực sự đến muộn trong bữa tiệc nhưng tôi đã thực hiện, như một bài tập mã hóa, một người giải boggle trong một số ngôn ngữ lập trình (C ++, Java, Go, C #, Python, Ruby, JavaScript, Julia, Lua, PHP, Perl) và Tôi nghĩ rằng ai đó có thể quan tâm đến những người đó, vì vậy tôi để lại liên kết ở đây: https://github.com/AmokHuginnsson/boggle-solvers


1

Đây là giải pháp Sử dụng các từ được xác định trước trong bộ công cụ NLTK NLTK có gói nltk.corpus trong đó chúng tôi có gói từ được gọi và nó chứa hơn 2Lakhs các từ tiếng Anh bạn có thể sử dụng tất cả vào chương trình của mình.

Khi tạo ma trận của bạn chuyển đổi nó thành một mảng ký tự và thực hiện mã này

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)

Đầu ra:

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

Tôi hi vọng bạn có được nó.


0

Đây là cách triển khai java của tôi: https://github.com/zouzhile/interview/blob/master/src/com/interview/alerskyms/tree/BoggleSolver.java

Quá trình xây dựng Trie mất 0 giờ, 0 phút, 1 giây, 535 mili giây
Tìm kiếm từ mất 0 giờ, 0 phút, 0 giây, 92 mili giây

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 

Lưu ý: Tôi đã sử dụng từ điển và ma trận ký tự ở đầu chuỗi này. Mã đã được chạy trên MacBookPro của tôi, dưới đây là một số thông tin về máy.

Tên model: MacBook Pro
Định danh mẫu: MacBookPro8,1
Tên bộ xử lý: Intel Core i5
Tốc độ bộ xử lý: 2.3 GHz
Số bộ xử lý: 1
Tổng số lõi: 2
L2 Cache (mỗi lõi): 256 KB
L3 Cache: 3 MB
Bộ nhớ: 4
Phiên bản ROM khởi động GB : MBP81.0047.B0E
Phiên bản SMC (hệ thống): 1.68f96


0

Tôi cũng đã giải quyết vấn đề này với Java. Việc thực hiện của tôi là 269 dòng dài và khá dễ sử dụng. Trước tiên, bạn cần tạo một thể hiện mới của lớp Boggler và sau đó gọi hàm giải quyết với lưới làm tham số. Mất khoảng 100 ms để tải từ điển 50 000 từ trên máy tính của tôi và nó tìm thấy các từ trong khoảng 10-20 ms. Các từ tìm thấy được lưu trữ trong một ArrayList , foundWords.

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;

public class Boggler {
    private ArrayList<String> words = new ArrayList<String>();      
    private ArrayList<String> roundWords = new ArrayList<String>(); 
    private ArrayList<Word> foundWords = new ArrayList<Word>();     
    private char[][] letterGrid = new char[4][4];                   
    private String letters;                                         

    public Boggler() throws FileNotFoundException, IOException, URISyntaxException {
        long startTime = System.currentTimeMillis();

        URL path = GUI.class.getResource("words.txt");
        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(new File(path.toURI()).getAbsolutePath()), "iso-8859-1"));
        String line;
        while((line = br.readLine()) != null) {
            if(line.length() < 3 || line.length() > 10) {
                continue;
            }

            this.words.add(line);
        }
    }

    public ArrayList<Word> getWords() {
        return this.foundWords;
    }

    public void solve(String letters) {
        this.letters = "";
        this.foundWords = new ArrayList<Word>();

        for(int i = 0; i < letters.length(); i++) {
            if(!this.letters.contains(letters.substring(i, i + 1))) {
                this.letters += letters.substring(i, i + 1);
            }
        }

        for(int i = 0; i < 4; i++) {
            for(int j = 0; j < 4; j++) {
                this.letterGrid[i][j] = letters.charAt(i * 4 + j);
            }
        }

        System.out.println(Arrays.deepToString(this.letterGrid));               

        this.roundWords = new ArrayList<String>();      
        String pattern = "[" + this.letters + "]+";     

        for(int i = 0; i < this.words.size(); i++) {

            if(this.words.get(i).matches(pattern)) {
                this.roundWords.add(this.words.get(i));
            }
        }

        for(int i = 0; i < this.roundWords.size(); i++) {
            Word word = checkForWord(this.roundWords.get(i));

            if(word != null) {
                System.out.println(word);
                this.foundWords.add(word);
            }
        }       
    }

    private Word checkForWord(String word) {
        char initial = word.charAt(0);
        ArrayList<LetterCoord> startPoints = new ArrayList<LetterCoord>();

        int x = 0;  
        int y = 0;
        for(char[] row: this.letterGrid) {
            x = 0;

            for(char letter: row) {
                if(initial == letter) {
                    startPoints.add(new LetterCoord(x, y));
                }

                x++;
            }

            y++;
        }

        ArrayList<LetterCoord> letterCoords = null;
        for(int initialTry = 0; initialTry < startPoints.size(); initialTry++) {
            letterCoords = new ArrayList<LetterCoord>();    

            x = startPoints.get(initialTry).getX(); 
            y = startPoints.get(initialTry).getY();

            LetterCoord initialCoord = new LetterCoord(x, y);
            letterCoords.add(initialCoord);

            letterLoop: for(int letterIndex = 1; letterIndex < word.length(); letterIndex++) {
                LetterCoord lastCoord = letterCoords.get(letterCoords.size() - 1);  
                char currentChar = word.charAt(letterIndex);                        

                ArrayList<LetterCoord> letterLocations = getNeighbours(currentChar, lastCoord.getX(), lastCoord.getY());

                if(letterLocations == null) {
                    return null;    
                }       

                for(int foundIndex = 0; foundIndex < letterLocations.size(); foundIndex++) {
                    if(letterIndex != word.length() - 1 && true == false) {
                        char nextChar = word.charAt(letterIndex + 1);
                        int lastX = letterCoords.get(letterCoords.size() - 1).getX();
                        int lastY = letterCoords.get(letterCoords.size() - 1).getY();

                        ArrayList<LetterCoord> possibleIndex = getNeighbours(nextChar, lastX, lastY);
                        if(possibleIndex != null) {
                            if(!letterCoords.contains(letterLocations.get(foundIndex))) {
                                letterCoords.add(letterLocations.get(foundIndex));
                            }
                            continue letterLoop;
                        } else {
                            return null;
                        }
                    } else {
                        if(!letterCoords.contains(letterLocations.get(foundIndex))) {
                            letterCoords.add(letterLocations.get(foundIndex));

                            continue letterLoop;
                        }
                    }
                }
            }

            if(letterCoords != null) {
                if(letterCoords.size() == word.length()) {
                    Word w = new Word(word);
                    w.addList(letterCoords);
                    return w;
                } else {
                    return null;
                }
            }
        }

        if(letterCoords != null) {
            Word foundWord = new Word(word);
            foundWord.addList(letterCoords);

            return foundWord;
        }

        return null;
    }

    public ArrayList<LetterCoord> getNeighbours(char letterToSearch, int x, int y) {
        ArrayList<LetterCoord> neighbours = new ArrayList<LetterCoord>();

        for(int _y = y - 1; _y <= y + 1; _y++) {
            for(int _x = x - 1; _x <= x + 1; _x++) {
                if(_x < 0 || _y < 0 || (_x == x && _y == y) || _y > 3 || _x > 3) {
                    continue;
                }

                if(this.letterGrid[_y][_x] == letterToSearch && !neighbours.contains(new LetterCoord(_x, _y))) {
                    neighbours.add(new LetterCoord(_x, _y));
                }
            }
        }

        if(neighbours.isEmpty()) {
            return null;
        } else {
            return neighbours;
        }
    }
}

class Word {
    private String word;    
    private ArrayList<LetterCoord> letterCoords = new ArrayList<LetterCoord>();

    public Word(String word) {
        this.word = word;
    }

    public boolean addCoords(int x, int y) {
        LetterCoord lc = new LetterCoord(x, y);

        if(!this.letterCoords.contains(lc)) {
            this.letterCoords.add(lc);

            return true;
        }

        return false;
    }

    public void addList(ArrayList<LetterCoord> letterCoords) {
        this.letterCoords = letterCoords;
    } 

    @Override
    public String toString() {
        String outputString = this.word + " ";
        for(int i = 0; i < letterCoords.size(); i++) {
            outputString += "(" + letterCoords.get(i).getX() + ", " + letterCoords.get(i).getY() + ") ";
        }

        return outputString;
    }

    public String getWord() {
        return this.word;
    }

    public ArrayList<LetterCoord> getList() {
        return this.letterCoords;
    }
}

class LetterCoord extends ArrayList {
    private int x;          
    private int y;          

    public LetterCoord(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return this.x;
    }

    public int getY() {
        return this.y;
    }

    @Override
    public boolean equals(Object o) {
        if(!(o instanceof LetterCoord)) {
            return false;
        }

        LetterCoord lc = (LetterCoord) o;

        if(this.x == lc.getX() &&
                this.y == lc.getY()) {
            return true;
        }

        return false;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 29 * hash + this.x;
        hash = 24 * hash + this.y;
        return hash;
    }
}

0

Tôi đã giải quyết điều này trong c. Mất khoảng 48 ms để chạy trên máy của tôi (với khoảng 98% thời gian dành để tải từ điển từ đĩa và tạo bộ ba). Từ điển là / usr / share / dict / American-english có 62886 từ.

Mã nguồn


0

Tôi đã giải quyết điều này một cách hoàn hảo và rất nhanh. Tôi đặt nó vào một ứng dụng Android. Xem video tại liên kết cửa hàng play để xem nó hoạt động.

Word Cheats là một ứng dụng "bẻ khóa" bất kỳ trò chơi chữ kiểu ma trận nào. Ứng dụng này được xây dựng để giúp tôi gian lận trong trò chơi scrambler. Nó có thể được sử dụng để tìm kiếm từ, đánh lừa, từ, tìm từ, bẻ khóa từ, boggle, và nhiều hơn nữa!

Có thể xem tại đây https://play.google.com/store/apps/details?id=com.harris.wordcracker

Xem ứng dụng hoạt động trong video https://www.youtube.com/watch?v=DL2974WmNAI

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.