How I built an NYT Spelling Bee solver with Elixir
Tags: elixir, functional, devjournal, showdev
Published: 2022-03-09 14:11:41

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:

![Screenshot of a game of NYT Spelling Bee](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t9o9i9oxiewlycj60jk6.png)

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](https://github.com/brew-install-buzzwords/spelling_bee_solver).

## 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](https://www.brewinstallbuzzwords.com/posts/my-first-hex-package/).

## 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`

```elixir
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.

```elixir
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:

```elixir
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](https://dev.to/brewinstallbuzzwords) or subscribe to my [brief monthly newsletter](https://mailchi.mp/89dc077154c7/brewinstallbuzzwords).

* [Reading the Elixir source code to learn how if blocks work](https://www.brewinstallbuzzwords.com/posts/elixir-if-block-source-code/)
* [I published my first Elixir package to hex!](https://www.brewinstallbuzzwords.com/posts/my-first-hex-package/)
* [What's the best questions you've been asked in a job interview?](https://dev.to/brewinstallbuzzwords/whats-the-best-question-youve-been-asked-in-a-job-interview-4a1j)
* [What was the first program you wrote?](https://dev.to/brewinstallbuzzwords/what-was-the-first-program-you-wrote-1gjp)
* [The weird quirk of JavaScript arrays (that you should never use)](https://www.brewinstallbuzzwords.com/posts/js-arrays-are-objects/)
* [Does Elixir have for loops?](https://www.brewinstallbuzzwords.com/posts/does-elixir-have-for-loops/)
* [Learn Elixir with me!](https://www.brewinstallbuzzwords.com/posts/learn-elixir-with-me/)
* [Project Tours: Bread Ratio Calculator](https://www.brewinstallbuzzwords.com/posts/bread-ratio-calculator/)
* [Changing Emoji Skin Tones Programmatically](https://www.brewinstallbuzzwords.com/posts/emoji-skin-tone/)
* [I made my first svg animation!](https://dev.to/brewinstallbuzzwords/i-made-my-first-svg-animation-29j3)
* [5 tips for publishing your first npm package](https://www.brewinstallbuzzwords.com/posts/tips-for-first-npm-package/)
* [4 Hugo Beginner Mistakes](https://www.brewinstallbuzzwords.com/posts/hugo-beginner-mistakes/)