Skip to content

Commit 33c6eb3

Browse files
author
José Valim
committed
Ensure certain streams and enumerables are zippable
1 parent 405c14f commit 33c6eb3

File tree

5 files changed

+136
-104
lines changed

5 files changed

+136
-104
lines changed

lib/elixir/lib/hash_dict.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ defmodule HashDict do
402402
end
403403

404404
defp node_reduce(list, -1, { :suspend, acc }, fun, count, next) do
405-
{ :suspend, acc, &node_reduce(list, -1, &1, fun, count, next) }
405+
{ :suspended, acc, &node_reduce(list, -1, &1, fun, count, next) }
406406
end
407407

408408
defp node_reduce([[k|v]|t], -1, { :cont, acc }, fun, _count, next) do

lib/elixir/lib/stream.ex

Lines changed: 32 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -235,35 +235,49 @@ defmodule Stream do
235235
236236
"""
237237
@spec flat_map(Enumerable.t, (element -> any)) :: Enumerable.t
238-
def flat_map(enum, f) do
239-
lazy enum, fn(f1) ->
240-
fn(entry, acc) ->
241-
enum = f.(entry)
242-
fun = &do_flat_map_each(f1, &1, &2)
243-
do_flat_map(&Enumerable.reduce(enum, &1, fun), { :cont, acc })
244-
end
238+
def flat_map(enum, mapper) do
239+
&do_flat_map(enum, mapper, &1, &2)
240+
end
241+
242+
defp do_flat_map(enumerables, mapper, acc, fun) do
243+
fun = &do_flat_map_each(fun, &1, &2)
244+
step = &do_flat_map_step/2
245+
next = &Enumerable.reduce(enumerables, &1, step)
246+
do_flat_map([], next, mapper, acc, fun)
247+
end
248+
249+
defp do_flat_map(next_acc, next, mapper, acc, fun) do
250+
case next.({ :cont, next_acc }) do
251+
{ :suspended, [val|next_acc], next } ->
252+
enum = mapper.(val)
253+
do_flat_map(next_acc, next, mapper, acc, fun, &Enumerable.reduce(enum, &1, fun))
254+
{ reason, _ } ->
255+
{ reason, elem(acc, 1) }
245256
end
246257
end
247258

248-
defp do_flat_map(reduce, acc) do
259+
defp do_flat_map(next_acc, next, mapper, acc, fun, reduce) do
249260
try do
250261
reduce.(acc)
251262
catch
252-
{ :stream_flat_map, acc } -> acc
263+
{ :stream_flat_map, h } -> { :halted, h }
253264
else
254-
{ :done, acc } -> { :cont, acc }
255-
{ :halted, acc } -> { :cont, acc }
256-
{ :suspended, acc, c } -> { :suspend, acc, &do_flat_map(c, &1) }
265+
{ _, acc } -> do_flat_map(next_acc, next, mapper, { :cont, acc }, fun)
266+
{ :suspended, acc, c } -> { :suspended, acc, &do_flat_map(next_acc, next, mapper, &1, fun, c) }
257267
end
258268
end
259269

260-
defp do_flat_map_each(f1, x, acc) do
261-
case f1.(x, acc) do
262-
{ :halt, _ } = h -> throw({ :stream_flat_map, h })
263-
{ _, _ } = o -> o
270+
defp do_flat_map_each(f, x, acc) do
271+
case f.(x, acc) do
272+
{ :halt, h } -> throw({ :stream_flat_map, h })
273+
{ _, _ } = o -> o
264274
end
265275
end
266276

277+
defp do_flat_map_step(x, acc) do
278+
{ :suspend, [x|acc] }
279+
end
280+
267281
@doc """
268282
Creates a stream that will reject elements according to
269283
the given function on enumeration.
@@ -374,7 +388,7 @@ defmodule Stream do
374388
"""
375389
@spec concat(Enumerable.t) :: Enumerable.t
376390
def concat(enumerables) do
377-
&do_concat(enumerables, &1, &2)
391+
flat_map(enumerables, &(&1))
378392
end
379393

380394
@doc """
@@ -395,34 +409,7 @@ defmodule Stream do
395409
"""
396410
@spec concat(Enumerable.t, Enumerable.t) :: Enumerable.t
397411
def concat(first, second) do
398-
&do_concat([first, second], &1, &2)
399-
end
400-
401-
defp do_concat(enumerables, acc, fun) do
402-
step = &do_concat_step/2
403-
next = &Enumerable.reduce(enumerables, &1, step)
404-
do_concat([], next, acc, fun)
405-
end
406-
407-
defp do_concat(next_acc, next, acc, fun) do
408-
case next.({ :cont, next_acc }) do
409-
{ :suspended, [enum|next_acc], next } ->
410-
do_concat(next_acc, next, acc, fun, &Enumerable.reduce(enum, &1, fun))
411-
{ reason, _ } ->
412-
{ reason, elem(acc, 1) }
413-
end
414-
end
415-
416-
defp do_concat(next_acc, next, acc, fun, reduce) do
417-
case reduce.(acc) do
418-
{ :done, acc } -> do_concat(next_acc, next, { :cont, acc }, fun)
419-
{ :halted, acc } = h -> h
420-
{ :suspended, acc, c } -> { :suspended, acc, &do_concat(next_acc, next, &1, fun, c) }
421-
end
422-
end
423-
424-
defp do_concat_step(x, acc) do
425-
{ :suspend, [x|acc] }
412+
flat_map([first, second], &(&1))
426413
end
427414

428415
@doc """

lib/elixir/test/elixir/hash_dict_test.exs

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,57 +5,57 @@ defmodule HashDictTest do
55

66
@dict HashDict.new(foo: :bar)
77

8-
test :is_serializable_as_attribute do
8+
test "is serializable as attribute" do
99
assert @dict == HashDict.new(foo: :bar)
1010
end
1111

12-
test :access_the_serialized_as_attribute do
12+
test "is accessible as attribute" do
1313
assert @dict[:foo] == :bar
1414
end
1515

16-
test :smoke_small_range_test do
16+
test "small dict smoke test" do
1717
smoke_test(1..8)
1818
smoke_test(8..1)
1919
end
2020

21-
test :smoke_medium_range_test do
21+
test "medium dict smoke test" do
2222
smoke_test(1..80)
2323
smoke_test(80..1)
2424
end
2525

26-
test :smoke_large_range_test do
26+
test "large dict smoke test" do
2727
smoke_test(1..1200)
2828
smoke_test(1200..1)
2929
end
3030

31-
test :fetch! do
31+
test "fetch!/2" do
3232
dict = filled_dict(8)
3333
assert HashDict.fetch!(dict, 1) == 1
3434
assert_raise KeyError, fn ->
3535
HashDict.fetch!(dict, 11)
3636
end
3737
end
3838

39-
test :empty do
39+
test "empty/1" do
4040
assert HashDict.empty filled_dict(8) == HashDict.new
4141
assert HashDict.empty filled_dict(20) == HashDict.new
4242
assert HashDict.empty filled_dict(120) == HashDict.new
4343
end
4444

45-
test :fetch do
45+
test "fetch/2" do
4646
dict = filled_dict(8)
4747
assert HashDict.fetch(dict, 4) == { :ok, 4 }
4848
assert HashDict.fetch(dict, 16) == :error
4949
end
5050

51-
test :equal? do
51+
test "equal?/2" do
5252
assert HashDict.equal?(filled_dict(3), filled_dict(3)) == true
5353

5454
assert HashDict.equal?(HashDict.new([{:a, 1}, {:b, 2}]),
5555
HashDict.new([{:a, 2}, {:b, 3}])) == false
5656
end
5757

58-
test :has_key? do
58+
test "has_key?/2" do
5959
dict = filled_dict(8)
6060
assert HashDict.has_key? dict, 4
6161
refute HashDict.has_key? dict, 16
@@ -69,7 +69,7 @@ defmodule HashDictTest do
6969
refute HashDict.has_key? dict, 240
7070
end
7171

72-
test :put_new do
72+
test "put_new/3" do
7373
dict = filled_dict(8)
7474

7575
dict = HashDict.put_new(dict, 1, 11)
@@ -86,7 +86,7 @@ defmodule HashDictTest do
8686
assert HashDict.size(dict) == 10
8787
end
8888

89-
test :update do
89+
test "update/3" do
9090
dict = filled_dict(8)
9191

9292
dict = HashDict.update!(dict, 1, &(&1 * 2))
@@ -119,7 +119,7 @@ defmodule HashDictTest do
119119
assert HashDict.size(dict) == 10
120120
end
121121

122-
test :to_list do
122+
test "to_list/1" do
123123
dict = filled_dict(8)
124124
list = dict |> HashDict.to_list
125125
assert length(list) == 8
@@ -139,7 +139,7 @@ defmodule HashDictTest do
139139
assert list == Enum.to_list(dict)
140140
end
141141

142-
test :keys do
142+
test "keys/1" do
143143
list = filled_dict(8) |> HashDict.keys
144144
assert length(list) == 8
145145
assert 1 in list
@@ -153,7 +153,7 @@ defmodule HashDictTest do
153153
assert 1 in list
154154
end
155155

156-
test :values do
156+
test "values/1" do
157157
list = filled_dict(8) |> HashDict.values
158158
assert length(list) == 8
159159
assert 1 in list
@@ -167,7 +167,7 @@ defmodule HashDictTest do
167167
assert 1 in list
168168
end
169169

170-
test :enum do
170+
test "implements Enumerable" do
171171
dict = filled_dict(10)
172172
assert Enum.empty?(HashDict.new)
173173
refute Enum.empty?(dict)
@@ -177,17 +177,17 @@ defmodule HashDictTest do
177177
assert Enum.map(filled_dict(3), fn({ k, v }) -> k + v end) == [2, 4, 6]
178178
end
179179

180-
test :access do
180+
test "access" do
181181
assert filled_dict(8)[1] == 1
182182
assert filled_dict(8)[5] == 5
183183
assert filled_dict(8)[9] == nil
184184
end
185185

186-
test :inspect do
186+
test "inspect" do
187187
assert inspect(filled_dict(8)) =~ "#HashDict<"
188188
end
189189

190-
test :small_range_merge do
190+
test "small dict merge" do
191191
dict1 = filled_dict(8)
192192
dict2 = Enum.reduce 6..10, HashDict.new, fn(i, d) -> HashDict.put(d, i, i * 2) end
193193

@@ -217,7 +217,7 @@ defmodule HashDictTest do
217217
assert HashDict.size(dict) == 10
218218
end
219219

220-
test :medium_range_merge do
220+
test "medium dict merge" do
221221
dict1 = filled_dict(20)
222222
dict2 = Enum.reduce 18..22, HashDict.new, fn(i, d) -> HashDict.put(d, i, i * 2) end
223223

@@ -247,7 +247,7 @@ defmodule HashDictTest do
247247
assert HashDict.size(dict) == 22
248248
end
249249

250-
test :large_range_merge do
250+
test "large dict merge" do
251251
dict1 = filled_dict(120)
252252
dict2 = Enum.reduce 118..122, HashDict.new, fn(i, d) -> HashDict.put(d, i, i * 2) end
253253

@@ -277,12 +277,22 @@ defmodule HashDictTest do
277277
assert HashDict.size(dict) == 122
278278
end
279279

280-
test :trie_contract do
280+
test "trie contraction" do
281281
dict = filled_dict(120)
282282
dict = Enum.reduce 16..120, dict, fn(x, acc) -> HashDict.delete(acc, x) end
283283
assert (Enum.filter 1..120, fn(x) -> HashDict.get(dict, x) == x end) == (Enum.sort 1..15)
284284
end
285285

286+
test "is zippable" do
287+
dict = filled_dict(8)
288+
list = Dict.to_list(dict)
289+
assert Enum.zip(list, list) == Enum.zip(dict, dict)
290+
291+
dict = filled_dict(120)
292+
list = Dict.to_list(dict)
293+
assert Enum.zip(list, list) == Enum.zip(dict, dict)
294+
end
295+
286296
defp smoke_test(range) do
287297
{ dict, _ } = Enum.reduce range, { HashDict.new, 1 }, fn(x, { acc, i }) ->
288298
acc = HashDict.put(acc, x, x)

lib/elixir/test/elixir/hash_set_test.exs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,27 +57,27 @@ defmodule HashSetTest do
5757
refute HashSet.member?(filled_set(120), 200)
5858
end
5959

60-
test :subset? do
60+
test "subset?" do
6161
assert HashSet.subset?(HashSet.new, HashSet.new)
6262
assert HashSet.subset?(filled_set(6), filled_set(10))
6363
assert HashSet.subset?(filled_set(6), filled_set(120))
6464

6565
refute HashSet.subset?(filled_set(120), filled_set(6))
6666
end
6767

68-
test :equal? do
68+
test "equal?" do
6969
assert HashSet.equal?(HashSet.new, HashSet.new)
7070
assert HashSet.equal?(filled_set(20), HashSet.delete(filled_set(21), 21))
7171
assert HashSet.equal?(filled_set(120), filled_set(120))
7272
end
7373

74-
test :empty do
74+
test "empty" do
7575
assert HashSet.empty filled_set(8) == HashSet.new
7676
assert HashSet.empty filled_set(20) == HashSet.new
7777
assert HashSet.empty filled_set(120) == HashSet.new
7878
end
7979

80-
test :to_list do
80+
test "to_list" do
8181
set = filled_set(8)
8282
list = set |> HashSet.to_list
8383
assert length(list) == 8
@@ -97,7 +97,7 @@ defmodule HashSetTest do
9797
assert list == Enum.to_list(set)
9898
end
9999

100-
test :delete do
100+
test "delete" do
101101
assert HashSet.delete(filled_set(8), 8) == filled_set(7)
102102
assert HashSet.delete(filled_set(8), 9) == filled_set(8)
103103
assert HashSet.delete(HashSet.new, 10) == HashSet.new
@@ -107,6 +107,16 @@ defmodule HashSetTest do
107107
assert HashSet.delete(filled_set(121), 121) == filled_set(120)
108108
end
109109

110+
test "is zippable" do
111+
set = filled_set(8)
112+
list = Dict.to_list(set)
113+
assert Enum.zip(list, list) == Enum.zip(set, set)
114+
115+
set = filled_set(120)
116+
list = Dict.to_list(set)
117+
assert Enum.zip(list, list) == Enum.zip(set, set)
118+
end
119+
110120
defp filled_set(range) do
111121
Enum.reduce 1..range, HashSet.new, &HashSet.put(&2, &1)
112122
end

0 commit comments

Comments
 (0)