Skip to content

用正则表达式解决wordle

Posted on:2022年12月26日 at 11:23

本文章遵守知识共享协议 CC-BY-NC-SA ,转载时须在文章的任一位置附上原文链接和作者署名(rickyxrc)。推荐在我的个人博客阅读。

前言

正则表达式是一种强大的字符串匹配算法,利用它可以解决许多字符串模拟的题目。

而今天,我们要使用它解决wordle。

什么是wordle?

wordle是一种文字游戏,它的布局大概是这个样子:

wordle的大概布局

你每次可以输入一个5个字母的单词(必须是合法单词,我这里有一份单词表,点击后ctrl+s下载),然后颜色反应了你词的正确性。

例如,这是一次实际游玩:

一次实际游玩

这次运气可能有点好,换一次:

另一次实际游玩

知道为什么我会有这样的运气吗?别急,这就引出今天的重头戏————正则表达式。

正则表达式

知道正则表达式是什么的小伙伴估计已经跳起来了,因为用这个解wordle太简单了。

用第一次的图来举例吧。

一次实际游玩

像刚刚图中的条件,就可以用 [^fiskaaghbyodc][^fiskaaghbyodre][^fiskaaghbyodr]er 这样一个表达式来匹配。

不要慌,我们从头开始。

前置

首先返回到上面的定义:

推一下可以得到:

然后根据正则表达式的大概规则:

开始模拟

第一行

第一个空随便填:

第一行

全是灰色,看起来运气很不好,实际上为我们提供了大量的信息。

因为每一位都不可能有 f,r,i,s,k 这五个字符,所以正则串可以这样写:

[^frisk][^frisk][^frisk][^frisk][^frisk]

在词表里搜索到 16971697 / 88698869 个单词。

第二行,第三行

在匹配到的单词里,我们随便挑一个 aahed 交上去。

第二行,第三行

还是全灰色,同理。

[^friskahed][^friskahed][^friskahed][^friskahed][^friskahed]

在词表里搜索到 185185 / 88698869 个单词。

[^friskahedblom][^friskahedblom][^friskahedblom][^friskahedblom][^friskahedblom]

在词表里搜索到 1818 / 88698869 个单词。

第四行

再挑一个 cuppy 交上去。

第四行

这次前两个单词里有黄色(部分匹配),所以 c 肯定不可能出现在第一个但是绝对在单词里出现了,u 同理。

[^friskahedblompyc][^friskahedblompyu][^friskahedblompy][^friskahedblompy][^friskahedblompy]

完了之后程序过滤一遍包含 cu 的字符串(因为这个不太好写)。

在词表里搜索到 11 / 88698869 个单词 : uncut,完美!

全过程:

全过程

代码实现

这里我用的是python,因为简单(主要反映算法核心思想)。

分析刚刚上面的正则串,其实就是 55 个字符的匹配,可以用程序自动生成。

query_str = ''
for i in range(5):
    query_str += get_pos(i) # 获取第i位的正则串

每一部分的正则串又分为以下两种情况:

所以可以写出 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] # 否则直接返回确定的,对应第一种情况

而在猜的时候有 33 种情况,分别是 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

建议分屏使用,像这样

使用方法

后记

这玩意都有用信息熵算法的卷王……

洛谷有一道猜词,就是这个思路,字典也是从那个题里面拿的。



在 Rickyxrc's blog 出现的文章,若无特殊注明,均采用 CC BY-NC-SA 4.0 协议共享,也就是转载时需要注明本文章的地址,并且引用本文章的文章也要使用相同的方式共享。