罗兰在MATLAB的艺术

把想法变成MATLAB

请注意

罗兰在MATLAB的艺术已经存档,不会被更新。

建立一个Wordle解算器

今天的嘉宾是亚当•Filion MathWorks高级数据科学家。亚当曾在MathWorks数据科学的许多领域,包括帮助客户理解和实现数据科学技术,管理和优化我们的开发工作,建筑 Coursera类 ,并领导内部数据科学项目。
我妻子最近推出了我上瘾的益智游戏 Wordle 。在游戏中,你做出的一系列猜测算出一天的秘密回答道。答案总是一个五个字母的英语单词,你有六个试图猜出正确的答案。每次猜测后,游戏会给你一些信息关于你接近答案。
图1:如何Wordle给予反馈的例子。
我一直的一个数字的人,所以在陷入每天Wordle我决定看看我能做出更好的使用MATLAB的猜测。在这篇文章中,我将介绍一个简单的方法生成的建议先游戏,可以得到94%的时间在6猜测正确答案不知道Wordle官方的单词列表。从2022年1月12日,谜题的例子是。
wordle0crop.png
图2:一个空白Wordle难题。剩下六个猜测!

生成我们的词汇

如果我们要先玩游戏,我们需要一个五个字母的英语单词的词汇列表。游戏的粉丝已经刮Wordle源代码和共享2315年的列表 神秘的文字 和12972年 可推测的单词 (由于 FiveThirtyEight !)之后我们会回到神秘的话来检查我们的准确性,但使用解算器的感觉有点像作弊,让我们假装我们不知道Wordle使用列表。没有一个全面的英语单词列表,所以我们选择一个共同的源编码人员寻找一个英语单词列表,列表/usr/share/dict/words.下unix系统提供如果你在Windows上,在这样的地方你可以找到同样的列表 github 。这样我们可以很容易地读取文本文件直接从web文本处理使用 readline 。这个列表包括缩略词和专有名词,我们可以通过忽略条目删除从一个大写字母开始。虽然它不包含完整的英语,它给了我们一个列表4581年的五个字母单词。我们可能会错过一些Wordle的神秘的单词列表,但仍应密切足以让有用的建议。
%的单词列表读入一个字符串数组
r = readline (“https://gist.githubusercontent.com/wchargin/8927565/raw/d9783627c731268fb2935a731a618aa8e95cf465/words”);
%替代变音符号从附录中使用自定义函数
rs = removediacritics (r);
%只保留条目以小写字母开始
rs = rs (startsWith (rs, characterListPattern (“一个”,“z”)));
%去掉撇号的条目,如收缩
rs = rs(~包含(rs,“”));
% Wordle使用所有大写字母
rs =上(rs);
%获得独特的五个字母单词的列表
经常=独特(rs (strlength (rs) = = 5))
经常= 4581×1的字符串
“算盘”
“吓”
“在船尾”
“自卑”
使局促不安”
“减弱”
“abb”
“艾比”
“方丈”
“横”

找到最常用的字母

现在我们有我们的五个字母单词列表,但如何选择哪个词先猜?我们的第一个猜测是盲目的,没有最终答案的线索。因为Wordle给反馈信,一个简单的方法是选择最常用的单词字母。
先把每个单词分解为它的字母和观察整个直方图的信件。我们可以看到,有些字母是远远比其他人更经常使用。
%我们的话分割成各自的信件
信=分裂(经常,”“);
%,这也创造了前导和尾随空白字符串,删除它们
字母=字母(:2:end-1);
%查看信使用的计数
h =直方图(分类(字母(:)));
ylabel (“五个字母单词的使用数量)
让我们把这个表中用于创建词分数。
lt =表(h.Categories’, h.Values’,“VariableNames”,(“字母”,“分数”])
lt = 26日×2表
分数
1 “一个” 1841年
2 “B” 556年
3 “C” 790年
4 ' D ' 989年
5 “E” 2449年
6 “F” 445年
7 ‘G’ 543年
8 “H” 646年
9 “我” 1258年
10 “J” 69年
11 “K” 477年
12 “L” 1301年
13 “米” 639年
14 “N” 1019年

为每个单词创建一个分数

我们现在可以创建一个词得分基于它所使用的字母的流行。首先将每个字母替换其个人得分,然后信分数加起来创建词分数。
%每个字母,代之以相应的字母分数
letters_score = arrayfun (@ (x) lt.score (lt.letters = = x)字母);
%和创建单词的字母分数分数
word_score = (letters_score, 2)之和;
%找到前分数和相应的单词
[top_scores, top_idx] =排序(word_score 1“下”);
word_scores =表((top_idx),经常top_scores,“VariableNames”,(“单词”,“分数”]);

选择一个单词,让我们第一次猜测

虽然我没有 博弈理论家 ,很明显我们的开放应该使用五种不同的和受欢迎的字母来最大化的机会我们会得到有用的反馈来缩小搜索范围。与重复的字母删除单词后,我们看到出现的首选是第一个词让我们试试。
%找到每个单词有多少独特的字母
word_scores。num_letters= arrayfun(@(x) numel(unique(char(x))),word_scores.words);
%只保留这句话没有重复的字母
top_words_norep = word_scores (word_scores.num_letters = = 5,:);
头(top_words_norep)
ans = 8×3表
单词 分数 num_letters
1 “起来” 9792年 5
2 “伯爵” 9628年 5
3 “激光” 9628年 5
4 “实数” 9628年 5
5 “沉香” 9609年 5
6 “ASTER” 9589年 5
7 “利率” 9589年 5
8 “盯着” 9589年 5

先占的反馈

wordle1crop.png
图3:Wordle难题后第一个猜测。
提交第一个猜测后,我们可以看到三个字母,,R,和O,在最后的答案,但在不同的位置。信件和E并不在这个词。这种反馈可以消除大量可能的单词。
现在我们有这样的反馈,我们怎样才能把它呢?这是一个相当简单的问题代表反馈收到然后遍历这些结果和消除单词不再是可能的解决方案。金宝搏官方网站我们这样做的 filter_words 助手函数中发现的 附录 。我们通过我们的词汇表和他们的分数,这句话我们已经猜到了到目前为止,和编码这些猜测的结果。结果编码为一个矩阵每猜一行和一列每封信。如果这封信是错误的编码为0,如果这封信是答案而不是在那个位置编码为1,如果它是在正确的位置编码为2。

让我们猜第二次

我们了一个好的开始!将此信息传递给 filter_words ,我们已经缩小了我们的候选人从4581字35。
%我们之前的猜测
猜测=“起来”;
%编码反馈
结果= [1 1 1 0 0];
%过滤剩下的候选人
top_words_filtered = filter_words (word_scores,猜测,结果)
top_words_filtered = 35×3表
单词 分数 num_letters
1 “塔罗牌” 7314年 4
2 “比” 7310年 5
3 “广播” 7037年 5
4 “卡罗” 6881年 5
5 “珊瑚” 6881年 5
6 “极地” 6845年 5
7 “氡” 6798年 5
8 “摩尔” 6730年 5
9 “道德” 6730年 5
10 “皇家” 6726年 5
11 “劳动” 6647年 5
12 “缓慢的” 6634年 5
13 “庄园” 6448年 5
14 “罗马” 6448年 5
我们可以看到下一个单词的最高得分是塔罗牌,但在这一点上我们可能更好使用单词以五个独特的字母,所以让我们试着比。
wordle2crop.png
图4:我们Wordle难题后第二次猜测。

让我们的第三个猜

现在的“A”是在正确的位置,我们已经消除了两个字母更受欢迎。在这个信息添加后,只剩下10个候选人了,卡罗尔是首选。
%我们之前的猜测
猜测= [“起来”;“比”];
%编码反馈
结果= [1 1 1 0 0;
1、2 0,0,1];
%过滤剩下的候选人,独特的字母上没有要求
top_words_filtered = filter_words (word_scores,猜测,结果)
top_words_filtered = 10×3表
单词 分数 num_letters
1 “卡罗” 6881年 5
2 “劳动” 6647年 5
3 “庄园” 6448年 5
4 “男爵” 6365年 5
5 “英勇” 6350年 5
6 “反弹” 6219年 5
7 “市长” 6064年 5
8 “蒸汽” 5803年 5
9 “主要的” 5498年 5
10 “帮忙” 5494年 5
wordle3crop.png
图5:我们Wordle难题后第三个猜测。

使我们的第四次猜

现在我们有两个字母在正确的位置,并通过消除我们知道“R”的过程必须是最后一次。添加这个信息,我们看到的只有五个选择离开,三个人开始与M我们庄园。
%我们之前的猜测
猜测= [“起来”;“比”;“卡罗”];
%编码反馈
结果= [1 1 1 0 0;
1、2 0 0 1;
0 2 1 2 0);
%过滤剩下的候选人
top_words_filtered = filter_words (word_scores,猜测,结果)
top_words_filtered = 5×3表
单词 分数 num_letters
1 “庄园” 6448年 5
2 “市长” 6064年 5
3 “蒸汽” 5803年 5
4 “主要的” 5498年 5
5 “帮忙” 5494年 5
wordle4crop.png
图6:我们Wordle难题后第四猜想。

使我们的第五个猜

现在我们只剩下两个选择两个猜测。
%我们之前的猜测
猜测= [“起来”;“比”;“卡罗”;“庄园”];
%编码反馈
结果= [1 1 1 0 0;
1、2 0 0 1;
0 2 1 2 0;
0 2 0 2 2);
%过滤剩下的候选人
top_words_filtered = filter_words (word_scores,猜测,结果)
top_words_filtered = 2×3表
单词 分数 num_letters
1 “蒸汽” 5803年 5
2 “帮忙” 5494年 5
wordle5crop.png
图7:我们Wordle难题后第五个猜测。

使我们的第六个也是最后一个猜测

一个选择留给我们最后的猜测。祈祷!
%我们之前的猜测
猜测= [“起来”;“比”;“卡罗”;“庄园”;“蒸汽”];
%编码反馈
结果= [1 1 1 0 0;
1、2 0 0 1;
0 2 1 2 0;
0 2 0 2 2;
1、2 0 2 2);
%过滤剩下的候选人
top_words_filtered = filter_words (word_scores,猜测,结果)
top_words_filtered = 1×3表
单词 分数 num_letters
1 “帮忙” 5494年 5
wordle6crop.png
图8:我们Wordle难题后第六个猜测。成功!
所以这个Wordle难题,但它把所有六个猜测,所以我们把它关闭。如何将这项工作?

先玩一个随机的游戏

如果MATLAB知道答案是什么,我们可以玩游戏的过程中自动化Wordle,看看我们的算法将正确地猜。我们将首先创建一个helper函数 wordle_feedback 附录 编码为每个想根据我们收到的反馈正确的答案。
现在我们可以使用我们的自动玩一个游戏 play_wordle helper函数。这个接受我们的五个字母单词表和他们的分数,连同一个词作为答案。它将返回答案我们试图猜测,是否我们就在玩,猜测了。我们玩,我们会要求我们的前三个猜测不使用重复的字母(假设这样的词汇还可以),但从第四猜字母可以重复。
因为我们知道有一个列表 神秘的文字 直接从谷歌表,我们可以读到MATLAB。
mystery_id =“1-M0RIVVZqbeh0mZacdAsJyBrLuEmhKUhNaVAI-7pr2Y”;%来自上面的表的URL链接
mystery_url = sprintf (“https://docs.google.com/spreadsheets/d/%s/gviz/tq?tqx=out: csv”,mystery_id);
mystery_words = readline (mystery_url);
%有一组额外的包括双引号,那么让我们带出来
mystery_words =擦掉(mystery_words,”“”“);
%我们使用大写
mystery_words =上(mystery_words);
我们的算法只能猜单词词汇我们给它。大约4%的神秘文字缺少我们的词汇,所以即使我们玩完全使用的话,我们知道,我们可以期待最好的赢利率是96%。
num_missing =总和(~ ismember (mystery_words word_scores.words))
num_missing = 94
perc_missing = num_missing /元素个数(mystery_words) * 100
perc_missing = 4.0605
现在我们已经神秘列表,我们可以用随机回答想玩一个游戏。
answer_idx =兰迪(元素个数(mystery_words));
[答案,赢得,played_words] = play_wordle (word_scores, mystery_words (answer_idx))
回答=“喷”
赢得= 1
played_words = 1×6弦
“出现”“巢穴”“小人物”“喷”“”“

Wordle玩所有的游戏

我们可以测试算法在整个2315年神秘词词汇通过运行在一个循环中。我们可以看到,这个简单的方法会让我们在六猜测正确答案94%的时间,这是非常接近96%的最大可能!当我们赢了,我们最常赢得四个猜测。
num_games =元素个数(mystery_words);
赢得=南(num_games, 1);
猜测=字符串(num_games 6);
答案=字符串(num_games, 1);
2 = 1:num_games%为每个单词在我们的词汇量
%先玩一个游戏,这个词是我们猜测的答案
(回答(2),(2)获胜,猜测(ii):)] = play_wordle (word_scores, mystery_words (ii));
结束
流(“这一战略的结果赢得~ % 0.1 f % %。\ n”总和(赢得)/元素个数(赢得)* 100)
这一战略的结果赢得~ 94.2%的时间。
num_guesses =总和(猜测(赢了= = 1:)~ =”“2);
直方图(num_guesses“归一化”,“概率”)
包含(“许多猜测当赢得Wordle”)
ylabel (“胜利的一部分”)
的比赛我们没有得到正确的答案。
missed_answers =答案(赢了= = 0);
[答案,赢得,played_words] = play_wordle (word_scores, missed_answers (1))
回答=“”,
赢得= 0
played_words = 1×6弦
“出现”“外星人”“”“”“”“
似乎有两个模式错过的答案。
  1. 正如上面提到的,大约4%的答案并不在我们的词汇,如拉面和兴致很高的。你可以告诉,当这一切发生的时候,因为我们输掉这场比赛不使用我们所有的猜测由于耗尽允许的话。
  2. 一些答案结合常见字母模式以很少使用字母,我们没有足够的猜测缩小下来。例如,当答案是固定器,有39个词汇在我们的词汇,使用“我”在第二位置和“嗯”。所有的固定器词得分最低由于F和X 7底部至少都是用字母。我们六个猜测起来,升,餐馆,成熟,徒步旅行者,纤维和我们之前的猜测固定器。

需要改进的地方

有什么其他的事情我们可以尝试让我们的赢率100% ?这里有一些想法:
  • 我们确定了上面的两种主要模式错过了答案。显然第一种模式可以解决通过添加Wordle神秘的话我们的词汇量。
  • 第二种模式解决尚不明朗。我们当前的单词评分方法的一个缺点是,分数是静态的,因此如果一个字像固定器开始得分较低,这将永远不会改变。我们可能会得到更多正确的猜测通过更新我们的分数我们玩的不合格的单词和/或解决信位置的分数计算。
  • 我们也可以尝试提高评分法通过寻找常见的模式,调用字格。最常见的字格是用来发现常见的单词组合,但它也可以用来找到常见的字母组合。我们可以提取前信字格并整合到我们的分数,因为猜一个词和一个共同的语法会让我们反馈很多类似的单词。
  • 我们已经要求我们的前三个猜测使用无重复字母,这是一个策略,我选择通过试错和可能不是最优的。我们还可以使用重叠词的最初几个猜测,即使我们已经得到了一些字母正确。这需要我们总是使用10个独特的字母在前两个猜测,即使我们不得不做出猜测我们知道不能为了这么做是正确的。我尝试使用这个普遍,它实际上降低了整体获胜的几率非常小,但可能是一个更聪明的方法是情境中使用它。
你有什么其他的想法更好的策略吗?让我们知道 在评论里

附录

函数word_scores_filtered = filter_words (word_scores words_guessed,结果)
%去除words_guessed自那些不可能的答案
word_scores_filtered = word_scores;
word_scores_filtered(匹配(word_scores_filtered.words words_guessed):) = [];
%过滤器来正确的字母单词,在正确的位置(绿色字母)
(rlp, clp) =找到(结果= = 2);
如果~ isempty (rlp)
2 = 1:元素个数(rlp)
信=提取(words_guessed (rlp (ii)),中电控股(ii));
%只保留正确的单词,字母在正确的位置
word_scores_filtered = word_scores_filtered(提取(word_scores_filtered.words clp (ii)) = =信:);
结束
结束
%过滤词也包含正确的字母在其他位置(黄色字母)
(rl, cl) =找到(结果= = 1);
如果~ isempty (rl)
jj = 1:元素个数(rl)
信=提取(words_guessed (rl (jj)), cl (jj));
%删除单词与字母在同一位置
word_scores_filtered(提取(word_scores_filtered.words, cl (jj)) = =信:)= [];
%去除不含字母的单词
word_scores_filtered(~包含(word_scores_filtered.words信):)= [];
结束
结束
%过滤器,单词也不包含不正确的字母(灰色字母)
(ri, ci) =找到(结果= = 0);
如果~ isempty (ri)
kk = 1:元素个数(ri)
信=提取(words_guessed (ri (kk)), ci (kk));
%删除单词包含不正确的字母
word_scores_filtered(包含(word_scores_filtered.words信):)= [];
结束
结束
结束% filter_words
函数结果= wordle_feedback(回答,猜)
结果=南(1、5);
2 = 1:5%为每个字母在我们的猜测
信=提取(猜,ii);%提取那封信
如果提取(答案,ii) = =信
%如果回答这封信在同一位置
结果(ii) = 2;
elseif包含(答案,信)
%如果回答这封信在另一个位置
结果(ii) = 1;
其他的
%如果回答不包含那封信
结果(ii) = 0;
结束
结束
结束% wordle_feedback
函数[word_to_guess,赢得,猜测]= play_wordle (word_scores word_to_guess)
top_words = sortrows (word_scores 2“下”);%保证分数排序
猜测=字符串(1,6);
结果=南(6,5);
max_guesses = 6;
2 = 1:max_guesses%为每个我们的猜测
%滤波器总词汇逐步候选人猜测使用不同的策略
如果2 = = 1%为我们第一个猜测,过滤词和五个独特的字母和花费最高的分数
top_words_filtered = top_words (top_words.num_letters = = 5,:);
elseif二< = 3%如果我们生成第二个或第三个猜测
%过滤掉不合格的单词,需要五个独特的字母,如果可能的话
min_uniq = 5;
top_words_filtered = filter_words (top_words (top_words.num_letters = = min_uniq:),猜测(1:ii-1),结果(1:ii-1,:));
%如果过滤五个独特的字母会删除所有单词,允许更多的重复的字母
高度(top_words_filtered) = = 0 & & min_uniq > min (word_scores.num_letters)
min_uniq = min_uniq - 1;
top_words_filtered = filter_words (top_words (top_words.num_letters = = min_uniq:),猜测(1:ii-1),结果(1:ii-1,:));
结束
其他的%之后第三个猜,没有限制重复字母
top_words_filtered = filter_words (top_words,猜测(1:ii-1),结果(1:ii-1,:));
结束
%生成我们想(如果有的话)
如果高度(top_words_filtered) = = 0%如果没有合格的单词在我们的词汇量
赢得= 0;%我们不知道,我们已经失去了这个词
返回%没有更多的猜测
其他的%否则生成一个新的猜得到结果
猜测(1、2)= top_words_filtered.words (1);
结果(二世:)= wordle_feedback (word_to_guess,猜测(1、2);
结束
%评估如果我们赢了,输了,还是继续玩
如果猜测(1、2)= = word_to_guess%如果我们的猜测是正确的
赢得= 1;%的胜利旗帜
返回%没有更多的猜测
elseif2 = = max_guesses%如果我们已经使用我们所有的猜测,他们都错了
赢得= 0;%我们失去了和循环结束
其他的%,否则我们还玩
结束
结束
结束% play_wordle
%的引用:吉姆·古道尔,2020年。堆栈溢出,可以在:https://stackoverflow.com/a/60181033
函数[clean_s] = removediacritics (s)
% REMOVEDIACRITICS删除从文本变音符号。
%这个函数从字符串,删除许多常见变音符号等
%——急性口音
%——严重的口音
%——弯曲的口音
% u -分音符,trema或者元音变音
% n -波浪号
% c -变音符号
%——环或中。bolle
%ø——削减或固相,或短斜线
%大写
= regexprep(年代,‘(?:| | | | |一个)的,“一个”);
= regexprep(年代,“(?:Æ)”,“AE”);
= regexprep(年代,“(?:ß)”,“党卫军”);
= regexprep(年代,“(?:C)”,“C”);
= regexprep(年代,“(?:Ð)”,' D ');
= regexprep(年代,”(?:E E E | | | E)”,“E”);
= regexprep(年代,”(?:我| | |我)”,“我”);
= regexprep(年代,“(?:N)”,“N”);
= regexprep(年代,”(?:O O O O O | | | | |Ø)”,“O”);
= regexprep(年代,“(?:œ)”,“OE”);
= regexprep(年代,”(?:U | | | U)”,“U”);
= regexprep(年代,”(?:Y |ÿ)',“Y”);
%小写
= regexprep(年代,‘(?:| | | | |一个)的,“一个”);
= regexprep(年代,“(?:æ)”,“ae”);
= regexprep(年代,“(?:c)”,“c”);
= regexprep(年代,“(?:ð)”,' d ');
= regexprep(年代,”(?:e e e | | | e)”,“e”);
= regexprep(年代,”(?:我| | |我)”,“我”);
= regexprep(年代,“(?:n)”,“n”);
= regexprep(年代,”(?:o o o o o | | | | |ø)”,“o”);
= regexprep(年代,“(?:œ)”,“oe”);
= regexprep(年代,”(?:u | | | u)”,“u”);
= regexprep(年代,(:| y)的,“y”);
%返回清洗字符串
clean_s = s;
结束

评论

留下你的评论,请点击在这里MathWorks账户登录或创建一个新的。