Skip to content

Commit ef81ded

Browse files
authored
Add Enum.product_by/2 (#13683)
1 parent 0df128c commit ef81ded

File tree

3 files changed

+73
-2
lines changed

3 files changed

+73
-2
lines changed

lib/elixir/lib/enum.ex

+43-2
Original file line numberDiff line numberDiff line change
@@ -3476,9 +3476,9 @@ defmodule Enum do
34763476
end
34773477

34783478
@doc """
3479-
Maps and sums the given enumerable in one pass.
3479+
Maps and sums the given `enumerable` in one pass.
34803480
3481-
Raises `ArithmeticError` if `fun` returns a non-numeric value.
3481+
Raises `ArithmeticError` if `mapper` returns a non-numeric value.
34823482
34833483
## Examples
34843484
@@ -3514,6 +3514,8 @@ defmodule Enum do
35143514
35153515
Raises `ArithmeticError` if `enumerable` contains a non-numeric value.
35163516
3517+
If you need to apply a transformation first, consider using `Enum.product_by/2` instead.
3518+
35173519
## Examples
35183520
35193521
iex> Enum.product([])
@@ -3530,6 +3532,40 @@ defmodule Enum do
35303532
reduce(enumerable, 1, &*/2)
35313533
end
35323534

3535+
@doc """
3536+
Maps and computes the product of the given `enumerable` in one pass.
3537+
3538+
Raises `ArithmeticError` if `mapper` returns a non-numeric value.
3539+
3540+
## Examples
3541+
3542+
iex> Enum.product_by([%{count: 2}, %{count: 4}, %{count: 3}], fn x -> x.count end)
3543+
24
3544+
3545+
iex> Enum.product_by(1..3, fn x -> x ** 2 end)
3546+
36
3547+
3548+
iex> Enum.product_by([], fn x -> x.count end)
3549+
1
3550+
3551+
Filtering can be achieved by returning `1` to ignore elements:
3552+
3553+
iex> Enum.product_by([2, -1, 3], fn x -> if x > 0, do: x, else: 1 end)
3554+
6
3555+
3556+
"""
3557+
@doc since: "1.18.0"
3558+
@spec product_by(t, (element -> number)) :: number
3559+
def product_by(enumerable, mapper)
3560+
3561+
def product_by(list, mapper) when is_list(list) and is_function(mapper, 1) do
3562+
product_by_list(list, mapper, 1)
3563+
end
3564+
3565+
def product_by(enumerable, mapper) when is_function(mapper, 1) do
3566+
reduce(enumerable, 1, fn x, acc -> acc * mapper.(x) end)
3567+
end
3568+
35333569
@doc """
35343570
Takes an `amount` of elements from the beginning or the end of the `enumerable`.
35353571
@@ -4811,6 +4847,11 @@ defmodule Enum do
48114847
defp sum_by_list([], _, acc), do: acc
48124848
defp sum_by_list([h | t], mapper, acc), do: sum_by_list(t, mapper, acc + mapper.(h))
48134849

4850+
## product_by
4851+
4852+
defp product_by_list([], _, acc), do: acc
4853+
defp product_by_list([h | t], mapper, acc), do: product_by_list(t, mapper, acc * mapper.(h))
4854+
48144855
## take
48154856

48164857
defp take_list(_list, 0), do: []

lib/elixir/pages/cheatsheets/enum-cheat.cheatmd

+9
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,15 @@ iex> cart |> Enum.map(& &1.count) |> Enum.product()
313313
18
314314
```
315315

316+
Note: this should typically be done in one pass using `Enum.product_by/2`.
317+
318+
### [`product_by(enum, mapper)`](`Enum.product_by/2`)
319+
320+
```elixir
321+
iex> Enum.product_by(cart, & &1.count)
322+
18
323+
```
324+
316325
## Sorting
317326
{: .col-2}
318327

lib/elixir/test/elixir/enum_test.exs

+21
Original file line numberDiff line numberDiff line change
@@ -1360,6 +1360,27 @@ defmodule EnumTest do
13601360
end
13611361
end
13621362

1363+
test "product_by/2" do
1364+
assert Enum.product_by([], &hd/1) == 1
1365+
assert Enum.product_by([[1]], &hd/1) == 1
1366+
assert Enum.product_by([[1], [2], [3], [4], [5]], &hd/1) == 120
1367+
assert Enum.product_by([[1], [-2], [3], [4], [5]], &hd/1) == -120
1368+
assert Enum.product_by(1..5, & &1) == 120
1369+
assert Enum.product_by(11..-17//-1, & &1) == 0
1370+
1371+
assert_raise ArithmeticError, fn ->
1372+
Enum.product_by([[{}]], &hd/1)
1373+
end
1374+
1375+
assert_raise ArithmeticError, fn ->
1376+
Enum.product_by([[1], [{}]], &hd/1)
1377+
end
1378+
1379+
assert_raise ArithmeticError, fn ->
1380+
Enum.product_by(%{a: 1, b: 2}, & &1)
1381+
end
1382+
end
1383+
13631384
test "take/2" do
13641385
assert Enum.take([1, 2, 3], 0) == []
13651386
assert Enum.take([1, 2, 3], 1) == [1]

0 commit comments

Comments
 (0)