Skip to content

Commit 3c64178

Browse files
author
José Valim
committed
Add Stream.take_every/2
1 parent 23a4fc6 commit 3c64178

File tree

4 files changed

+90
-16
lines changed

4 files changed

+90
-16
lines changed

lib/elixir/lib/enum.ex

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -659,9 +659,8 @@ defmodule Enum do
659659
end
660660

661661
def filter_map(collection, filter, mapper) do
662-
reduce(collection, [], fn(entry, acc) ->
663-
if filter.(entry), do: [mapper.(entry)|acc], else: acc
664-
end) |> :lists.reverse
662+
Enumerable.reduce(collection, { :cont, [] }, R.filter_map(filter, mapper))
663+
|> elem(1) |> :lists.reverse
665664
end
666665

667666
@doc """
@@ -1490,17 +1489,9 @@ defmodule Enum do
14901489
@spec take_every(t, integer) :: list
14911490
def take_every(_collection, 0), do: []
14921491
def take_every(collection, nth) do
1493-
res =
1494-
reduce(collection, nil, fn
1495-
x, { acc, ^nth } -> { [x | acc], 1 }
1496-
_, { acc, count } -> { acc, count + 1 }
1497-
x, nil -> { [x], 1 }
1498-
end)
1499-
1500-
case res do
1501-
nil -> []
1502-
{ list, _ } -> :lists.reverse(list)
1503-
end
1492+
{ _, { res, _ } } =
1493+
Enumerable.reduce(collection, { :cont, { [], :first } }, R.take_every(nth))
1494+
:lists.reverse(res)
15041495
end
15051496

15061497
@doc """

lib/elixir/lib/stream.ex

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ defmodule Stream do
208208
end
209209

210210
@doc """
211-
Creates a stream that will filter elements according to
211+
Creates a stream that filters elements according to
212212
the given function on enumeration.
213213
214214
## Examples
@@ -223,6 +223,24 @@ defmodule Stream do
223223
lazy enum, fn(f1) -> R.filter(fun, f1) end
224224
end
225225

226+
@doc """
227+
Creates a stream that filters and then map elements according
228+
to given funtions.
229+
230+
It exists for simmetry with Enum.filter_map/3.
231+
232+
## Examples
233+
234+
iex> stream = Stream.filter_map([1, 2, 3], fn(x) -> rem(x, 2) == 0 end)
235+
iex> Enum.to_list(stream)
236+
[2]
237+
238+
"""
239+
@spec filter_map(Enumerable.t, (element -> as_boolean(term)), (element -> any)) :: Enumerable.t
240+
def filter_map(enum, filter, mapper) do
241+
lazy enum, fn(f1) -> R.filter_map(filter, mapper, f1) end
242+
end
243+
226244
@doc """
227245
Creates a stream that will apply the given function on
228246
enumeration.
@@ -330,7 +348,26 @@ defmodule Stream do
330348
lazy enum, n, fn(f1) -> R.take(f1) end
331349
end
332350

333-
def take(_enum, 0), do: Lazy[enum: [], funs: [&(&1)]]
351+
def take(_enum, 0), do: Lazy[enum: []]
352+
353+
@doc """
354+
Creates a stream that takes every `n` item from the enumerable.
355+
356+
The first item is always included, unless n is 0.
357+
358+
## Examples
359+
360+
iex> stream = Stream.take_every(1..10, 2)
361+
iex> Enum.to_list(stream)
362+
[1,3,5,7,9]
363+
364+
"""
365+
@spec take(Enumerable.t, non_neg_integer) :: Enumerable.t
366+
def take_every(enum, n) when n > 0 do
367+
lazy enum, n, fn(f1) -> R.take_every(n, f1) end
368+
end
369+
370+
def take_every(_enum, 0), do: Lazy[enum: []]
334371

335372
@doc """
336373
Lazily takes elements of the enumerable while the given

lib/elixir/lib/stream/reducers.ex

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ defmodule Stream.Reducers do
3737
end
3838
end
3939

40+
defmacro filter_map(filter, mapper, f // nil) do
41+
quote do
42+
fn(entry, acc) ->
43+
if unquote(filter).(entry) do
44+
cont(unquote(f), unquote(mapper).(entry), acc)
45+
else
46+
{ :cont, acc }
47+
end
48+
end
49+
end
50+
end
51+
4052
defmacro map(callback, f // nil) do
4153
quote do
4254
fn(entry, acc) ->
@@ -69,6 +81,18 @@ defmodule Stream.Reducers do
6981
end
7082
end
7183

84+
defmacro take_every(nth, f // nil) do
85+
quote do
86+
fn
87+
entry, acc(h, n, t) when n === :first
88+
when n === unquote(nth) ->
89+
cont_with_acc(unquote(f), entry, h, 1, t)
90+
entry, acc(h, n, t) ->
91+
{ :cont, acc(h, n+1, t) }
92+
end
93+
end
94+
end
95+
7296
defmacro take_while(callback, f // nil) do
7397
quote do
7498
fn(entry, acc) ->

lib/elixir/test/elixir/stream_test.exs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,16 @@ defmodule StreamTest do
126126
assert Stream.filter(nats, &(rem(&1, 2) == 0)) |> Enum.take(5) == [2,4,6,8,10]
127127
end
128128

129+
test "filter_map" do
130+
stream = Stream.filter_map([1,2,3], fn(x) -> rem(x, 2) == 0 end, &(&1 * 2))
131+
assert is_lazy(stream)
132+
assert Enum.to_list(stream) == [4]
133+
134+
nats = Stream.iterate(1, &(&1 + 1))
135+
assert Stream.filter_map(nats, &(rem(&1, 2) == 0), &(&1 * 2))
136+
|> Enum.take(5) == [4,8,12,16,20]
137+
end
138+
129139
test "flat_map" do
130140
stream = Stream.flat_map([1, 2, 3], &[&1, &1 * 2])
131141
assert is_lazy(stream)
@@ -228,6 +238,18 @@ defmodule StreamTest do
228238
assert Enum.zip(list, list) == Enum.zip(stream, stream)
229239
end
230240

241+
test "take_every" do
242+
assert 1..10
243+
|> Stream.take_every(2)
244+
|> Enum.to_list == [1, 3, 5, 7, 9]
245+
246+
assert 1..10
247+
|> Stream.drop(2)
248+
|> Stream.take_every(2)
249+
|> Stream.drop(1)
250+
|> Enum.to_list == [5, 7, 9]
251+
end
252+
231253
test "take_while" do
232254
stream = Stream.take_while(1..1000, &(&1 <= 5))
233255
assert is_lazy(stream)

0 commit comments

Comments
 (0)