3 ms·
I love when people get fascinated by a puzzle and deep dive into it like this. Great article. Similarly I was fascinated by LetterBoxed, another NYT game and t
by hlfshell 3y ago
I love when people get fascinated by a puzzle and deep dive into it like this. Great article.
Similarly I was fascinated by LetterBoxed, another NYT game and took a crack at a solver. https://hlfshell.ai/posts/letter-puzzles/ https://hlfshell.ai/posts/letter-puzzles/
- tkgally 3y agoI’m a fan of LetterBoxed, too. The NYT’s solutions are always two words, which I rarely get on my own. But once, a couple of years ago, I discovered a one-word solution to one day’s puzzle: LEXICOGRAPHY. Very elegant, I thought to myself smugly. I happened to remember that solution a couple of months ago, and I decided to see if I could find others. I am not a programmer, but by asking ChatGPT 4 for help I was able to create a Python program and run it in Google Colab using a large list of English words that I had compiled from various online word lists. Here is the beginning of the resulting list of one-word solutions to LetterBoxed: acetylcholinesterase [a c e] [h i l] [n o r] [s t y] acetylcholinesterases [a c e] [h i l] [n o r] [s t y] achondroplastic [a c d] [h i l] [n o p] [r s t] acknowledgement [a c d] [e g k] [l m n] [o t w] acknowledgment [a c d] [e g k] [l m n] [o t w] The code that ChatGPT 4 wrote for me and the full list of solutions are here: https://gally.net/temp/20240318onewordsolutionstoletterboxed.html https://gally.net/temp/20240318onewordsolutionstoletterboxed...
- tkgally 3y agoWhoops. Looking through that list of words again, I see that some of the puzzle configurations could not yield those solutions, because adjacent letters in the solution are in the same triplet. The configuration for acknowledgment, for example, has a and c in the same triplet, which is not possible in LetterBox. My bad. I’ll take a look at the code again later.
- tkgally 3y agoWith Claude 3 Opus’s help, I think I have fixed the code. The corrected code, which took more than two hours to run in Google Colab, and the resulting list of one-word solutions to LetterBoxed are here: https://gally.net/temp/20240318onewordsolutionstoletterboxed.html https://gally.net/temp/20240318onewordsolutionstoletterboxed...
- hlfshell 3y agoI'll admit that I'm impressed it was able to one-shot create a solution to such a complex search problem.
- tkgally 3y agoIt wasn’t quite one-shot. I had multiple interactions with ChatGPT on the first run-through before I could get the code to work, and a couple with Claude after I noticed that the partitions weren’t correct. But all I did was report the errors to the LLMs and paste their revised code back into Colab. The core logic of the search algorithm was created entirely by the LLMs based on my natural-language description of the LetterBoxed rules and the solutions I was looking for. I could not have written that code myself.
- angiosperm 3y agoI have a short program to solve letterboxed. I can't measure how fast it is. #include <array> #include <algorithm> #include <bitset> #include <iostream> #include <string_view> #include <vector> #include <unistd.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> struct Word { using Bits = std::bitset<32>; // A bit for each a..z, plus an "error" bit at index 26. using Rules = std::array<Bits, 27>; // Map from letter indices to forbidden letters (plus a spare). std::string_view str; Bits bits; static unsigned ix(char c) { return c - 'a'; }; // 'a'..'z' -> 0..25; others -> >25 bool ok() const { return !bits.test(26) && str.size() > 1; } explicit Word(std::string_view s, Bits accept = {0x3ffffff}, Rules const& rules = {}) : str(s) { for (unsigned i = 0, prev_ix = 26, bit_ix = 0; i != str.size(); prev_ix = bit_ix, ++i) { bit_ix = std::min(ix(str[i]), 26u); if (accept.test(bit_ix) && !rules.at(prev_ix).test(bit_ix)) { bits.set(bit_ix); } else { bits.set(26); return; }}} }; int main(int ac, char** av) { auto usage = [av](){ std::cerr << "usage: " << av[0] << " abcdefghijkl [<wordlist>]\n"; }; if (ac != 2 && ac != 3) { return usage(), 1; } char const* const name = (ac == 3) ? av[2] : "/usr/share/dict/american-english-large"; const int fd = ::open(name , O_RDONLY); if (fd < 0) { return std::cerr << av[0] << ": failed to open " << name << '\n', 3; } const std::size_t file_size = ::lseek(fd, 0, SEEK_END); void const* const addr = ::mmap(nullptr, file_size, PROT_READ, MAP_SHARED, fd, 0); if (addr == MAP_FAILED) { return std::cerr << av[0] << ": failed to open " << name << '\n', 3; } const auto target = Word{av[1]}; if (target.bits.test(26) || target.str.size() != 12 || target.bits.count() != 12) { return usage(), 3; } const auto rules = [w=target.str, ix=Word::ix](Word::Rules rules = {}) { for (int i = 0; i < 12; i += 3) rules.at(ix(w[i])) = rules.at(ix(w[i+1])) = rules.at(ix(w[i+2])) = Word(w.substr(i, 3)).bits; return rules; }(); const auto candidates = [&](std::array<std::vector<Word>, 26> candidates = {}) { for (std::string_view in = {static_cast<char const*>(addr), file_size}; !in.empty();) { const Word word{in.substr(0, in.find('\n')), target.bits, rules}; if (word.ok()) { candidates.at(Word::ix(word.str.front())).push_back(word); } in = in.substr(word.str.size() + 1); // Skip past '\n' if present (might not be, at EOF). } return candidates; }(); for (auto const& firsts : candidates) { for (const auto first : firsts) { for (const auto second : candidates.at(Word::ix(first.str.back()))) { if ((first.bits | second.bits) == target.bits) { std::cout << first.str << ' ' << second.str << '\n'; }}}} }