timers 4.2.0 → 4.3.3
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 +4 -4
- data/lib/timers.rb +23 -8
- data/lib/timers/events.rb +58 -56
- data/lib/timers/group.rb +42 -27
- data/lib/timers/interval.rb +20 -5
- data/lib/timers/priority_heap.rb +174 -0
- data/lib/timers/timer.rb +63 -45
- data/lib/timers/version.rb +21 -6
- data/lib/timers/wait.rb +28 -13
- metadata +25 -51
- data/.gitignore +0 -17
- data/.rspec +0 -3
- data/.travis.yml +0 -19
- data/Gemfile +0 -15
- data/README.md +0 -149
- data/Rakefile +0 -6
- data/spec/spec_helper.rb +0 -38
- data/spec/timers/cancel_spec.rb +0 -50
- data/spec/timers/events_spec.rb +0 -61
- data/spec/timers/every_spec.rb +0 -38
- data/spec/timers/group_spec.rb +0 -260
- data/spec/timers/performance_spec.rb +0 -125
- data/spec/timers/strict_spec.rb +0 -41
- data/spec/timers/wait_spec.rb +0 -35
- data/timers.gemspec +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a9f471bfb9cac57f9462498ab12527eded7533318cec13604a10d32b159c6689
|
4
|
+
data.tar.gz: 69802b0774101037596274f2acf114c3f4234875ed8a82146e8ef1075407bf6d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f15934803658ec4b1d0d7b4a8ea92c9a21e2267544ec11c9619604288264c7f0add19dd12b71a0680827d6d37b853192d98266f29b06ef27cb36e53408f8c67b
|
7
|
+
data.tar.gz: a50a935d7b73681b4b751341c6eda0143c64beb9d469641ea68b8481cd1c36bd16ad21f7ec274f06d1389b53c5849e06b20182d3faea6497f64773d8fba2dae6
|
data/lib/timers.rb
CHANGED
@@ -1,11 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
#
|
3
|
-
# This file is part of the "timers" project and released under the MIT license.
|
4
|
-
#
|
5
|
-
# Copyright, 2018, by Samuel Williams. All rights reserved.
|
6
|
-
#
|
7
2
|
|
8
|
-
|
3
|
+
# Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
9
22
|
|
10
|
-
|
11
|
-
|
23
|
+
require_relative "timers/version"
|
24
|
+
|
25
|
+
require_relative "timers/group"
|
26
|
+
require_relative "timers/wait"
|
data/lib/timers/events.rb
CHANGED
@@ -1,113 +1,115 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
2
|
+
|
3
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
7
22
|
|
8
23
|
require_relative "timer"
|
24
|
+
require_relative "priority_heap"
|
9
25
|
|
10
26
|
module Timers
|
11
|
-
# Maintains
|
27
|
+
# Maintains a PriorityHeap of events ordered on time, which can be cancelled.
|
12
28
|
class Events
|
13
29
|
# Represents a cancellable handle for a specific timer event.
|
14
30
|
class Handle
|
31
|
+
include Comparable
|
32
|
+
|
15
33
|
def initialize(time, callback)
|
16
34
|
@time = time
|
17
35
|
@callback = callback
|
18
36
|
end
|
19
|
-
|
37
|
+
|
20
38
|
# The absolute time that the handle should be fired at.
|
21
39
|
attr_reader :time
|
22
|
-
|
40
|
+
|
23
41
|
# Cancel this timer, O(1).
|
24
42
|
def cancel!
|
25
43
|
# The simplest way to keep track of cancelled status is to nullify the
|
26
44
|
# callback. This should also be optimal for garbage collection.
|
27
45
|
@callback = nil
|
28
46
|
end
|
29
|
-
|
47
|
+
|
30
48
|
# Has this timer been cancelled? Cancelled timer's don't fire.
|
31
49
|
def cancelled?
|
32
50
|
@callback.nil?
|
33
51
|
end
|
34
|
-
|
35
|
-
def
|
36
|
-
@time
|
37
|
-
end
|
38
|
-
|
39
|
-
def to_f
|
40
|
-
@time
|
52
|
+
|
53
|
+
def <=> other
|
54
|
+
@time <=> other.time
|
41
55
|
end
|
42
|
-
|
56
|
+
|
43
57
|
# Fire the callback if not cancelled with the given time parameter.
|
44
58
|
def fire(time)
|
45
59
|
@callback.call(time) if @callback
|
46
60
|
end
|
47
61
|
end
|
48
|
-
|
62
|
+
|
49
63
|
def initialize
|
50
64
|
# A sequence of handles, maintained in sorted order, future to present.
|
51
65
|
# @sequence.last is the next event to be fired.
|
52
|
-
@sequence =
|
66
|
+
@sequence = PriorityHeap.new
|
67
|
+
@queue = []
|
53
68
|
end
|
54
|
-
|
69
|
+
|
55
70
|
# Add an event at the given time.
|
56
71
|
def schedule(time, callback)
|
57
72
|
handle = Handle.new(time.to_f, callback)
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
@sequence.insert(index, handle)
|
63
|
-
|
64
|
-
handle
|
73
|
+
|
74
|
+
@queue << handle
|
75
|
+
|
76
|
+
return handle
|
65
77
|
end
|
66
|
-
|
78
|
+
|
67
79
|
# Returns the first non-cancelled handle.
|
68
80
|
def first
|
69
|
-
|
81
|
+
merge!
|
82
|
+
|
83
|
+
while (handle = @sequence.peek)
|
70
84
|
return handle unless handle.cancelled?
|
71
85
|
@sequence.pop
|
72
86
|
end
|
73
87
|
end
|
74
|
-
|
88
|
+
|
75
89
|
# Returns the number of pending (possibly cancelled) events.
|
76
90
|
def size
|
77
|
-
@sequence.size
|
91
|
+
@sequence.size + @queue.size
|
78
92
|
end
|
79
|
-
|
93
|
+
|
80
94
|
# Fire all handles for which Handle#time is less than the given time.
|
81
95
|
def fire(time)
|
82
|
-
|
96
|
+
merge!
|
97
|
+
|
98
|
+
while handle = @sequence.peek and handle.time <= time
|
99
|
+
@sequence.pop
|
83
100
|
handle.fire(time)
|
84
101
|
end
|
85
102
|
end
|
86
|
-
|
103
|
+
|
87
104
|
private
|
88
|
-
|
89
|
-
#
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
end
|
96
|
-
|
97
|
-
# Return the left-most index where to insert item e, in a list a, assuming
|
98
|
-
# a is sorted in descending order.
|
99
|
-
def bisect_left(a, e, l = 0, u = a.length)
|
100
|
-
while l < u
|
101
|
-
m = l + (u - l).div(2)
|
102
|
-
|
103
|
-
if a[m] > e
|
104
|
-
l = m + 1
|
105
|
-
else
|
106
|
-
u = m
|
107
|
-
end
|
105
|
+
|
106
|
+
# Move all non-cancelled timers from the pending queue to the priority heap
|
107
|
+
def merge!
|
108
|
+
while handle = @queue.pop
|
109
|
+
next if handle.cancelled?
|
110
|
+
|
111
|
+
@sequence.push(handle)
|
108
112
|
end
|
109
|
-
|
110
|
-
l
|
111
113
|
end
|
112
114
|
end
|
113
115
|
end
|
data/lib/timers/group.rb
CHANGED
@@ -1,9 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
2
|
+
|
3
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
7
22
|
|
8
23
|
require "set"
|
9
24
|
require "forwardable"
|
@@ -16,62 +31,62 @@ module Timers
|
|
16
31
|
# A collection of timers which may fire at different times
|
17
32
|
class Group
|
18
33
|
include Enumerable
|
19
|
-
|
34
|
+
|
20
35
|
extend Forwardable
|
21
36
|
def_delegators :@timers, :each, :empty?
|
22
|
-
|
37
|
+
|
23
38
|
def initialize
|
24
39
|
@events = Events.new
|
25
|
-
|
40
|
+
|
26
41
|
@timers = Set.new
|
27
42
|
@paused_timers = Set.new
|
28
|
-
|
43
|
+
|
29
44
|
@interval = Interval.new
|
30
45
|
@interval.start
|
31
46
|
end
|
32
|
-
|
47
|
+
|
33
48
|
# Scheduled events:
|
34
49
|
attr_reader :events
|
35
|
-
|
50
|
+
|
36
51
|
# Active timers:
|
37
52
|
attr_reader :timers
|
38
|
-
|
53
|
+
|
39
54
|
# Paused timers:
|
40
55
|
attr_reader :paused_timers
|
41
|
-
|
56
|
+
|
42
57
|
# Call the given block after the given interval. The first argument will be
|
43
58
|
# the time at which the group was asked to fire timers for.
|
44
59
|
def after(interval, &block)
|
45
60
|
Timer.new(self, interval, false, &block)
|
46
61
|
end
|
47
|
-
|
62
|
+
|
48
63
|
# Call the given block immediately, and then after the given interval. The first
|
49
64
|
# argument will be the time at which the group was asked to fire timers for.
|
50
65
|
def now_and_after(interval, &block)
|
51
66
|
yield
|
52
67
|
after(interval, &block)
|
53
68
|
end
|
54
|
-
|
69
|
+
|
55
70
|
# Call the given block periodically at the given interval. The first
|
56
71
|
# argument will be the time at which the group was asked to fire timers for.
|
57
72
|
def every(interval, recur = true, &block)
|
58
73
|
Timer.new(self, interval, recur, &block)
|
59
74
|
end
|
60
|
-
|
75
|
+
|
61
76
|
# Call the given block immediately, and then periodically at the given interval. The first
|
62
77
|
# argument will be the time at which the group was asked to fire timers for.
|
63
78
|
def now_and_every(interval, recur = true, &block)
|
64
79
|
yield
|
65
80
|
every(interval, recur, &block)
|
66
81
|
end
|
67
|
-
|
82
|
+
|
68
83
|
# Wait for the next timer and fire it. Can take a block, which should behave
|
69
84
|
# like sleep(n), except that n may be nil (sleep forever) or a negative
|
70
85
|
# number (fire immediately after return).
|
71
86
|
def wait
|
72
87
|
if block_given?
|
73
88
|
yield wait_interval
|
74
|
-
|
89
|
+
|
75
90
|
while (interval = wait_interval) && interval > 0
|
76
91
|
yield interval
|
77
92
|
end
|
@@ -81,10 +96,10 @@ module Timers
|
|
81
96
|
sleep interval
|
82
97
|
end
|
83
98
|
end
|
84
|
-
|
99
|
+
|
85
100
|
fire
|
86
101
|
end
|
87
|
-
|
102
|
+
|
88
103
|
# Interval to wait until when the next timer will fire.
|
89
104
|
# - nil: no timers
|
90
105
|
# - -ve: timers expired already
|
@@ -94,36 +109,36 @@ module Timers
|
|
94
109
|
handle = @events.first
|
95
110
|
handle.time - Float(offset) if handle
|
96
111
|
end
|
97
|
-
|
112
|
+
|
98
113
|
# Fire all timers that are ready.
|
99
114
|
def fire(offset = current_offset)
|
100
115
|
@events.fire(offset)
|
101
116
|
end
|
102
|
-
|
117
|
+
|
103
118
|
# Pause all timers.
|
104
119
|
def pause
|
105
120
|
@timers.dup.each(&:pause)
|
106
121
|
end
|
107
|
-
|
122
|
+
|
108
123
|
# Resume all timers.
|
109
124
|
def resume
|
110
125
|
@paused_timers.dup.each(&:resume)
|
111
126
|
end
|
112
|
-
|
127
|
+
|
113
128
|
alias continue resume
|
114
|
-
|
129
|
+
|
115
130
|
# Delay all timers.
|
116
131
|
def delay(seconds)
|
117
132
|
@timers.each do |timer|
|
118
133
|
timer.delay(seconds)
|
119
134
|
end
|
120
135
|
end
|
121
|
-
|
136
|
+
|
122
137
|
# Cancel all timers.
|
123
138
|
def cancel
|
124
139
|
@timers.dup.each(&:cancel)
|
125
140
|
end
|
126
|
-
|
141
|
+
|
127
142
|
# The group's current time.
|
128
143
|
def current_offset
|
129
144
|
@interval.to_f
|
data/lib/timers/interval.rb
CHANGED
@@ -1,9 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
2
|
+
|
3
|
+
# Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
7
22
|
|
8
23
|
module Timers
|
9
24
|
# A collection of timers which may fire at different times
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright, 2021, by Wander Hillen.
|
4
|
+
# Copyright, 2021, by Samuel G. D. Williams. <http://www.codeotaku.com>
|
5
|
+
#
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
# of this software and associated documentation files (the "Software"), to deal
|
8
|
+
# in the Software without restriction, including without limitation the rights
|
9
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
# copies of the Software, and to permit persons to whom the Software is
|
11
|
+
# furnished to do so, subject to the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
14
|
+
# all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
22
|
+
# THE SOFTWARE.
|
23
|
+
|
24
|
+
module Timers
|
25
|
+
# A priority queue implementation using a standard binary minheap. It uses straight comparison
|
26
|
+
# of its contents to determine priority. This works because a Handle from Timers::Events implements
|
27
|
+
# the '<' operation by comparing the expiry time.
|
28
|
+
# See <https://en.wikipedia.org/wiki/Binary_heap> for explanations of the main methods.
|
29
|
+
class PriorityHeap
|
30
|
+
def initialize
|
31
|
+
# The heap is represented with an array containing a binary tree. See
|
32
|
+
# https://en.wikipedia.org/wiki/Binary_heap#Heap_implementation for how this array
|
33
|
+
# is built up.
|
34
|
+
@contents = []
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the earliest timer or nil if the heap is empty.
|
38
|
+
def peek
|
39
|
+
@contents[0]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the number of elements in the heap
|
43
|
+
def size
|
44
|
+
@contents.size
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the earliest timer if the heap is non-empty and removes it from the heap.
|
48
|
+
# Returns nil if the heap is empty. (and doesn't change the heap in that case)
|
49
|
+
def pop
|
50
|
+
# If the heap is empty:
|
51
|
+
if @contents.empty?
|
52
|
+
return nil
|
53
|
+
end
|
54
|
+
|
55
|
+
# If we have only one item, no swapping is required:
|
56
|
+
if @contents.size == 1
|
57
|
+
return @contents.pop
|
58
|
+
end
|
59
|
+
|
60
|
+
# Take the root of the tree:
|
61
|
+
value = @contents[0]
|
62
|
+
|
63
|
+
# Remove the last item in the tree:
|
64
|
+
last = @contents.pop
|
65
|
+
|
66
|
+
# Overwrite the root of the tree with the item:
|
67
|
+
@contents[0] = last
|
68
|
+
|
69
|
+
# Bubble it down into place:
|
70
|
+
bubble_down(0)
|
71
|
+
|
72
|
+
# validate!
|
73
|
+
|
74
|
+
return value
|
75
|
+
end
|
76
|
+
|
77
|
+
# Inserts a new timer into the heap, then rearranges elements until the heap invariant is true again.
|
78
|
+
def push(element)
|
79
|
+
# Insert the item at the end of the heap:
|
80
|
+
@contents.push(element)
|
81
|
+
|
82
|
+
# Bubble it up into position:
|
83
|
+
bubble_up(@contents.size - 1)
|
84
|
+
|
85
|
+
# validate!
|
86
|
+
|
87
|
+
return self
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# Validate the heap invariant.
|
93
|
+
def validate!(index = 0)
|
94
|
+
if value = @contents[index]
|
95
|
+
left_index = index*2 + 1
|
96
|
+
if left_value = @contents[left_index]
|
97
|
+
unless value < left_value
|
98
|
+
raise "Invalid left index from #{index}!"
|
99
|
+
end
|
100
|
+
|
101
|
+
validate!(left_index)
|
102
|
+
end
|
103
|
+
|
104
|
+
right_index = left_index + 1
|
105
|
+
if right_value = @contents[right_index]
|
106
|
+
unless value < right_value
|
107
|
+
raise "Invalid right index from #{index}!"
|
108
|
+
end
|
109
|
+
|
110
|
+
validate!(right_index)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def swap(i, j)
|
116
|
+
@contents[i], @contents[j] = @contents[j], @contents[i]
|
117
|
+
end
|
118
|
+
|
119
|
+
def bubble_up(index)
|
120
|
+
parent_index = (index - 1) / 2 # watch out, integer division!
|
121
|
+
|
122
|
+
while index > 0 && @contents[index] < @contents[parent_index]
|
123
|
+
# if the node has a smaller value than its parent, swap these nodes
|
124
|
+
# to uphold the minheap invariant and update the index of the 'current'
|
125
|
+
# node. If the node is already at index 0, we can also stop because that
|
126
|
+
# is the root of the heap.
|
127
|
+
# swap(index, parent_index)
|
128
|
+
@contents[index], @contents[parent_index] = @contents[parent_index], @contents[index]
|
129
|
+
|
130
|
+
index = parent_index
|
131
|
+
parent_index = (index - 1) / 2 # watch out, integer division!
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def bubble_down(index)
|
136
|
+
swap_value = 0
|
137
|
+
swap_index = nil
|
138
|
+
|
139
|
+
while true
|
140
|
+
left_index = (2 * index) + 1
|
141
|
+
left_value = @contents[left_index]
|
142
|
+
|
143
|
+
if left_value.nil?
|
144
|
+
# This node has no children so it can't bubble down any further.
|
145
|
+
# We're done here!
|
146
|
+
return
|
147
|
+
end
|
148
|
+
|
149
|
+
# Determine which of the child nodes has the smallest value:
|
150
|
+
right_index = left_index + 1
|
151
|
+
right_value = @contents[right_index]
|
152
|
+
|
153
|
+
if right_value.nil? or right_value > left_value
|
154
|
+
swap_value = left_value
|
155
|
+
swap_index = left_index
|
156
|
+
else
|
157
|
+
swap_value = right_value
|
158
|
+
swap_index = right_index
|
159
|
+
end
|
160
|
+
|
161
|
+
if @contents[index] < swap_value
|
162
|
+
# No need to swap, the minheap invariant is already satisfied:
|
163
|
+
return
|
164
|
+
else
|
165
|
+
# At least one of the child node has a smaller value than the current node, swap current node with that child and update current node for if it might need to bubble down even further:
|
166
|
+
# swap(index, swap_index)
|
167
|
+
@contents[index], @contents[swap_index] = @contents[swap_index], @contents[index]
|
168
|
+
|
169
|
+
index = swap_index
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|