[Перевод] Решение головоломки Wordle в командной строке
Я терпеть не мог, когда родственники заставляли меня играть в Scrabble. Единственное, в чём я был хорош — так это в игре Countdown, в той её части, которая связана с числами. А теперь мне досаждает новая игра со словами Wordle, которая вот уже недели две то и дело выскакивает в моей Twitter-ленте.
Моя проблема в том, что я постоянно забываю слова. Даже между тем моментом, когда начинаю предложение, и тем, когда добираюсь до того его места, где я собирался использовать какое-то слово. Я знаю слова, и не могу сказать, что не понимаю их, когда их мне говорят. Но иногда я не могу их вспомнить, и ничего особо не улучшается даже тогда, когда у меня есть несколько букв, входящих в их состав. А вид перемешанных букв, похоже, только всё ухудшает. Неудивительно то, что любые игры, нацеленные на выдумывание слов, мне безразличны.
Мои первые три попытки решить головоломку Wordle провалились. На четвёртой попытке я застрял, так как не мог придумать ни одного слова, которое соответствовало бы подсказкам. Поэтому я сжульничал и обнаружил, что передо мной было одно слово, которое в точности подходило. Это слово выглядело абсолютно очевидным после того, как я понял, что могу использовать одну и ту же букву дважды. Но, как известно, любой вопрос — это ерунда, когда знаешь ответ.
Как бы там ни было, если вы в чём-то похожи на меня, и при этом у вас под рукой имеются вполне обычные инструменты командной строки Unix, возможно, вам будет интересно узнать о том, как и вы можете играть в Wordle. Даже если вы совсем не дружите со словами.
Подготовка первого предположения
Для начала можно обратиться к файлу /usr/share/dict/words
, который содержит весьма обширный список… слов, конечно. Найдём в нём слова из пяти букв:
grep '^.....$' /usr/share/dict/words
Grep
— это команда для поиска по шаблону с использованием регулярных выражений. Знак «крышка» (^
) ограничивает поиск началом строки, знак «доллар» ($
) указывает на конец строки, а точка (.
) означает любой символ. В результате использованный выше шаблон поиска говорит grep
о том, что этой команде надо «вывести все строки, в которых имеется пять любых символов». Давайте немного ограничим то, что выведет команда. Точно я этого не знаю, но подозреваю, что в Wordle вряд ли будут использоваться имена собственные.
grep '^[a-z]....$' /usr/share/dict/words
Тут мы заменили первую точку на конструкцию в квадратных скобках ([a-z]
). Она описывает диапазон символов и расшифровывается как «любой символ между a и z». В результате в итоговый список не попадут слова, начинающиеся с заглавной буквы.
Теперь уберём слова с дублирующимися буквами. Начнём решать эту задачу с построения механизма разбиения слов на отдельные буквы. Поэкспериментируем со словом boost
.
echo -n boost | sed 's/\(.\)/\1\n/g'
Это даст нам буквы, расположенные в отдельных строках. Команда sed
(Stream EDitor) — это потоковый текстовый редактор. Её аргумент вида 's/…/…/'
выполняет операцию поиска и замены символов. Шаблон поиска тоже может представлять собой регулярное выражение. В данном случае «любой символ» (на что указывает точка в круглых скобках, ((.)
)) захватывается, а потом используется в операции замены. Для того чтобы скобки в этой конструкции воспринимались бы не как обычные символы, а как нечто особенное, их нужно экранировать, в результате перед ними стоят символы обратной косой черты. Шаблон, по которому осуществляется замена, (\1\n
), означает «первый захваченный символ и символ новой строки». Последняя часть шаблона (g
) — это флаг, указывающий системе на то, что поиск надо повторять до тех пор, пока больше совпадений найдено не будет. Без него, по умолчанию, поиск остановится после первого совпадения.
Теперь можно убрать дубликаты, воспользовавшись флагом -u
команды sort
, который позволяет игнорировать повторяющиеся строки.
echo -n boost | sed 's/\(.\)/\1\n/g' | sort -u
Благодаря этому, например, буква o
из слова boost
будет выводиться лишь один раз, а не два. Теперь, подсчитав количество строк и проверив, чтобы их было 5, мы будем знать о том, что в слове имеется 5 различных букв. Поместим всё это в цикл, благодаря чему сможем проверить все слова, состоящие из пяти букв.
for word in $(grep '^[a-z]....$' /usr/share/dict/words); do
letters=$(
echo -n $word \
| sed 's/\(.\)/\1\n/g' \
| sort -u \
| wc -l
)
[ $letters = 5 ] && echo $word
done > initial-wordles.txt
Воспользовавшись конструкцией вида for word in ...; do ... done
, мы перебираем ранее найденные слова и помещаем каждое слово в переменную с осмысленным именем word
. Затем мы устанавливаем значение переменной letters
в количество уникальных букв. Слово мы выводим только в том случае, если в нём 5 уникальных букв. А перенаправив вывод в файл, мы можем сохранить результаты работы этого кода и воспользоваться ими тогда, когда это будет нужно.
На моём iMac 2015 года обработка списка слов из /usr/share/dict/words
заняла меньше минуты. Конечно, слова можно обрабатывать гораздо быстрее, воспользовавшись достаточно быстрым языком программирования, но на то, чтобы написать программу на таком языке, уйдёт столько же времени, сколько ушло на написание программы с использованием инструментов командной строки.
Теперь мы можем сформировать наше первое предположение.
% shuf -n1 initial-wordles.txt
forth
Команда shuf
перемешивает слова в ранее сформированном файле. Ключ -n
указывает на то, что вывести нужно только первые n
строк. В нашем случае — это одна строка. Нам досталось слово forth
, им мы и воспользуемся.
Уточнение списка слов
Оказалось, что буквы R
и T
входят в состав нужного слова из Wordle, но они находятся на неправильных местах. А других букв в слове нет.
% grep '^[a-z]....$' /usr/share/dict/words \
| grep -v '[foh]' | wc -l
Мы снова берём все слова из пяти букв и отфильтровываем слова, содержащие буквы F
, O
или H
. Конструкция grep -v
означает выдачу всех строк, кроме тех, в которых есть указанные символы. В результате выданы будут все строки, в которых нет этих символов. В конце имеется команда wc -l
. Она нужна для того чтобы узнать о том, сколько было найдено подходящих слов. Из исходного набора размером 8497 слов отобрано 4666 слов. В результате мы избавились от половины возможных вариантов. Теперь выберем те слова, которые содержат буквы R
и T
.
% grep '^[a-z]....$' /usr/share/dict/words \
| grep -v '[foh]' | grep r | grep t
Мы применили две отдельные команды grep
из-за того, что в каждом из выбранных слов должны содержаться обе буквы. В результате теперь у нас 399 совпадений. Ещё мы знаем о том, в каких местах искомого слова нет букв R
и T
. Воспользуемся этими сведениями.
% grep '^[a-z]....$' /usr/share/dict/words \
| grep -v '[foh]' | grep r | grep t \
| grep '..[^r][^t].'
Использование крышки (^
) в начале выражения в квадратных скобках обращает выражение. Получается, что заданному шаблону будут соответствовать слова, символы в которых стоят следующим образом: любой-символ любой-символ не-r не-t любой-символ
. После этого осталось 275 слов. Выберем одно из них.
% grep '^[a-z]....$' /usr/share/dict/words \
| grep -v '[foh]' | grep r | grep t \
| grep '..[^r][^t].' \
| shuf -n1
react
После пробы этого слова буквы R
и T
снова оказываются не на своих местах, а других букв из нашего слова найдено не было. Добавление этих сведений в состав ограничений серьёзно сужает круг поиска нужного слова. Теперь слов осталось всего 16.
% grep '^[a-z]....$' /usr/share/dict/words \
| grep -v '[aecfoh]' | grep r | grep t \
| grep '[^r].[^r][^t][^t]'
butyr
stirk
stirp
sturk
tikur
trill
trink
tripy
trubu
trull
truly
trump
trunk
truss
twirk
twirl
Решение головоломки
На данном этапе работы стоит отметить, что некоторые слова из «словаря» Unix игра Wordle не распознаёт. Это, например, наше первое слово butyr
. Но не проблема вывести список 5-буквенных слов в файл и, просмотрев его, удалить из него нераспознаваемые слова.
Я выбрал слово сам: truly
. Первых три буквы совпали! И хотя в этот момент я уже мог бы подбирать подходящие слова в уме, я, для полноты изложения, снова прибегнул к командной строке. А именно: добавил L
и Y
в список тех букв, которых в слове быть не должно, и поместив T
, R
и Y
в шаблон, с которым должно совпасть начало искомого слова.
% grep '^[a-z]....$' /usr/share/dict/words \
| grep -v '[aecflohy]' | grep r | grep t \
| grep 'tru..'
trubu
trump
trunk
truss
trust
Первым из этих слов я выбрал, чёрт подери, trust
. А в итоге правильным словом оказалось truss
.
А вы играете в Wordle?