burt-delay_queue 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ /.rvmrc
2
+ /pkg
@@ -0,0 +1,21 @@
1
+ $: << File.expand_path('../lib', __FILE__)
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'burt-delay_queue'
5
+ s.version = '1.1.0'
6
+ s.platform = Gem::Platform::RUBY
7
+ s.authors = ['Theo Hultberg']
8
+ s.email = ['theo@burtcorp.com']
9
+ s.homepage = 'https://github.com/iconara/delay_queue'
10
+ s.summary = 'A TTL based priority queue'
11
+ s.description = 'Delay queue keeps it\'s elements ordered by a timestamp, popping off the items with the lowest timestamp first'
12
+
13
+ s.rubyforge_project = 'delay_queue'
14
+
15
+ s.add_development_dependency 'rspec', '~> 2.5.0'
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ # s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ # s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ['lib']
21
+ end
@@ -0,0 +1,157 @@
1
+ # encoding: utf-8
2
+
3
+ if RUBY_PLATFORM == 'java'
4
+ require 'java'
5
+
6
+ class DelayQueue
7
+ java_import 'java.util.TreeMap'
8
+ java_import 'java.util.HashSet'
9
+
10
+ def initialize(clock=Time)
11
+ @clock = clock
12
+ @timestamp_to_elements = TreeMap.new
13
+ @element_to_timestamp = {}
14
+ end
15
+
16
+ def put(element, timestamp=Time.now.to_i, options={})
17
+ existing_timestamp = @element_to_timestamp[element]
18
+ if !existing_timestamp || existing_timestamp < timestamp || options[:force]
19
+ remove(element) if existing_timestamp
20
+ elements = @timestamp_to_elements.get(timestamp)
21
+ elements ||= HashSet.new
22
+ elements.add(element)
23
+ @timestamp_to_elements.put(timestamp, elements)
24
+ @element_to_timestamp[element] = timestamp
25
+ end
26
+ end
27
+
28
+ def remove(element)
29
+ timestamp = @element_to_timestamp.delete(element)
30
+ if timestamp
31
+ elements = @timestamp_to_elements.get(timestamp)
32
+ elements.remove(element)
33
+ @timestamp_to_elements.remove(timestamp) if elements.empty?
34
+ end
35
+ end
36
+
37
+ def pop(n=1)
38
+ popped_elements = []
39
+ loop do
40
+ entry = @timestamp_to_elements.first_entry
41
+ break unless entry
42
+ break if entry.key > @clock.now.to_i
43
+ elements = entry.value
44
+ iterator = elements.iterator
45
+ while iterator.has_next && popped_elements.size < n
46
+ element = iterator.next
47
+ @element_to_timestamp.delete(element)
48
+ iterator.remove
49
+ popped_elements << element
50
+ end
51
+ break unless elements.empty?
52
+ @timestamp_to_elements.delete(entry.key)
53
+ end
54
+ if n == 1
55
+ popped_elements.first
56
+ else
57
+ popped_elements
58
+ end
59
+ end
60
+
61
+ def pop_all
62
+ popped_elements = []
63
+ cutoff = @timestamp_to_elements.floor_key(@clock.now.to_i)
64
+ if cutoff
65
+ loop do
66
+ entry = @timestamp_to_elements.poll_first_entry
67
+ elements = entry.value
68
+ elements.each do |element|
69
+ @element_to_timestamp.delete(element)
70
+ popped_elements << element
71
+ end
72
+ break if entry.key == cutoff
73
+ end
74
+ end
75
+ popped_elements
76
+ end
77
+
78
+ def include?(element)
79
+ @element_to_timestamp.key?(element)
80
+ end
81
+
82
+ def to_h
83
+ @element_to_timestamp.dup
84
+ end
85
+
86
+ def size
87
+ @element_to_timestamp.size
88
+ end
89
+ end
90
+ else
91
+ require 'set'
92
+
93
+ class DelayQueue
94
+ def initialize(clock=Time)
95
+ @clock = clock
96
+ @elements = {}
97
+ @timestamps = SortedSet.new
98
+ @reverse_elements = Hash.new { |h, k| h[k] = Set.new }
99
+ end
100
+
101
+ def put(element, timestamp=Time.now.to_i, options={})
102
+ if @elements[element]
103
+ return unless options[:force] || timestamp > @elements[element]
104
+ remove(element)
105
+ end
106
+ @elements[element] = timestamp
107
+ @reverse_elements[timestamp] << element
108
+ @timestamps << timestamp
109
+ end
110
+
111
+ def remove(element)
112
+ timestamp = @elements.delete(element)
113
+ return unless timestamp
114
+ @reverse_elements[timestamp].delete(element)
115
+ if @reverse_elements[timestamp].empty?
116
+ @reverse_elements.delete(timestamp)
117
+ @timestamps.delete(timestamp)
118
+ end
119
+ end
120
+
121
+ def pop(n=1)
122
+ elements = peek_all.take(n)
123
+ elements.each { |e| remove(e) }
124
+ if n == 1
125
+ elements.first
126
+ else
127
+ elements
128
+ end
129
+ end
130
+
131
+ def pop_all
132
+ elements = peek_all
133
+ elements.each { |e| remove(e) }
134
+ elements
135
+ end
136
+
137
+ def include?(element)
138
+ @elements.key?(element)
139
+ end
140
+
141
+ def to_h
142
+ @elements.dup
143
+ end
144
+
145
+ def size
146
+ @elements.size
147
+ end
148
+
149
+ private
150
+
151
+ def peek_all
152
+ now = @clock.now.to_i
153
+ expired_timestamps = @timestamps.take_while { |ts| ts <= now }
154
+ expired_timestamps.flat_map { |ts| @reverse_elements[ts].to_a }
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,117 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative 'spec_helper'
4
+
5
+
6
+ class Clock
7
+ attr_accessor :now
8
+ end
9
+
10
+ describe DelayQueue do
11
+ before do
12
+ @clock = Clock.new
13
+ @q = DelayQueue.new(@clock)
14
+ end
15
+
16
+ describe '#put' do
17
+ it 'does not replace elements whose timestamp has been updated to an earlier time (by default)' do
18
+ @clock.now = 4
19
+ @q.put('blopp', 5)
20
+ @q.put('blipp', 4)
21
+ @q.put('blupp', 3)
22
+ @q.put('blopp', 1)
23
+ @q.pop.should == 'blupp'
24
+ end
25
+
26
+ it 'does not replace elements whose timestamp has been updated to an earlier time unless specifically asked to' do
27
+ @clock.now = 4
28
+ @q.put('blopp', 5)
29
+ @q.put('blipp', 4)
30
+ @q.put('blupp', 3)
31
+ @q.put('blopp', 1, :force => true)
32
+ @q.pop.should == 'blopp'
33
+ end
34
+ end
35
+
36
+ describe '#pop' do
37
+ it 'returns nil if no elements have expired' do
38
+ @clock.now = 1
39
+ @q.put('blipp', 4)
40
+ @q.pop.should be_nil
41
+ end
42
+
43
+ it 'returns the oldest expired element' do
44
+ @clock.now = 5
45
+ @q.put('blopp', 4)
46
+ @q.put('blipp', 3)
47
+ @q.pop.should == 'blipp'
48
+ end
49
+
50
+ it 'removes elements' do
51
+ @clock.now = 5
52
+ @q.put('blopp', 4)
53
+ @q.pop
54
+ @q.pop.should be_nil
55
+ end
56
+
57
+ it 'returns as many expired elements as you want, in age order' do
58
+ @clock.now = 5
59
+ @q.put('blopp', 5)
60
+ @q.put('blipp', 4)
61
+ @q.put('blupp', 3)
62
+ @q.pop(2).should == %w(blupp blipp)
63
+ end
64
+
65
+ it 'returns only as many elements as are available' do
66
+ @clock.now = 4
67
+ @q.put('blopp', 5)
68
+ @q.put('blipp', 4)
69
+ @q.put('blupp', 3)
70
+ @q.pop(10).should == %w(blupp blipp)
71
+ end
72
+
73
+ it 'does not return elements whose timestamp has been updated to a later time' do
74
+ @clock.now = 4
75
+ @q.put('blopp', 5)
76
+ @q.put('blipp', 4)
77
+ @q.put('blupp', 3)
78
+ @q.put('blipp', 10)
79
+ @q.pop(2).should == %w(blupp)
80
+ end
81
+ end
82
+
83
+ describe '#pop_all' do
84
+ it 'returns all expired elements' do
85
+ @clock.now = 3
86
+ @q.put('blopp', 1)
87
+ @q.put('blipp', 2)
88
+ @q.put('blupp', 3)
89
+ @q.put('blepp', 4)
90
+ @q.pop_all.should == %w(blopp blipp blupp)
91
+ end
92
+
93
+ it 'removes elements' do
94
+ @clock.now = 3
95
+ @q.put('blopp', 1)
96
+ @q.put('blipp', 2)
97
+ @q.put('blupp', 3)
98
+ @q.put('blepp', 4)
99
+ @q.pop_all.should == %w(blopp blipp blupp)
100
+ @q.pop_all.should == []
101
+ @clock.now = 4
102
+ @q.pop_all.should == ['blepp']
103
+ @q.pop_all.should == []
104
+ end
105
+ end
106
+
107
+ describe '#include?' do
108
+ it 'returns true if the element is in the queue' do
109
+ @q.put('x', 3)
110
+ @q.should include('x')
111
+ end
112
+
113
+ it 'returns false if the element is not in the queue' do
114
+ @q.should_not include('x')
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+
3
+ $: << File.expand_path('../../lib', __FILE__)
4
+
5
+ require 'delay_queue'
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: burt-delay_queue
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Theo Hultberg
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-19 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70312679188040 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 2.5.0
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70312679188040
25
+ description: Delay queue keeps it's elements ordered by a timestamp, popping off the
26
+ items with the lowest timestamp first
27
+ email:
28
+ - theo@burtcorp.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - .gitignore
34
+ - delay_queue.gemspec
35
+ - lib/delay_queue.rb
36
+ - spec/delay_queue_spec.rb
37
+ - spec/spec_helper.rb
38
+ homepage: https://github.com/iconara/delay_queue
39
+ licenses: []
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ! '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubyforge_project: delay_queue
58
+ rubygems_version: 1.8.15
59
+ signing_key:
60
+ specification_version: 3
61
+ summary: A TTL based priority queue
62
+ test_files: []