lazily 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +19 -5
- data/lib/lazily/associating.rb +44 -0
- data/lib/lazily/combining.rb +1 -0
- data/lib/lazily/version.rb +1 -1
- data/spec/lazily/associating_spec.rb +96 -0
- data/spec/lazily/threading_spec.rb +6 -6
- metadata +7 -4
data/README.md
CHANGED
@@ -41,14 +41,14 @@ See how it printed all the numbers from 1 to 10, indicating that the block given
|
|
41
41
|
4
|
42
42
|
=> [1, 4, 9, 16]
|
43
43
|
|
44
|
-
Same result, but notice how the block was only evaluated four times.
|
44
|
+
Same result, but notice how the block was only evaluated four times.
|
45
45
|
|
46
46
|
Lazy pipelines
|
47
47
|
--------------
|
48
48
|
|
49
49
|
By combining two or more lazy operations, you can create an efficient "pipeline", e.g.
|
50
50
|
|
51
|
-
User.to_enum(:find_each).lazily.select do |u|
|
51
|
+
User.to_enum(:find_each).lazily.select do |u|
|
52
52
|
u.first_name[0] == u.last_name[0]
|
53
53
|
end.collect(&:company).uniq.to_a
|
54
54
|
|
@@ -73,7 +73,7 @@ This is analogous to a Unix shell pipeline, though of course here we're talking
|
|
73
73
|
Lazy multi-threaded processing
|
74
74
|
------------------------------
|
75
75
|
|
76
|
-
The `#in_threads` method is a multi-threaded version of `#collect`, allowing multiple elements of a collection to be processed in parallel. It requires a numeric argument specifying the maximum number of Threads to use.
|
76
|
+
The `#in_threads` method is a multi-threaded version of `#collect`, allowing multiple elements of a collection to be processed in parallel. It requires a numeric argument specifying the maximum number of Threads to use.
|
77
77
|
|
78
78
|
Benchmark.realtime do
|
79
79
|
[1,2,3,4].lazily.in_threads(10) do |x|
|
@@ -82,7 +82,7 @@ The `#in_threads` method is a multi-threaded version of `#collect`, allowing mul
|
|
82
82
|
end.to_a #=> [2,4,6,8]
|
83
83
|
end.to_i #=> 1
|
84
84
|
|
85
|
-
Outputs will be yielded in the expected order, making it a drop-in replacement for `#collect`.
|
85
|
+
Outputs will be yielded in the expected order, making it a drop-in replacement for `#collect`.
|
86
86
|
|
87
87
|
Unlike some other "parallel map" implementations, the output of `#in_threads` is lazy (though it does need to pre-fetch elements from the source collection as required to start Threads).
|
88
88
|
|
@@ -116,10 +116,24 @@ A block can be provided to determine the sort-order.
|
|
116
116
|
Lazily.merge(array1, array2) { |x| x.length }
|
117
117
|
#=> %w(a dd eee cccc bbbbb)
|
118
118
|
|
119
|
+
`Lazily.associate` matches up "like" elements from separate collections. Again, it assumes it's inputs sorted.
|
120
|
+
|
121
|
+
fruit = %w(apple banana orange)
|
122
|
+
nautical_terms = %w(anchor boat flag)
|
123
|
+
Lazily.associate(:fruit => fruit, :nautical => nautical_terms) do |word|
|
124
|
+
word.chars.first
|
125
|
+
end
|
126
|
+
#=> [
|
127
|
+
{ :fruit => ["apple"], :nautical => ["anchor"] },
|
128
|
+
{ :fruit => ["banana"], :nautical => ["boat"] },
|
129
|
+
{ :fruit => [], :nautical => ["flag"] },
|
130
|
+
{ :fruit => ["orange"], :nautical => [] }
|
131
|
+
]
|
132
|
+
|
119
133
|
Same but different
|
120
134
|
------------------
|
121
135
|
|
122
|
-
There are numerous similar implementations of lazy operations on Enumerables.
|
136
|
+
There are numerous similar implementations of lazy operations on Enumerables.
|
123
137
|
|
124
138
|
### Lazily vs. Enumerating
|
125
139
|
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "lazily/enumerable"
|
2
|
+
|
3
|
+
module Lazily
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
# Associate "like" elements of two or more Enumerables.
|
8
|
+
#
|
9
|
+
# @param labelled_enumerables [Hash<key,Enumerable>]
|
10
|
+
# @return [Enumerable] a lazy collection of Hashes
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# Lazily.associate(a: [1,2,4], b: [1,3,4,4])
|
14
|
+
# #=> [
|
15
|
+
# { a: [1], b: [1] },
|
16
|
+
# { a: [2] },
|
17
|
+
# { b: [3] },
|
18
|
+
# { a: [4], b: [4, 4] }
|
19
|
+
# ]
|
20
|
+
#
|
21
|
+
def associate(source_map, &block)
|
22
|
+
labels = source_map.keys
|
23
|
+
tagged_element_sources = source_map.map do |label, enumerable|
|
24
|
+
enumerable.lazily.map do |value|
|
25
|
+
key = block ? block.call(value) : value
|
26
|
+
TaggedElement.new(key, value, label)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
tagged_elements = Lazily.merge(*tagged_element_sources) { |te| te.key }
|
30
|
+
tagged_elements.chunk { |te| te.key }.map do |_, tagged_elements|
|
31
|
+
association = {}
|
32
|
+
labels.each { |label| association[label] = [] }
|
33
|
+
tagged_elements.each do |te|
|
34
|
+
association[te.label] << te.value
|
35
|
+
end
|
36
|
+
association
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
TaggedElement = Struct.new(:key, :value, :label)
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
data/lib/lazily/combining.rb
CHANGED
data/lib/lazily/version.rb
CHANGED
@@ -0,0 +1,96 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Lazily, "associating" do
|
4
|
+
|
5
|
+
describe ".associate" do
|
6
|
+
|
7
|
+
context "with two identical Enumerables" do
|
8
|
+
|
9
|
+
let(:collectionA) { [2,4,6] }
|
10
|
+
let(:collectionB) { collectionA }
|
11
|
+
|
12
|
+
let(:result) do
|
13
|
+
Lazily.associate(:A => collectionA, :B => collectionB)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "yields matched pairs" do
|
17
|
+
expect(result.to_a).to eq [
|
18
|
+
{ :A => [2], :B => [2] },
|
19
|
+
{ :A => [4], :B => [4] },
|
20
|
+
{ :A => [6], :B => [6] }
|
21
|
+
]
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
context "with three identical Enumerables" do
|
27
|
+
|
28
|
+
let(:collectionA) { [1,2]}
|
29
|
+
let(:collectionB) { collectionA }
|
30
|
+
let(:collectionC) { collectionA }
|
31
|
+
|
32
|
+
let(:result) do
|
33
|
+
Lazily.associate(:A => collectionA, :B => collectionB, :C => collectionC)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "yields matched triples" do
|
37
|
+
expect(result.to_a).to eq [
|
38
|
+
{ :A => [1], :B => [1], :C => [1] },
|
39
|
+
{ :A => [2], :B => [2], :C => [2] }
|
40
|
+
]
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
context "when elements are missing" do
|
46
|
+
|
47
|
+
let(:collectionA) { [1,2] }
|
48
|
+
let(:collectionB) { [2,3] }
|
49
|
+
let(:collectionC) { [1,3] }
|
50
|
+
|
51
|
+
let(:result) do
|
52
|
+
Lazily.associate(:A => collectionA, :B => collectionB, :C => collectionC)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "returns an empty Array for the corresponding label" do
|
56
|
+
expect(result.to_a).to eq [
|
57
|
+
{ :A => [1], :B => [ ], :C => [1] },
|
58
|
+
{ :A => [2], :B => [2], :C => [ ] },
|
59
|
+
{ :A => [ ], :B => [3], :C => [3] }
|
60
|
+
]
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
context "with a block" do
|
66
|
+
|
67
|
+
let(:colours) { %w(blue red) }
|
68
|
+
let(:fruits) { %w(apple banana cranberry) }
|
69
|
+
let(:shapes) { %w(circle rectangle triangle) }
|
70
|
+
|
71
|
+
let(:result) do
|
72
|
+
Lazily.associate(:c => colours, :f => fruits, :s => shapes) do |word|
|
73
|
+
word.chars.first
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it "uses the block to associate the elements" do
|
78
|
+
expect(result.to_a).to eq [
|
79
|
+
{ :c => [], :f => ['apple'], :s => [] },
|
80
|
+
{ :c => ['blue'], :f => ['banana'], :s => [] },
|
81
|
+
{ :c => [], :f => ['cranberry'], :s => ['circle'] },
|
82
|
+
{ :c => ['red'], :f => [], :s => ['rectangle'] },
|
83
|
+
{ :c => [], :f => [], :s => ['triangle'] }
|
84
|
+
]
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
it "is lazy" do
|
90
|
+
result = Lazily.associate(:a => [1,2].ecetera, :b => [3,4].ecetera )
|
91
|
+
expect(result.first).to eq(:a => [1], :b => [])
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end if ::Enumerable.method_defined?(:chunk)
|
@@ -16,27 +16,27 @@ describe Lazily, "threading" do
|
|
16
16
|
[1,2,3].ecetera.lazily.in_threads(2) { |x| x * 2 }.should be_lazy
|
17
17
|
end
|
18
18
|
|
19
|
-
def round(n, accuracy =
|
20
|
-
(n
|
19
|
+
def round(n, accuracy = 20)
|
20
|
+
(n * accuracy).round.to_f / accuracy
|
21
21
|
end
|
22
22
|
|
23
23
|
it "runs the specified number of threads in parallel" do
|
24
|
-
delays = [0.
|
24
|
+
delays = [0.05, 0.05, 0.05]
|
25
25
|
start = Time.now
|
26
26
|
delays.lazily.in_threads(2) do |delay|
|
27
27
|
sleep(delay)
|
28
28
|
end.to_a
|
29
|
-
round(Time.now - start).should eq(0.
|
29
|
+
round(Time.now - start).should eq(0.1)
|
30
30
|
end
|
31
31
|
|
32
32
|
it "acts as a sliding window" do
|
33
|
-
delays = [0.1, 0.
|
33
|
+
delays = [0.1, 0.15, 0.05, 0.05, 0.05]
|
34
34
|
start = Time.now
|
35
35
|
elapsed_times = delays.lazily.in_threads(3) do |delay|
|
36
36
|
sleep(delay)
|
37
37
|
round(Time.now - start)
|
38
38
|
end
|
39
|
-
elapsed_times.to_a.should eq([0.1, 0.
|
39
|
+
elapsed_times.to_a.should eq([0.1, 0.15, 0.05, 0.15, 0.2])
|
40
40
|
end
|
41
41
|
|
42
42
|
it "surfaces exceptions" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lazily
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-07-14 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: ! " Lazily implements \"lazy\" versions of many Enumerable methods,\n
|
15
15
|
\ allowing streamed processing of large (or even infinite) collections.\n\n It's
|
@@ -30,6 +30,7 @@ files:
|
|
30
30
|
- benchmarks/pipeline_bench.rb
|
31
31
|
- lazily.gemspec
|
32
32
|
- lib/lazily.rb
|
33
|
+
- lib/lazily/associating.rb
|
33
34
|
- lib/lazily/combining.rb
|
34
35
|
- lib/lazily/concatenating.rb
|
35
36
|
- lib/lazily/enumerable.rb
|
@@ -40,6 +41,7 @@ files:
|
|
40
41
|
- lib/lazily/threading.rb
|
41
42
|
- lib/lazily/version.rb
|
42
43
|
- lib/lazily/zipping.rb
|
44
|
+
- spec/lazily/associating_spec.rb
|
43
45
|
- spec/lazily/bugs_spec.rb
|
44
46
|
- spec/lazily/concatenating_spec.rb
|
45
47
|
- spec/lazily/filtering_spec.rb
|
@@ -63,7 +65,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
63
65
|
version: '0'
|
64
66
|
segments:
|
65
67
|
- 0
|
66
|
-
hash:
|
68
|
+
hash: 3722161259490547720
|
67
69
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
70
|
none: false
|
69
71
|
requirements:
|
@@ -72,7 +74,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
72
74
|
version: '0'
|
73
75
|
segments:
|
74
76
|
- 0
|
75
|
-
hash:
|
77
|
+
hash: 3722161259490547720
|
76
78
|
requirements: []
|
77
79
|
rubyforge_project:
|
78
80
|
rubygems_version: 1.8.23
|
@@ -80,6 +82,7 @@ signing_key:
|
|
80
82
|
specification_version: 3
|
81
83
|
summary: Lazy Enumerables for everybody!
|
82
84
|
test_files:
|
85
|
+
- spec/lazily/associating_spec.rb
|
83
86
|
- spec/lazily/bugs_spec.rb
|
84
87
|
- spec/lazily/concatenating_spec.rb
|
85
88
|
- spec/lazily/filtering_spec.rb
|