假设我们有地图:
%{"a": %{"b": 2, "c":5}, "d": 1}
elixr中是否有与this function(js回答相同问题)类似的内容?
最终结果应为:
%{"a.b": 4, "a.c":5, "d": 1}
答案 0 :(得分:2)
我不知道内置函数,但当然有办法执行转换:
defmodule MyMaps do
def flatten(map) when is_map(map) do
map
|> to_list_of_tuples
|> Enum.into(%{})
end
defp to_list_of_tuples(m) do
m
|> Enum.map(&process/1)
|> List.flatten
end
defp process({key, sub_map}) when is_map(sub_map) do
for { sub_key, value } <- sub_map do
{ join(key, sub_key), value }
end
end
defp process({key, value}) do
{ key, value }
end
defp join(a, b) do
to_string(a) <> "." <> to_string(b)
end
end
m = %{ "a" => %{ "b" => 2, "c" => 5 }, "d" => 1 }
MyMaps.flatten m
# %{"a.b" => 2, "a.c" => 5, "d" => 1}
m1 = %{ a: %{ b: 2, c: 5}, d: 1}
MyMaps.flatten m1
# %{:d => 1, "a.b" => 2, "a.c" => 5}
答案 1 :(得分:2)
由于我已经多次遇到过这个任务,而且我自己需要它,所以我为此创建了十六进制包iteraptor
:
将它添加到mix.exs中的依赖项列表中:
def deps do
[{:iteraptor, "~> 0.1.0"}]
end
并使用它:
iex(1)> %{a: %{b: 2, c: 5}, d: 1} |> Iteraptor.to_flatmap
%{"a.b": 2, "a.c": 5, d: 1}
它支持无限嵌套以及map
和list
s。
代码的相应部分如下:
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
答案 2 :(得分:0)
从版本1.3开始,没有像你在Elixir中描述的那样展平/不展平。
但是,这是您在问题中提到的javascript implementation的翻译。
flatten = fn
(data) when is_list(data) or is_map(data) ->
recurse = fn
(recurse, cur, prop, result) when is_list(cur) and length(cur) == 0 ->
Map.put(result, prop, [])
(recurse, cur, prop, result) when is_list(cur) ->
Enum.reduce(Enum.with_index(cur), result, fn ({value, index}, acc) ->
recurse.(recurse, value, << prop :: binary, ?[, to_string(index) :: binary, ?] >>, acc)
end)
(recurse, cur, prop, result) when is_map(cur) and map_size(cur) == 0 ->
Map.put(result, prop, %{})
(recurse, cur, prop, result) when is_map(cur) ->
Enum.reduce(cur, result, fn ({key, value}, acc) ->
recurse.(recurse, value, if byte_size(prop) == 0 do
key
else
<< prop :: binary, ?., to_string(key) :: binary >>
end, acc)
end)
(recurse, cur, prop, result) ->
Map.put(result, prop, cur)
end
result = recurse.(recurse, data, <<>>, %{})
result
(data) ->
data
end
unflatten = fn
(data) when is_map(data) ->
regex = ~r/\.?([^.\[\]]+)|\[(\d+)\]/
array_get = fn
(array, index, default) when length(array) <= index -> default
(array, index, _default) -> :lists.nth(index + 1, array)
end
array_new = fn (size, array) when is_integer(size) and size >= 0 ->
fill = fn
(_, 0, array) -> array
(fill, n, array) -> fill.(fill, n - 1, [nil | array])
end
fill.(fill, size, array)
end
array_put = fn (array, index, value) when is_integer(index) and index >= 0 ->
case length(array) do
0 when index == 0 -> [value]
0 -> array_new.(index, [value])
^index -> array ++ [value]
length when length > index -> List.replace_at(array, index, value)
length -> array ++ array_new.(index - length, [value])
end
end
Enum.reduce(data, nil, fn ({prop, value}, cur) ->
recurse = fn
(recurse, [[_, key] | rest], cur) ->
cur = cur || %{}
Map.put(cur, key, recurse.(recurse, rest, Map.get(cur, key, nil)))
(recurse, [[_, _, index] | rest], cur) ->
index = String.to_integer(index)
cur = cur || []
array_put.(cur, index, recurse.(recurse, rest, array_get.(cur, index, nil)))
(_, [], _) ->
value
end
recurse.(recurse, Regex.scan(regex, prop), cur)
end)
(data) ->
data
end
这些flatten / unflatten函数处理列表和深层嵌套结构。例如:
# TEST 1
input = %{"a" => %{"b" => 2, "c" => 5}, "d" => 1}
flat = flatten.(input) # %{"a.b" => 2, "a.c" => 5, "d" => 1}
unflat = unflatten.(flat)
unflat == input # true
# TEST 2
input = %{"a" => %{"b" => [5, 1, %{"c" => [3, %{"d" => 4}]}, 2]}, "e" => []}
flat = flatten.(input) # %{"a.b[0]" => 5, "a.b[1]" => 1, "a.b[2].c[0]" => 3, "a.b[2].c[1].d" => 4, "a.b[3]" => 2, "e" => []}
unflat = unflatten.(flat)
unflat == input # true
# TEST 3
input = []
flat = flatten.(input) # %{"" => []}
unflat = unflatten.(flat)
unflat == input # true
# TEST 4
input = %{}
flat = flatten.(input) # %{"" => %{}}
unflat = unflatten.(flat)
unflat == input # true
# TEST 5
input = [1, 2, %{"a" => 3}]
flat = flatten.(input) # %{"[0]" => 1, "[1]" => 2, "[2].a" => 3}
unflat = unflatten.(flat)
unflat == input # true
此处还提供了一个工作示例:http://elixirplayground.com?gist=5d1b166557cbeb65c019f3caa356a5e8
答案 3 :(得分:0)
你可以调整@ tompave的样本,让它适用于嵌套地图:
defmodule MyMaps do
def flatten(map) when is_map(map) do
map
|> to_list_of_tuples
|> Enum.into(%{})
end
defp to_list_of_tuples(m) do
m
|> Enum.map(&process/1)
|> List.flatten
end
defp process({key, sub_map}) when is_map(sub_map) do
for { sub_key, value } <- flatten(sub_map) do
{ "#{key}.#{sub_key}", value }
end
end
defp process(next), do: next
end
m = %{"a" => %{"b" => %{"c" => 1}, "d" => 2}}
MyMaps.flatten(m) # => %{"a.b.c" => 1, "a.d" => 2}
答案 4 :(得分:0)
我选择了@ vitalii-elenhaupt提出的解决方案,该方案适用于几个角落案例。
特别是,我使用的是一个DateTime
结构作为值的地图,会抱怨以下错误:
** (Protocol.UndefinedError) protocol Enumerable not implemented for #DateTime<2018-05-29 10:43:07.993000Z>. This protocol is implemented for: Amnesia.Table.Stream, Date.Range, File.Stream, Function, GenEvent.Stream, HashDict, HashSet, IO.Stream, List, Map, MapSet, Range, Stream, Timex.Interval
为了解决这个问题,我必须为不支持的值添加特定的模式匹配:
defmodule MyMaps do
def flatten(map) when is_map(map) do
map
|> to_list_of_tuples
|> Enum.into(%{})
end
defp to_list_of_tuples(m) do
m
|> Enum.map(&process/1)
|> List.flatten()
end
defp process({key, %DateTime{} = datetime}), do: {"#{key}", datetime}
defp process({key, %Date{} = date}), do: {"#{key}", date}
defp process({key, sub_map}) when is_map(sub_map) do
for {sub_key, value} <- flatten(sub_map) do
{"#{key}.#{sub_key}", value}
end
end
defp process(next), do: next
end