Skip to content

Instantly share code, notes, and snippets.

@boydm
Last active December 21, 2019 12:14
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save boydm/ab7aec16d54709862ec3491c2b01ddda to your computer and use it in GitHub Desktop.
Save boydm/ab7aec16d54709862ec3491c2b01ddda to your computer and use it in GitHub Desktop.
Elixir meyers_difference for Maps
defmodule Boydm.Utilities.Map do
#============================================================================
# similar to List.meyers_difference, the below code compares two maps
# and generates a list of actions that can be applied to the first map
# to transform it into the second map.
#--------------------------------------------------------
def difference(map_1, map_2) when is_map(map_1) and is_map(map_2) do
# remove any keys from map_1 that are simply not present (at all) in map_2
difference = Enum.reduce(map_1, [], fn({k,_}, d)->
case Map.has_key?(map_2, k) do
false -> [{:del, k} | d]
true -> d
end
end)
# add any puts for keys that have changed between map_2 to map_1
Enum.reduce(map_2, difference, fn({k,v}, d)->
case Map.has_key?(map_1, k) && (Map.get(map_1, k) == v) do
false -> [{:put, k, v} | d]
true -> d
end
end)
end
#--------------------------------------------------------
def apply_difference(map, difference) when is_map(map) and is_list(difference) do
Enum.reduce(difference, map, fn(diff, acc)->
case diff do
{:put, k, v} -> Map.put(acc, k, v)
{:del, k} -> Map.delete(acc, k)
end
end)
end
end
@kylethebaker
Copy link

Here is a golfed version, just for fun.

def difference(%{} = m1, %{} = m2), do:
  for {k, v} <- m2,
    not match?(%{^k => ^v}, m1),
    into: (for {k, _} <- m1, not Map.has_key?(m2, k), do: {:del, k}),
    do: {:put, k, v}

def apply(%{} = m, [_|_] = d), do:
  Enum.reduce d, m, fn
    {:put, k, v}, m -> Map.put m, k, v
    {:del, k}, m -> Map.delete m, k
  end

@kylethebaker
Copy link

kylethebaker commented Sep 26, 2017

Here is another (non-golfed) version that lets you invert the diffs by keeping track of the initial values. This way you can apply the diff either way like such:

diff = difference(m1, m2)
apply(m1, diff) === m2 # true
apply(m2, invert(diff)) === m1 # true
def difference(m1, m2) when is_map(m1) and is_map(m2) do
  difference =
    for {k, v} <- m1,
      not Map.has_key?(m2, k),
      do: {:del, k, v}

  Enum.reduce m2, difference, fn {k, v}, diff ->
    if not Map.has_key?(m1, k) do
      [{:new, k, v} | diff]
    else
      case Map.get(m1, k) do
        ^v -> diff
        old_v -> [{:update, k, old_v, v} | diff]
      end
    end
  end
end

def apply(map, difference) when is_map(map) and is_list(difference) do
  Enum.reduce difference, map, fn
    {:new, k, v}, map -> Map.put(map, k, v)
    {:update, k, _old_v, v}, map -> Map.put(map, k, v)
    {:del, k, _old_v}, map -> Map.delete(map, k)
  end
end

def invert(diff) when is_list(diff) do
  Enum.map diff, fn
    {:new, k, v} -> {:del, k, v}
    {:update, k, from, to} -> {:update, k, to, from}
    {:del, k, v} -> {:new, k, v}
  end
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment