对于智能手机,有一款名为Ruzzle的游戏
这是一个找到游戏的词。
快速说明:
游戏板是一个4x4的字母网格
您从任何单元格开始,尝试通过向上,向下,向左,向右或对角线拖动来拼写单词
电路板不会换行,您不能重复使用已选择的字母。
平均而言,我的朋友和我发现了大约40个单词,并且在本轮结束时,游戏会告诉您可以获得多少可能的单词。这个数字通常约为250 - 350 我们想知道哪个董事会会产生最多可能的单词。
我如何找到最佳的电路板?
我用C语言编写了一个16个字符的程序并输出了所有相应的单词
测试超过80,000个单词,处理大约需要一秒钟。
问题:
游戏板排列的数量是26 ^ 16
那是43608742899428874059776(43 sextillion)。
我需要某种启发式方法。
我是否应该跳过所有 z , q , x ,等的主板,因为预计它们没有那么多的话?我不想在不确定的情况下排除一封信
每个电路板也有4个重复,因为旋转电路板仍会产生相同的结果
但即使有这些限制,我也不认为我的生命中有足够的时间来找到答案。
也许董事会代表不是答案 有没有更快捷的方法来查找单词列表中的答案?
答案 0 :(得分:8)
tldr;
S E R O
P I T S
L A N E
S E R G
或其任何反思。
这个主板包含1212个单词(事实证明,你可以排除'z','q'和'x')。
首先,事实证明你正在使用错误的词典。在没有与Ruzzle的字数完全匹配之后,我调查了一下,似乎Ruzzle使用了一个名为TWL06的字典,它有大约180,000个单词。不要问我它代表什么,但它可以在txt中免费获得。
我还编写了代码来查找给出16字符板的所有可能的单词,如下所示。它将字典构建为树形结构,然后在有文字被发现时,几乎只是递归地进行。它按照长度顺序打印它们。 STL集结构保持唯一性。
#include <cstdlib>
#include <ctime>
#include <map>
#include <string>
#include <set>
#include <algorithm>
#include <fstream>
#include <iostream>
using namespace std;
struct TreeDict {
bool existing;
map<char, TreeDict> sub;
TreeDict() {
existing = false;
}
TreeDict& operator=(TreeDict &a) {
existing = a.existing;
sub = a.sub;
return *this;
}
void insert(string s) {
if(s.size() == 0) {
existing = true;
return;
}
sub[s[0]].insert(s.substr(1));
}
bool exists(string s = "") {
if(s.size() == 0)
return existing;
if(sub.find(s[0]) == sub.end())
return false;
return sub[s[0]].exists(s.substr(1));
}
TreeDict* operator[](char alpha) {
if(sub.find(alpha) == sub.end())
return NULL;
return &sub[alpha];
}
};
TreeDict DICTIONARY;
set<string> boggle_h(const string board, string word, int index, int mask, TreeDict *dict) {
if(index < 0 || index >= 16 || (mask & (1 << index)))
return set<string>();
word += board[index];
mask |= 1 << index;
dict = (*dict)[board[index]];
if(dict == NULL)
return set<string>();
set<string> rt;
if((*dict).exists())
rt.insert(word);
if((*dict).sub.empty())
return rt;
if(index % 4 != 0) {
set<string> a = boggle_h(board, word, index - 4 - 1, mask, dict);
set<string> b = boggle_h(board, word, index - 1, mask, dict);
set<string> c = boggle_h(board, word, index + 4 - 1, mask, dict);
rt.insert(a.begin(), a.end());
rt.insert(b.begin(), b.end());
rt.insert(c.begin(), c.end());
}
if(index % 4 != 3) {
set<string> a = boggle_h(board, word, index - 4 + 1, mask, dict);
set<string> b = boggle_h(board, word, index + 1, mask, dict);
set<string> c = boggle_h(board, word, index + 4 + 1, mask, dict);
rt.insert(a.begin(), a.end());
rt.insert(b.begin(), b.end());
rt.insert(c.begin(), c.end());
}
set<string> a = boggle_h(board, word, index + 4, mask, dict);
set<string> b = boggle_h(board, word, index - 4, mask, dict);
rt.insert(a.begin(), a.end());
rt.insert(b.begin(), b.end());
return rt;
}
set<string> boggle(string board) {
set<string> words;
for(int i = 0; i < 16; i++) {
set<string> a = boggle_h(board, "", i, 0, &DICTIONARY);
words.insert(a.begin(), a.end());
}
return words;
}
void buildDict(string file, TreeDict &dict = DICTIONARY) {
ifstream fstr(file.c_str());
string s;
if(fstr.is_open()) {
while(fstr.good()) {
fstr >> s;
dict.insert(s);
}
fstr.close();
}
}
struct lencmp {
bool operator()(const string &a, const string &b) {
if(a.size() != b.size())
return a.size() > b.size();
return a < b;
}
};
int main() {
srand(time(NULL));
buildDict("/Users/XXX/Desktop/TWL06.txt");
set<string> a = boggle("SEROPITSLANESERG");
set<string, lencmp> words;
words.insert(a.begin(), a.end());
set<string>::iterator it;
for(it = words.begin(); it != words.end(); it++)
cout << *it << endl;
cout << words.size() << " words." << endl;
}
随机生成电路板并对它们进行测试并没有太有效,预计,我并没有真正打扰它,但如果他们超过200字我会感到惊讶。相反,我改变了电路板生成以生成字母,其字母与TWL06中的频率成比例分布,通过快速累积频率(频率降低100倍)实现,低于。
string randomBoard() {
string board = "";
for(int i = 0; i < 16; i++)
board += (char)('A' + rand() % 26);
return board;
}
char distLetter() {
int x = rand() % 15833;
if(x < 1209) return 'A';
if(x < 1510) return 'B';
if(x < 2151) return 'C';
if(x < 2699) return 'D';
if(x < 4526) return 'E';
if(x < 4726) return 'F';
if(x < 5161) return 'G';
if(x < 5528) return 'H';
if(x < 6931) return 'I';
if(x < 6957) return 'J';
if(x < 7101) return 'K';
if(x < 7947) return 'L';
if(x < 8395) return 'M';
if(x < 9462) return 'N';
if(x < 10496) return 'O';
if(x < 10962) return 'P';
if(x < 10987) return 'Q';
if(x < 12111) return 'R';
if(x < 13613) return 'S';
if(x < 14653) return 'T';
if(x < 15174) return 'U';
if(x < 15328) return 'V';
if(x < 15452) return 'W';
if(x < 15499) return 'X';
if(x < 15757) return 'Y';
if(x < 15833) return 'Z';
}
string distBoard() {
string board = "";
for(int i = 0; i < 16; i++)
board += distLetter();
return board;
}
这显着更有效,非常容易实现400多个字板。我让它保持运行(比我想要的时间更长),检查了超过一百万个电路板后,发现的最高值约为650字。这仍然是随机生成,这有其局限性。
相反,我选择了一种贪婪的最大化策略,其中我会选择一个电路板并对其进行一些小改动,然后仅在增加字数时才提交更改。
string changeLetter(string x) {
int y = rand() % 16;
x[y] = distLetter();
return x;
}
string swapLetter(string x) {
int y = rand() % 16;
int z = rand() % 16;
char w = x[y];
x[y] = x[z];
x[z] = w;
return x;
}
string change(string x) {
if(rand() % 2)
return changeLetter(x);
return swapLetter(x);
}
int main() {
srand(time(NULL));
buildDict("/Users/XXX/Desktop/TWL06.txt");
string board = "SEROPITSLANESERG";
int locmax = boggle(board).size();
for(int j = 0; j < 5000; j++) {
int changes = 1;
string board2 = board;
for(int k = 0; k < changes; k++)
board2 = change(board);
int loc = boggle(board2).size();
if(loc >= locmax && board != board2) {
j = 0;
board = board2;
locmax = loc;
}
}
}
尽管随机化了起点,但这很快就让我获得了1000多个字板,字母图案大致相似。是什么让我相信所提供的电路板是最好的电路板,它是在最大化随机电路板的前100次尝试中反复出现的,或者是其各种反射之一。/ p>
怀疑主义的最大原因是这种算法的贪婪,并且这会以某种方式导致算法错过更好的电路板。所做的微小变化在结果上非常灵活 - 也就是说,它们有能力将网格从其(随机)起始位置完全转换。可能的更改数量,新信函的26 * 16和字母交换的16 * 15,都明显小于5000,允许连续丢弃的更改数量。
程序能够在前100多次内重复此电路板输出这一事实意味着局部最大值的数量相对较小,并且存在未被发现的最大值的概率。
虽然贪婪似乎是直觉上正确的 - 但是随着随机板的增量变化到达给定的网格实际上不太可能 - 并且两个可能的变化,交换和一个新的字母确实似乎封装了所有可能的改进,我更改了程序,以便在检查增加之前允许它进行更多更改。这又一次又一次地回到同一块板上。
int main() {
srand(time(NULL));
buildDict("/Users/XXX/Desktop/TWL06.txt");
int glomax = 0;
int i = 0;
while(true) {
string board = distBoard();
int locmax = boggle(board).size();
for(int j = 0; j < 500; j++) {
string board2 = board;
for(int k = 0; k < 2; k++)
board2 = change(board);
int loc = boggle(board2).size();
if(loc >= locmax && board != board2) {
j = 0;
board = board2;
locmax = loc;
}
}
if(glomax <= locmax) {
glomax = locmax;
cout << board << " " << glomax << " words." << endl;
}
if(++i % 10 == 0)
cout << i << endl;
}
}
迭代了这个循环1000次左右,这个特殊的电路板配置显示了大约10次,我非常有信心这是现在Ruzzle板上最独特的单词,直到英语发生变化。 / p>
答案 1 :(得分:3)
有趣的问题。我看到(至少,但主要是)两个approches
一种方法是尝试根据字典尽可能多地粘贴尽可能多的可识别的字母(在所有方向上)。如你所说,有许多可能的组合,这条路线需要一个精心设计和复杂的算法来达到有形的东西
根据我更喜欢的概率,还有另一个“松散”的解决方案。您建议删除一些低调字母以最大化电路板产量。对此的扩展可能是在字典中使用更多高级字母。
进一步的步骤可能是:
基于80k字典D
,您可以找到我们的L字母26个字母的每个 l1 字母,字母 l2 的概率在 l1 之前或之后。这是一个L x L
概率数组,非常小,所以你甚至可以扩展到L x L x L
,即考虑 l1 和 l2 的概率是多少 l3 以适应。如果算法想要估计准确的概率,这有点复杂,因为概率总和取决于3个字母的相对位置,例如在“三角形”配置中(例如位置(3,3),(3,4) )和(3,5))结果可能比字母对齐时更少屈服[只是一个假设]。为什么不上升到L x L x L x L
,这需要一些优化......
然后你在棋盘上随机分发一些高调的字母(比如4~6个)(在8个可能的方向中至少有5个至少有1个空白区域),然后使用你的{{ 1}} probas数组完成 - 意味着基于现有的字母,下一个单元格填充了一个字母,其中proba为高配给[再次,字母按proba降序排序,如果两个字母紧密关系则使用随机性]
例如,仅采用水平配置,并且具有以下字母,我们希望在L x L [xL]
和ER
之间找到最佳的2
TO
使用 ...ER??TO...
,循环(l1和l2是我们丢失的两个字母)。找到绝对更好的字母 - 但是 bestchoice 和 bestproba 可以是数组而是保留 - 比如说 - 10个最佳选择。
注意:在这种情况下,没有必要将proba保持在[0,1]范围内,我们可以总结一下probas(不提供问题 - 但数字很重要。数学问题可能类似于p =(p(l0,l1)+ p(l2,l3))/ 2,l0和l3是我们的L x L
例子中的 R 和 T
L x L
算法可以考虑更多因素,并且还需要考虑垂直和对角线。使用 bestproba = 0
bestchoice = (none, none)
for letter l1 in L
for letter l2 in L
p = proba('R',l1) + proba(l2,'T')
if ( p > bestproba )
bestproba = p
bestchoice = (l1, l2)
fi
rof
rof
时,会考虑更多方向上的更多字母,例如L x L x L
,ER?
,R??
,??T
- 这需要通过算法进行更多思考 - 也许从?TO
开始可以了解该算法的相关性。
请注意,其中很多可能是预先计算的,L x L
数组当然是其中之一。