A few days ago, I saw a Guess my word game on the front page of Hacker News. Before spoiling the fun for myself by checking out the comments, I decided to try my hand at writing a solution in Elixir. Afterwards, I generalized the code to choose its own word from the UNIX dictionary and then “guess” it, applying a binary search based on the feedback of whether each guess was alphabetically greater or less than the word itself.
defmodule Words do
@doc """
Module for read a list of words from a text file.
Contains functions for `split`ting the list and `find`ing a word
"""
def read(filename) do
filename
|> File.read!()
|> String.split()
end
def split(list) do
mid = div(length(list), 2)
Enum.split(list, mid)
end
def find(word, words) do
{first_half, second_half} = split(words)
guess = (List.last(first_half) || List.first(second_half))
|> String.downcase
cond do
word < guess ->
IO.puts("Less than: #{guess}")
find(word, first_half)
word > guess ->
IO.puts("Greater than: #{guess}")
find(word, second_half)
:else ->
IO.puts("Found word: #{guess}")
word
end
end
end
defmodule Random do
@doc """
Module for choosing a psudo-random element from a list
"""
def init do
:random.seed(:os.timestamp)
end
def random_element(list) do
Enum.at(list, :random.uniform(length(list)) - 1)
end
end
# Entry point
# Set the random seed
Random.init
# Choose a random word
word = Random.random_element(words)
# Read the UNIX words dictionary
words = Words.read("/usr/share/dict/words")
IO.puts("Word is: #{word}")
# Perform the binary search to "guess" the word
Words.find(word, words)
Example output:
$ iex words.exs
Word is: barruly
Less than: modificatory
Less than: eagerness
Less than: canari
Greater than: asthenosphere
Less than: bifoliolate
Greater than: barad
Less than: beguilement
Less than: batzen
Less than: basaltic
Greater than: barmbrack
Greater than: barreler
Less than: bartholomew
Greater than: barrio
Found word: barruly
Something I encountered worth mentioning is how Elixir compares strings that have different capitalization. Capital letters are “less than” their lower case versions:
iex> "B" < "b"
true
Knowing this, we use String.downcase
in our implementation to avoid comparison issues in the binary search. Binary search has a time complexity of logโ(N).
Given that the UNIX dictionary has 235,886 words
$ cat /usr/share/dict/words | wc -l
235886
the fact the our algorithm took 14 steps to “guess” the word is plausible given
O(logโ(235886)) โ O(17.85)
which is the number of steps we would expect it to take to guess our word.