How to Use Enum.find in Elixir

How to Use Enum.find in Elixir
Photo by Markus Winkler / Unsplash

What's the best way to find a value inside of a collection in Elixir?  If you're used to a non-functional language like C, or JavaScript, you might think of using a for-loop.  But Elixir's functional nature means we have to take a different approach: the function Enum.find.

The function Enum.find (and its friends Enum.find_index and Enum.find_value) work on any type that implements the Enumerable protocol, most notably List, Map and Range.  For the examples in this article, we'll stick to simple lists.

Programming Elixir ≥ 1.6: Functional |> Concurrent |> Pragmatic |> Fun 1st Edition

by Dave Thomas

⭐️⭐️⭐️⭐️

Finding a Simple Value

To find a value within an Enumerable, we invoke Enum.find with the enumberble data, and a finder function:

e = [5, 10, 15]
Enum.find(e, fn v -> v == 10 end)

=> 10

If the value we're looking for isn't found, we'll get nil instead:

e = [5, 10, 15]
Enum.find(e, &(&1 == 11))

=> nil

Finding an Index: Enum.find_index

If we're interested in what position an item appears inside the enumerable, we can use Enum.find_index:

e = [5, 10, 15]
Enum.find_index(e, fn v -> v == 10 end)

=> 1

Finding with Maps

When we invoke Enum.find with a Map, we get a tuple containing a key and a value passed to our finder function:

e = %{a: 5, b: 10, c: 15}
Enum.find(e, fn {k, v} -> k == :b end)

=> {:b, 10}

(Side note, because we don't use the value v from the tuple, we may get an unused variable warning.  We really should have named it _v, but we left it alone for illustrative purposes).

The value returned is of course also a tuple containing both the key and value.  This might not be what you'd like to happen, but the next section on Enum.find_value will help us out.

Finding and Transforming a Value: Enum.find_value

In our previous example, we got back a tuple representing a key and a value, but what if we actually just wanted just one element of the tuple?  We can use Enum.find_value:

e = %{a: 5, b: 10, c: 15}
Enum.find_value(e, fn {k, v} -> if k == :b, do: v, else: nil end)

=> v

Finding with the Capture Operator (&)

In all of the examples we've used so far, we've written out our test function like this:

fn v -> some_test(v) end

But there's a shorthand we can use with the capture operator (&).  We can rewrite the function above like this:

&some_test(&1)

Here, the function arguments are captured as &1, &2, etc, though with Enum.find the finder function only takes a single argument.  So to rewrite one of our earlier examples:

e = [5, 10, 15]
Enum.find(e, &(&1 == 10))

=> 10

Bonus: How to Build Find From Scratch

We started by mentioning that some languages would use a for-loop to implement this functionality and that we should use Enum.find instead.  But we don't want to give the appearance that Enum.find is some kind of magical black-box.  Let's look at how we could implement this functionality in Elixir.

Our implementation below uses recursion, a common functional pattern, instead of a loop to find a value within a List.  We'll ignore other Enumerables in this example to keep it simple.  

It also uses another common Elixir idiom, which is pattern matching on the function input: if the list is empty, the answer is always nil.  Otherwise, we pattern-match out the first item of the list (v) and the rest of the list.  If the test on value v matches, we've found our answer–otherwise, we invoke recursively:

defmodule ListUtil do
  def find([], _), do
    nil
  end

  def find([v | rest], f) do
    # invoke the test function on the first value in the list
    if f.(v),  do: v, else: find(rest, f)
  end
end

# test our implementation
ListUtil.find([1,2,3], fn i -> i == 2 end)

And that's it!  Though most of the time you can do what you need with Enum.find, it's good to understand how to approach this kind of problem in Elixir should the need arise to iterate over some other kind of data structure.