diff --git a/lib/elixir/lib/enum.ex b/lib/elixir/lib/enum.ex index 5b919bf45b5..f449359e72e 100644 --- a/lib/elixir/lib/enum.ex +++ b/lib/elixir/lib/enum.ex @@ -3476,9 +3476,9 @@ defmodule Enum do end @doc """ - Maps and sums the given enumerable in one pass. + Maps and sums the given `enumerable` in one pass. - Raises `ArithmeticError` if `fun` returns a non-numeric value. + Raises `ArithmeticError` if `mapper` returns a non-numeric value. ## Examples @@ -3514,6 +3514,8 @@ defmodule Enum do Raises `ArithmeticError` if `enumerable` contains a non-numeric value. + If you need to apply a transformation first, consider using `Enum.product_by/2` instead. + ## Examples iex> Enum.product([]) @@ -3530,6 +3532,40 @@ defmodule Enum do reduce(enumerable, 1, &*/2) end + @doc """ + Maps and computes the product of the given `enumerable` in one pass. + + Raises `ArithmeticError` if `mapper` returns a non-numeric value. + + ## Examples + + iex> Enum.product_by([%{count: 2}, %{count: 4}, %{count: 3}], fn x -> x.count end) + 24 + + iex> Enum.product_by(1..3, fn x -> x ** 2 end) + 36 + + iex> Enum.product_by([], fn x -> x.count end) + 1 + + Filtering can be achieved by returning `1` to ignore elements: + + iex> Enum.product_by([2, -1, 3], fn x -> if x > 0, do: x, else: 1 end) + 6 + + """ + @doc since: "1.18.0" + @spec product_by(t, (element -> number)) :: number + def product_by(enumerable, mapper) + + def product_by(list, mapper) when is_list(list) and is_function(mapper, 1) do + product_by_list(list, mapper, 1) + end + + def product_by(enumerable, mapper) when is_function(mapper, 1) do + reduce(enumerable, 1, fn x, acc -> acc * mapper.(x) end) + end + @doc """ Takes an `amount` of elements from the beginning or the end of the `enumerable`. @@ -4811,6 +4847,11 @@ defmodule Enum do defp sum_by_list([], _, acc), do: acc defp sum_by_list([h | t], mapper, acc), do: sum_by_list(t, mapper, acc + mapper.(h)) + ## product_by + + defp product_by_list([], _, acc), do: acc + defp product_by_list([h | t], mapper, acc), do: product_by_list(t, mapper, acc * mapper.(h)) + ## take defp take_list(_list, 0), do: [] diff --git a/lib/elixir/pages/cheatsheets/enum-cheat.cheatmd b/lib/elixir/pages/cheatsheets/enum-cheat.cheatmd index d742abeb748..35198aeee8f 100644 --- a/lib/elixir/pages/cheatsheets/enum-cheat.cheatmd +++ b/lib/elixir/pages/cheatsheets/enum-cheat.cheatmd @@ -313,6 +313,15 @@ iex> cart |> Enum.map(& &1.count) |> Enum.product() 18 ``` +Note: this should typically be done in one pass using `Enum.product_by/2`. + +### [`product_by(enum, mapper)`](`Enum.product_by/2`) + +```elixir +iex> Enum.product_by(cart, & &1.count) +18 +``` + ## Sorting {: .col-2} diff --git a/lib/elixir/test/elixir/enum_test.exs b/lib/elixir/test/elixir/enum_test.exs index a2aba41ab92..483849c4f22 100644 --- a/lib/elixir/test/elixir/enum_test.exs +++ b/lib/elixir/test/elixir/enum_test.exs @@ -1360,6 +1360,27 @@ defmodule EnumTest do end end + test "product_by/2" do + assert Enum.product_by([], &hd/1) == 1 + assert Enum.product_by([[1]], &hd/1) == 1 + assert Enum.product_by([[1], [2], [3], [4], [5]], &hd/1) == 120 + assert Enum.product_by([[1], [-2], [3], [4], [5]], &hd/1) == -120 + assert Enum.product_by(1..5, & &1) == 120 + assert Enum.product_by(11..-17//-1, & &1) == 0 + + assert_raise ArithmeticError, fn -> + Enum.product_by([[{}]], &hd/1) + end + + assert_raise ArithmeticError, fn -> + Enum.product_by([[1], [{}]], &hd/1) + end + + assert_raise ArithmeticError, fn -> + Enum.product_by(%{a: 1, b: 2}, & &1) + end + end + test "take/2" do assert Enum.take([1, 2, 3], 0) == [] assert Enum.take([1, 2, 3], 1) == [1]