Skip to content

Commit

Permalink
Add Enum.product_by/2 (elixir-lang#13683)
Browse files Browse the repository at this point in the history
  • Loading branch information
sabiwara authored Jun 21, 2024
1 parent 0df128c commit ef81ded
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 2 deletions.
45 changes: 43 additions & 2 deletions lib/elixir/lib/enum.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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([])
Expand All @@ -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`.
Expand Down Expand Up @@ -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: []
Expand Down
9 changes: 9 additions & 0 deletions lib/elixir/pages/cheatsheets/enum-cheat.cheatmd
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand Down
21 changes: 21 additions & 0 deletions lib/elixir/test/elixir/enum_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down

0 comments on commit ef81ded

Please sign in to comment.