# Wordle Solving Using MongoDB Query API Operators

This article details one of my MongoDB Atlas learning journeys. I joined MongoDB in the fall of 2022 as a Developer Advocate for Atlas Search. With a couple of decades of Lucene experience, I know search, but I had little experience with MongoDB itself. As part of my initiation, I needed to learn the MongoDB Query API, and coupled my learning with my Wordle interest.

## Game on: Introduction to Wordle

The online game Wordle took the world by storm in 2022. For many, including myself, Wordle has become a part of the daily routine. If you’re not familiar with Wordle, let me first apologize for introducing you to your next favorite time sink. The Wordle word guessing game gives you six chances to guess the five-letter word of the day. After a guess, each letter of the guessed word is marked with clues indicating how well it matches the answer. Let’s jump right into an example, with our first guess being the word `ZESTY`. Wordle gives us these hints after that guess:

The hints tell us that the letter E is in the goal word though not in the second position and that the letters `Z`, `S`, `T`, and `Y` are not in the solution in any position. Our next guess factors in these clues, giving us more information about the answer:

Do you know the answer at this point? Before we reveal it, let’s learn some MongoDB and build a tool to help us choose possible solutions given the hints we know.

## Modeling the data as BSON in MongoDB

We can easily use the forever free tier of hosted MongoDB, called Atlas. To play along, visit the Atlas homepage and create a new account or log in to your existing one.

Once you have an Atlas account, create a database to contain a collection of words. All of the possible words that can be guessed or used as daily answers are built into the source code of the single page Wordle app itself. These words have been extracted into a list that we can quickly ingest into our Atlas collection.

I created a repository for the data and code here. The README shows how to import the word list into your Atlas collection so you can play along.

The query operations needed are:

* Find words that have a specific letter in an exact position.
* Find words that do not contain any of a set of letters.
* Find words that contain a set of specified letters, but not in any known positions.

In order to accommodate these types of criteria, a word document looks like this, using the word MONGO to illustrate:

```
{
  "_id":"MONGO",
  "letter1":"M",
  "letter2":"O",
  "letter3":"N",
  "letter4":"G",
  "letter5":"O",
  "letters":"M","O","N","G"]
}
```

## Finding the matches with the MongoDB Query API

Each word is its own document and structured to facilitate the types of queries needed. I come from a background of full-text search where it makes sense to break down documents into the atomic findable units for clean query-ability and performance. There are, no doubt, other ways to implement the document structure and query patterns for this challenge, but bear with me while we learn how to use MongoDB Query API with this particular structure. Each letter position of the word has its own field, so we can query for exact matches. There is also a catch-all field containing an array of all unique characters in the word so queries do not have to be necessarily concerned with positions.

Let’s build up the MongoDB Query API to find words that match the hints from our initial guess. First, what words do not contain `Z`, `S`, `T`, or `Y`? Using MongoDB Query API [query operators in a `.find()` API call, we can use the `$nin` \(not in\) operator as follows:

```
{
  "letters":{
    "$nin":"Z","S","T","Y"]
  }
}
```
Independently, a `.find()` for all words that have a letter `E` but not in the second position looks like this, using the [`$all` operator as there could be potentially multiple letters we know are in the solution but not which position they are in:

```
{
  "letters":{
    "$all":"E"]
  },
  "letter2":{"$nin":["E"]}
}
```

To find the possible solutions, we combine all criteria for all the hints. After our `ZESTY` guess, the full `.find()` criteria is:

```
{
  "letters":{
    "$nin":["Z","S","T","Y"],
    "$all":["E"]
  },
  "letter2":{"$nin":["E"]}
}
```

Out of the universe of all 2,309 words, there are 394 words possible after our first guess.

Now on to our second guess, `BREAD`, which gave us several other tidbits of information about the answer. We now know that the answer also does not contain the letters `B` or `D`, so we add that to our letters field `$nin` clause. We also know the answer has an `R` and `A` somewhere, but not in the positions we initially guessed. And we have now know the third letter is an `E`, which is matched using the [`$eq` operator. Combining all of this information from both of our guesses, `ZESTY` and `BREAD`, we end up with this criteria:

```
{
  "letters":{
    "$nin":"Z","S","T","Y","B","D"],
    "$all":["E","R","A"]
  },
  "letter2":{"$nin":["E","R"]},
  "letter3":{"$eq":"E"},
  "letter4":{"$nin":["A"]}
}
```

Has the answer revealed itself yet to you? If not, go ahead and import the word list into your Atlas cluster and run the aggregation.

It’s tedious to accumulate all of the hints into `.find()` criteria manually, and duplicate letters in the answer can present a challenge when translating the color-coded hints to MongoDB Query API, so I wrote a bit of Ruby code to handle the details. From the command-line, using [this code, the possible words after our first guess looks like this….

```
$ ruby word_guesser.rb "ZESTY x~xxx"
{"letters":{"$nin":"Z","S","T","Y"],"$all":["E"]},"letter2":{"$nin":["E"]}}
ABIDE
ABLED
ABODE
ABOVE
.
.
.
WOVEN
WREAK
WRECK
394
```

The output of running `word_guesser.rb` consists first of the MongoDB Query API generated, followed by all of the possible matching words given the hints provided, ending with the number of words listed. The command-line arguments to the word guessing script are one or more quoted strings consisting of the guessed word and a representation of the hints provided from that word where `x` is a greyed out letter, `~` is a yellow letter, and `^` is a green letter. It’s up to the human solver to pick one of the listed words to try for the next guess. After our second guess, the command and output are:

```
$ ruby word_guesser.rb "ZESTY x~xxx" "BREAD x~^~x"
{"letters":{"$nin":["Z","S","T","Y","B","D"],"$all":["E","R","A"]},"letter2":{"$nin":["E","R"]},"letter3":{"$eq":"E"},"letter4":{"$nin":["A"]}}
OPERA
1
```

Voila, solved! Only one possible word after our second guess.

![OPERA

In summary, this fun exercise allowed me to learn MongoDB’s Query API operators, specifically `$all`, `$eq`, and `$nin` operators for this challenge.

To learn more about the MongoDB Query API, check out these resources:
* Introduction to MongoDB Query API
* Getting Started with Atlas and the MongoDB Query Language \(MQL\)(now referred to as the MongoDB Query API)
* The free MongoDB CRUD Operations: Insert and Find Documents course at MongoDB University