What is an efficient way to fetch an attribute from a list of structs (based on another attribute) and insert into relevant map in list?

Ok, that’s a messy question, so let me explain.

I have a list of structs:

[
  %Struct{id: 123, foreign_id: 5},
  %Struct{id: 456, foreign_id: 6}
]

And a list of maps:

[
  %{id: 5},
  %{id: 6}
]

What I want to do is pull in the relevant id from the structs (based on the struct foreign_id => map id). The result I’m looking for:

[
  %{id: 5, foreign_id: 123},
  %{id: 6, foreign_id: 456}
]

I can stumble my way through this but am wondering what would be an elegant and efficient way to go about it in Elixir? I will be running this on maximum (probably) 500 in each list so doubt I need a stream. I just want to avoid any unnecessary passes.

1 Like

Can you please give a bit more example data, please?

This is essentially what I want to do:

  def test do
    structs = [
      %Struct{id: 123, foreign_id: 5},
      %Struct{id: 456, foreign_id: 6}
    ]

    maps = [
      %{id: 5},
      %{id: 6}
    ]

    Enum.map(maps, fn map ->
      foreign_id = Enum.find(structs, fn struct -> struct.foreign_id == map.id end).id
      Map.put(map, :foreign_id, foreign_id)
    end)
  end

I’m just not sure if I can avoid the Enum.find for every element in maps. Also aware the flow of that could be improved to be more idiomatic.

To give more context, the structs are being returned from a bulk insert query and the maps belong to each of those structs which have been inserted. I am returning the ids for the initial bulk insert and then attempting to run a second bulk insert (the maps) with the foreign key mapping.

:wave:

I’d probably either sort both lists and iterate over them, or turn structs into a map %{struct_id => struct} and iterate over maps.

4 Likes

I’d probably do:

iex(1)> defmodule Struct do
...(1)>   defstruct [:id, :foreign_id]
...(1)> end
{:module, Struct,
 <<70, 79, 82, 49, 0, 0, 5, 184, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 183,
   0, 0, 0, 18, 13, 69, 108, 105, 120, 105, 114, 46, 83, 116, 114, 117, 99, 116,
   8, 95, 95, 105, 110, 102, 111, 95, 95, ...>>,
 %Struct{foreign_id: nil, id: nil}}
iex(2)> s = [
...(2)>   %Struct{id: 123, foreign_id: 5},
...(2)>   %Struct{id: 456, foreign_id: 6}
...(2)> ]
[%Struct{foreign_id: 5, id: 123}, %Struct{foreign_id: 6, id: 456}]
iex(3)> m = [
...(3)>   %{id: 5},
...(3)>   %{id: 6}
...(3)> ]
[%{id: 5}, %{id: 6}]
iex(4)> 
nil
iex(5)> # So I'd do this, assuming each struct has a unique foreign_id, if not then adjust:
nil
iex(6)> sf = Map.new(s, &{&1.foreign_id, &1.id})
%{5 => 123, 6 => 456}
iex(7)> result = Enum.map(m, &Map.put_new(&1, :foreign_id, sf[&1.id]))
[%{foreign_id: 123, id: 5}, %{foreign_id: 456, id: 6}]

Converting the find to a map lookup should speed it up overall once the count is above a certain amount, maybe a quick guess of 40 or so, plus it is just easier to reason about. :slight_smile:

1 Like