How I built an NYT Spelling Bee solver with Elixir

by Adam Davis

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:

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:

  1. Get the stream of English words
  2. Apply a filter for length greater than 3, since words must be at least four letters long
  3. Apply a filter that checks that the word contains the center letter
  4. 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:

Adam Davis

Share this post: