How I built an NYT Spelling Bee solver with Elixir
Adam Davis
Posted on March 9, 2022
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.
More content
If you liked this, you might also like some of my other posts. If you want to be notified of my new posts, follow me on Dev or subscribe to my brief monthly newsletter.
- Reading the Elixir source code to learn how if blocks work
- I published my first Elixir package to hex!
- What's the best questions you've been asked in a job interview?
- What was the first program you wrote?
- The weird quirk of JavaScript arrays (that you should never use)
- Does Elixir have for loops?
- Learn Elixir with me!
- Project Tours: Bread Ratio Calculator
- Changing Emoji Skin Tones Programmatically
- I made my first svg animation!
- 5 tips for publishing your first npm package
- 4 Hugo Beginner Mistakes
Posted on March 9, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.