本文章遵守知识共享协议 CC-BY-NC-SA ,转载时须在文章的任一位置附上原文链接和作者署名(rickyxrc)。推荐在我的个人博客阅读。
前言
正则表达式是一种强大的字符串匹配算法,利用它可以解决许多字符串模拟的题目。
而今天,我们要使用它解决wordle。
什么是wordle?
wordle是一种文字游戏,它的布局大概是这个样子:
你每次可以输入一个5个字母的单词(必须是合法单词,我这里有一份单词表,点击后ctrl+s
下载),然后颜色反应了你词的正确性。
- 如果单词上这个字母的颜色为灰色,就代表答案里面没有这个字母 (grey)。
- 如果单词上这个字母的颜色为黄色,就代表答案里面有这个字母,但是位置不正确 (silver)。
- 如果单词上这个字母的颜色为绿色,就代表答案里面有这个字母,并且位置正确 (gold)。
例如,这是一次实际游玩:
这次运气可能有点好,换一次:
知道为什么我会有这样的运气吗?别急,这就引出今天的重头戏————正则表达式。
正则表达式
知道正则表达式是什么的小伙伴估计已经跳起来了,因为用这个解wordle太简单了。
用第一次的图来举例吧。
像刚刚图中的条件,就可以用 [^fiskaaghbyodc][^fiskaaghbyodre][^fiskaaghbyodr]er
这样一个表达式来匹配。
不要慌,我们从头开始。
前置
首先返回到上面的定义:
- 如果单词上这个字母的颜色为灰色,就代表答案里面没有这个字母 (grey)。
- 如果单词上这个字母的颜色为黄色,就代表答案里面有这个字母,但是位置不正确 (silver)。
- 如果单词上这个字母的颜色为绿色,就代表答案里面有这个字母,并且位置正确 (gold)。
推一下可以得到:
- 如果单词上这个字母的颜色为灰色,就代表答案里面没有这个字母 (grey),那么所有位置上的字母都不能有这个。
- 如果单词上这个字母的颜色为黄色,就代表答案里面有这个字母,但是位置不正确 (silver),那么这个位置上的字母不能有这个,但是这个字母必须在词语中出现。
- 如果单词上这个字母的颜色为绿色,就代表答案里面有这个字母,并且位置正确 (gold),那么这个位置上的字母必须是这一个。
然后根据正则表达式的大概规则:
- 匹配任意非空白字符
.
。 - 匹配以下字符中的一个
[abcde]
代表这一位可以是a,b,c,d,e
中的任意一个字符。 - 匹配除了以下字符中的一个
[^abcde]
代表这一位可以是除了a,b,c,d,e
中的任意一个字符。
开始模拟
第一行
第一个空随便填:
全是灰色,看起来运气很不好,实际上为我们提供了大量的信息。
因为每一位都不可能有 f,r,i,s,k
这五个字符,所以正则串可以这样写:
[^frisk][^frisk][^frisk][^frisk][^frisk]
在词表里搜索到 / 个单词。
第二行,第三行
在匹配到的单词里,我们随便挑一个 aahed
交上去。
还是全灰色,同理。
[^friskahed][^friskahed][^friskahed][^friskahed][^friskahed]
在词表里搜索到 / 个单词。
[^friskahedblom][^friskahedblom][^friskahedblom][^friskahedblom][^friskahedblom]
在词表里搜索到 / 个单词。
第四行
再挑一个 cuppy
交上去。
这次前两个单词里有黄色(部分匹配),所以 c
肯定不可能出现在第一个但是绝对在单词里出现了,u
同理。
[^friskahedblompyc][^friskahedblompyu][^friskahedblompy][^friskahedblompy][^friskahedblompy]
完了之后程序过滤一遍包含 c
和 u
的字符串(因为这个不太好写)。
在词表里搜索到 / 个单词 : uncut
,完美!
全过程:
代码实现
这里我用的是python,因为简单(主要反映算法核心思想)。
分析刚刚上面的正则串,其实就是 个字符的匹配,可以用程序自动生成。
query_str = ''
for i in range(5):
query_str += get_pos(i) # 获取第i位的正则串
每一部分的正则串又分为以下两种情况:
- 已经确定,直接是字母
- 没有确定,用
[^abcde]
的语法排除
所以可以写出 get_pos
函数:
common_type_ex = [] # 在所有位置都不能出现的字符
spec_type_ex = [[],[],[],[],[]] # 只在单个位置不能出现的字符
spec_filt = [] # 必须有的字符
confirmed = ['.','.','.','.','.'] # 确定的字符
def get_pos(pos):
global confirmed,common_type_ex,spec_type_ex # 要用到的全局变量,先global声明一下是个好习惯
if confirmed[pos] == '.': # 这一位不能确定
strr = ''
for t in common_type_ex: # 排除所有位置都不能出现的字符
strr+=t
for t in spec_type_ex[pos]: # 排除单个位置不能出现的字符
strr+=t
return '[^' + strr + ']' # 所有排除掉的拼串,对应第二种情况
return confirmed[pos] # 否则直接返回确定的,对应第一种情况
而在猜的时候有 种情况,分别是 gold
,silver
,grey
(上文已提及)。
分别写出对应的函数:
def add_except(chart):
global common_type_ex,spec_type_ex
# 这里是一个特判,因为在某些wordle里面重复出现silver会标记为grey,所以特判下
for i in range(5):
if chart in spec_type_ex[i]:
return
common_type_ex.append(chart)
def num_except(pos,chart):
global spec_type_ex,spec_filt
spec_type_ex[pos].append(chart) # 这一位排除这个字母
spec_filt.append(chart) # 但是必须包含这个字母
def confirm(pos,chart):
global confirmed
confirmed[pos]=chart # 标记已确认
然后封装成一个 guess
函数,方便调用。
def guess(strr,res):
for i in range(5):
if res[i] == 'g' :
confirm(i,strr[i]) # 确认
elif res[i] == 's' :
num_except(i,strr[i]) # 特别排除这个
elif res[i] == 'n' :
add_except(strr[i]) # 全部排除
主函数里这么写:
guess('单词','状态') # 状态为 g,s,n 三个字符组成的字符串
query_str = ''
for i in range(5):
query_str += get_pos(i) # 拼串
res = re.findall(get_pos(0)+get_pos(1)+get_pos(2)+get_pos(3)+get_pos(4)+'\n',word_data) # 正则查询的核心函数
for t in res:
yes=True
for ch in spec_filt: # 过滤一遍,必须包含这些字符
if not ch in t:
yes=False
break
if yes: # 输出第一个
print(t,end='')
word=t
break
像刚刚那个样例,我们的 guess
部分应该这样写:
guess('frisk','nnnnn')
guess('aahed','nnnnn')
guess('bloom','nnnnn')
guess('cuppy','ssnnn')
输出返回值:
PS C:\Users\Ricky\Desktop> & "C:/Program Files (x86)/Python39-32/python.exe" ****/main.py
uncut
PS C:\Users\Ricky\Desktop>
很好,程序已经开始向期望的方向运行了。
然后在加上一点点代码,就可以做到一个自动 AC wordle 的程序!
使用方法:
运行代码需要安装 pyautogui
,可以一行命令安装:pip install pyautogui -i https://pypi.tuna.tsinghua.edu.cn/simple
建议分屏使用,像这样
后记
这玩意都有用信息熵算法的卷王……
洛谷有一道猜词,就是这个思路,字典也是从那个题里面拿的。