lazily 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|