lazily 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/Rakefile +12 -0
- data/lazily.gemspec +28 -0
- data/lib/lazily.rb +4 -0
- data/lib/lazily/combining.rb +3 -0
- data/lib/lazily/concatenating.rb +37 -0
- data/lib/lazily/enumerable.rb +13 -0
- data/lib/lazily/filtering.rb +118 -0
- data/lib/lazily/merging.rb +72 -0
- data/lib/lazily/prefetching.rb +43 -0
- data/lib/lazily/proxy.rb +32 -0
- data/lib/lazily/threading.rb +16 -0
- data/lib/lazily/version.rb +3 -0
- data/lib/lazily/zipping.rb +46 -0
- data/spec/lazily/concatenating_spec.rb +39 -0
- data/spec/lazily/filtering_spec.rb +134 -0
- data/spec/lazily/merging_spec.rb +35 -0
- data/spec/lazily/prefetching_spec.rb +69 -0
- data/spec/lazily/threading_spec.rb +50 -0
- data/spec/lazily/zipping_spec.rb +37 -0
- data/spec/lazily_spec.rb +17 -0
- data/spec/spec_helper.rb +41 -0
- metadata +78 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YTE1ODg4ZDgzNDFjMTc0YzlhMWE0ODU5MWRmNjcyYzQyMTNkMGNmYw==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MDlhZjZhMWI5ZDNiMDdmMzk3OWU4NGM4MGVmYzhlODUyMzEyYzg3Ng==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NWNkYWNkOTViODA2OWM0ZWM1NWJkNTlhY2Y2NWUwNmQ0NGQ1MjZiZjFkODgz
|
10
|
+
MGFiYjRlYmZjYTQzZDkyYjMyMzFhYjU1ZTE3Y2JmMTMwZWEwMzBhYWFhZDIw
|
11
|
+
Nzg3Y2I0MjUyYzQzOTJkOTg2MmI4NjdlZmQ0MTkwZTkzMzA2YTY=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
OGQ5ZTBhZTgyNTUwOTc3NTEwOGE3MTgwMmM0OWFiZjBiZDY1ZGJjYWRlMDJl
|
14
|
+
YTM2NmY5ZmYzMGRhNDI3Yjk4NTgwYTU5MTdmYjllM2U4Y2M0ZDVlODM1ZTI1
|
15
|
+
MThmOTkyZDNhZDNjNDVkYWVkZjBmMDJmMGEwMGZjMGRiZTAxYzI=
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
data/lazily.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
$:.push File.expand_path("../lib", __FILE__)
|
4
|
+
require "lazily/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
|
8
|
+
s.name = "lazily"
|
9
|
+
s.version = Lazily::VERSION.dup
|
10
|
+
s.platform = Gem::Platform::RUBY
|
11
|
+
s.authors = ["Mike Williams"]
|
12
|
+
s.email = "mdub@dogbiscuit.org"
|
13
|
+
s.homepage = "http://github.com/mdub/lazily"
|
14
|
+
|
15
|
+
s.summary = %{Lazy Enumerables for everybody!}
|
16
|
+
s.description = <<-EOT
|
17
|
+
Lazily implements "lazy" versions of many Enumerable methods,
|
18
|
+
allowing streamed processing of large (or even infinite) collections.
|
19
|
+
|
20
|
+
It's equivalent to Ruby-2.x's Enumerable#lazy, but is implemented in
|
21
|
+
pure Ruby, and works even in Ruby-1.8.x.
|
22
|
+
EOT
|
23
|
+
|
24
|
+
s.files = `git ls-files`.split("\n")
|
25
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
26
|
+
s.require_paths = ["lib"]
|
27
|
+
|
28
|
+
end
|
data/lib/lazily.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require "lazily/enumerable"
|
2
|
+
|
3
|
+
module Lazily
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def concat(*enumerables)
|
8
|
+
Concatenator.new(enumerables)
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
module Enumerable
|
14
|
+
|
15
|
+
def concat(*others)
|
16
|
+
Lazily.concat(self, *others)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
class Concatenator
|
22
|
+
|
23
|
+
include Lazily::Enumerable
|
24
|
+
|
25
|
+
def initialize(enumerables)
|
26
|
+
@enumerables = enumerables
|
27
|
+
end
|
28
|
+
|
29
|
+
def each(&block)
|
30
|
+
@enumerables.each do |enumerable|
|
31
|
+
enumerable.each(&block)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require "lazily/enumerable"
|
2
|
+
|
3
|
+
module Lazily
|
4
|
+
|
5
|
+
module Enumerable
|
6
|
+
|
7
|
+
def collect
|
8
|
+
Filter.new do |output|
|
9
|
+
each do |element|
|
10
|
+
output.call yield(element)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
alias map collect
|
16
|
+
|
17
|
+
def select
|
18
|
+
Filter.new do |output|
|
19
|
+
each do |element|
|
20
|
+
output.call(element) if yield(element)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
alias find_all select
|
26
|
+
|
27
|
+
def reject
|
28
|
+
Filter.new do |output|
|
29
|
+
each do |element|
|
30
|
+
output.call(element) unless yield(element)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def uniq
|
36
|
+
Filter.new do |output|
|
37
|
+
seen = Set.new
|
38
|
+
each do |element|
|
39
|
+
output.call(element) if seen.add?(element)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def uniq_by
|
45
|
+
Filter.new do |output|
|
46
|
+
seen = Set.new
|
47
|
+
each do |element|
|
48
|
+
output.call(element) if seen.add?(yield element)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def take(n)
|
54
|
+
Filter.new do |output|
|
55
|
+
if n > 0
|
56
|
+
each_with_index do |element, index|
|
57
|
+
output.call(element)
|
58
|
+
throw Lazily::Filter::DONE if index + 1 == n
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def take_while
|
65
|
+
Filter.new do |output|
|
66
|
+
each do |element|
|
67
|
+
throw Lazily::Filter::DONE unless yield(element)
|
68
|
+
output.call(element)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def drop(n)
|
74
|
+
Filter.new do |output|
|
75
|
+
each_with_index do |element, index|
|
76
|
+
next if index < n
|
77
|
+
output.call(element)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def drop_while
|
83
|
+
Filter.new do |output|
|
84
|
+
take = false
|
85
|
+
each do |element|
|
86
|
+
take ||= !yield(element)
|
87
|
+
output.call(element) if take
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def [](n)
|
93
|
+
drop(n).first
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
class Filter
|
99
|
+
|
100
|
+
include Lazily::Enumerable
|
101
|
+
|
102
|
+
def initialize(&generator)
|
103
|
+
@generator = generator
|
104
|
+
end
|
105
|
+
|
106
|
+
DONE = "Lazily::DONE".to_sym
|
107
|
+
|
108
|
+
def each
|
109
|
+
return to_enum unless block_given?
|
110
|
+
yielder = proc { |x| yield x }
|
111
|
+
catch DONE do
|
112
|
+
@generator.call(yielder)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require "lazily/enumerable"
|
2
|
+
|
3
|
+
module Lazily
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def merge(*enumerables)
|
8
|
+
Merger.new(enumerables)
|
9
|
+
end
|
10
|
+
|
11
|
+
def merge_by(*enumerables, &block)
|
12
|
+
Merger.new(enumerables, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
class Merger
|
18
|
+
|
19
|
+
include Lazily::Enumerable
|
20
|
+
|
21
|
+
def initialize(enumerables, &transformer)
|
22
|
+
@enumerables = enumerables
|
23
|
+
@transformer = transformer
|
24
|
+
end
|
25
|
+
|
26
|
+
def each(&block)
|
27
|
+
return to_enum unless block_given?
|
28
|
+
Generator.new(@enumerables.map(&:to_enum), @transformer).each(&block)
|
29
|
+
end
|
30
|
+
|
31
|
+
class Generator
|
32
|
+
|
33
|
+
def initialize(enumerators, transformer)
|
34
|
+
@enumerators = enumerators
|
35
|
+
@transformer = transformer
|
36
|
+
end
|
37
|
+
|
38
|
+
def each
|
39
|
+
loop do
|
40
|
+
discard_empty_enumerators
|
41
|
+
break if @enumerators.empty?
|
42
|
+
yield next_enumerator.next
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def discard_empty_enumerators
|
49
|
+
@enumerators.delete_if do |e|
|
50
|
+
begin
|
51
|
+
e.peek
|
52
|
+
false
|
53
|
+
rescue StopIteration
|
54
|
+
true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def next_enumerator
|
60
|
+
@enumerators.min_by { |enumerator| transform(enumerator.peek) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def transform(item)
|
64
|
+
return item unless @transformer
|
65
|
+
@transformer.call(item)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require "lazily/enumerable"
|
2
|
+
|
3
|
+
module Lazily
|
4
|
+
|
5
|
+
module Enumerable
|
6
|
+
|
7
|
+
def prefetch(size)
|
8
|
+
Prefetcher.new(self, size)
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
class Prefetcher
|
14
|
+
|
15
|
+
include Lazily::Enumerable
|
16
|
+
|
17
|
+
def initialize(source, buffer_size)
|
18
|
+
@source = source.to_enum
|
19
|
+
@buffer_size = buffer_size
|
20
|
+
end
|
21
|
+
|
22
|
+
def each(&block)
|
23
|
+
return @source.each(&block) if @buffer_size <= 0
|
24
|
+
buffered_elements = []
|
25
|
+
i = 0
|
26
|
+
@source.each do |element|
|
27
|
+
slot = i % @buffer_size
|
28
|
+
if i >= @buffer_size
|
29
|
+
yield buffered_elements[slot]
|
30
|
+
end
|
31
|
+
buffered_elements[slot] = element
|
32
|
+
i += 1
|
33
|
+
end
|
34
|
+
buffered_elements.size.times do
|
35
|
+
slot = i % buffered_elements.size
|
36
|
+
yield buffered_elements[slot]
|
37
|
+
i += 1
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
data/lib/lazily/proxy.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require "lazily/enumerable"
|
2
|
+
|
3
|
+
def Lazily(enumerable)
|
4
|
+
Lazily::Proxy.new(enumerable)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ::Enumerable
|
8
|
+
|
9
|
+
def lazily
|
10
|
+
Lazily::Proxy.new(self)
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
module Lazily
|
16
|
+
|
17
|
+
class Proxy
|
18
|
+
|
19
|
+
include Lazily::Enumerable
|
20
|
+
|
21
|
+
def initialize(source)
|
22
|
+
@source = source
|
23
|
+
end
|
24
|
+
|
25
|
+
def each(&block)
|
26
|
+
return to_enum unless block
|
27
|
+
@source.each(&block)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "lazily/prefetching"
|
2
|
+
|
3
|
+
module Lazily
|
4
|
+
|
5
|
+
module Enumerable
|
6
|
+
|
7
|
+
def in_threads(max_threads, &block)
|
8
|
+
collect do |item|
|
9
|
+
Thread.new { block.call(item) }
|
10
|
+
end.prefetch(max_threads - 1).collect do |thread|
|
11
|
+
thread.join; thread.value
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "lazily/enumerable"
|
2
|
+
|
3
|
+
module Lazily
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def zip(*enumerables)
|
8
|
+
Zipper.new(enumerables)
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
module Enumerable
|
14
|
+
|
15
|
+
def zip(*others)
|
16
|
+
Lazily.zip(self, *others)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
class Zipper
|
22
|
+
|
23
|
+
include Lazily::Enumerable
|
24
|
+
|
25
|
+
def initialize(enumerables)
|
26
|
+
@enumerables = enumerables
|
27
|
+
end
|
28
|
+
|
29
|
+
def each
|
30
|
+
enumerators = @enumerables.map(&:to_enum)
|
31
|
+
while true
|
32
|
+
chunk = enumerators.map do |enumerator|
|
33
|
+
begin
|
34
|
+
enumerator.next
|
35
|
+
rescue StopIteration
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
break if chunk.all?(&:nil?)
|
40
|
+
yield chunk
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Lazily, "concatenating" do
|
4
|
+
|
5
|
+
describe ".concat" do
|
6
|
+
|
7
|
+
let(:array1) { [1,5,3] }
|
8
|
+
let(:array2) { [2,9,4] }
|
9
|
+
|
10
|
+
it "concatenates multiple Enumerables" do
|
11
|
+
result = Lazily.concat([1,5,3], [2,9,4])
|
12
|
+
result.to_a.should == [1,5,3,2,9,4]
|
13
|
+
end
|
14
|
+
|
15
|
+
it "is lazy" do
|
16
|
+
result = Lazily.concat([3,4], [1,2].with_time_bomb)
|
17
|
+
result.take(3).to_a.should == [3,4,1]
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#concat" do
|
23
|
+
|
24
|
+
let(:array1) { [1,5,3] }
|
25
|
+
let(:array2) { [2,9,4] }
|
26
|
+
|
27
|
+
it "concatenates multiple Enumerables" do
|
28
|
+
result = [1,5,3].lazily.concat([2,9,4])
|
29
|
+
result.to_a.should == [1,5,3,2,9,4]
|
30
|
+
end
|
31
|
+
|
32
|
+
it "is lazy" do
|
33
|
+
result = [3,4].lazily.concat([1,2].with_time_bomb)
|
34
|
+
result.take(3).to_a.should == [3,4,1]
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Lazily, "filter" do
|
4
|
+
|
5
|
+
describe "#collect" do
|
6
|
+
|
7
|
+
it "transforms items" do
|
8
|
+
[1,2,3].lazily.collect { |x| x * 2 }.to_a.should == [2,4,6]
|
9
|
+
end
|
10
|
+
|
11
|
+
it "is lazy" do
|
12
|
+
[1,2,3].with_time_bomb.lazily.collect { |x| x * 2 }.first.should == 2
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "#select" do
|
18
|
+
|
19
|
+
it "excludes items that don't pass the predicate" do
|
20
|
+
(1..6).lazily.select { |x| x%2 == 0 }.to_a.should == [2,4,6]
|
21
|
+
end
|
22
|
+
|
23
|
+
it "is lazy" do
|
24
|
+
(1..6).with_time_bomb.lazily.select { |x| x%2 == 0 }.first == 2
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#reject" do
|
30
|
+
|
31
|
+
it "excludes items that do pass the predicate" do
|
32
|
+
(1..6).lazily.reject { |x| x%2 == 0 }.to_a.should == [1,3,5]
|
33
|
+
end
|
34
|
+
|
35
|
+
it "is lazy" do
|
36
|
+
(1..6).with_time_bomb.lazily.reject { |x| x%2 == 0 }.first == 1
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "#uniq" do
|
42
|
+
|
43
|
+
it "removes duplicates" do
|
44
|
+
[1,3,2,4,3,5,4,6].lazily.uniq.to_a.should == [1,3,2,4,5,6]
|
45
|
+
end
|
46
|
+
|
47
|
+
it "is lazy" do
|
48
|
+
[1,2,3].with_time_bomb.lazily.uniq.first.should == 1
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#uniq_by" do
|
54
|
+
|
55
|
+
it "uses the block to derive identity" do
|
56
|
+
@array = %w(A1 A2 B1 A3 C1 B2 C2)
|
57
|
+
@array.lazily.uniq_by { |s| s[0,1] }.to_a.should == %w(A1 B1 C1)
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "#take" do
|
63
|
+
|
64
|
+
it "includes the specified number" do
|
65
|
+
@array = [1,2,3,4]
|
66
|
+
@array.lazily.take(3).to_a.should == [1,2,3]
|
67
|
+
end
|
68
|
+
|
69
|
+
it "is lazy" do
|
70
|
+
[1,2].with_time_bomb.lazily.take(2).to_a.should == [1,2]
|
71
|
+
end
|
72
|
+
|
73
|
+
it "copes with 0" do
|
74
|
+
[].with_time_bomb.lazily.take(0).to_a.should == []
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "#take_while" do
|
80
|
+
|
81
|
+
it "takees elements as long as the predicate is true" do
|
82
|
+
@array = [2,4,6,3]
|
83
|
+
@array.lazily.take_while(&:even?).to_a.should == [2,4,6]
|
84
|
+
end
|
85
|
+
|
86
|
+
it "is lazy" do
|
87
|
+
[2,3].with_time_bomb.lazily.take_while(&:even?).to_a.should == [2]
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "#drop" do
|
93
|
+
|
94
|
+
it "excludes the specified number" do
|
95
|
+
@array = [1,2,3,4]
|
96
|
+
@array.lazily.drop(2).to_a.should == [3,4]
|
97
|
+
end
|
98
|
+
|
99
|
+
it "is lazy" do
|
100
|
+
[1,2,3,4].with_time_bomb.lazily.drop(2).lazily.take(1).to_a.should == [3]
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "#drop_while" do
|
106
|
+
|
107
|
+
it "drops elements as long as the predicate is true" do
|
108
|
+
@array = [2,4,6,3,4]
|
109
|
+
@array.lazily.drop_while(&:even?).to_a.should == [3,4]
|
110
|
+
end
|
111
|
+
|
112
|
+
it "is lazy" do
|
113
|
+
[2,3].with_time_bomb.lazily.drop_while(&:even?).lazily.take(1).to_a.should == [3]
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "#[]" do
|
119
|
+
|
120
|
+
before do
|
121
|
+
@evens = [1,2,3,4,5].lazily.collect { |x| x * 2 }
|
122
|
+
end
|
123
|
+
|
124
|
+
it "finds the specified element" do
|
125
|
+
@evens.lazily[2].should == 6
|
126
|
+
end
|
127
|
+
|
128
|
+
it "is lazy" do
|
129
|
+
@evens.with_time_bomb.lazily[3].should == 8
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Lazily, :needs_enumerators => true do
|
4
|
+
|
5
|
+
describe ".merge" do
|
6
|
+
|
7
|
+
it "merges multiple Enumerators" do
|
8
|
+
@array1 = [1,3,6]
|
9
|
+
@array2 = [2,4,7]
|
10
|
+
@array3 = [5,8]
|
11
|
+
@merge = Lazily.merge(@array1, @array2, @array3)
|
12
|
+
@merge.to_a.should == [1,2,3,4,5,6,7,8]
|
13
|
+
end
|
14
|
+
|
15
|
+
it "is lazy" do
|
16
|
+
@enum1 = [1,3,6]
|
17
|
+
@enum2 = [2,4,7].with_time_bomb
|
18
|
+
@merge = Lazily.merge(@enum1, @enum2)
|
19
|
+
@merge.take(4).to_a.should == [1,2,3,4]
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
describe ".merge_by" do
|
25
|
+
|
26
|
+
it "uses the block to determine order" do
|
27
|
+
@array1 = %w(cccc dd a)
|
28
|
+
@array2 = %w(eeeee bbb)
|
29
|
+
@merge = Lazily.merge_by(@array1, @array2) { |s| -s.length }
|
30
|
+
@merge.to_a.should == %w(eeeee cccc bbb dd a)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Lazily do
|
4
|
+
|
5
|
+
Counter = Class.new do
|
6
|
+
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
def initialize(source)
|
10
|
+
@source = source
|
11
|
+
@number_yielded = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def each
|
15
|
+
@source.each do |item|
|
16
|
+
@number_yielded += 1
|
17
|
+
yield item
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :number_yielded
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#prefetch" do
|
26
|
+
|
27
|
+
let(:source) { [1, 2, 3, 4, nil, false, 7] }
|
28
|
+
|
29
|
+
it "yields all items" do
|
30
|
+
source.lazily.prefetch(2).to_a.should eq(source)
|
31
|
+
source.lazily.prefetch(3).to_a.should eq(source)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "is stateless" do
|
35
|
+
source.lazily.prefetch(2).first.should eq(source.first)
|
36
|
+
source.lazily.prefetch(2).first.should eq(source.first)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "is lazy" do
|
40
|
+
source.with_time_bomb.lazily.prefetch(2).first.should eq(source.first)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "pre-computes the specified number of elements" do
|
44
|
+
counter = Counter.new(source)
|
45
|
+
counter.lazily.prefetch(2).first
|
46
|
+
counter.number_yielded.should eq(3)
|
47
|
+
end
|
48
|
+
|
49
|
+
context "with a buffer size of zero" do
|
50
|
+
|
51
|
+
it "does not pre-fetch anything" do
|
52
|
+
counter = Counter.new(source)
|
53
|
+
counter.lazily.prefetch(0).first
|
54
|
+
counter.number_yielded.should eq(1)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
context "with a buffer bigger than the source Enumerable" do
|
60
|
+
|
61
|
+
it "yields all items" do
|
62
|
+
source.lazily.prefetch(20).to_a.should eq(source)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Lazily, "threading" do
|
4
|
+
|
5
|
+
describe "#in_threads" do
|
6
|
+
|
7
|
+
it "acts like #collect" do
|
8
|
+
[1,2,3].lazily.in_threads(5) { |x| x * 2 }.to_a.should == [2,4,6]
|
9
|
+
end
|
10
|
+
|
11
|
+
it "runs things in separate threads" do
|
12
|
+
[1,2,3].lazily.in_threads(5) { Thread.current.object_id }.to_a.uniq.size.should eq(3)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "is lazy" do
|
16
|
+
[1,2,3].with_time_bomb.lazily.in_threads(2) { |x| x * 2 }.first.should == 2
|
17
|
+
end
|
18
|
+
|
19
|
+
def round(n, accuracy = 0.02)
|
20
|
+
(n / accuracy).round.to_f * accuracy
|
21
|
+
end
|
22
|
+
|
23
|
+
it "runs the specified number of threads in parallel" do
|
24
|
+
delays = [0.03, 0.03, 0.03]
|
25
|
+
start = Time.now
|
26
|
+
delays.lazily.in_threads(2) do |delay|
|
27
|
+
sleep(delay)
|
28
|
+
end.to_a
|
29
|
+
round(Time.now - start).should eq(0.06)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "acts as a sliding window" do
|
33
|
+
delays = [0.1, 0.08, 0.06, 0.04, 0.02]
|
34
|
+
start = Time.now
|
35
|
+
elapsed_times = delays.lazily.in_threads(3) do |delay|
|
36
|
+
sleep(delay)
|
37
|
+
round(Time.now - start)
|
38
|
+
end
|
39
|
+
elapsed_times.to_a.should eq([0.1, 0.08, 0.06, 0.14, 0.12])
|
40
|
+
end
|
41
|
+
|
42
|
+
it "surfaces exceptions" do
|
43
|
+
lambda do
|
44
|
+
[1,2,3].lazily.in_threads(5) { raise "hell" }.to_a
|
45
|
+
end.should raise_error(RuntimeError, "hell")
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Lazily, "zipping", :needs_enumerators => true do
|
4
|
+
|
5
|
+
let(:array1) { [1,3,6] }
|
6
|
+
let(:array2) { [2,4,7] }
|
7
|
+
let(:array3) { [5,8] }
|
8
|
+
|
9
|
+
describe ".zip" do
|
10
|
+
|
11
|
+
it "zips together multiple Enumerables" do
|
12
|
+
zip = Lazily.zip(array1, array2, array3)
|
13
|
+
zip.to_a.should == [[1,2,5], [3,4,8], [6,7,nil]]
|
14
|
+
end
|
15
|
+
|
16
|
+
it "is lazy" do
|
17
|
+
zip = Lazily.zip(%w(a b c), [1,2].with_time_bomb)
|
18
|
+
zip.take(2).to_a.should == [["a", 1], ["b", 2]]
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#zip" do
|
24
|
+
|
25
|
+
it "zips an Enumerable with multiple others" do
|
26
|
+
zip = array1.lazily.zip(array2, array3)
|
27
|
+
zip.to_a.should == [[1,2,5], [3,4,8], [6,7,nil]]
|
28
|
+
end
|
29
|
+
|
30
|
+
it "is lazy" do
|
31
|
+
zip = %w(a b c).lazily.zip([1,2].with_time_bomb)
|
32
|
+
zip.take(2).to_a.should == [["a", 1], ["b", 2]]
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
data/spec/lazily_spec.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Lazily", "method" do
|
4
|
+
|
5
|
+
it "returns a lazy proxy" do
|
6
|
+
Lazily([1,2,3]).should be_lazy
|
7
|
+
end
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "Enumerable", "#lazily" do
|
12
|
+
|
13
|
+
it "returns a lazy proxy" do
|
14
|
+
[1,2,3].lazily.should be_lazy
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require "lazily"
|
2
|
+
|
3
|
+
class NotLazyEnough < StandardError; end
|
4
|
+
|
5
|
+
class WithTimeBomb
|
6
|
+
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
def initialize(source)
|
10
|
+
@source = source
|
11
|
+
end
|
12
|
+
|
13
|
+
def each(&block)
|
14
|
+
@source.each(&block)
|
15
|
+
raise NotLazyEnough
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
module Enumerable
|
21
|
+
|
22
|
+
# extend an Enumerable to throw an exception after last element
|
23
|
+
def with_time_bomb
|
24
|
+
WithTimeBomb.new(self)
|
25
|
+
end
|
26
|
+
|
27
|
+
unless method_defined?(:first)
|
28
|
+
def first
|
29
|
+
each do |first_item|
|
30
|
+
return first_item
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
RSpec.configure do |config|
|
38
|
+
unless defined?(::Enumerator)
|
39
|
+
config.filter_run_excluding :needs_enumerators => true
|
40
|
+
end
|
41
|
+
end
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lazily
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mike Williams
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-04-19 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: ! " Lazily implements \"lazy\" versions of many Enumerable methods,\n
|
14
|
+
\ allowing streamed processing of large (or even infinite) collections.\n\n It's
|
15
|
+
equivalent to Ruby-2.x's Enumerable#lazy, but is implemented in\n pure Ruby,
|
16
|
+
and works even in Ruby-1.8.x.\n"
|
17
|
+
email: mdub@dogbiscuit.org
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- .gitignore
|
23
|
+
- .rspec
|
24
|
+
- .travis.yml
|
25
|
+
- Gemfile
|
26
|
+
- Rakefile
|
27
|
+
- lazily.gemspec
|
28
|
+
- lib/lazily.rb
|
29
|
+
- lib/lazily/combining.rb
|
30
|
+
- lib/lazily/concatenating.rb
|
31
|
+
- lib/lazily/enumerable.rb
|
32
|
+
- lib/lazily/filtering.rb
|
33
|
+
- lib/lazily/merging.rb
|
34
|
+
- lib/lazily/prefetching.rb
|
35
|
+
- lib/lazily/proxy.rb
|
36
|
+
- lib/lazily/threading.rb
|
37
|
+
- lib/lazily/version.rb
|
38
|
+
- lib/lazily/zipping.rb
|
39
|
+
- spec/lazily/concatenating_spec.rb
|
40
|
+
- spec/lazily/filtering_spec.rb
|
41
|
+
- spec/lazily/merging_spec.rb
|
42
|
+
- spec/lazily/prefetching_spec.rb
|
43
|
+
- spec/lazily/threading_spec.rb
|
44
|
+
- spec/lazily/zipping_spec.rb
|
45
|
+
- spec/lazily_spec.rb
|
46
|
+
- spec/spec_helper.rb
|
47
|
+
homepage: http://github.com/mdub/lazily
|
48
|
+
licenses: []
|
49
|
+
metadata: {}
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ! '>='
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
requirements: []
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 2.0.0
|
67
|
+
signing_key:
|
68
|
+
specification_version: 4
|
69
|
+
summary: Lazy Enumerables for everybody!
|
70
|
+
test_files:
|
71
|
+
- spec/lazily/concatenating_spec.rb
|
72
|
+
- spec/lazily/filtering_spec.rb
|
73
|
+
- spec/lazily/merging_spec.rb
|
74
|
+
- spec/lazily/prefetching_spec.rb
|
75
|
+
- spec/lazily/threading_spec.rb
|
76
|
+
- spec/lazily/zipping_spec.rb
|
77
|
+
- spec/lazily_spec.rb
|
78
|
+
- spec/spec_helper.rb
|