Best way to do reduce map list to certain ones?

I’m very new to Elixir, but know that Enum is powerful. I just don’t have the hang of it. Hoping someone can tell me the best way to do this:

I have a list of maps like this:

Resource_Type_List =
Resource_Type [
%{Resource_Type{ id:1, type: "Blog },
%{Resource_type{ id:2, type: "Book },
%{Resource_type{ id:3, type: "Video },
]

From my form, the user has selected Resource_Types with id: 1 and id: 2. So that is in it’s own list that looks like this:

Selected_IDs = [“1”, “2”] ** NOTE: those are strings

What is the most efficient way to create a new list of maps that ONLY has id:1 and id:2. I feel like it should maybe be Enum.reduce but I can’t quite format the fn part:

Reduced_List =
Enum.reduce( Resource_Type_List, fn, x, “go through Resource_Type_List and see if the ID matches one of the IDs in Selected_IDs (which need to be converted from strings to ints). If so, add it to the Reduced_List.”
end)

It’s the logic in the quotes that I don’t know how to write. I’ve just not learned pattern matching well enough. Would appreciate any recommendations.

Why don’t you simply:

items = [
  %{id: 1, type: "Blog" },
  %{id: 2, type: "Book" },
  %{id: 3, type: "Video" }
]
Enum.filter(items, fn %{id: id} -> to_string(id) in ["1", "2"] end)

I don’t know if you “need” to be performant about this (others might present a better solution).

3 Likes

The good tool is Enum.filter.

Please don’t forget to wrap your code with ``` as in markdown :slight_smile:

1 Like

Hi @annad, as a minor note, formatting code will really help people make this easier.

The other thing that is important is to actually show the code you’re using. The code and datastructures in your post are not valid Elixir, which makes this much harder than it needs to be. Here is a solution using reduce

reduced_list = Enum.reduce(resource_type_list, [], fn resource, list ->
  if to_string(resource.id) in selected_ids do
    [resource | list]
  else
    list
  end
end)

This can be simplified to an Enum.filter

reduced_list = Enum.filter(resource_type_list, fn resource -> to_string(resource.id) in selected_ids end)
5 Likes

Thank you! I’ll be sure to post code next time. It was coming from different places in my Phoenix app and I was trying to simplify it to make it more readable. Unfortunately, I made errors in the translation. Darn.

Sorry. Very new at this. I didn’t understand your comment. What is markdown? Should I use triple quotes for code?

Enum.reduce is sufficiently powerful that it can (and is!) used to implement almost all of the functions in Enum. That doesn’t mean that you shouldn’t use it, but it does mean that sometimes there’s a shorter / more-legible solution involving a simpler, specific function.

When solving problems, it’s best to avoid trying to do everything in one giant function call. For instance, converting input types is best done separately.

selected_id_strings = ["1", "2"] # from the input

selected_ids = Enum.map(selected_id_strings, &String.to_integer/1)

For the next part, think about exactly what you’re looking for in the result. For instance, do you want the results in the same order as the input?

# preserves order
Enum.map(selected_ids, fn id -> Enum.find(resource_type_list, & &1.id == id) end)

# doesn't preserve order
Enum.filter(resource_type_list, & &1.id in selected_ids)

These two approaches also have different behavior in edge-cases:

  • if an ID that isn’t present in the data is passed in - for instance, 4. The former will result in a nil value in the results, the latter will not
  • if an ID appears more than once in resource_type_list, the former will only return the first one while the latter will return all of them
2 Likes

When starting functional programming, what really helped me understanding the functions in Enum was this exercise at https://exercism.io (which is a great place to learn anyway).

List Ops

Implement basic list operations.

In functional languages list operations like length, map, and reduce are very common. Implement a series of basic list operations, without using existing functions.

1 Like

Markdown is a markup language for formatted text. It is used in this forum for readability.

This is code without ```

defmodule Koko do
def hello_world, do: IO.puts “welcome!”
end

and with ``` at the start and the end of your code.

defmodule Koko do
  def hello_world, do: IO.puts "welcome!"
end
1 Like

The provided answers are good, but they’re all O(n*m) runtime. Converting your list to a MapSet would improve that:

set = MapSet.new(["1", "2"])
items = [
  %{id: 1, type: "Blog" },
  %{id: 2, type: "Book" },
  %{id: 3, type: "Video" }
]
Enum.filter(items, fn %{id: id} -> MapSet.member?(set, to_string(id)) end)
2 Likes

Just realized I never thanked you for this help. I come back to this often, since I still struggle with Enums. This helped me a lot and continues to help me. :slight_smile:

1 Like

Your answer reminded me of this post- I guess depending on the size of the lookup list the “naive” implementation might perform better

2 Likes