Current section
Files
Jump to
Current section
Files
lib/prelude/map.ex
defmodule Prelude.Map do
@moduledoc "Functions operating on `maps`."
@doc ~s"""
Group a map by an array of keys
Provide a list of maps, and a list of keys to group by. All maps must
have all the group_by fields, other fields can vary.
For example:
iex> Prelude.Map.group_by(
...> [%{name: "stian", group: 1, cat: 2},
...> %{name: "per", group: 1, cat: 1}],
...> [:group, :cat])
%{1 =>
%{1 => [%{cat: 1, group: 1, name: "per"}],
2 => [%{cat: 2, group: 1, name: "stian"}] } }
"""
def group_by(maps, groups) when is_list(maps) and is_list(groups) do
Enum.reduce(maps, %{}, fn x, acc ->
extract_and_put(acc, x, groups)
end)
end
defp extract_and_put(map, item, groups) do
path = Enum.map(groups, fn group -> Map.get(item, group) end)
deep_put(map, path, [item])
end
@doc ~s"""
Put an arbitrarily deep key into an existing map
If a value already exists at that level, it is turned into a list
For example:
iex> Prelude.Map.deep_put(%{}, [:a, :b, :c], "0")
%{a: %{b: %{c: "0"}}}
iex> Prelude.Map.deep_put(%{a: %{b: %{c: "1"}}}, [:a, :b, :c, :d], "2")
%{a: %{b: %{c: [{:d, "2"}, "1"]}}}
"""
def deep_put(map, path, val) do
state = {map, []}
Enum.reduce(path, state, fn x, {acc, cursor} ->
cursor = [ x | cursor ]
final = length(cursor) == length(path)
newval = case get_in(acc, Enum.reverse(cursor)) do
h when is_list(h) -> [ val | h ]
nil -> if final, do: val, else: %{}
h = %{} -> if final, do: [val, h], else: h
h -> if final, do: [ val, h ], else: [h]
end
{ put_in(acc, Enum.reverse(cursor), newval), cursor }
end)
|> fn x -> elem(x, 0) end.()
end
@doc ~s"""
Remove a map key arbitrarily deep in a structure, similar to put_in
For example:
iex> a = %{a: %{b: %{c: %{d: 1, e: 1}}}}
...> Prelude.Map.del_in(a, [:a, :b, :c], :d)
%{a: %{b: %{c: %{e: 1}}}}
"""
def del_in(object, path, item) do
obj = get_in(object, path)
put_in(object, path, Map.delete(obj, item))
end
@doc "Turns all string map keys into atoms, leaving existing atoms alone (only top level)"
def atomify(map) do
Enum.map(map, fn {k,v} -> {Prelude.String.to_atom(k), v} end)
|> Enum.into(%{})
end
@doc "Turns all atom map keys into strings, leaving existing strings alone (only top level)"
def stringify(map) do
Enum.map(map, fn {k,v} -> {Prelude.Atom.to_string(k), v} end)
|> Enum.into(%{})
end
@doc "Converts strings to atoms, but leaves existing atoms alone"
def to_atom(x) when is_atom(x), do: x
def to_atom(x) when is_binary(x), do: String.to_atom(x)
@doc "Appends to an array value in a map, creating one if the key does not exist"
def append_list(map, key, val) do
Map.update(map, key, [val], fn x -> List.insert_at(x, 0, val) end)
end
@doc "Switch the keys with the values in a map"
def switch(map) when is_map(map) do
map
|> Enum.map(fn {k, v} -> {v, k} end)
|> Enum.into(%{})
end
end