Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flatten/merge nested map

Tags:

elixir

Say we have map like:

%{"a": %{"b": 2, "c":5}, "d": 1}

Is there anything similar to this function(js answer to same question) built in elixr?

End result should be:

%{"a.b": 4, "a.c":5, "d": 1}
like image 617
Nema Ga Avatar asked Jan 20 '26 10:01

Nema Ga


1 Answers

Since I have done this task many times met already, and I need it on my own, I have created the hex package iteraptor for that:

Add it to your list of dependencies in mix.exs:

def deps do
  [{:iteraptor, "~> 1.13"}]
end

And use it like:

iex(1)> %{a: %{b: 2, c: 5}, d: 1} |> Iteraptor.to_flatmap
%{"a.b": 2, "a.c": 5, d: 1}

It supports infinite nesting and both maps and lists.

The respective part of the code follows:

    defmodule Iteraptor do
      @joiner "."
    
      @doc """
        iex> [:a, 42] |> Iteraptor.to_flatmap
        %{"0": :a, "1": 42}
    
        iex> %{a: 42} |> Iteraptor.to_flatmap
        %{a: 42}
    
        iex> %{a: 42, b: 42} |> Iteraptor.to_flatmap
        %{a: 42, b: 42}
    
        iex> %{a: %{b: 42}, d: 42} |> Iteraptor.to_flatmap
        %{"a.b": 42, d: 42}
    
        iex> %{a: [:b, 42], d: 42} |> Iteraptor.to_flatmap
        %{"a.0": :b, "a.1": 42, d: 42}
    
        iex> %{a: %{b: [:c, 42]}, d: 42} |> Iteraptor.to_flatmap
        %{"a.b.0": :c, "a.b.1": 42, d: 42}
    
        iex> %{a: %{b: 42}} |> Iteraptor.to_flatmap
        %{"a.b": 42}
    
        iex> %{a: %{b: %{c: 42}}} |> Iteraptor.to_flatmap
        %{"a.b.c": 42}
    
        iex> %{a: %{b: %{c: 42}}, d: 42} |> Iteraptor.to_flatmap
        %{"a.b.c": 42, d: 42}
    
        iex> %{a: %{b: %{c: 42, d: [nil, 42]}, e: [:f, 42]}} |> Iteraptor.to_flatmap
        %{"a.b.c": 42, "a.b.d.0": nil, "a.b.d.1": 42, "a.e.0": :f, "a.e.1": 42}
      """
    
      def to_flatmap(input, joiner \\ @joiner) when is_map(input) or is_list(input) do
        process(input, joiner)
      end
    
      @doc """
        iex> %{a: %{b: %{c: 42}}} |> Iteraptor.each(fn {k, v} -> IO.inspect({k, v}) end)
        %{"a.b.c": 42}
      """
      def each(input, joiner \\ @joiner, fun) do
        unless is_function(fun, 1), do: raise "Function or arity fun/1 is required"
        process(input, joiner, "", %{}, fun)
      end
    
      defp process(input, joiner, prefix \\ "", acc \\ %{}, fun \\ nil)
    
      defp process(input, joiner, prefix, acc, fun) when is_map(input) do
        input |> Enum.reduce(acc, fn({k, v}, memo) ->
          prefix = join(prefix, k, joiner)
          if is_map(v) or is_list(v) do
            process(v, joiner, prefix, memo, fun)
          else
            unless is_nil(fun), do: fun.({prefix, v})
            Map.put memo, prefix, v
          end
        end)
      end
    
      defp process(input, joiner, prefix, acc, fun) when is_list(input) do
        input
          |> Enum.with_index
          |> Enum.map(fn({k, v}) -> {v, k} end)
          |> Enum.into(%{})
          |> process(joiner, prefix, acc, fun)
      end
    
      defp join(l, "", _) do
        String.to_atom(to_string(l))
      end
    
      defp join("", r, _) do
        String.to_atom(to_string(r))
      end
    
      defp join(l, r, joiner) do
        String.to_atom(to_string(l) <> joiner <> to_string(r))
      end
    end
like image 62
Aleksei Matiushkin Avatar answered Jan 22 '26 14:01

Aleksei Matiushkin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!