Hi all! I’m a newbie who just struggled for a day and a half implementing what I thought would be a very simple problem.
The problem: Given an existing list of maps, merge an incoming list of maps… appending to the list when a map with a new composite key (action
and legal_inventories
) arrives and updating a string if the composite key already exists.
Example:
iex(1)> existing_actions = [%{action: "provision", legal_inventories: ["development", "production"],
name: "Deploy SSL Certs & Provision Postfix Relay"},
%{action: "provision-deploy", legal_inventories: ["development", "production"],
name: "Deploy SSL Certs & Provision Postfix Relay"}]
iex(2)> new_or_updated_actions = [%{action: "deploy", legal_inventories: ["development", "production"],
name: "Deploy Postfix Relay Users"},
%{action: "provision-deploy", legal_inventories: ["development", "production"],
name: "Deploy Postfix Relay Users"}]
iex(3)> Testing.apply_new_or_updated_actions(existing_actions, new_or_updated_actions)
[%{action: "provision", legal_inventories: ["development", "production"],
name: "Deploy SSL Certs & Provision Postfix Relay"},
%{action: "provision-deploy", legal_inventories: ["development", "production"],
name: "Deploy SSL Certs & Provision Postfix Relay & Deploy Postfix Relay Users"},
%{action: "deploy", legal_inventories: ["development", "production"],
name: "Deploy Postfix Relay Users"}]
This code works:
defmodule Testing do
def apply_new_or_updated_actions(valid_actions, new_or_updated_actions) do
# first update valid_actions, then insert any new actions to the list.
valid_actions = update_valid_actions(valid_actions, [], new_or_updated_actions)
Enum.reduce(new_or_updated_actions, valid_actions, &insert_if_new/2)
end
defp update_valid_actions([], new_valid_actions, _new_or_updated_actions) do
new_valid_actions
end
defp update_valid_actions([head_valid_action|tail_valid_actions], new_valid_actions, new_or_updated_actions) do
update_valid_actions(tail_valid_actions, new_valid_actions ++ apply_updates_to_valid_action(new_or_updated_actions, head_valid_action), new_or_updated_actions)
end
defp apply_updates_to_valid_action([], valid_action) do
[valid_action]
end
defp apply_updates_to_valid_action([head_new_or_updated_action|tail_new_or_updated_action], valid_action) do
possibly_mutated_action =
if action_match?(head_new_or_updated_action, valid_action) do
Map.update(valid_action, :name, head_new_or_updated_action.name, &(&1 <> " & " <> head_new_or_updated_action.name))
else
valid_action
end
apply_updates_to_valid_action(tail_new_or_updated_action, possibly_mutated_action)
end
defp insert_if_new(new_or_updated_action, valid_actions) do
if action_match?(new_or_updated_action, valid_actions), do: valid_actions, else: valid_actions ++ [new_or_updated_action]
end
defp action_match?(action, list_of_actions) when is_list(list_of_actions) do
Enum.any?(list_of_actions, &((action.action == &1.action) and (action.legal_inventories == &1.legal_inventories)))
end
defp action_match?(action1, action2) when is_map(action2) do
(action1.action == action2.action) and (action1.legal_inventories == action2.legal_inventories)
end
end
I’ve tried many simpler solutions using functions from Enum
and for
list comprehensions before settling on a nested recursive pattern. This works but it seems overly complex and I feel there is a better and more idiomatic pattern. Also, I’m aware my variable and function names are too long but I needed them to be descriptive to be able to reason about the code.
I will be doing many similar data transformations like these in the future and would really appreciate if anyone can help point out better ways to do this.
Thanks!!!