Skip to content

Commit 3ac5706

Browse files
author
José Valim
committed
Add Stream.scan/2 and Stream.scan/3
1 parent db19cde commit 3ac5706

File tree

5 files changed

+118
-1
lines changed

5 files changed

+118
-1
lines changed

lib/elixir/lib/enum.ex

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1011,7 +1011,6 @@ defmodule Enum do
10111011
end
10121012
end
10131013

1014-
10151014
@doc """
10161015
Returns the minimum value.
10171016
Raises `EmptyError` if the collection is empty.
@@ -1205,6 +1204,42 @@ defmodule Enum do
12051204
end)
12061205
end
12071206

1207+
@doc """
1208+
Applies the given function to each element in the collection,
1209+
storing the result in a list and passing it as accumulator
1210+
for the next computation.
1211+
1212+
## Examples
1213+
1214+
iex> Enum.scan(1..5, &(&1 + &2))
1215+
[1,3,6,10,15]
1216+
1217+
"""
1218+
@spec scan(t, (element, any -> any)) :: list
1219+
def scan(enum, fun) do
1220+
{ _, { res, _ } } =
1221+
Enumerable.reduce(enum, { :cont, { [], :first } }, R.scan_2(fun))
1222+
:lists.reverse(res)
1223+
end
1224+
1225+
@doc """
1226+
Applies the given function to each element in the collection,
1227+
storing the result in a list and passing it as accumulator
1228+
for the next computation. Uses the given `acc` as starting value.
1229+
1230+
## Examples
1231+
1232+
iex> Enum.scan(1..5, 0, &(&1 + &2))
1233+
[1,3,6,10,15]
1234+
1235+
"""
1236+
@spec scan(t, any, (element, any -> any)) :: list
1237+
def scan(enum, acc, fun) do
1238+
{ _, { res, _ } } =
1239+
Enumerable.reduce(enum, { :cont, { [], acc } }, R.scan_3(fun))
1240+
:lists.reverse(res)
1241+
end
1242+
12081243
@doc """
12091244
Returns a list of collection elements shuffled.
12101245

lib/elixir/lib/stream.ex

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,40 @@ defmodule Stream do
377377
lazy enum, fn(f1) -> R.map(fun, f1) end
378378
end
379379

380+
@doc """
381+
Creates a stream that applies the given function to each
382+
element, emits the result and uses the same result as accumulator
383+
for the next computation.
384+
385+
## Examples
386+
387+
iex> stream = Stream.scan(1..5, &(&1 + &2))
388+
iex> Enum.to_list(stream)
389+
[1,3,6,10,15]
390+
391+
"""
392+
@spec scan(Enumerable.t, (element, acc -> any)) :: Enumerable.t
393+
def scan(enum, fun) do
394+
lazy enum, :first, fn(f1) -> R.scan_2(fun, f1) end
395+
end
396+
397+
@doc """
398+
Creates a stream that applies the given function to each
399+
element, emits the result and uses the same result as accumulator
400+
for the next computation. Uses the given `acc` as starting value.
401+
402+
## Examples
403+
404+
iex> stream = Stream.scan(1..5, 0, &(&1 + &2))
405+
iex> Enum.to_list(stream)
406+
[1,3,6,10,15]
407+
408+
"""
409+
@spec scan(Enumerable.t, acc, (element, acc -> any)) :: Enumerable.t
410+
def scan(enum, acc, fun) do
411+
lazy enum, acc, fn(f1) -> R.scan_3(fun, f1) end
412+
end
413+
380414
@doc """
381415
Creates a stream that will apply the given function on enumeration and
382416
flatten the result.

lib/elixir/lib/stream/reducers.ex

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,27 @@ defmodule Stream.Reducers do
108108
end
109109
end
110110

111+
defmacro scan_2(callback, f // nil) do
112+
quote do
113+
fn
114+
entry, acc(h, :first, t) ->
115+
cont_with_acc(unquote(f), entry, h, { :ok, entry }, t)
116+
entry, acc(h, { :ok, acc }, t) ->
117+
value = unquote(callback).(entry, acc)
118+
cont_with_acc(unquote(f), value, h, { :ok, value }, t)
119+
end
120+
end
121+
end
122+
123+
defmacro scan_3(callback, f // nil) do
124+
quote do
125+
fn(entry, acc(h, acc, t)) ->
126+
value = unquote(callback).(entry, acc)
127+
cont_with_acc(unquote(f), value, h, value, t)
128+
end
129+
end
130+
end
131+
111132
defmacro take(f // nil) do
112133
quote do
113134
fn(entry, acc(h, n, t) = orig) ->

lib/elixir/test/elixir/enum_test.exs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,14 @@ defmodule EnumTest.List do
231231
assert Enum.reverse([1, 2, 3], [4, 5, 6]) == [3, 2, 1, 4, 5, 6]
232232
end
233233

234+
test :scan do
235+
assert Enum.scan([1,2,3,4,5], &(&1 + &2)) == [1,3,6,10,15]
236+
assert Enum.scan([], &(&1 + &2)) == []
237+
238+
assert Enum.scan([1,2,3,4,5], 0, &(&1 + &2)) == [1,3,6,10,15]
239+
assert Enum.scan([], 0, &(&1 + &2)) == []
240+
end
241+
234242
test :shuffle do
235243
# set a fixed seed so the test can be deterministic
236244
:random.seed(1374, 347975, 449264)
@@ -636,6 +644,11 @@ defmodule EnumTest.Range do
636644
assert Enum.partition(range, fn(x) -> rem(x, 2) == 0 end) == { [2], [1, 3] }
637645
end
638646

647+
test :scan do
648+
assert Enum.scan(1..5, &(&1 + &2)) == [1,3,6,10,15]
649+
assert Enum.scan(1..5, 0, &(&1 + &2)) == [1,3,6,10,15]
650+
end
651+
639652
test :shuffle do
640653
# set a fixed seed so the test can be deterministic
641654
:random.seed(1374, 347975, 449264)

lib/elixir/test/elixir/stream_test.exs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,20 @@ defmodule StreamTest do
280280
assert r1 != r2
281281
end
282282

283+
test "scan/2" do
284+
stream = Stream.scan(1..5, &(&1 + &2))
285+
assert is_lazy(stream)
286+
assert Enum.to_list(stream) == [1,3,6,10,15]
287+
assert Stream.scan([], &(&1 + &2)) |> Enum.to_list == []
288+
end
289+
290+
test "scan/3" do
291+
stream = Stream.scan(1..5, 0, &(&1 + &2))
292+
assert is_lazy(stream)
293+
assert Enum.to_list(stream) == [1,3,6,10,15]
294+
assert Stream.scan([], 0, &(&1 + &2)) |> Enum.to_list == []
295+
end
296+
283297
test "take" do
284298
stream = Stream.take(1..1000, 5)
285299
assert is_lazy(stream)

0 commit comments

Comments
 (0)