I have a shopping cart that is represented by a list of products that are modeled as such: %{product_id: 123, quantity: 5}
When adding a new item to the shopping cart I want to update an existing product’s quantity if it already exists, otherwise I want to add the new product to the list. Pretty straightforward, but what I’ve come up with doesn’t seem ideal. The code I currently have is:
defp consolidate_line_items(new_item, existing_items) do
item_exists = Enum.any? existing_items, fn item ->
item.product_id == new_item.product_id
end
if item_exists do
Enum.map existing_items, fn existing_item ->
if existing_item.product_id == new_item.product_id do
%{existing_item | quantity: new_item.quantity + existing_item.quantity}
else
existing_item
end
end
else
[new_item | existing_items]
end
end
Currently I am traversing the list twice: once to see if the item exists, and then once to update it. The Enum.any?
predicate is also repeated inside of the map. I feel like there is probably a way to avoid traversing the list twice.
One thing I could do is use map_reduce, using the map to update the item if it exists and using a boolean as the accumulator to keep track of whether or not the item has been found (which I can then use to determine if I need to prepend the new item or not). This gets me down to just a single pass over the cart. It would then look like:
defp consolidate_line_items(new_item, existing_items) do
{items, found} = Enum.map_reduce existing_items, false, fn (existing_item, found) ->
if existing_item.product_id == new_item.product_id do
updated_item = %{existing_item | quantity: new_item.quantity + existing_item.quantity}
{updated_item, true}
else
{existing_item, found}
end
end
if found do
items
else
[new_item | existing_items]
end
end
I still feel like there is probably a simpler solution. Any ideas?
(Also, unrelated, but why doesn’t the second code block get syntax highlighting? )