diff --git a/lib/elixir/lib/enum.ex b/lib/elixir/lib/enum.ex index 661244bbd84..4ce95fe3bc2 100644 --- a/lib/elixir/lib/enum.ex +++ b/lib/elixir/lib/enum.ex @@ -1362,6 +1362,43 @@ defmodule Enum do end end + @doc """ + Enumerates the `enumerable`, merging all duplicated elements + into one using the function `merge_fun`. + ## Examples + iex> Enum.merge([1, 2, 3, 3, 2, 1], fn (x, y) -> x + y end) + [2, 4, 6] + + # This is simmilar to `Enum.uniq/1` + iex> Enum.merge([1, 2, 3, 3, 2, 1], fn (x, _y) -> x end) + [1, 2, 3] + """ + @spec merge(t, (element, element -> term)) :: list + def merge(enumerable, merge_fun) do + do_merge(enumerable, %{}, merge_fun, fn x -> x end, []) + end + + @doc """ + Enumerates the `enumerable`, by merging the elements for which + function `fun` returned duplicate items. + The function `fun` maps every element to a term which is used to + determine if two elements are duplicates. + Merging applies by using the function `merge_fun`. + ## Example + iex> Enum.merge_by([{1, :x}, {2, :y}, {1, :z}], fn (x, _) -> x end, fn {x, _} -> x end) + [{1, :x}, {2, :y}] + iex> Enum.merge_by([a: {:tea, 2}, b: {:tea, 2}, c: {:coffee, 1}], fn (x, _) -> x end, fn {_, y} -> y end) + [a: {:tea, 2}, c: {:coffee, 1}] + iex> Enum.merge_by([%{k: "a", v: 1}, %{k: "b", v: 2}, %{k: "a", v: 3}, %{k: "b", v: 4}], \ + fn(t1, t2) -> %{t1 | v: t1.v + t2.v} end, fn s -> s.k end) + [%{k: "a", v: 4}, %{k: "b", v: 6}] + + """ + @spec merge_by(t, (element, element -> term), (element -> term)) :: list + def merge_by(enumerable, merge_fun, fun) do + do_merge(enumerable, %{}, merge_fun, fun, []) + end + @doc """ Returns the minimal element in the enumerable according to Erlang's term ordering. @@ -2593,6 +2630,21 @@ defmodule Enum do default end + ## merge + + defp do_merge([h | t], set, merge_fun, fun, acc) do + value = fun.(h) + case set do + %{^value => index} -> + acc = List.update_at(acc, index, &(merge_fun.(&1, h))) + do_merge(t, set, merge_fun, fun, acc) + %{} -> do_merge(t, Map.put(set, value, Enum.count(acc)), merge_fun, fun, acc ++ [h]) + end + end + defp do_merge([], _set, _merge_fun, _fun, acc) do + acc + end + ## shuffle defp unwrap([{_, h} | enumerable], t) do diff --git a/lib/elixir/test/elixir/enum_test.exs b/lib/elixir/test/elixir/enum_test.exs index 67b0de28b0d..ccb86d67719 100644 --- a/lib/elixir/test/elixir/enum_test.exs +++ b/lib/elixir/test/elixir/enum_test.exs @@ -296,6 +296,20 @@ defmodule EnumTest do refute Enum.member?([1, 2, 3], 0) end + test "merge/2" do + assert Enum.merge([], fn x, _y -> x end) == [] + assert Enum.merge([1], fn x, _y -> x end) == [1] + assert Enum.merge([1, -1], fn x, _y -> x end) == [1, -1] + assert Enum.merge([1, 1], fn x, _y -> x end) == [1] + assert Enum.merge([1, 2, 3, 3, 2, 1], fn x, y -> x + y end) == [2, 4, 6] + end + + test "merge/3" do + list = [%{k: "a", v: 1}, %{k: "b", v: 2}, %{k: "a", v: 3}, %{k: "b", v: 4}] + assert Enum.merge_by(list, fn(t1, _t2) -> t1 end, fn s -> s.k end) == [%{k: "a", v: 1}, %{k: "b", v: 2}] + assert Enum.merge_by(list, fn(t1, t2) -> %{t1 | v: t1.v + t2.v} end, fn s -> s.k end) == [%{k: "a", v: 4}, %{k: "b", v: 6}] + end + test "min/1" do assert Enum.min([1]) == 1 assert Enum.min([1, 2, 3]) == 1