Let’s rewrite our anagram code to use both tasks and an agent.
We’ll load words in parallel from a number of separate
dictionaries. A separate task handles each dictionary. We’ll use
an agent to store the resulting list of words and signatures.
| def start_link, |
| do: Agent.start_link(fn -> %{} end, name: @name) |
| |
| def add_words(words), |
| do: Agent.update(@name, &do_add_words(&1, words)) |
| |
| def anagrams_of(word), |
| do: Agent.get(@name, &Map.get(&1, signature_of(word))) |
| |
| |
| |
| |
| defp do_add_words(map, words), |
| do: Enum.reduce(words, map, &add_one_word(&1, &2)) |
| |
| defp add_one_word(word, map), |
| do: Map.update(map, signature_of(word), [word], &[word|&1]) |
| |
| defp signature_of(word), |
| do: word |> to_charlist |> Enum.sort |> to_string |
| |
| end |
| |
| defmodule WordlistLoader do |
| def load_from_files(file_names) do |
| file_names |
| |> Stream.map(fn name -> Task.async(fn -> load_task(name) end) end) |
| |> Enum.map(&Task.await/1) |
| end |
| |
| defp load_task(file_name) do |
| File.stream!(file_name, [], :line) |
| |> Enum.map(&String.trim/1) |
| |> Dictionary.add_words |
| end |
| end |
Agents and tasks run as OTP servers, so they are easy to distribute—just
give our agent a
globally accessible name. That’s a one-line change:
Now we’ll load our code into two separate nodes and connect
them. (Remember that we have to specify names for the nodes so they can talk.)
We’ll start the dictionary agent in node one—this is where the
actual dictionary will end up. We’ll then load the
dictionary using both nodes one and two: