Skip to content

Commit 4113dbc

Browse files
author
José Valim
committed
chunk/4 -> chunk_every/4 and chunk_by/4 -> chunk_while/4
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
1 parent 07167ea commit 4113dbc

File tree

5 files changed

+142
-107
lines changed

5 files changed

+142
-107
lines changed

lib/elixir/lib/enum.ex

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -314,11 +314,21 @@ defmodule Enum do
314314
end
315315
end
316316

317+
# Deprecate on v1.7
318+
@doc false
319+
def chunk(enumerable, count), do: chunk(enumerable, count, count, nil)
320+
321+
# Deprecate on v1.7
322+
@doc false
323+
def chunk(enumerable, count, step, leftover \\ nil) do
324+
chunk_every(enumerable, count, step, leftover || :discard)
325+
end
326+
317327
@doc """
318-
Shortcut to `chunk(enumerable, count, count)`.
328+
Shortcut to `chunk_every(enumerable, count, count)`.
319329
"""
320-
@spec chunk(t, pos_integer) :: [list]
321-
def chunk(enumerable, count), do: chunk(enumerable, count, count, nil)
330+
@spec chunk_every(t, pos_integer) :: [list]
331+
def chunk_every(enumerable, count), do: chunk_every(enumerable, count, count, [])
322332

323333
@doc """
324334
Returns list of lists containing `count` items each, where
@@ -327,40 +337,36 @@ defmodule Enum do
327337
`step` is optional and, if not passed, defaults to `count`, i.e.
328338
chunks do not overlap.
329339
330-
If the final chunk does not have `count` elements to fill the chunk,
331-
the final chunk is dropped unless `leftover` is given.
340+
If the last chunk does not have `count` elements to fill the chunk,
341+
elements are taken from `leftover` to fill in the chunk. If `leftover`
342+
does not have enough elements to fill the chunk, then a partial chunk
343+
is returned with less than `count` elements.
332344
333-
If `leftover` is given, elements are taken from `leftover` to fill in
334-
the chunk. If `leftover` is passed and does not have enough elements
335-
to fill the chunk, then a partial chunk is returned with less than
336-
`count` elements. Therefore, an empty list can be given to `leftover`
337-
when one simply desires for the last chunk to not be discarded.
345+
If `:discard` is given in `leftover`, the last chunk is discarded
346+
unless it has exactly `count` elements.
338347
339348
## Examples
340349
341-
iex> Enum.chunk([1, 2, 3, 4, 5, 6], 2)
350+
iex> Enum.chunk_every([1, 2, 3, 4, 5, 6], 2)
342351
[[1, 2], [3, 4], [5, 6]]
343352
344-
iex> Enum.chunk([1, 2, 3, 4, 5, 6], 3, 2)
353+
iex> Enum.chunk_every([1, 2, 3, 4, 5, 6], 3, 2, :discard)
345354
[[1, 2, 3], [3, 4, 5]]
346355
347-
iex> Enum.chunk([1, 2, 3, 4, 5, 6], 3, 2, [7])
356+
iex> Enum.chunk_every([1, 2, 3, 4, 5, 6], 3, 2, [7])
348357
[[1, 2, 3], [3, 4, 5], [5, 6, 7]]
349358
350-
iex> Enum.chunk([1, 2, 3, 4], 3, 3, [])
359+
iex> Enum.chunk_every([1, 2, 3, 4], 3, 3, [])
351360
[[1, 2, 3], [4]]
352361
353-
iex> Enum.chunk([1, 2, 3, 4], 10)
354-
[]
355-
356-
iex> Enum.chunk([1, 2, 3, 4], 10, 10, [])
362+
iex> Enum.chunk_every([1, 2, 3, 4], 10)
357363
[[1, 2, 3, 4]]
358364
359365
"""
360-
@spec chunk(t, pos_integer, pos_integer, t | nil) :: [list]
361-
def chunk(enumerable, count, step, leftover \\ nil)
366+
@spec chunk_every(t, pos_integer, pos_integer, t | :discard) :: [list]
367+
def chunk_every(enumerable, count, step, leftover \\ [])
362368
when is_integer(count) and count > 0 and is_integer(step) and step > 0 do
363-
R.chunk(&chunk_by/4, enumerable, count, step, leftover)
369+
R.chunk_every(&chunk_while/4, enumerable, count, step, leftover)
364370
end
365371

366372
@doc """
@@ -389,15 +395,22 @@ defmodule Enum do
389395
...> [] -> {:cont, []}
390396
...> acc -> {:cont, Enum.reverse(acc), []}
391397
...> end
392-
iex> Enum.chunk_by(1..10, [], chunk_fun, after_fun)
398+
iex> Enum.chunk_while(1..10, [], chunk_fun, after_fun)
393399
[[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
394400
395401
"""
396-
@spec chunk_by(t, acc,
397-
(element, acc -> {:cont, chunk, acc} | {:cont, acc}),
398-
(acc -> {:cont, chunk, acc} | {:cont, acc})) :: Enumerable.t when chunk: any
399-
def chunk_by(enum, acc, chunk_fun, after_fun) do
400-
{res, acc} = reduce(enum, {[], acc}, R.chunk_by(chunk_fun))
402+
@spec chunk_while(t, acc,
403+
(element, acc -> {:cont, chunk, acc} | {:cont, acc} | {:halt, acc}),
404+
(acc -> {:cont, chunk, acc} | {:cont, acc})) :: Enumerable.t when chunk: any
405+
def chunk_while(enum, acc, chunk_fun, after_fun) do
406+
{_, {res, acc}} =
407+
Enumerable.reduce(enum, {:cont, {[], acc}}, fn entry, {buffer, acc} ->
408+
case chunk_fun.(entry, acc) do
409+
{:cont, emit, acc} -> {:cont, {[emit | buffer], acc}}
410+
{:cont, acc} -> {:cont, {buffer, acc}}
411+
{:halt, acc} -> {:halt, {buffer, acc}}
412+
end
413+
end)
401414

402415
case after_fun.(acc) do
403416
{:cont, _acc} -> :lists.reverse(res)
@@ -419,7 +432,7 @@ defmodule Enum do
419432
"""
420433
@spec chunk_by(t, (element -> any)) :: [list]
421434
def chunk_by(enumerable, fun) do
422-
R.chunk_by(&chunk_by/4, enumerable, fun)
435+
R.chunk_by(&chunk_while/4, enumerable, fun)
423436
end
424437

425438
@doc """

lib/elixir/lib/stream.ex

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -121,47 +121,57 @@ defmodule Stream do
121121

122122
## Transformers
123123

124+
# Deprecate on v1.7
125+
@doc false
126+
def chunk(enum, n), do: chunk(enum, n, n, nil)
127+
128+
# Deprecate on v1.7
129+
@doc false
130+
def chunk(enum, n, step, leftover \\ nil)
131+
when is_integer(n) and n > 0 and is_integer(step) and step > 0 do
132+
R.chunk(&chunk_while/4, enum, n, step, leftover)
133+
end
134+
124135
@doc """
125-
Shortcut to `chunk(enum, n, n)`.
136+
Shortcut to `chunk_every(enum, count, count)`.
126137
"""
127-
@spec chunk(Enumerable.t, pos_integer) :: Enumerable.t
128-
def chunk(enum, n), do: chunk(enum, n, n, nil)
138+
@spec chunk_every(Enumerable.t, pos_integer) :: Enumerable.t
139+
def chunk_every(enum, count), do: chunk_every(enum, count, count, [])
129140

130141
@doc """
131-
Streams the enumerable in chunks, containing `n` items each, where
132-
each new chunk starts `step` elements into the enumerable.
142+
Streams the enumerable in chunks, containing `count` items each,
143+
where each new chunk starts `step` elements into the enumerable.
133144
134145
`step` is optional and, if not passed, defaults to `count`, i.e.
135146
chunks do not overlap.
136147
137-
If the final chunk does not have `count` elements to fill the chunk,
138-
the final chunk is dropped unless `leftover` is given.
148+
If the last chunk does not have `count` elements to fill the chunk,
149+
elements are taken from `leftover` to fill in the chunk. If `leftover`
150+
does not have enough elements to fill the chunk, then a partial chunk
151+
is returned with less than `count` elements.
139152
140-
If `leftover` is given, elements are taken from `leftover` to fill in
141-
the chunk. If `leftover` is passed and does not have enough elements
142-
to fill the chunk, then a partial chunk is returned with less than
143-
`count` elements. Therefore, an empty list can be given to `leftover`
144-
when one simply desires for the last chunk to not be discarded.
153+
If `:discard` is given in `leftover`, the last chunk is discarded
154+
unless it has exactly `count` elements.
145155
146156
## Examples
147157
148-
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 2) |> Enum.to_list
158+
iex> Stream.chunk_every([1, 2, 3, 4, 5, 6], 2) |> Enum.to_list
149159
[[1, 2], [3, 4], [5, 6]]
150160
151-
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 3, 2) |> Enum.to_list
161+
iex> Stream.chunk_every([1, 2, 3, 4, 5, 6], 3, 2, :discard) |> Enum.to_list
152162
[[1, 2, 3], [3, 4, 5]]
153163
154-
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 3, 2, [7]) |> Enum.to_list
164+
iex> Stream.chunk_every([1, 2, 3, 4, 5, 6], 3, 2, [7]) |> Enum.to_list
155165
[[1, 2, 3], [3, 4, 5], [5, 6, 7]]
156166
157-
iex> Stream.chunk([1, 2, 3, 4, 5, 6], 3, 3, []) |> Enum.to_list
167+
iex> Stream.chunk_every([1, 2, 3, 4, 5, 6], 3, 3, []) |> Enum.to_list
158168
[[1, 2, 3], [4, 5, 6]]
159169
160170
"""
161-
@spec chunk(Enumerable.t, pos_integer, pos_integer, Enumerable.t | nil) :: Enumerable.t
162-
def chunk(enum, n, step, leftover \\ nil)
163-
when is_integer(n) and n > 0 and is_integer(step) and step > 0 do
164-
R.chunk(&chunk_by/4, enum, n, step, leftover)
171+
@spec chunk_every(Enumerable.t, pos_integer, pos_integer, Enumerable.t | :discard) :: Enumerable.t
172+
def chunk_every(enum, count, step, leftover \\ [])
173+
when is_integer(count) and count > 0 and is_integer(step) and step > 0 do
174+
R.chunk_every(&chunk_while/4, enum, count, step, leftover)
165175
end
166176

167177
@doc """
@@ -178,7 +188,7 @@ defmodule Stream do
178188
"""
179189
@spec chunk_by(Enumerable.t, (element -> any)) :: Enumerable.t
180190
def chunk_by(enum, fun) do
181-
R.chunk_by(&chunk_by/4, enum, fun)
191+
R.chunk_by(&chunk_while/4, enum, fun)
182192
end
183193

184194
@doc """
@@ -205,21 +215,21 @@ defmodule Stream do
205215
...> [] -> {:cont, []}
206216
...> acc -> {:cont, Enum.reverse(acc), []}
207217
...> end
208-
iex> stream = Stream.chunk_by(1..10, [], chunk_fun, after_fun)
218+
iex> stream = Stream.chunk_while(1..10, [], chunk_fun, after_fun)
209219
iex> Enum.to_list(stream)
210220
[[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
211221
212222
"""
213-
@spec chunk_by(Enumerable.t, acc,
214-
(element, acc -> {:cont, chunk, acc} | {:cont, acc}),
215-
(acc -> {:cont, chunk, acc} | {:cont, acc})) :: Enumerable.t when chunk: any
216-
def chunk_by(enum, acc, chunk_fun, after_fun) do
223+
@spec chunk_while(Enumerable.t, acc,
224+
(element, acc -> {:cont, chunk, acc} | {:cont, acc} | {:halt, acc}),
225+
(acc -> {:cont, chunk, acc} | {:cont, acc})) :: Enumerable.t when chunk: any
226+
def chunk_while(enum, acc, chunk_fun, after_fun) do
217227
lazy enum, acc,
218-
fn(f1) -> R.chunk_by(chunk_fun, f1) end,
219-
&after_chunk_by(&1, &2, after_fun)
228+
fn(f1) -> R.chunk_while(chunk_fun, f1) end,
229+
&after_chunk_while(&1, &2, after_fun)
220230
end
221231

222-
defp after_chunk_by(acc(h, acc, t), f1, after_fun) do
232+
defp after_chunk_while(acc(h, acc, t), f1, after_fun) do
223233
case after_fun.(acc) do
224234
{:cont, emit, acc} -> next_with_acc(f1, emit, h, acc, t)
225235
{:cont, acc} -> {:cont, acc(h, acc, t)}

lib/elixir/lib/stream/reducers.ex

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ defmodule Stream.Reducers do
22
# Collection of reducers and utilities shared by Enum and Stream.
33
@moduledoc false
44

5-
def chunk(chunk_by, enumerable, count, step, leftover) do
5+
def chunk_every(chunk_by, enumerable, count, step, leftover) do
66
limit = :erlang.max(count, step)
77
chunk_by.(enumerable, {[], 0}, fn entry, {acc_buffer, acc_count} ->
88
acc_buffer = [entry | acc_buffer]
@@ -22,7 +22,7 @@ defmodule Stream.Reducers do
2222
{:cont, new_state}
2323
end
2424
end, fn {acc_buffer, acc_count} ->
25-
if is_nil(leftover) || acc_count == 0 do
25+
if leftover == :discard or acc_count == 0 do
2626
{:cont, []}
2727
else
2828
{:cont, :lists.reverse(acc_buffer, Enum.take(leftover, count - acc_count)), []}
@@ -45,12 +45,13 @@ defmodule Stream.Reducers do
4545
end)
4646
end
4747

48-
defmacro chunk_by(callback, fun \\ nil) do
48+
defmacro chunk_while(callback, fun \\ nil) do
4949
quote do
5050
fn entry, acc(head, acc, tail) ->
5151
case unquote(callback).(entry, acc) do
5252
{:cont, emit, acc} -> next_with_acc(unquote(fun), emit, head, acc, tail)
5353
{:cont, acc} -> skip(acc(head, acc, tail))
54+
{:halt, acc} -> {:halt, acc(head, acc, tail)}
5455
end
5556
end
5657
end

lib/elixir/test/elixir/enum_test.exs

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,15 @@ defmodule EnumTest do
6666
assert Enum.chunk_by([1], fn _ -> true end) == [[1]]
6767
end
6868

69-
test "chunk_by/4" do
69+
test "chunk_while/4" do
7070
chunk_fun = fn i, acc ->
71-
if rem(i, 2) == 0 do
72-
{:cont, Enum.reverse([i | acc]), []}
73-
else
74-
{:cont, [i | acc]}
71+
cond do
72+
i > 10 ->
73+
{:halt, acc}
74+
rem(i, 2) == 0 ->
75+
{:cont, Enum.reverse([i | acc]), []}
76+
true ->
77+
{:cont, [i | acc]}
7578
end
7679
end
7780

@@ -80,12 +83,16 @@ defmodule EnumTest do
8083
acc -> {:cont, Enum.reverse(acc), []}
8184
end
8285

83-
assert Enum.chunk_by([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [], chunk_fun, after_fun) ==
86+
assert Enum.chunk_while([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [], chunk_fun, after_fun) ==
8487
[[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
85-
assert Enum.chunk_by(0..10, [], chunk_fun, after_fun) ==
88+
assert Enum.chunk_while(0..9, [], chunk_fun, after_fun) ==
89+
[[0], [1, 2], [3, 4], [5, 6], [7, 8], [9]]
90+
assert Enum.chunk_while(0..10, [], chunk_fun, after_fun) ==
8691
[[0], [1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
87-
assert Enum.chunk_by(0..11, [], chunk_fun, after_fun) ==
88-
[[0], [1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11]]
92+
assert Enum.chunk_while(0..11, [], chunk_fun, after_fun) ==
93+
[[0], [1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
94+
assert Enum.chunk_while([5, 7, 9, 11], [], chunk_fun, after_fun) ==
95+
[[5, 7, 9]]
8996
end
9097

9198
test "concat/1" do
@@ -821,16 +828,16 @@ defmodule EnumTest.Range do
821828
assert Enum.at(2..6, -8) == nil
822829
end
823830

824-
test "chunk/2" do
825-
assert Enum.chunk(1..5, 2) == [[1, 2], [3, 4]]
831+
test "chunk_every/2" do
832+
assert Enum.chunk_every(1..5, 2) == [[1, 2], [3, 4], [5]]
826833
end
827834

828-
test "chunk/4" do
829-
assert Enum.chunk(1..5, 2, 2, [6]) == [[1, 2], [3, 4], [5, 6]]
830-
assert Enum.chunk(1..6, 3, 2) == [[1, 2, 3], [3, 4, 5]]
831-
assert Enum.chunk(1..6, 2, 3) == [[1, 2], [4, 5]]
832-
assert Enum.chunk(1..6, 3, 2, []) == [[1, 2, 3], [3, 4, 5], [5, 6]]
833-
assert Enum.chunk(1..5, 4, 4, 6..10) == [[1, 2, 3, 4], [5, 6, 7, 8]]
835+
test "chunk_every/4" do
836+
assert Enum.chunk_every(1..5, 2, 2) == [[1, 2], [3, 4], [5]]
837+
assert Enum.chunk_every(1..6, 3, 2, :discard) == [[1, 2, 3], [3, 4, 5]]
838+
assert Enum.chunk_every(1..6, 2, 3, :discard) == [[1, 2], [4, 5]]
839+
assert Enum.chunk_every(1..6, 3, 2, []) == [[1, 2, 3], [3, 4, 5], [5, 6]]
840+
assert Enum.chunk_every(1..5, 4, 4, 6..10) == [[1, 2, 3, 4], [5, 6, 7, 8]]
834841
end
835842

836843
test "chunk_by/2" do

0 commit comments

Comments
 (0)