lazily 0.1.2 → 0.2.0
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.
- 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
|