Skip to content

Commit bc852a6

Browse files
author
José Valim
committed
Add Enum.flat_map_reduce/3
1 parent 28ff6b3 commit bc852a6

File tree

5 files changed

+59
-11
lines changed

5 files changed

+59
-11
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# v0.12.2-dev
22

33
* Enhancements
4+
* [Enum] Add `Enum.flat_map_reduce/3`
45
* [ExUnit] Support @moduletag in ExUnit cases
56
* [Kernel] Improve stacktraces to be relative to the compilation path and include the related application
67
* [Stream] Add `Stream.transform/3`

lib/elixir/lib/enum.ex

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -766,7 +766,7 @@ defmodule Enum do
766766
Returns a new collection appending the result of invoking `fun`
767767
on each corresponding item of `collection`.
768768
769-
The given function should return a list.
769+
The given function should return an enumerable.
770770
771771
## Examples
772772
@@ -777,15 +777,48 @@ defmodule Enum do
777777
[1, 2, 3, 4, 5, 6]
778778
779779
"""
780-
@spec flat_map(t, (element -> any)) :: list
781-
def flat_map([], _fun), do: []
782-
780+
@spec flat_map(t, (element -> t)) :: list
783781
def flat_map(collection, fun) do
784782
reduce(collection, [], fn(entry, acc) ->
785783
reduce(fun.(entry), acc, &[&1|&2])
786784
end) |> :lists.reverse
787785
end
788786

787+
@doc """
788+
Maps and reduces a collection, flattening the given results.
789+
790+
It expects an accumulator and a function that receives each stream item
791+
and an accumulator, and must return a tuple containing a new stream
792+
(often a list) with the new accumulator or a tuple with `:halt` as first
793+
element and the accumulator as second.
794+
795+
## Examples
796+
797+
iex> enum = 1..100
798+
iex> n = 3
799+
iex> Enum.flat_map_reduce(enum, 0, fn i, acc ->
800+
...> if acc < n, do: { [i], acc + 1 }, else: { :halt, acc }
801+
...> end)
802+
{ [1,2,3], 3 }
803+
804+
"""
805+
@spec flat_map_reduce(t, acc, fun) :: list when
806+
fun: (element, acc -> { t, acc } | { :halt, acc }),
807+
acc: any
808+
def flat_map_reduce(collection, acc, fun) do
809+
{ _, { list, acc } } =
810+
Enumerable.reduce(collection, { :cont, { [], acc } }, fn(entry, { list, acc }) ->
811+
case fun.(entry, acc) do
812+
{ :halt, acc } ->
813+
{ :halt, { list, acc } }
814+
{ entries, acc } ->
815+
{ :cont, { reduce(entries, list, &[&1|&2]), acc } }
816+
end
817+
end)
818+
819+
{ :lists.reverse(list), acc }
820+
end
821+
789822
@doc """
790823
Intersperses `element` between each element of the enumeration.
791824

lib/elixir/lib/stream.ex

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,12 @@ defmodule Stream do
602602
603603
It expects an accumulator and a function that receives each stream item
604604
and an accumulator, and must return a tuple containing a new stream
605-
(often a list) with the new accumulator or simply return the atom `:halt`.
605+
(often a list) with the new accumulator or a tuple with `:halt` as first
606+
element and the accumulator as second.
607+
608+
Note: this function is similar to `Enum.flat_map_reduce/3` except the
609+
latter returns both the flat list and accumulator, while this one returns
610+
only the stream.
606611
607612
## Examples
608613
@@ -613,14 +618,14 @@ defmodule Stream do
613618
iex> enum = 1..100
614619
iex> n = 3
615620
iex> stream = Stream.transform(enum, 0, fn i, acc ->
616-
...> if acc < n, do: { [i], acc + 1 }, else: :halt
621+
...> if acc < n, do: { [i], acc + 1 }, else: { :halt, acc }
617622
...> end)
618623
iex> Enum.to_list(stream)
619624
[1,2,3]
620625
621626
"""
622627
@spec transform(Enumerable.t, acc, fun) :: Enumerable.t when
623-
fun: (element, acc -> { Enumerable.t, acc } | :halt),
628+
fun: (element, acc -> { Enumerable.t, acc } | { :halt, acc }),
624629
acc: any
625630
def transform(enum, acc, reducer) do
626631
&do_transform(enum, acc, reducer, &1, &2)
@@ -647,11 +652,11 @@ defmodule Stream do
647652
do_transform(user_acc, user, fun, next_acc, next, inner_acc, inner)
648653
{ list, user_acc } when is_list(list) ->
649654
do_list_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.List.reduce(list, &1, fun))
650-
{ other, user_acc } ->
651-
do_other_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.reduce(other, &1, inner))
652-
:halt ->
655+
{ :halt, _user_acc } ->
653656
next.({ :halt, next_acc })
654657
{ :halted, elem(inner_acc, 1) }
658+
{ other, user_acc } ->
659+
do_other_transform(user_acc, user, fun, next_acc, next, inner_acc, inner, &Enumerable.reduce(other, &1, inner))
655660
end
656661
{ reason, _ } ->
657662
{ reason, elem(inner_acc, 1) }

lib/elixir/test/elixir/enum_test.exs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,15 @@ defmodule EnumTest.List do
170170
assert Enum.flat_map([1, 2, 3], fn(x) -> x..x+1 end) == [1, 2, 2, 3, 3, 4]
171171
end
172172

173+
test :flat_map_reduce do
174+
assert Enum.flat_map_reduce([1, 2, 3], 0, &{ [&1, &2], &1 + &2 }) ==
175+
{ [1, 0, 2, 1, 3, 3], 6 }
176+
177+
assert Enum.flat_map_reduce(1..100, 0, fn i, acc ->
178+
if acc < 3, do: { [i], acc + 1 }, else: { :halt, acc }
179+
end) == { [1,2,3], 3 }
180+
end
181+
173182
test :reduce do
174183
assert Enum.reduce([], 1, fn(x, acc) -> x + acc end) == 1
175184
assert Enum.reduce([1, 2, 3], 1, fn(x, acc) -> x + acc end) == 7

lib/elixir/test/elixir/stream_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ defmodule StreamTest do
313313
stream = Stream.resource(fn -> 1 end,
314314
fn acc -> { acc, acc + 1 } end,
315315
fn _ -> Process.put(:stream_transform, true) end)
316-
stream = Stream.transform(stream, 0, fn i, acc -> if acc < 3, do: { [i], acc + 1 }, else: :halt end)
316+
stream = Stream.transform(stream, 0, fn i, acc -> if acc < 3, do: { [i], acc + 1 }, else: { :halt, acc } end)
317317

318318
Process.put(:stream_transform, false)
319319
assert Enum.to_list(stream) == [1,2,3]

0 commit comments

Comments
 (0)