# How to [elegantly] extract values for keys in map, reject nils and determine if all values match true

Ok, here’s the problem:

Given a map of variable keys and values, select a set of those keys which may be either `nil`, `false`, or `true`. Only return true if there is at least one true value and no false values.

Here is an example map I have, where I would be extracting the 3 `matches_` keys:

``````%{
random_key: 123,
another: 456,
matches_1: nil,
matches_2: true,
matches_3: false
}
``````

In the above, the check would fail because the matches include false. Here is another false (as no true values):

``````%{
random_key: 123,
another: 456,
matches_1: nil,
matches_2: nil,
matches_3: nil
}
``````

Here is a pass:

``````%{
random_key: 123,
another: 456,
matches_1: nil,
matches_2: true,
matches_3: nil
}
``````

My attempt at this:

``````  def pass?(map) do
Map.take(map, [:matches_1, :matches_2, :matches_3])
|> Map.values
|> Enum.reject(&is_nil/1)
|> (fn(values) -> length(values) > 0 && Enum.all?(values, fn x -> x == true end) end).()
end
``````

Would love to see if this can be more elegantly expressed. The crux of the conditions and what I’m not that happy with is the last line:

`(fn(values) -> length(values) > 0 && Enum.all?(values, fn x -> x == true end) end).()`

1 Like

Would this work?

``````@spec pass?(map) :: boolean
def pass?(%{matches_1: matches_1, matches_2: matches_2, matches_3: matches_3}) do
[matches_1, matches_2, matches_3]
|> Enum.reject(&is_nil/1)
|> Enum.find(true, &(!&1))
end
``````

Probably not. Let me think again.

What would happen in the case of nil in those conditions? A nil on `matches_1` would continue to `matches_2`?

That appears to be testing that all matches return true?

Just saw the edit, cheers! I like the direction it is going with pattern matching. Hadn’t thought of using that.

But the edit will return true if any values match true, even if other values are false.

Idiomatic answers to un-idiomatic data are always the trickiest. Is there a way to not do `matches_1, matches_2` and instead have `matches: [nil, true false]` ?

The matches themselves map to database table columns and I’d prefer to keep the matches as descriptive as possible during operations, that’s the only reason it seems a little odd. I could transform the structure into what you suggest first… though that is essentially what I’m doing with `Map.values`.

Gotcha. Here’s my take:

``````def pass?(map) do
map
|> Map.take([:matches_1, :matches_2, :matches_3])
|> Map.values
|> Enum.map(fn
nil -> true
val -> val
end)
|> Enum.all?
end
``````
2 Likes

Clever! I like.

Hold on… what is `nil -> true` doing? Any ref docs?

essentially yeah, although I’m realizing that my logic isn’t what you requested. “Only return true if there is at least one true value and no false values.”

I think a variation on @idi527’s solution might work:

``````def pass?(map) do
map
|> Map.take([:matches_1, :matches_2, :matches_3])
|> Map.values
|> Enum.reject(&is_nil/1)
|> Enum.find(false, &(&1))
end
``````
1 Like

Was thinking that, threw me off a little

That looks more like it, thanks. I think `Enum.find` should be `Enum.find(enumerable, ...)`

Do you have any ref docs for the `nil -> true` mapping, and the `&(&1)`? Or some keywords to give me something to go on?

This is my attempt. You can try with `DecideMap.is_pass?(map)`.

``````defmodule DecideMap do
def is_pass?(map) do
values =
map
|> Map.take([:matches_1, :matches_2, :matches_3])
|> Map.values()
|> Enum.reject(&is_nil/1)

with true <- false not in values,
true <- true in values do
true
else
_ -> false
end
end
end``````
1 Like

Just started to separate it out into `values`. Cheers!

well the `nil -> true` thing is just normal pattern matching. If you had a regular function it’d look like:

``````def map_nil_to_true(nil), do: true
def map_nil_to_true(value), do: value
``````

As an anonymous function this just looks like:

``````fn
nil -> true
value -> value
end
``````

Basically, anonymous functions can have multiple bodies just like named functions.

`&(&1)` is anonymous function short hand, you can read about it here:
https://hexdocs.pm/elixir/Kernel.SpecialForms.html#&/1-anonymous-functions

4 Likes

Enum.reduce_while/3

``````defmodule Demo do

def run(map, keys) do
check = fn (key, has_true) ->
case Map.get(map, key) do
true -> {:cont, true}
false -> {:halt, false}
_ -> {:cont, has_true}
end
end

Enum.reduce_while(keys, false, check)
end

end

sample1 = %{
random_key: 123,
another: 456,
matches_1: nil,
matches_2: true,
matches_3: false
}

sample2 = %{
random_key: 123,
another: 456,
matches_1: nil,
matches_2: nil,
matches_3: nil
}

sample3 = %{
random_key: 123,
another: 456,
matches_1: nil,
matches_2: true,
matches_3: nil
}

keys = [:matches_1, :matches_2, :matches_3]

IO.inspect(Demo.run(sample1, keys)) // false
IO.inspect(Demo.run(sample2, keys)) // false
IO.inspect(Demo.run(sample3, keys)) // true``````
4 Likes

With that values you could just do this:

`false not in values and true in values`

so it could be simpler:

``````defmodule DecideMap do
def is_pass?(map) do
values =
map
|> Map.take([:matches_1, :matches_2, :matches_3])
|> Map.values()
|> Enum.reject(&is_nil/1)

false not in values and true in values
end
end
``````

`with` is there, just in case you want to easily add other kind of conditions in the future.

3 Likes

I really like that! Very simple to follow.

A slightly different spin:

``````def pass?(%{matches_1: m1, matches_2: m2, matches_3: m3}) do
[true, false] -- [m1, m2, m3] == [false]
end
``````

edit One more:

``````def pass?(%{matches_1: m1, matches_2: m2, matches_3: m3}) do
[true] == [m1, m2, m3]
|> Enum.reject(&is_nil/1)
|> Enum.uniq()
end``````
1 Like

That last one is really cool! I need to make more use of pattern matching and assertive programming.