Elixir binary search
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 endend
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) endend
# Entry point
# Set the random seedRandom.init# Choose a random wordword = Random.random_element(words)# Read the UNIX words dictionarywords = Words.read("/usr/share/dict/words")IO.puts("Word is: #{word}")# Perform the binary search to "guess" the wordWords.find(word, words)
Example output:
$ iex words.exs
Word is: barrulyLess than: modificatoryLess than: eagernessLess than: canariGreater than: asthenosphereLess than: bifoliolateGreater than: baradLess than: beguilementLess than: batzenLess than: basalticGreater than: barmbrackGreater than: barrelerLess than: bartholomewGreater than: barrioFound 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 -l235886
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.
Recommended
Creating an Elixir module
Creating an Elixir module