How to append and create a list of tuple

I have to create a list of tuple

I have a function which takes a map


def some_func() do
     list_of_map = Utils.decode_json(@path)
     Enum.flat_map(list_of_map, fn i -> 
        list = i_distance(i)
        IO.inspect list
      end)
  end

  defp i_distance(%{
         "a" => a,
         "b" => b,
         "c" => c,
         "d" => d
       }) do
    {a, _} = Float.parse(a)
    {b, _} = Float.parse(b)

    distance =
      calculate_distance(@e, @f, a, b)

      if distance < 100, do: {c, d}
      
   end

I want to create a list of [{user_id, name}] append together for each element of the map only when they are less than 100 needs to be in a list

so the output will be those element appended together [{123, "xyz"}, {241, "zxy"}] and soon

:wave:

Would you be able to provide an example of the input and the desired output?

This will really help in understanding the issue.

In the meantime, try taking a look at filter/2 and filtering within comprehensions for ideas about keeping only the desired elements in your list.

1 Like

Hello

I want to achieve some like this what we would do in OO language

list_map = [%{a:1, b:2, c: 3, d: 4}, %{a:1, b:2, c: 3, d: 4}...]

list_of_tuple = []
for each element in list_map 
   distance = // do something 
if (distance <= 100) 
    list_of_tuple.append([{c, d}]

Thanks for the extra details.

So, what I understand is: for every element in our input list, do the following;

  1. perform a calculation
  2. filter elements based on that calculation
  3. change the shape of each remaining element

Elixir’s pipe operator |> is really helpful for this type of problem.

First, I took the liberty of creating some distinct input data based on your example:

list_of_maps = [
  %{avg_kph: 25, hours: 3, id: 2, name: "bicyclist"},
  %{avg_kph: 60, hours: 2, id: 1, name: "motorist"},
  %{avg_kph: 10, hours: 1, id: 3, name: "runner"}
]

And here’s one way to approach the problem.

First, we can create some helper functions:

# performs some calculation
calc_distance = & &1.avg_kph * &1.hours

# puts a new :distance attribute into the map
put_distance = &Map.put(&1, :distance, calc_distance.(&1))

# is the map's distance acceptable? 
distance_ok? = & &1.distance <= 100

# transforms the map into an {id, name} tuple
as_tuple = &{&1.id, &1.name}

Now we’re ready to use |>:

list_of_maps
|> Enum.map(put_distance)
|> Enum.filter(distance_ok?)
|> Enum.map(as_tuple)

# result: [{2, "bicyclist"}, {3, "runner"}]

I’m still learning, too, so this may not be the best way to do it, but it works (and seems like it’s the “Elixir way”).

1 Like

Not tested (only pseudo code) … but as previous response, use Enum module

list_map 
|> Enum.map(fn %{a: a, b: b, c: c, d: d} -> {calculate_distance(a, b), {c, d}} end)
|> Enum.filter(fn {distance, _} -> distance <= 100 end)

BTW this should return a map of tuples with distance (<= 100) as first element, and c, d tuple as second element.

1 Like

It’s probably possible to make it a one liner with list comprehension, because it accepts a filter (and/or a collector) too…

for %{a: a, b: b, c: c, d: d} <- list_map, calculate_distance(a, b) <= 100, do: {c, d}

This one only collects c, d, and not the distance

3 Likes

Alternative to @kokolegorille solution would be use of Enum.flat_map/2:

Enum.flat_map(list_map, fn %{a: a, b: b, c: c, d: d} ->
  case calculate_distance(a, b) do
    true -> [{c, d}]
    false -> []
  end
end)
5 Likes

Nice approaches, @kokolegorille and @hauleth.

It’s really cool to see more functional idioms; I’ve got more practice ahead of me. :thinking:

@hauleth: I think there might be a minor typo in your Enum.flat_map/2 example.

Should
case calculate_distance(a, b) do
actually be
case calculate_distance(a, b) <= 100 do?

1 Like

Yeah, it should be.

1 Like

BTW if You want to do something like this… I would advise You to look at geo. If You activate GIS extension with PostGres, You could filter for distance at db level.

1 Like