Написание алгоритма для скрэббл - PullRequest
17 голосов
/ 23 марта 2010

Я работаю над кроссвордоподобной проблемой, но не знаю, как разработать алгоритм.

Например:

  • в словаре есть такие слова, как «машина», «яблоко».
  • на доске дается слово «приложение».
  • есть такие буквы, как 'l' 'e' 'c' 'r' .... для создания слов.

Таким образом, задача алгоритма состоит в том, чтобы составить правильные слова, которые хранятся в словаре.

app -> lapp -> leapp -> lecapp -> .... -> lappe -> eappc -> ... -> appl -> apple (правильный ответ)

Какое лучшее решение для этого алгоритма?

Ответы [ 9 ]

11 голосов
/ 23 марта 2010

Возможно, вас заинтересует поиск в Google исследовательской работы "Самая быстрая в мире программа для скрэббл" , написанной Аппелем и Якобсоном (1988). Алгоритмы обрисованы в общих чертах в псевдокоде, таким образом, требуется немного работы, чтобы преобразовать это в пригодную форму и склеить все это вместе; Тем не менее, программа, которую обрисовали авторы, прекрасно работает.

9 голосов
/ 23 марта 2010

Сохраните свой словарь в виде дерева, например:

          *
          |
     +----+----+
     |         |
     A*        B
     |         |
  +--+--+      E*
  |     |      |
  P     S    +-+-+
  |     |    |   |
  P*    K*   A   E*
  |          |
+-+-+      +-+-+
|   |      |   |
E   L      D*  N*
|   |
A   E*
|
L*

Спасибо paxdiablo за то, что мое дерево стало более читабельным.

В этом дереве есть слова a, app, обращение, яблоко,спроси, бусинка, боб, быть и пчела.Узлы, отмеченные звездочкой, обозначают «Если бы я остановился здесь, это было бы правильное слово», например, «е» ниже «б» для «быть».

Когда вы найдете письмо, которое вы надеваетеНе знаю, используйте подстановочный знак (т. е. выбирайте всех детей и повторяйте все пути).

Вы сказали кроссворд, но затем ваши "буквы ... для создания слов", казалось, указывали на Эрудит.Это будет работать для любого.Не самый быстрый, но достаточно быстрый.

Спасибо Андреасу за напоминание о том, что это называется три.

Если вы хотите сказать, что «вторая буква - это буква P», вы начинаете скорневой узел и берут каждую ветвь (которая будет каждой буквой в алфавите, предполагая, что это правильный словарь), затем ветвь "P" и далее оттуда.

5 голосов
/ 23 марта 2010

Я уже писал программу кроссвордов (загадочно, но теория, лежащая в основе конструкции, идентична).

У меня была база данных слов и их подсказок, которые можно было отсортировать по времени (чтобы я не получал повторяющиеся кроссворды при последующих запусках).

Первое, что вы должны сделать, это создать свои шаблоны (черные, где вы не можете поместить буквы и белые, где можете). Попытка поместить слова в сетку при создании шаблона на лету очень трудоемка и подвержена ошибкам. Если вы посмотрите на большинство кроссвордов, они, как правило, следуют определенным правилам, чтобы облегчить их. Такие вещи, как симметричность вокруг одной из диагоналей и запрет квадрата из четырех белых клеток (чтобы упростить задачу выбора подходящих слов).

Когда у вас есть шаблон, , затем вы начинаете находить слова для размещения в нем. Таким образом, вы будете знать, что «приложение» является началом слова и сможете ограничить поиск тем, которые начинаются с «приложения», а не каждым словом, в котором есть «приложение». Аналогично для слов, где вы знаете буквы на любых позициях. Гораздо проще найти слова с буквами в известных позициях, чем оценивать эти буквы в любой начальной позиции в слове.

Мой закончил тем, что был написан в сценарии оболочки (хотите верьте, хотите нет) и использовал словарь, полученный из Linux, в качестве инструмента поиска слов. Если вы знаете, что у вас есть 5-буквенное слово, начинающееся с «app», его довольно просто использовать:

grep '^app..$' words.txt

, чтобы получить список всех допустимых возможностей.

И, поскольку каждое слово было найдено, оно было скопировано в файл clues.txt, который содержал как слово, так и несколько возможных подсказок. Фактическим форматом было использование {count, word, clue}, где одно и то же слово может существовать в нескольких строках с разным ключом - это позволило использовать конвейер от grep до sort, так что менее употребляемые слова / подсказки всплыли наверх ( всякий раз, когда использовалось слово / подсказка, его счет увеличивался, что уменьшало вероятность его использования в следующий раз).

Как только этот файл имел приличный размер, программа сначала использовала бы его для поиска слов, и, только если он не был найден, он вернется к файлу слов (без подсказок), где потребуется ручное вмешательство.

Это на самом деле оказалось довольно хорошим делом. Это было не слишком быстро, но мне не нужно было генерировать его каждые три секунды - это было для рассылки сообщества, рассылаемой раз в неделю.


Теперь, когда вы изменили вопрос на вариант Scrabble, на самом деле это намного сложнее.

Вы должны принять во внимание буквы, которые у вас есть, буквы на доске и тот факт, что есть гораздо больше мест, которые вы должны оценить. Это делает метод грубой силы намного сложнее.

То, что я хотел бы сделать в качестве начального среза, - это выбрать случайно выбранные возможности (начальная позиция и направление на доске), а затем использовать тот же алгоритм, что и для варианта кроссворда выше, чтобы найти все слова, которые могут поместиться там. Затем, если у вас есть буквы, соответствующие этому слову, сохраните его (вместе с его счетом) в списке.

Имейте в виду, что вам нужно остерегаться других слов на доске.

Я бы продолжал изучать возможности до тех пор, пока:

  • Ваш список достаточно велик, чтобы выбрать.
  • у вас закончилось время.
  • вы изучили достаточно возможностей, чтобы удовлетворить свой уровень компетенции.

Это последнее важно - если вы играете новичка, вы не хотите исчерпывающе исследовать миллионы возможностей.

Затем выберите лучший ход из своего списка (или, возможно, не самый лучший, если играете на уровне новичка - все зависит от того, насколько хорошим вы хотите, чтобы компьютер был).

4 голосов
/ 12 марта 2011

Стивен А. Гордон написал интересную статью о том, как искать возможные ходы Эрудита (я полагаю) (см. Статья Гордона о GADDAG ).Хотя между поиском ходов и выигрышем в Scrabble существует большой разрыв - как упоминается в статье - это не относится к исходному вопросу.

В случае, если вы найдете наиболее полезным просто перейти прямо кчитая некоторый код, есть хороший проигрыватель с открытым исходным кодом, Quackle .

1 голос
/ 15 октября 2011

Большинство газет Scrabble говорят о поиске по всему игровому столу лучшего слова для игры. Но для решения вашей проблемы, как уже говорилось, есть довольно простой алгоритм.

Во-первых, вы знаете, что нужное вам слово содержит «приложение», и вы знаете, что самое большое слово, которое вы можете сделать, состоит из семи букв (3 буквы уже на доске и 4 в вашем трее). Итак, ищите в вашей базе данных оператор SQL, такой как:

Выберите слово из словаря, где слово LIKE '% app%' и len (слово) <= 7 </p>

Затем поместите все семь букв в массив {l, e, c, r, a, p, p}

Читайте каждое слово из базы данных, по одному за раз. Затем посмотрите на каждый символ словарного слова и посмотрите, существует ли он в массиве. Если в массиве найдена первая буква слова из словаря, удалите этот элемент из массива и перейдите к следующей букве из словаря.

Если какая-либо буква слова из словаря не найдена в массиве, тогда слово не подходит, поэтому перейдите к следующему слову.

Если вы просмотрели все буквы в словаре и все они были найдены в массиве, тогда слово подходит, и вы записываете его в список.

Обратите внимание, причина, по которой вы помещаете свои листы в массив, состоит в том, что после того, как вы сопоставите букву из словарного слова с листом в вашем массиве, вам нужно будет удалить эту букву из дальнейшего рассмотрения, удалив этот элемент в массив.

Так, например, словарная база данных возвращает слово «обращение». Первые четыре буквы находятся в вашем массиве, и эти элементы удаляются, оставляя в массиве только {l, c, r}. Когда вы ищите пятую букву «а», вы не найдете ее, поэтому слово дисквалифицировано.

Слово «яблоко» будет определяться, оставляя {c, r} в вашем массиве.

Довольно просто написать код на любом языке. Однако это не самый быстрый способ сделать это. Я сам ищу более быстрый путь!

0 голосов
/ 28 февраля 2014

То, что вы ищете, - это способность вашего решателя анаграмм находить «подстановочные» буквы, чтобы увидеть, что над словами можно сделать с помощью дополнительных букв. У меня есть решатель анаграмм, который я написал, который делает именно эту вещь. Одна важная вещь, которую я обнаружил, чтобы сделать это, а также для скорости решателя, состоит в том, чтобы заранее определить, сколько букв и количество слов в каждой таблице слов.

Для Instance You таблица должна быть структурирована следующим образом

word | a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z | score
-------------------------------------------------------------------------------------------------------------
test | 0 | 0 | 0 | 0 | 1 | 0 | 0 | h | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 2 | 0 | 0 | 0 | 0 | 0 | 0 | 4

Как видите, есть отдельная колонка для слова, буквы и количества этой буквы, которое они содержат, и оценки этого слова. Я создал это заранее с помощью отдельного сценария, который просто выполнялся для каждого слова и заполнял его для меня до тех пор, пока это не было сделано.

Вот скрипт, который я написал для подсчета количества букв в каждом слове, а также для оценки и обновления каждой записи. Вы должны начать с таблицы, в которой есть только слова, прежде чем вы сможете запустить этот скрипт. После того, как вы запустите его, все готово, и вам не придется запускать его снова, если вы не добавите новые слова.

<?
include('/includes/connect.php');
$sql = "SELECT * FROM SOWPODS WHERE word LIKE 'z%' ORDER BY word ASC";
$result = mysql_query($sql);
while($row = mysql_fetch_array($result)) {
$string = $row['word'];
$rowwordid = $row['ID'];
echo $thisword = strtoupper($row['word']);
echo " - ";
for ($ii = 0; $ii < strlen($string); ++$ii) {
    $thisletter = strtolower($string{$ii});
    if ($thisletter == 'a') {
        $a = $a+1;
    } elseif ($thisletter == 'b') {
        $b = $b+1;
    } elseif ($thisletter == 'c') {
        $c = $c+1;
    } elseif ($thisletter == 'd') {
        $d = $d+1;
    } elseif ($thisletter == 'e') {
        $e = $e+1;
    } elseif ($thisletter == 'f') {
        $f = $f+1;
    } elseif ($thisletter == 'g') {
        $g = $g+1;
    } elseif ($thisletter == 'h') {
        $h = $h+1;
    } elseif ($thisletter == 'i') {
        $i = $i+1;
    } elseif ($thisletter == 'j') {
        $j = $j+1;
    } elseif ($thisletter == 'k') {
        $k = $k+1;
    } elseif ($thisletter == 'l') {
        $l = $l+1;
    } elseif ($thisletter == 'm') {
        $m = $m+1;
    } elseif ($thisletter == 'n') {
        $n = $n+1;
    } elseif ($thisletter == 'o') {
        $o = $o+1;
    } elseif ($thisletter == 'p') {
        $p = $p+1;
    } elseif ($thisletter == 'q') {
        $q = $q+1;
    } elseif ($thisletter == 'r') {
        $r = $r+1;
    } elseif ($thisletter == 's') {
        $s = $s+1;
    } elseif ($thisletter == 't') {
        $t = $t+1;
    } elseif ($thisletter == 'u') {
        $u = $u+1;
    } elseif ($thisletter == 'v') {
        $v = $v+1;
    } elseif ($thisletter == 'w') {
        $w = $w+1;
    } elseif ($thisletter == 'x') {
        $x = $x+1;
    } elseif ($thisletter == 'y') {
        $y = $y+1;
    } elseif ($thisletter == 'z') {
        $z = $z+1;
    }
}
$scorea = $a*1;
$scoreb = $b*4;
$scorec = $c*4;
$scored = $d*2;
$scoree = $e*1;
$scoref = $f*4;
$scoreg = $g*3;
$scoreh = $h*3;
$scorei = $i*1;
$scorej = $j*10;
$scorek = $k*5;
$scorel = $l*2;
$scorem = $m*4;
$scoren = $n*2;
$scoreo = $o*1;
$scorep = $p*4;
$scoreq = $q*10;
$scorer = $r*1;
$scores = $s*1;
$scoret = $t*1;
$scoreu = $u*2;
$scorev = $v*5;
$scorew = $w*4;
$scorex = $x*8;
$scorey = $y*3;
$scorez = $z*10;

$totalscore = $scorea + $scoreb + $scorec + $scored + $scoree + $scoref + $scoreg +     $scoreh + $scorei + $scorej + $scorek + $scorel + $scorem + $scoren + $scoreo + $scorep +      $scoreq + $scorer + $scores + $scoret + $scoreu + $scorev + $scorew + $scorex + $scorey + $scorez;
$SQL_update_count = "UPDATE TWL06 SET a = '$a', b = '$b', c = '$c', d = '$d', e = '$e', f = '$f', g = '$g', h = '$h', i = '$i', j = '$j', k = '$k', l = '$l', m = '$m', n= '$n', o = '$o', p = '$p', q = '$q', r = '$r', s = '$s', t = '$t', u = '$u', v = '$v', w = '$w', x = '$x', y = '$y', z = '$z', score = '$totalscore' WHERE ID = '$rowwordid'";
echo "<br>";
$result_update_count = mysql_query($SQL_update_count);

$a = 0;
$b = 0;
$c = 0;
$d = 0;
$e = 0;
$f = 0;
$g = 0;
$h = 0;
$i = 0;
$j = 0;
$k = 0;
$l = 0;
$m = 0;
$n = 0;
$o = 0;
$p = 0;
$q = 0;
$r = 0;
$s = 0;
$t = 0;
$u = 0;
$v = 0;
$w = 0;
$x = 0;
$y = 0;
$z = 0;
 }
?>

Как только это будет сделано, все, что вам нужно сделать, - это создать скрипт, который подсчитывает буквы в столбцах и сопоставляет их с буквами, которые вы ему даете. Вам нужно будет сначала взорвать буквы и выяснить, сколько у вас каждой буквы. Затем запустите SQL-оператор, который находит количество букв или меньше.

0 голосов
/ 15 декабря 2011

Если я правильно понял вопрос (вы начинаете с букв подсказки, подстроки слова и пытаетесь переставить буквы, чтобы получить правильное слово), вот другое решение:

Вы можете начать с обратного пути. У вас уже есть слова в словаре, и вам нужно показать часть слова (подстроку) и список букв из слова, чтобы люди могли их упорядочить. Учитывая все это, вы начинаете со слова из словаря и создаете график слов на расстоянии 1 редактирования.

Пример

Начните с apple и продолжайте удалять письмо. Вот небольшой график (для которого я не рисовал все ребра, чтобы уменьшить беспорядок):

apple -> appe -> ape -> ...
& nbsp & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp & nbsp \
& nbsp & nbsp \ _-> appl -> app -> ...

Когда вы удаляете письмо, вы помещаете его в список подсказок.

подсказки: l, p

подсказки: l, e

Поскольку игрок использует буквы из списка для формирования исходного слова, вы принимаете только правильные записи, которые являются узлами, которые ведут к предыдущему родителю. Вы просто перемещаетесь по графику назад, чтобы найти оригинальное слово.

Пример * +1031 ** * одна тысяча тридцать две Если слово , приложение и подсказки: l, p

Если пользователь дает вам l: appl, вы переходите на предыдущий узел приложения, который заявл.

Если пользователь дает вам e: appe, вы переходите на предыдущий узел приложения, который апп в этом случае.

Любая другая буква, которую вводит пользователь, запрещается оставаться на текущем узле.

0 голосов
/ 01 мая 2011

Найдите докторскую диссертацию Брайана Шеппарда (автор Maven) под названием «На пути к идеальной игре в скрэббл».Это информативно и очень интересно.Но тоже очень долго.

0 голосов
/ 23 марта 2010

Если вы пытаетесь создать индекс слов, такой, чтобы вы могли попытаться «разгадать» (или создать) кроссворды, тогда, я думаю, вы бы начали со словаря слов, проиндексированных по длине. Затем вы бы создали еще один словарь словарей словарей ... первый индекс по общей длине слова, а второй по длине, затем по буквенной позиции и, наконец, по буквам (шесть букв и вторая буква «я» "например).

После того, как вы создали этот индекс, вы можете выразить каждый шаг в попытке установить или решить головоломку с точки зрения операций над множествами, выполняемых над ними. (Например, 8-буквенное слово, начинающееся с "w" и заканчивающееся на "k", будет пересечением всех 8-буквенных слов, начинающихся с "w", и всех тех, которые заканчиваются на "k" --- что, что неудивительно, будет включать "домашнюю работу" «). Построив индексированную структуру данных, которую я описал, конечно, можно было бы обеспечить гораздо более эффективный поиск возможных совпадений, чем это было бы возможно при выполнении линейного сканирования только глобального списка слов или даже линейного сканирования списков, разделенных по длине).

Если у вас есть эта базовая структура данных, то остальная часть программы, предположительно, будет генерировать и обходить дерево (конечно, с возвратом). Создайте программу, которая генерирует каждую возможность (используя описанную структуру данных) и всякий раз, когда она «застревает», возвращает ее назад, пока не найдет новые возможности.

Как следует из paxdiablo, вам придется включить большой пул «слов», чтобы генератор имел разумный шанс создать законченное «решение». Любой, кто имеет опыт работы с кроссвордами, понимает, что они позволяют установщику использовать довольно много свобод (например, частое использование точек компаса, архаичные термины и поэтические контракты), чтобы получить себя как бы горбом.

Я лично не написал генератор кроссвордов. Я написал решатели криптограмм, которые использовали похожую, но гораздо более простую структуру индексации. (Чтобы найти каждое слово, которое zyzxw может быть в криптограмме, вы «абстрагируете» его в шаблон: abacd. Ваш словарь содержит каждое слово, проиндексированное по его абстракции, и вы можете легко найти, что «каждое» соответствует «zyzxw»). В этом случае линейный поиск по спискам, начатым в каждой абстракции, достаточно быстр, даже если вы коррелируете, чтобы выяснить, что «uvz» с «zyzxw» действительно могут быть «например» ... например). Я также написал простую игру «Jotto», в которой индексирование вообще не имеет смысла - линейное сканирование по нескольким тысячам 5 или 6 буквенных букв на каждом шаге исключения, которое раньше занимало намного меньше секунды на моем старом 6. Mhz XT в предыстории современных компьютерных вычислений).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...