Should zip / zip_while have a reducer?

38 views
Skip to first unread message

ad...@a-corp.co.uk

unread,
Feb 2, 2021, 5:27:49 PM2/2/21
to elixir-lang-core
Hello!

I was thinking about zip_with/3 et al. (don't judge me I'm in a lockdown).

Currently they work by returning a list, but would it not make more sense for it to take an accumulator?

```elixir
Enum.zip_with([1, 2], [3, 4], %{}, fn left, right, acc -> Map.put(acc, left, right) end)
%{ 1 => 3, 2 => 4 }

Enum.zip_with([1, 2], [3, 4], [], fn left, right, acc -> [{left, right} | acc] end)
[{1, 3}, {2, 4}]
```
If so... I guess it also opens to door for a zip_while.

Am I mad? Has science gone too far?

Best

Adz


ad...@a-corp.co.uk

unread,
Feb 2, 2021, 5:29:27 PM2/2/21
to elixir-lang-core
How embarrasing I typo'd the subject. It should read Should zip / zip_with have a reducer?

Best

Adam

José Valim

unread,
Feb 3, 2021, 12:59:34 AM2/3/21
to elixir-l...@googlegroups.com
I think zip_with is still valuable as is because a zip_with without an accumulator can be streamable (i.e. in the Stream module) but a zip_with with an accumulator cannot. We may be looking at a zip_reduce in the future though, given use cases are provided. :)

--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/468649a3-7e6f-4214-99d8-6e8ef5e6d72an%40googlegroups.com.

ad...@a-corp.co.uk

unread,
Mar 20, 2021, 10:10:29 AM3/20/21
to elixir-lang-core
Here are some use cases that having a reducer would help with:

```elixir
Enum.zip([:a, :b, :c], [1, 2, 3], %{}, fn k, val, acc -> Map.put(acc, k, val)  end)
# %{a: 1, b: 2, c: 3}

def zip_into_map(left, right) do
  Enum.zip(left, right, %{}, fn k, val, acc -> Map.put(acc, k, val)  end)
end

csv_headers = ["id", "date", "whatever"]

Enum.map(csv_rows, fn row ->
  zip_into_map(csv_headers, row)
end)

dot_product =
  Enum.zip([1, 2], [3, 4], [], fn l, r, acc -> [ l * r | acc] end)
  |> Enum.reverse

def zip_merge_values(left, right) do
  Enum.zip(left, right, %{}, fn
    {key, left_val}, {key, right_val}, acc ->
      Map.put(acc, key, left_val ++ right_val)

    {left_key, left_val}, {right_key, right_val}, acc ->
      Map.merge(acc, %{left_key => left_val, right_key => right_val})
  end)
end

zip_merge_values(%{a: [1], c: [5]}, %{a: [2], b: [7])
# %{a: [1, 2], b: [7] c: [5]}

Enum.zip_with(["a", "key", "list"], Stream.repeatedly(fn -> 10 end), %{}, fn k, v, acc ->
  Map.merge(acc, {k, v})
end)

# => %{"a" => 10, "key" => 10, "list" => 10}
```

Though I think if we are really going for it if you had a Zippable protocol a la Eumerable and defined Zippable.reduce_while you'd get a lot of functions for free I suspect.

Best

Adam

José Valim

unread,
Mar 20, 2021, 11:15:21 AM3/20/21
to elixir-l...@googlegroups.com
Thanks, I also needed zip_reduce a couple days ago while working on Nx, so zip_reduce is appreciated. The cool thing is that we can also implement both zip_with and zip on top of zip_reduce, so it shouldn’t make the code more complex either.

A PR is welcome!



Adam Lancaster

unread,
Mar 20, 2021, 11:18:13 AM3/20/21
to elixir-l...@googlegroups.com
Awesome, yea exactly. 

Ah, i wondered if you would come up with a use case in Nx.

I’d love to give this a stab if that’s okay so I’ll get a pr together.

Adz

José Valim

unread,
Mar 20, 2021, 11:20:25 AM3/20/21
to elixir-l...@googlegroups.com
Reply all
Reply to author
Forward
0 new messages