Updating one list based with conditions based on another list

I have two lists where one set of values will be updated based on values from another list.

For example :

List1:  [a, b, c]
List2:  [{a,1}, {b,2} , {d,3},  {e,7}, {g,9}]

If any items in List2 have an “a”, “b” or “c” then I want the value to be updated to “10”

So the returning list in the above example would look like the following:
[{a,10}, {b,10} , {d,3}, {e,7}, {g,9}]

I was going to do the following

for item1 <- List1 do
    for item2 <- List2 do
        //Do some logic to check for each if item1 is in item 2 
    end 
end

When i used that approach it returned a list of lists and am looking for a more efficient way to just return one list with the updated values.

I was thinking I would need recursion with a “third” list as an accumulator but not sure.

Any suggestions on how better to approach this problem ?

Thanks,

Assuming that both lists are not sorted, you will need to loop through the second list once for each element in the first list, and the algorithm you used for this is correct; the only thing to do at the end is combine the list-of-lists. This combining will be more difficult if either of the two lists might contain a letter multiple times.

To make it faster, you could sort the lists, in which case you know when you’ve finished a certain letter. This could indeed be done using a third list that is being constructed as accumulator.

However, what probably would be better suited for your data, is to use a map instead of a list for List 2, as it seems to be a histogram.

Thanks for the feedback. I will look into a map as the alternative.

set = MapSet.new(list1)
for {key, value} <- list2, do: MapSet.member?(set, key) && {key, 10} || {key, value}
1 Like

if you know list1 at compile time:

[a: 1, b: 2, d: 3, e: 7, g: 9] |> Enum.map(fn {k, v} when k in [:a, :b, :c] -> {k, 10}; x -> x end)

This works great. Brilliant!

Just curious if I changed from a two item tuple to stuct with like 5 or 6 properties then I would have to evaluate the whole struct as the “key”. Is there a way to go down to an individual property within the set ?

Unfortunately I do not know the list at compile time it will be random every time otherwise this would have been a nice solution too. Thanks!

Can you give an example?

cars = [
%Automobile{id: 1, name: :accord, rank: 4, type: :honda, engine: :four},
%Automobile{id: 2, name: :accord, rank: 3, type: :honda, engine: :six},
%Automobile{id: 3, name: :civic, rank: 4, type: :honda, engine: :four},
%Automobile{id: 4, name: :ram, rank: 5 , type: :dodge, engine: :eight},
%Automobile{id: 5, name: :corvette, rank: 6, type: :chevrolet, engine: :eight}
]

Above is the struct list and lets say any struct engine in [:six, :eight] would have the rank updated to a 1.

What you’re doing is still conceptually a mapping operation. Look at each Automobile, if the engine is special then return a modified rank, else pass through unchanged. You have a one-to-one relationship between input list and output list.

1 Like

As @gregvaughn said,

set = MapSet.new(list1)
for car <- list2, do: MapSet.member?(set, car.engine) && %{car | rank: 1} || car

Right, that’s my basic thought, though I’d use an explicit map and if/else

set = MapSet.new(list1)
Enum.map(list2, fn car → if MapSet.member?(set, car.engine), do: %{car | rank: 1}, else: car end)

Thanks to both of you for the example and insight. This is exactly what I need and works great! Im still very new Elixir and need to become more familiar with the Maps and now MapSet :grinning: