Returning Single Value from a list after applying condition

I am iterating a list with indexes (Enum.with_index), and based on the first matching condition ( against the value in a map for list element), I wish to split the list into 2 lists at that index.

For example: map= ['a': false, 'b':'false, 'c':true, 'd':false]
my list: [:d, :a, :c, :b]

Expected Output: [:d, :a, :c], [:b]

My approach: Iterate through the list and if the value (first occurrence) is true in map, return the index, and then split the original list. If no value from the list is true in the map, then I wish to return [ ], [d, a, c, b]

However, I am struggling to return the index at all from Enum.each.

splitter = fn(list) ->
        list
        |> Enum.with_index
        |> Enum.each(fn({elem, i}) ->
            if map[elem] do
              i
            end
          end)
      end

IO.puts "#{splitter.(list)}"

From a quick glance it seems like you might want to use Enum.map instead of Enum.each. Enum.map will return the value on each iteration resulting in a new list.

If you didn’t need the first element that matches on the left side of the split, you could accomplish this by using Enum.split_while/2 with a negated match fun. Something like:

iex> map = %{a: false, b: false, c: true, d: false}
iex> my_list = [:d, :a, :c, :b]
iex> {left, right} = Enum.split_while(my_list, &!map[&1])
{[:d, :a], [:c, :b]}

Your requirement that when no element matches the left side should be an empty list also complicates things. The more direct approach would be to accumulate on the left until the first match occurs, and accumulate on the right afterwards. This means that if none matches, the empty list would be the right side.

All considered, this should do it:

defmodule MyEnum do
  def split_after_first_match(enumerable, fun) do
    {left, right, found} =
      Enum.reduce(enumerable, {[], [], false}, fn e, {left, right, found} ->
        if found do
          {left, [e | right], found || fun.(e)}
        else
          {[e | left], right, found || fun.(e)}
        end
      end)
    if found do
      {Enum.reverse(left), Enum.reverse(right)}
    else
      {[], Enum.to_list(enumerable)}
    end
  end
end
```

```
iex> map = %{a: false, b: false, c: true, d: false}
iex> my_list = [:d, :a, :c, :b]
iex> {left, right} = MyEnum.split_after_first_match(my_list, &map[&1])
{[:d, :a, :c], [:b]}
```
1 Like

Not sure I am completely understanding your intent but I suspect that you might benefit from Enum.group_by. Something like the following (its not the same result as your example but I didn’t understand your intent so you’ll need to adjust the function in Enum.group_by

map = %{"a" => false, "b" => false, "c" => true, "d" => false}
list = ["d", "a", "c", "b"]

Enum.group_by(list, fn item -> 
  # Returns the value which is truthy if it exists
  # or false is there is no value hence splits into
  # two buckets
  Map.get(map, item) 
end) |> Map.values

[["d", "a", "b"], ["c"]]

@pma has the better answer for sure. I always forget Enum.split_while/2

@pma is on the right track, but there is a much simpler way to ensure you will return an empty left-list if no item matches:

defmodule Splitter do
  def split(list, map) do
    case Enum.split_while(list, &!map[&1]) do
      {all, []} -> {[], all}
      any_other_result -> any_other_result
    end
  end
end
1 Like