@@ -3419,9 +3419,11 @@ defmodule Enum do
3419
3419
3420
3420
"""
3421
3421
@ spec zip ( t , t ) :: [ { any , any } ]
3422
- def zip ( enumerable1 , enumerable2 )
3423
- when is_list ( enumerable1 ) and is_list ( enumerable2 ) do
3424
- zip_list ( enumerable1 , enumerable2 )
3422
+ 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 ( )
3425
3427
end
3426
3428
3427
3429
def zip ( enumerable1 , enumerable2 ) do
@@ -3448,20 +3450,35 @@ defmodule Enum do
3448
3450
def zip ( [ ] ) , do: [ ]
3449
3451
3450
3452
def zip ( enumerables ) do
3451
- Stream . zip ( enumerables ) . ( { :cont , [ ] } , & { :cont , [ & 1 | & 2 ] } )
3452
- |> elem ( 1 )
3453
+ zip_reduce_while ( enumerables , [ ] , & { :cont , [ List . to_tuple ( & 1 ) | & 2 ] } )
3453
3454
|> :lists . reverse ( )
3454
3455
end
3455
3456
3456
3457
@ doc """
3457
3458
Zips corresponding elements from two enumerables into a list, transforming them with
3458
3459
the `zip_fun` function as it goes.
3459
3460
3460
- The corresponding elements from each collection are passed to the provided 2-arity `zip_fun` function in turn.
3461
- Returns a list that contains the result of calling `zip_fun` for each pair of elements.
3461
+ The corresponding elements from each collection are passed to the provided 2-arity `zip_fun`
3462
+ function in turn. Returns a list that contains the result of calling `zip_fun` for each pair of
3463
+ elements.
3462
3464
3463
3465
The zipping finishes as soon as either enumerable runs out of elements.
3464
3466
3467
+ ## Zipping Maps
3468
+
3469
+ It's important to remember that zipping inherently relies on order. If you zip two lists you get
3470
+ the element at the index from each list in turn. If we zip two maps together it's tempting to
3471
+ think that you will get the given key in the left map and the matching key in the right map, but
3472
+ there is no such guarantee because map keys are not ordered! Consider the following:
3473
+
3474
+ left = %{:a => 1, 1 => 3}
3475
+ right = %{:a => 1, :b => :c}
3476
+ Enum.zip(left, right)
3477
+ # [{{1, 3}, {:a, 1}}, {{:a, 1}, {:b, :c}}]
3478
+
3479
+ As you can see `:a` does not get paired with `:a`. If this is what you want, you should use
3480
+ `Map.merge/3`
3481
+
3465
3482
## Examples
3466
3483
3467
3484
iex> Enum.zip_with([1, 2], [3, 4], fn x, y -> x + y end)
@@ -3476,28 +3493,39 @@ defmodule Enum do
3476
3493
"""
3477
3494
@ doc since: "1.12.0"
3478
3495
@ spec zip_with ( t , t , ( enumerable1_elem :: term , enumerable2_elem :: term -> term ) ) :: [ term ]
3479
- def zip_with ( enumerable1 , enumerable2 , zip_fun )
3480
- when is_list ( enumerable1 ) and is_list ( enumerable2 ) and is_function ( zip_fun , 2 ) do
3481
- zip_list ( enumerable1 , enumerable2 , zip_fun )
3482
- end
3483
-
3484
3496
def zip_with ( enumerable1 , enumerable2 , zip_fun ) when is_function ( zip_fun , 2 ) do
3485
- # zip_with/2 passes a list to the zip_fun containing the nth element from each enumerable
3486
- # That's different from zip_with/3 where each element is a different argument to the zip_fun
3487
- # apply/2 ensures that zip_fun gets the right number of arguments.
3488
- zip_with ( [ enumerable1 , enumerable2 ] , & apply ( zip_fun , & 1 ) )
3497
+ reducer = fn l , r , acc -> { :cont , [ zip_fun . ( l , r ) | acc ] } end
3498
+
3499
+ zip_reduce_while ( enumerable1 , enumerable2 , [ ] , reducer )
3500
+ |> :lists . reverse ( )
3489
3501
end
3490
3502
3491
3503
@ doc """
3492
3504
Zips corresponding elements from a finite collection of enumerables into list, transforming them with
3493
3505
the `zip_fun` function as it goes.
3494
3506
3495
- The first element from each of the enums in `enumerables` will be put into a list which is then passed to
3496
- the 1-arity `zip_fun` function. Then, the second elements from each of the enums are put into a list and passed to
3497
- `zip_fun`, and so on until any one of the enums in `enumerables` runs out of elements.
3507
+ The first element from each of the enums in `enumerables` will be put into a list which is then
3508
+ passed to the 1-arity `zip_fun` function. Then, the second elements from each of the enums are
3509
+ put into a list and passed to `zip_fun`, and so on until any one of the enums in `enumerables`
3510
+ runs out of elements.
3498
3511
3499
3512
Returns a list with all the results of calling `zip_fun`.
3500
3513
3514
+ ## Zipping Maps
3515
+
3516
+ It's important to remember that zipping inherently relies on order. If you zip two lists you get
3517
+ the element at the index from each list in turn. If we zip two maps together it's tempting to
3518
+ think that you will get the given key in the left map and the matching key in the right map, but
3519
+ there is no such guarantee because map keys are not ordered! Consider the following:
3520
+
3521
+ left = %{:a => 1, 1 => 3}
3522
+ right = %{:a => 1, :b => :c}
3523
+ Enum.zip(left, right)
3524
+ # [{{1, 3}, {:a, 1}}, {{:a, 1}, {:b, :c}}]
3525
+
3526
+ As you can see `:a` does not get paired with `:a`. If this is what you want, you should use
3527
+ `Map.merge/3`
3528
+
3501
3529
## Examples
3502
3530
3503
3531
iex> Enum.zip_with([[1, 2], [3, 4], [5, 6]], fn [x, y, z] -> x + y + z end)
@@ -3512,11 +3540,197 @@ defmodule Enum do
3512
3540
def zip_with ( [ ] , _fun ) , do: [ ]
3513
3541
3514
3542
def zip_with ( enumerables , zip_fun ) do
3515
- Stream . zip_with ( enumerables , zip_fun ) . ( { :cont , [ ] } , & { :cont , [ & 1 | & 2 ] } )
3516
- |> elem ( 1 )
3543
+ reducer = fn values , acc -> { :cont , [ zip_fun . ( values ) | acc ] } end
3544
+
3545
+ zip_reduce_while ( enumerables , [ ] , reducer )
3517
3546
|> :lists . reverse ( )
3518
3547
end
3519
3548
3549
+ @ doc """
3550
+ Reduces a over two enumerables halting as soon as either enumerable is empty.
3551
+
3552
+ ## Zipping Maps
3553
+
3554
+ It's important to remember that zipping inherently relies on order. If you zip two lists you get
3555
+ the element at the index from each list in turn. If we zip two maps together it's tempting to
3556
+ think that you will get the given key in the left map and the matching key in the right map, but
3557
+ there is no such guarantee because map keys are not ordered! Consider the following:
3558
+
3559
+ left = %{:a => 1, 1 => 3}
3560
+ right = %{:a => 1, :b => :c}
3561
+ Enum.zip(left, right)
3562
+ # [{{1, 3}, {:a, 1}}, {{:a, 1}, {:b, :c}}]
3563
+
3564
+ As you can see `:a` does not get paired with `:a`. If this is what you want, you should use
3565
+ `Map.merge/3`
3566
+
3567
+ ## Examples
3568
+
3569
+ iex> Enum.zip_reduce([1, 2], [3, 4], 0, fn x, y, acc -> x + y + acc end)
3570
+ 10
3571
+
3572
+ iex> Enum.zip_reduce([1, 2], [3, 4], [], fn x, y, acc -> [x + y |acc] end)
3573
+ [6, 4]
3574
+ """
3575
+ def zip_reduce ( left , right , acc , reducer ) do
3576
+ non_stop_reducer = & { :cont , reducer . ( & 1 , & 2 , & 3 ) }
3577
+ zip_reduce_while ( left , right , acc , non_stop_reducer )
3578
+ end
3579
+
3580
+ @ doc """
3581
+ Reduces a over all of the given enums, halting as soon as any enumerable is empty.
3582
+
3583
+ The reducer will receive 2 args, a list of elements (one from each enum) and the
3584
+ accumulator.
3585
+
3586
+ ## Zipping Maps
3587
+
3588
+ It's important to remember that zipping inherently relies on order. If you zip two lists you get
3589
+ the element at the index from each list in turn. If we zip two maps together it's tempting to
3590
+ think that you will get the given key in the left map and the matching key in the right map, but
3591
+ there is no such guarantee because map keys are not ordered! Consider the following:
3592
+
3593
+ left = %{:a => 1, 1 => 3}
3594
+ right = %{:a => 1, :b => :c}
3595
+ Enum.zip(left, right)
3596
+ # [{{1, 3}, {:a, 1}}, {{:a, 1}, {:b, :c}}]
3597
+
3598
+ As you can see `:a` does not get paired with `:a`. If this is what you want, you should use
3599
+ `Map.merge/3`
3600
+
3601
+ ## Examples
3602
+
3603
+ iex> enums = [[1, 1], [2, 2], [3, 3]]
3604
+ ...> Enum.zip_reduce(enums, [], fn elements, acc ->
3605
+ ...> [List.to_tuple(elements) | acc]
3606
+ ...> end)
3607
+ [{1, 2, 3}, {1, 2, 3}]
3608
+
3609
+ iex> enums = [[1, 2], %{a: 3, b: 4}, [5, 6]]
3610
+ ...> Enum.zip_reduce(enums, [], fn elements, acc ->
3611
+ ...> [List.to_tuple(elements) | acc]
3612
+ ...> end)
3613
+ [{2, {:b, 4}, 6}, {1, {:a, 3}, 5}]
3614
+ """
3615
+ def zip_reduce ( enums , acc , reducer ) do
3616
+ non_stop_reducer = & { :cont , reducer . ( & 1 , & 2 ) }
3617
+ zip_reduce_while ( enums , acc , non_stop_reducer )
3618
+ end
3619
+
3620
+ @ doc """
3621
+ Reduces over two enumerables halting if the accumulator returns `{:halt, value}` or if
3622
+ either of the enumerables is empty.
3623
+
3624
+ The reducer will receive 3 args, the left enumerable's element, the right enumberable's
3625
+ element and the accumulator. It should return one of:
3626
+
3627
+ * `{:halt, value}` - This will halt the reduction and return `value`
3628
+ * `{:cont, value}` - This will continue with the next step of the reduction.
3629
+
3630
+ ## Zipping Maps
3631
+
3632
+ It's important to remember that zipping inherently relies on order. If you zip two lists you get
3633
+ the element at the index from each list in turn. If we zip two maps together it's tempting to
3634
+ think that you will get the given key in the left map and the matching key in the right map, but
3635
+ there is no such guarantee because map keys are not ordered! Consider the following:
3636
+
3637
+ left = %{:a => 1, 1 => 3}
3638
+ right = %{:a => 1, :b => :c}
3639
+ Enum.zip(left, right)
3640
+ # [{{1, 3}, {:a, 1}}, {{:a, 1}, {:b, :c}}]
3641
+
3642
+ As you can see `:a` does not get paired with `:a`. If this is what you want, you should use
3643
+ `Map.merge/3`
3644
+
3645
+ ## Examples
3646
+
3647
+ iex> reducer = fn left, right, acc -> {:cont, [left + right | acc ]} end
3648
+ ...> Enum.zip_reduce_while([1], [2], [], reducer)
3649
+ [3]
3650
+
3651
+ iex> Enum.zip_reduce_while([1, 2], [2, 2], [], fn left, right, acc ->
3652
+ ...> if left <= 1, do: {:cont, [left + right | acc]}, else: {:halt, acc}
3653
+ ...> end)
3654
+ [3]
3655
+
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]
3662
+ """
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 )
3665
+ end
3666
+
3667
+ def zip_reduce_while ( left , right , acc , reducer ) do
3668
+ reduce = fn [ l , r ] , acc -> reducer . ( l , r , acc ) end
3669
+ Stream . zip_with ( [ left , right ] , & & 1 ) . ( { :cont , acc } , reduce ) |> elem ( 1 )
3670
+ end
3671
+
3672
+ @ doc """
3673
+ Reduces over all enumerables halting if the accumulator returns `{:halt, value}` or if
3674
+ any of the enumerables is empty.
3675
+
3676
+ The reducer will receive 2 args, a list of the yielded elements and the accumulator.
3677
+ It should return one of:
3678
+
3679
+ * `{:halt, value}` - This will halt the reduction and return `value`
3680
+ * `{:cont, value}` - This will continue with the next step of the reduction.
3681
+
3682
+ ## Zipping Maps
3683
+
3684
+ It's important to remember that zipping inherently relies on order. If you zip two lists you get
3685
+ the element at the index from each list in turn. If we zip two maps together it's tempting to
3686
+ think that you will get the given key in the left map and the matching key in the right map, but
3687
+ there is no such guarantee because map keys are not ordered! Consider the following:
3688
+
3689
+ left = %{:a => 1, 1 => 3}
3690
+ right = %{:a => 1, :b => :c}
3691
+ Enum.zip(left, right)
3692
+ # [{{1, 3}, {:a, 1}}, {{:a, 1}, {:b, :c}}]
3693
+
3694
+ As you can see `:a` does not get paired with `:a`. If this is what you want, you should use
3695
+ `Map.merge/3`
3696
+
3697
+ ## Examples
3698
+
3699
+ iex> enums = [[1, 2],[3, 4]]
3700
+ ...> reducer = fn values, acc -> {:cont, Enum.sum(values) + acc} end
3701
+ ...> Enum.zip_reduce_while(enums, 0, reducer)
3702
+ 10
3703
+
3704
+ iex> enums = [[1, 2],[3, 4]]
3705
+ ...> reducer = fn values, acc -> {:suspend, [Enum.sum(values) | acc]} end
3706
+ ...> Enum.zip_reduce_while(enums, [], reducer)
3707
+ [4]
3708
+
3709
+ iex> enums = [[1, 2],[3, 4]]
3710
+ ...> reducer = fn values, acc -> {:halt, [Enum.sum(values) | acc]} end
3711
+ ...> Enum.zip_reduce_while(enums, [], reducer)
3712
+ [4]
3713
+ """
3714
+ def zip_reduce_while ( [ ] , acc , _reducer ) , do: acc
3715
+
3716
+ def zip_reduce_while ( enums , acc , reducer ) do
3717
+ Stream . zip_with ( enums , & & 1 ) . ( { :cont , acc } , reducer ) |> elem ( 1 )
3718
+ end
3719
+
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
+
3520
3734
## Helpers
3521
3735
3522
3736
@ compile { :inline , entry_to_string: 1 , reduce: 3 , reduce_by: 3 , reduce_enumerable: 3 }
@@ -4118,18 +4332,6 @@ defmodule Enum do
4118
4332
defp uniq_list ( [ ] , _set , _fun ) do
4119
4333
[ ]
4120
4334
end
4121
-
4122
- ## zip
4123
- defp zip_list ( enumerable1 , enumerable2 ) do
4124
- zip_list ( enumerable1 , enumerable2 , fn x , y -> { x , y } end )
4125
- end
4126
-
4127
- defp zip_list ( [ head1 | next1 ] , [ head2 | next2 ] , fun ) do
4128
- [ fun . ( head1 , head2 ) | zip_list ( next1 , next2 , fun ) ]
4129
- end
4130
-
4131
- defp zip_list ( _ , [ ] , _fun ) , do: [ ]
4132
- defp zip_list ( [ ] , _ , _fun ) , do: [ ]
4133
4335
end
4134
4336
4135
4337
defimpl Enumerable , for: List do
0 commit comments