How I built an NYT Spelling Bee solver with Elixir
Table of contents:
While Wordle has caught Twitter feeds by storm, there’s something I love about playing the New York Times Spelling Bee game each morning.
It works by providing a set of letters, and you have to come up with as many words as you can that:
- Only use the provided letters
- Use the center letter at least once
- Are at least 4 characters long
Here’s what a game might look like:
Recently, I took it upon myself to build a solver for this game using Elixir. Most of the code is in this post, but if you’d like to see the whole project you can check it out on GitHub.
Defining success
Because the words in Spelling Bee are curated and intentionally exclude obscure words, we should expect that this program will suggest words that are not included in the solution.
So I’ll be judging the success of this program by the percentage of the solution words that are identified, ignoring suggested words that are not part of the official solution.
Getting a list of words
My approach was pretty simple. I needed to start with a list of English words, then filter based on the criteria of the game.
To get a list of English words, I used the word_list package that I created.
Starting the project
Once I created my new project, I imported my word_list
package by adding it in the deps
array in mix.exs
defp deps do
[
{:word_list, "~> 0.1.0"}
]
end
Then, I installed the dependency by running mix deps.get
Test-driven development
Next, I wrote some basic tests so I can define some of the basic functionality.
defmodule SpellingBeeSolverTest do
use ExUnit.Case
doctest SpellingBeeSolver
test "finds some valid words" do
word_stream = SpellingBeeSolver.solve("t", ["m","y","r","i","f","o"])
assert "mortify" in word_stream
assert "fifty" in word_stream
end
test "all words include center letter" do
word_stream = SpellingBeeSolver.solve("t", ["m","y","r","i","f","o"])
assert Enum.all?(word_stream, fn word -> String.contains?(word, "t") end)
end
end
These tests make sure that at least some valid words are found, and that all of the words include the center letter.
Writing the code
The solve/2
function takes the following approach:
- Get the stream of English words
- Apply a filter for length greater than 3, since words must be at least four letters long
- Apply a filter that checks that the word contains the center letter
- Apply a filter that checks that all letters in the word are either on the edge or the center letter
The following is the contents of the lib/spelling_bee_solver.ex
file:
defmodule SpellingBeeSolver do
def solve(center, edges) do
WordList.getStream!()
|> Stream.filter(fn word -> String.length(word) > 3 end)
|> Stream.filter(fn word -> String.contains?(word, center) end)
|> Stream.filter(fn word ->
String.split(word, "", trim: true)
|> Enum.all?(fn letter -> letter in edges ++ [center] end)
end)
end
def printSolution(center, edges) do
solve(center, edges)
|> Enum.each(fn x -> IO.puts(x) end)
end
end
To print the solution, the inputs can instead be passed to printSolution/2
But does it work?
I tested my program against the solutions of three different days, and my program found all the solutions every time.
Success! ✅
There were several extra words found each time, but since the solutions are a curated set of words this is to be expected.
Room for improvement
While I’m sure there’s more I could do, these are some areas that I think have room for improvement:
- It would probably be more convenient to pass in a string for the edges instead of an array of strings
- I haven’t done any input validation, so there could be some unexpected behavior if the input isn’t provided in the proper format
- Currently, the program must be run in iex or imported into another Elixir project in order to be functional. It would be more practical to put this logic into a cli tool or a web interface.
- If this were included as part of a larger project, it would be nice to track which words weren’t part of the previous solutions in order to avoid showing them in the future.
If you liked this post, click here to subscribe to my mailing list!