Skip to content

Commit b9474da

Browse files
committed
Bring zip optimizations for lists, add docs and specs
1 parent 7b4a57c commit b9474da

File tree

2 files changed

+62
-44
lines changed

2 files changed

+62
-44
lines changed

lib/elixir/lib/enum.ex

Lines changed: 61 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3420,10 +3420,7 @@ defmodule Enum do
34203420
"""
34213421
@spec zip(t, t) :: [{any, any}]
34223422
def zip(enumerable1, enumerable2) when is_list(enumerable1) and is_list(enumerable2) do
3423-
reducer = fn l, r, acc -> {:cont, [{l, r} | acc]} end
3424-
3425-
zip_reduce_while(enumerable1, enumerable2, [], reducer)
3426-
|> :lists.reverse()
3423+
zip_list(enumerable1, enumerable2)
34273424
end
34283425

34293426
def zip(enumerable1, enumerable2) do
@@ -3492,7 +3489,12 @@ defmodule Enum do
34923489
34933490
"""
34943491
@doc since: "1.12.0"
3495-
@spec zip_with(t, t, (enumerable1_elem :: term, enumerable2_elem :: term -> term)) :: [term]
3492+
@spec zip_with(t, t, (enum1_elem :: term, enum2_elem :: term -> term)) :: [term]
3493+
def zip_with(enumerable1, enumerable2, zip_fun)
3494+
when is_list(enumerable1) and is_list(enumerable2) and is_function(zip_fun, 2) do
3495+
zip_list(enumerable1, enumerable2, zip_fun)
3496+
end
3497+
34963498
def zip_with(enumerable1, enumerable2, zip_fun) when is_function(zip_fun, 2) do
34973499
reducer = fn l, r, acc -> {:cont, [zip_fun.(l, r) | acc]} end
34983500

@@ -3572,7 +3574,15 @@ defmodule Enum do
35723574
iex> Enum.zip_reduce([1, 2], [3, 4], [], fn x, y, acc -> [x + y |acc] end)
35733575
[6, 4]
35743576
"""
3575-
def zip_reduce(left, right, acc, reducer) do
3577+
@doc since: "1.12.0"
3578+
@spec zip_reduce(t, t, acc, (enum1_elem :: term, enum2_elem :: term, acc -> acc)) :: acc
3579+
when acc: term
3580+
def zip_reduce(left, right, acc, reducer)
3581+
when is_list(left) and is_list(right) and is_function(reducer, 3) do
3582+
zip_reduce_list(left, right, acc, reducer)
3583+
end
3584+
3585+
def zip_reduce(left, right, acc, reducer) when is_function(reducer, 3) do
35763586
non_stop_reducer = &{:cont, reducer.(&1, &2, &3)}
35773587
zip_reduce_while(left, right, acc, non_stop_reducer)
35783588
end
@@ -3612,7 +3622,11 @@ defmodule Enum do
36123622
...> end)
36133623
[{2, {:b, 4}, 6}, {1, {:a, 3}, 5}]
36143624
"""
3615-
def zip_reduce(enums, acc, reducer) do
3625+
@doc since: "1.12.0"
3626+
@spec zip_reduce(t, acc, ([term], acc -> acc)) :: acc when acc: term
3627+
def zip_reduce([], acc, reducer) when is_function(reducer, 2), do: acc
3628+
3629+
def zip_reduce(enums, acc, reducer) when is_function(reducer, 2) do
36163630
non_stop_reducer = &{:cont, reducer.(&1, &2)}
36173631
zip_reduce_while(enums, acc, non_stop_reducer)
36183632
end
@@ -3653,18 +3667,17 @@ defmodule Enum do
36533667
...> end)
36543668
[3]
36553669
3656-
iex> left = [1, 2]
3657-
...> right = [3, 4]
3658-
...> Enum.zip_reduce_while(left, right, [], fn l, r, acc ->
3659-
...> {:suspend, [l + r | acc]}
3660-
...> end)
3661-
[4]
36623670
"""
3663-
def zip_reduce_while(left, right, acc, reducer) when is_list(left) and is_list(right) do
3664-
zip_reduce_while_list(left, right, {:cont, acc}, reducer) |> elem(1)
3671+
@doc since: "1.12.0"
3672+
@spec zip_reduce_while(t, t, acc, (enum1_elem :: term, enum2_elem :: term, acc -> acc)) ::
3673+
{:cont | :halt, acc}
3674+
when acc: term
3675+
def zip_reduce_while(left, right, acc, reducer)
3676+
when is_list(left) and is_list(right) and is_function(reducer, 3) do
3677+
zip_reduce_while_list(left, right, {:cont, acc}, reducer)
36653678
end
36663679

3667-
def zip_reduce_while(left, right, acc, reducer) do
3680+
def zip_reduce_while(left, right, acc, reducer) when is_function(reducer, 3) do
36683681
reduce = fn [l, r], acc -> reducer.(l, r, acc) end
36693682
Stream.zip_with([left, right], & &1).({:cont, acc}, reduce) |> elem(1)
36703683
end
@@ -3711,26 +3724,14 @@ defmodule Enum do
37113724
...> Enum.zip_reduce_while(enums, [], reducer)
37123725
[4]
37133726
"""
3714-
def zip_reduce_while([], acc, _reducer), do: acc
3727+
@doc since: "1.12.0"
3728+
@spec zip_reduce_while([t], acc, ([term], acc -> acc)) :: {:cont | :halt, acc} when acc: term
3729+
def zip_reduce_while([], acc, reducer) when is_function(reducer, 2), do: acc
37153730

3716-
def zip_reduce_while(enums, acc, reducer) do
3731+
def zip_reduce_while(enums, acc, reducer) when is_function(reducer, 2) do
37173732
Stream.zip_with(enums, & &1).({:cont, acc}, reducer) |> elem(1)
37183733
end
37193734

3720-
# This speeds things up when zip reducing two lists.
3721-
defp zip_reduce_while_list(_left, _right, {:halt, acc}, _), do: {:halted, acc}
3722-
3723-
defp zip_reduce_while_list(left, right, {:suspend, acc}, reducer) do
3724-
{:suspended, acc, &zip_reduce_while_list(left, right, &1, reducer)}
3725-
end
3726-
3727-
defp zip_reduce_while_list([], _right, {:cont, acc}, _), do: {:done, acc}
3728-
defp zip_reduce_while_list(_left, [], {:cont, acc}, _), do: {:done, acc}
3729-
3730-
defp zip_reduce_while_list([l_head | l_tail], [r_head | r_tail], {:cont, acc}, reducer) do
3731-
zip_reduce_while_list(l_tail, r_tail, reducer.(l_head, r_head, acc), reducer)
3732-
end
3733-
37343735
## Helpers
37353736

37363737
@compile {:inline, entry_to_string: 1, reduce: 3, reduce_by: 3, reduce_enumerable: 3}
@@ -4332,6 +4333,34 @@ defmodule Enum do
43324333
defp uniq_list([], _set, _fun) do
43334334
[]
43344335
end
4336+
4337+
## zip
4338+
4339+
defp zip_list(enumerable1, enumerable2) do
4340+
zip_list(enumerable1, enumerable2, fn x, y -> {x, y} end)
4341+
end
4342+
4343+
defp zip_list([head1 | next1], [head2 | next2], fun) do
4344+
[fun.(head1, head2) | zip_list(next1, next2, fun)]
4345+
end
4346+
4347+
defp zip_list(_, [], _fun), do: []
4348+
defp zip_list([], _, _fun), do: []
4349+
4350+
defp zip_reduce_list([head1 | next1], [head2 | next2], acc, fun) do
4351+
zip_reduce_list(next1, next2, fun.(head1, head2, acc), fun)
4352+
end
4353+
4354+
defp zip_reduce_list(_, [], acc, _fun), do: acc
4355+
defp zip_reduce_list([], _, acc, _fun), do: acc
4356+
4357+
defp zip_reduce_while_list(_left, _right, {:halt, acc}, _), do: acc
4358+
defp zip_reduce_while_list([], _right, {:cont, acc}, _), do: acc
4359+
defp zip_reduce_while_list(_left, [], {:cont, acc}, _), do: acc
4360+
4361+
defp zip_reduce_while_list([l_head | l_tail], [r_head | r_tail], {:cont, acc}, reducer) do
4362+
zip_reduce_while_list(l_tail, r_tail, reducer.(l_head, r_head, acc), reducer)
4363+
end
43354364
end
43364365

43374366
defimpl Enumerable, for: List do

lib/elixir/test/elixir/enum_test.exs

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ defmodule EnumTest do
4646

4747
describe "zip_reduce/3" do
4848
test "when enums empty" do
49-
assert Enum.zip_reduce([], 0, fn x, y, acc -> x + y + acc end) == 0
49+
assert Enum.zip_reduce([], 0, fn _, acc -> acc end) == 0
5050
end
5151

5252
test "lists work" do
@@ -74,17 +74,6 @@ defmodule EnumTest do
7474
reducer = fn x, y, acc -> {:cont, [x + y | acc]} end
7575
assert Enum.zip_reduce_while(left, right, [], reducer) == [6, 4]
7676

77-
# Suspending the reduction
78-
left = [1, 2]
79-
right = [3, 4]
80-
81-
result =
82-
Enum.zip_reduce_while(left, right, [], fn l, r, acc ->
83-
{:suspend, [l + r | acc]}
84-
end)
85-
86-
assert result == [4]
87-
8877
# Halting the reduction
8978
left = [1, 2]
9079
right = [3, 4]

0 commit comments

Comments
 (0)