This commit is contained in:
+107
@@ -0,0 +1,107 @@
|
||||
require 'concurrent/synchronization/lockable_object'
|
||||
|
||||
module Concurrent
|
||||
module Collection
|
||||
|
||||
# A thread safe observer set implemented using copy-on-read approach:
|
||||
# observers are added and removed from a thread safe collection; every time
|
||||
# a notification is required the internal data structure is copied to
|
||||
# prevent concurrency issues
|
||||
#
|
||||
# @api private
|
||||
class CopyOnNotifyObserverSet < Synchronization::LockableObject
|
||||
|
||||
def initialize
|
||||
super()
|
||||
synchronize { ns_initialize }
|
||||
end
|
||||
|
||||
# @!macro observable_add_observer
|
||||
def add_observer(observer = nil, func = :update, &block)
|
||||
if observer.nil? && block.nil?
|
||||
raise ArgumentError, 'should pass observer as a first argument or block'
|
||||
elsif observer && block
|
||||
raise ArgumentError.new('cannot provide both an observer and a block')
|
||||
end
|
||||
|
||||
if block
|
||||
observer = block
|
||||
func = :call
|
||||
end
|
||||
|
||||
synchronize do
|
||||
@observers[observer] = func
|
||||
observer
|
||||
end
|
||||
end
|
||||
|
||||
# @!macro observable_delete_observer
|
||||
def delete_observer(observer)
|
||||
synchronize do
|
||||
@observers.delete(observer)
|
||||
observer
|
||||
end
|
||||
end
|
||||
|
||||
# @!macro observable_delete_observers
|
||||
def delete_observers
|
||||
synchronize do
|
||||
@observers.clear
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
# @!macro observable_count_observers
|
||||
def count_observers
|
||||
synchronize { @observers.count }
|
||||
end
|
||||
|
||||
# Notifies all registered observers with optional args
|
||||
# @param [Object] args arguments to be passed to each observer
|
||||
# @return [CopyOnWriteObserverSet] self
|
||||
def notify_observers(*args, &block)
|
||||
observers = duplicate_observers
|
||||
notify_to(observers, *args, &block)
|
||||
self
|
||||
end
|
||||
|
||||
# Notifies all registered observers with optional args and deletes them.
|
||||
#
|
||||
# @param [Object] args arguments to be passed to each observer
|
||||
# @return [CopyOnWriteObserverSet] self
|
||||
def notify_and_delete_observers(*args, &block)
|
||||
observers = duplicate_and_clear_observers
|
||||
notify_to(observers, *args, &block)
|
||||
self
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def ns_initialize
|
||||
@observers = {}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def duplicate_and_clear_observers
|
||||
synchronize do
|
||||
observers = @observers.dup
|
||||
@observers.clear
|
||||
observers
|
||||
end
|
||||
end
|
||||
|
||||
def duplicate_observers
|
||||
synchronize { @observers.dup }
|
||||
end
|
||||
|
||||
def notify_to(observers, *args)
|
||||
raise ArgumentError.new('cannot give arguments and a block') if block_given? && !args.empty?
|
||||
observers.each do |observer, function|
|
||||
args = yield if block_given?
|
||||
observer.send(function, *args)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
require 'concurrent/synchronization/lockable_object'
|
||||
|
||||
module Concurrent
|
||||
module Collection
|
||||
|
||||
# A thread safe observer set implemented using copy-on-write approach:
|
||||
# every time an observer is added or removed the whole internal data structure is
|
||||
# duplicated and replaced with a new one.
|
||||
#
|
||||
# @api private
|
||||
class CopyOnWriteObserverSet < Synchronization::LockableObject
|
||||
|
||||
def initialize
|
||||
super()
|
||||
synchronize { ns_initialize }
|
||||
end
|
||||
|
||||
# @!macro observable_add_observer
|
||||
def add_observer(observer = nil, func = :update, &block)
|
||||
if observer.nil? && block.nil?
|
||||
raise ArgumentError, 'should pass observer as a first argument or block'
|
||||
elsif observer && block
|
||||
raise ArgumentError.new('cannot provide both an observer and a block')
|
||||
end
|
||||
|
||||
if block
|
||||
observer = block
|
||||
func = :call
|
||||
end
|
||||
|
||||
synchronize do
|
||||
new_observers = @observers.dup
|
||||
new_observers[observer] = func
|
||||
@observers = new_observers
|
||||
observer
|
||||
end
|
||||
end
|
||||
|
||||
# @!macro observable_delete_observer
|
||||
def delete_observer(observer)
|
||||
synchronize do
|
||||
new_observers = @observers.dup
|
||||
new_observers.delete(observer)
|
||||
@observers = new_observers
|
||||
observer
|
||||
end
|
||||
end
|
||||
|
||||
# @!macro observable_delete_observers
|
||||
def delete_observers
|
||||
self.observers = {}
|
||||
self
|
||||
end
|
||||
|
||||
# @!macro observable_count_observers
|
||||
def count_observers
|
||||
observers.count
|
||||
end
|
||||
|
||||
# Notifies all registered observers with optional args
|
||||
# @param [Object] args arguments to be passed to each observer
|
||||
# @return [CopyOnWriteObserverSet] self
|
||||
def notify_observers(*args, &block)
|
||||
notify_to(observers, *args, &block)
|
||||
self
|
||||
end
|
||||
|
||||
# Notifies all registered observers with optional args and deletes them.
|
||||
#
|
||||
# @param [Object] args arguments to be passed to each observer
|
||||
# @return [CopyOnWriteObserverSet] self
|
||||
def notify_and_delete_observers(*args, &block)
|
||||
old = clear_observers_and_return_old
|
||||
notify_to(old, *args, &block)
|
||||
self
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def ns_initialize
|
||||
@observers = {}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def notify_to(observers, *args)
|
||||
raise ArgumentError.new('cannot give arguments and a block') if block_given? && !args.empty?
|
||||
observers.each do |observer, function|
|
||||
args = yield if block_given?
|
||||
observer.send(function, *args)
|
||||
end
|
||||
end
|
||||
|
||||
def observers
|
||||
synchronize { @observers }
|
||||
end
|
||||
|
||||
def observers=(new_set)
|
||||
synchronize { @observers = new_set }
|
||||
end
|
||||
|
||||
def clear_observers_and_return_old
|
||||
synchronize do
|
||||
old_observers = @observers
|
||||
@observers = {}
|
||||
old_observers
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
if Concurrent.on_jruby?
|
||||
|
||||
module Concurrent
|
||||
module Collection
|
||||
|
||||
|
||||
# @!macro priority_queue
|
||||
#
|
||||
# @!visibility private
|
||||
# @!macro internal_implementation_note
|
||||
class JavaNonConcurrentPriorityQueue
|
||||
|
||||
# @!macro priority_queue_method_initialize
|
||||
def initialize(opts = {})
|
||||
order = opts.fetch(:order, :max)
|
||||
if [:min, :low].include?(order)
|
||||
@queue = java.util.PriorityQueue.new(11) # 11 is the default initial capacity
|
||||
else
|
||||
@queue = java.util.PriorityQueue.new(11, java.util.Collections.reverseOrder())
|
||||
end
|
||||
end
|
||||
|
||||
# @!macro priority_queue_method_clear
|
||||
def clear
|
||||
@queue.clear
|
||||
true
|
||||
end
|
||||
|
||||
# @!macro priority_queue_method_delete
|
||||
def delete(item)
|
||||
found = false
|
||||
while @queue.remove(item) do
|
||||
found = true
|
||||
end
|
||||
found
|
||||
end
|
||||
|
||||
# @!macro priority_queue_method_empty
|
||||
def empty?
|
||||
@queue.size == 0
|
||||
end
|
||||
|
||||
# @!macro priority_queue_method_include
|
||||
def include?(item)
|
||||
@queue.contains(item)
|
||||
end
|
||||
alias_method :has_priority?, :include?
|
||||
|
||||
# @!macro priority_queue_method_length
|
||||
def length
|
||||
@queue.size
|
||||
end
|
||||
alias_method :size, :length
|
||||
|
||||
# @!macro priority_queue_method_peek
|
||||
def peek
|
||||
@queue.peek
|
||||
end
|
||||
|
||||
# @!macro priority_queue_method_pop
|
||||
def pop
|
||||
@queue.poll
|
||||
end
|
||||
alias_method :deq, :pop
|
||||
alias_method :shift, :pop
|
||||
|
||||
# @!macro priority_queue_method_push
|
||||
def push(item)
|
||||
raise ArgumentError.new('cannot enqueue nil') if item.nil?
|
||||
@queue.add(item)
|
||||
end
|
||||
alias_method :<<, :push
|
||||
alias_method :enq, :push
|
||||
|
||||
# @!macro priority_queue_method_from_list
|
||||
def self.from_list(list, opts = {})
|
||||
queue = new(opts)
|
||||
list.each{|item| queue << item }
|
||||
queue
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
require 'concurrent/synchronization/object'
|
||||
|
||||
module Concurrent
|
||||
|
||||
# @!macro warn.edge
|
||||
class LockFreeStack < Synchronization::Object
|
||||
|
||||
safe_initialization!
|
||||
|
||||
class Node
|
||||
# TODO (pitr-ch 20-Dec-2016): Could be unified with Stack class?
|
||||
|
||||
# @return [Node]
|
||||
attr_reader :next_node
|
||||
|
||||
# @return [Object]
|
||||
attr_reader :value
|
||||
|
||||
# @!visibility private
|
||||
# allow to nil-ify to free GC when the entry is no longer relevant, not synchronised
|
||||
attr_writer :value
|
||||
|
||||
def initialize(value, next_node)
|
||||
@value = value
|
||||
@next_node = next_node
|
||||
end
|
||||
|
||||
singleton_class.send :alias_method, :[], :new
|
||||
end
|
||||
|
||||
# The singleton for empty node
|
||||
EMPTY = Node[nil, nil]
|
||||
def EMPTY.next_node
|
||||
self
|
||||
end
|
||||
|
||||
attr_atomic(:head)
|
||||
private :head, :head=, :swap_head, :compare_and_set_head, :update_head
|
||||
|
||||
# @!visibility private
|
||||
def self.of1(value)
|
||||
new Node[value, EMPTY]
|
||||
end
|
||||
|
||||
# @!visibility private
|
||||
def self.of2(value1, value2)
|
||||
new Node[value1, Node[value2, EMPTY]]
|
||||
end
|
||||
|
||||
# @param [Node] head
|
||||
def initialize(head = EMPTY)
|
||||
super()
|
||||
self.head = head
|
||||
end
|
||||
|
||||
# @param [Node] head
|
||||
# @return [true, false]
|
||||
def empty?(head = head())
|
||||
head.equal? EMPTY
|
||||
end
|
||||
|
||||
# @param [Node] head
|
||||
# @param [Object] value
|
||||
# @return [true, false]
|
||||
def compare_and_push(head, value)
|
||||
compare_and_set_head head, Node[value, head]
|
||||
end
|
||||
|
||||
# @param [Object] value
|
||||
# @return [self]
|
||||
def push(value)
|
||||
while true
|
||||
current_head = head
|
||||
return self if compare_and_set_head current_head, Node[value, current_head]
|
||||
end
|
||||
end
|
||||
|
||||
# @return [Node]
|
||||
def peek
|
||||
head
|
||||
end
|
||||
|
||||
# @param [Node] head
|
||||
# @return [true, false]
|
||||
def compare_and_pop(head)
|
||||
compare_and_set_head head, head.next_node
|
||||
end
|
||||
|
||||
# @return [Object]
|
||||
def pop
|
||||
while true
|
||||
current_head = head
|
||||
return current_head.value if compare_and_set_head current_head, current_head.next_node
|
||||
end
|
||||
end
|
||||
|
||||
# @param [Node] head
|
||||
# @return [true, false]
|
||||
def compare_and_clear(head)
|
||||
compare_and_set_head head, EMPTY
|
||||
end
|
||||
|
||||
include Enumerable
|
||||
|
||||
# @param [Node] head
|
||||
# @return [self]
|
||||
def each(head = nil)
|
||||
return to_enum(:each, head) unless block_given?
|
||||
it = head || peek
|
||||
until it.equal?(EMPTY)
|
||||
yield it.value
|
||||
it = it.next_node
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
# @return [true, false]
|
||||
def clear
|
||||
while true
|
||||
current_head = head
|
||||
return false if current_head == EMPTY
|
||||
return true if compare_and_set_head current_head, EMPTY
|
||||
end
|
||||
end
|
||||
|
||||
# @param [Node] head
|
||||
# @return [true, false]
|
||||
def clear_if(head)
|
||||
compare_and_set_head head, EMPTY
|
||||
end
|
||||
|
||||
# @param [Node] head
|
||||
# @param [Node] new_head
|
||||
# @return [true, false]
|
||||
def replace_if(head, new_head)
|
||||
compare_and_set_head head, new_head
|
||||
end
|
||||
|
||||
# @return [self]
|
||||
# @yield over the cleared stack
|
||||
# @yieldparam [Object] value
|
||||
def clear_each(&block)
|
||||
while true
|
||||
current_head = head
|
||||
return self if current_head == EMPTY
|
||||
if compare_and_set_head current_head, EMPTY
|
||||
each current_head, &block
|
||||
return self
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# @return [String] Short string representation.
|
||||
def to_s
|
||||
format '%s %s>', super[0..-2], to_a.to_s
|
||||
end
|
||||
|
||||
alias_method :inspect, :to_s
|
||||
end
|
||||
end
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
require 'thread'
|
||||
require 'concurrent/collection/map/non_concurrent_map_backend'
|
||||
|
||||
module Concurrent
|
||||
|
||||
# @!visibility private
|
||||
module Collection
|
||||
|
||||
# @!visibility private
|
||||
class MriMapBackend < NonConcurrentMapBackend
|
||||
|
||||
def initialize(options = nil, &default_proc)
|
||||
super(options, &default_proc)
|
||||
@write_lock = Mutex.new
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
@write_lock.synchronize { super }
|
||||
end
|
||||
|
||||
def compute_if_absent(key)
|
||||
if NULL != (stored_value = @backend.fetch(key, NULL)) # fast non-blocking path for the most likely case
|
||||
stored_value
|
||||
else
|
||||
@write_lock.synchronize { super }
|
||||
end
|
||||
end
|
||||
|
||||
def compute_if_present(key)
|
||||
@write_lock.synchronize { super }
|
||||
end
|
||||
|
||||
def compute(key)
|
||||
@write_lock.synchronize { super }
|
||||
end
|
||||
|
||||
def merge_pair(key, value)
|
||||
@write_lock.synchronize { super }
|
||||
end
|
||||
|
||||
def replace_pair(key, old_value, new_value)
|
||||
@write_lock.synchronize { super }
|
||||
end
|
||||
|
||||
def replace_if_exists(key, new_value)
|
||||
@write_lock.synchronize { super }
|
||||
end
|
||||
|
||||
def get_and_set(key, value)
|
||||
@write_lock.synchronize { super }
|
||||
end
|
||||
|
||||
def delete(key)
|
||||
@write_lock.synchronize { super }
|
||||
end
|
||||
|
||||
def delete_pair(key, value)
|
||||
@write_lock.synchronize { super }
|
||||
end
|
||||
|
||||
def clear
|
||||
@write_lock.synchronize { super }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
require 'concurrent/constants'
|
||||
|
||||
module Concurrent
|
||||
|
||||
# @!visibility private
|
||||
module Collection
|
||||
|
||||
# @!visibility private
|
||||
class NonConcurrentMapBackend
|
||||
|
||||
# WARNING: all public methods of the class must operate on the @backend
|
||||
# directly without calling each other. This is important because of the
|
||||
# SynchronizedMapBackend which uses a non-reentrant mutex for performance
|
||||
# reasons.
|
||||
def initialize(options = nil, &default_proc)
|
||||
validate_options_hash!(options) if options.kind_of?(::Hash)
|
||||
set_backend(default_proc)
|
||||
@default_proc = default_proc
|
||||
end
|
||||
|
||||
def [](key)
|
||||
@backend[key]
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
@backend[key] = value
|
||||
end
|
||||
|
||||
def compute_if_absent(key)
|
||||
if NULL != (stored_value = @backend.fetch(key, NULL))
|
||||
stored_value
|
||||
else
|
||||
@backend[key] = yield
|
||||
end
|
||||
end
|
||||
|
||||
def replace_pair(key, old_value, new_value)
|
||||
if pair?(key, old_value)
|
||||
@backend[key] = new_value
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def replace_if_exists(key, new_value)
|
||||
if NULL != (stored_value = @backend.fetch(key, NULL))
|
||||
@backend[key] = new_value
|
||||
stored_value
|
||||
end
|
||||
end
|
||||
|
||||
def compute_if_present(key)
|
||||
if NULL != (stored_value = @backend.fetch(key, NULL))
|
||||
store_computed_value(key, yield(stored_value))
|
||||
end
|
||||
end
|
||||
|
||||
def compute(key)
|
||||
store_computed_value(key, yield(get_or_default(key, nil)))
|
||||
end
|
||||
|
||||
def merge_pair(key, value)
|
||||
if NULL == (stored_value = @backend.fetch(key, NULL))
|
||||
@backend[key] = value
|
||||
else
|
||||
store_computed_value(key, yield(stored_value))
|
||||
end
|
||||
end
|
||||
|
||||
def get_and_set(key, value)
|
||||
stored_value = get_or_default(key, nil)
|
||||
@backend[key] = value
|
||||
stored_value
|
||||
end
|
||||
|
||||
def key?(key)
|
||||
@backend.key?(key)
|
||||
end
|
||||
|
||||
def delete(key)
|
||||
@backend.delete(key)
|
||||
end
|
||||
|
||||
def delete_pair(key, value)
|
||||
if pair?(key, value)
|
||||
@backend.delete(key)
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def clear
|
||||
@backend.clear
|
||||
self
|
||||
end
|
||||
|
||||
def each_pair
|
||||
dupped_backend.each_pair do |k, v|
|
||||
yield k, v
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def size
|
||||
@backend.size
|
||||
end
|
||||
|
||||
def get_or_default(key, default_value)
|
||||
@backend.fetch(key, default_value)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_backend(default_proc)
|
||||
if default_proc
|
||||
@backend = ::Hash.new { |_h, key| default_proc.call(self, key) }
|
||||
else
|
||||
@backend = {}
|
||||
end
|
||||
end
|
||||
|
||||
def initialize_copy(other)
|
||||
super
|
||||
set_backend(@default_proc)
|
||||
self
|
||||
end
|
||||
|
||||
def dupped_backend
|
||||
@backend.dup
|
||||
end
|
||||
|
||||
def pair?(key, expected_value)
|
||||
NULL != (stored_value = @backend.fetch(key, NULL)) && expected_value.equal?(stored_value)
|
||||
end
|
||||
|
||||
def store_computed_value(key, new_value)
|
||||
if new_value.nil?
|
||||
@backend.delete(key)
|
||||
nil
|
||||
else
|
||||
@backend[key] = new_value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
require 'concurrent/collection/map/non_concurrent_map_backend'
|
||||
|
||||
module Concurrent
|
||||
|
||||
# @!visibility private
|
||||
module Collection
|
||||
|
||||
# @!visibility private
|
||||
class SynchronizedMapBackend < NonConcurrentMapBackend
|
||||
|
||||
def initialize(*args, &block)
|
||||
super
|
||||
|
||||
# WARNING: Mutex is a non-reentrant lock, so the synchronized methods are
|
||||
# not allowed to call each other.
|
||||
@mutex = Mutex.new
|
||||
end
|
||||
|
||||
def [](key)
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
|
||||
def compute_if_absent(key)
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
|
||||
def compute_if_present(key)
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
|
||||
def compute(key)
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
|
||||
def merge_pair(key, value)
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
|
||||
def replace_pair(key, old_value, new_value)
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
|
||||
def replace_if_exists(key, new_value)
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
|
||||
def get_and_set(key, value)
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
|
||||
def key?(key)
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
|
||||
def delete(key)
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
|
||||
def delete_pair(key, value)
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
|
||||
def clear
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
|
||||
def size
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
|
||||
def get_or_default(key, default_value)
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
|
||||
private
|
||||
def dupped_backend
|
||||
@mutex.synchronize { super }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
module Concurrent
|
||||
|
||||
# @!visibility private
|
||||
module Collection
|
||||
|
||||
# @!visibility private
|
||||
class TruffleRubyMapBackend < TruffleRuby::ConcurrentMap
|
||||
def initialize(options = nil)
|
||||
options ||= {}
|
||||
super(initial_capacity: options[:initial_capacity], load_factor: options[:load_factor])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
require 'concurrent/utility/engine'
|
||||
require 'concurrent/collection/java_non_concurrent_priority_queue'
|
||||
require 'concurrent/collection/ruby_non_concurrent_priority_queue'
|
||||
|
||||
module Concurrent
|
||||
module Collection
|
||||
|
||||
# @!visibility private
|
||||
# @!macro internal_implementation_note
|
||||
NonConcurrentPriorityQueueImplementation = case
|
||||
when Concurrent.on_jruby?
|
||||
JavaNonConcurrentPriorityQueue
|
||||
else
|
||||
RubyNonConcurrentPriorityQueue
|
||||
end
|
||||
private_constant :NonConcurrentPriorityQueueImplementation
|
||||
|
||||
# @!macro priority_queue
|
||||
#
|
||||
# A queue collection in which the elements are sorted based on their
|
||||
# comparison (spaceship) operator `<=>`. Items are added to the queue
|
||||
# at a position relative to their priority. On removal the element
|
||||
# with the "highest" priority is removed. By default the sort order is
|
||||
# from highest to lowest, but a lowest-to-highest sort order can be
|
||||
# set on construction.
|
||||
#
|
||||
# The API is based on the `Queue` class from the Ruby standard library.
|
||||
#
|
||||
# The pure Ruby implementation, `RubyNonConcurrentPriorityQueue` uses a heap algorithm
|
||||
# stored in an array. The algorithm is based on the work of Robert Sedgewick
|
||||
# and Kevin Wayne.
|
||||
#
|
||||
# The JRuby native implementation is a thin wrapper around the standard
|
||||
# library `java.util.NonConcurrentPriorityQueue`.
|
||||
#
|
||||
# When running under JRuby the class `NonConcurrentPriorityQueue` extends `JavaNonConcurrentPriorityQueue`.
|
||||
# When running under all other interpreters it extends `RubyNonConcurrentPriorityQueue`.
|
||||
#
|
||||
# @note This implementation is *not* thread safe.
|
||||
#
|
||||
# @see http://en.wikipedia.org/wiki/Priority_queue
|
||||
# @see http://ruby-doc.org/stdlib-2.0.0/libdoc/thread/rdoc/Queue.html
|
||||
#
|
||||
# @see http://algs4.cs.princeton.edu/24pq/index.php#2.6
|
||||
# @see http://algs4.cs.princeton.edu/24pq/MaxPQ.java.html
|
||||
#
|
||||
# @see http://docs.oracle.com/javase/7/docs/api/java/util/PriorityQueue.html
|
||||
#
|
||||
# @!visibility private
|
||||
class NonConcurrentPriorityQueue < NonConcurrentPriorityQueueImplementation
|
||||
|
||||
alias_method :has_priority?, :include?
|
||||
|
||||
alias_method :size, :length
|
||||
|
||||
alias_method :deq, :pop
|
||||
alias_method :shift, :pop
|
||||
|
||||
alias_method :<<, :push
|
||||
alias_method :enq, :push
|
||||
|
||||
# @!method initialize(opts = {})
|
||||
# @!macro priority_queue_method_initialize
|
||||
#
|
||||
# Create a new priority queue with no items.
|
||||
#
|
||||
# @param [Hash] opts the options for creating the queue
|
||||
# @option opts [Symbol] :order (:max) dictates the order in which items are
|
||||
# stored: from highest to lowest when `:max` or `:high`; from lowest to
|
||||
# highest when `:min` or `:low`
|
||||
|
||||
# @!method clear
|
||||
# @!macro priority_queue_method_clear
|
||||
#
|
||||
# Removes all of the elements from this priority queue.
|
||||
|
||||
# @!method delete(item)
|
||||
# @!macro priority_queue_method_delete
|
||||
#
|
||||
# Deletes all items from `self` that are equal to `item`.
|
||||
#
|
||||
# @param [Object] item the item to be removed from the queue
|
||||
# @return [Object] true if the item is found else false
|
||||
|
||||
# @!method empty?
|
||||
# @!macro priority_queue_method_empty
|
||||
#
|
||||
# Returns `true` if `self` contains no elements.
|
||||
#
|
||||
# @return [Boolean] true if there are no items in the queue else false
|
||||
|
||||
# @!method include?(item)
|
||||
# @!macro priority_queue_method_include
|
||||
#
|
||||
# Returns `true` if the given item is present in `self` (that is, if any
|
||||
# element == `item`), otherwise returns false.
|
||||
#
|
||||
# @param [Object] item the item to search for
|
||||
#
|
||||
# @return [Boolean] true if the item is found else false
|
||||
|
||||
# @!method length
|
||||
# @!macro priority_queue_method_length
|
||||
#
|
||||
# The current length of the queue.
|
||||
#
|
||||
# @return [Fixnum] the number of items in the queue
|
||||
|
||||
# @!method peek
|
||||
# @!macro priority_queue_method_peek
|
||||
#
|
||||
# Retrieves, but does not remove, the head of this queue, or returns `nil`
|
||||
# if this queue is empty.
|
||||
#
|
||||
# @return [Object] the head of the queue or `nil` when empty
|
||||
|
||||
# @!method pop
|
||||
# @!macro priority_queue_method_pop
|
||||
#
|
||||
# Retrieves and removes the head of this queue, or returns `nil` if this
|
||||
# queue is empty.
|
||||
#
|
||||
# @return [Object] the head of the queue or `nil` when empty
|
||||
|
||||
# @!method push(item)
|
||||
# @!macro priority_queue_method_push
|
||||
#
|
||||
# Inserts the specified element into this priority queue.
|
||||
#
|
||||
# @param [Object] item the item to insert onto the queue
|
||||
|
||||
# @!method self.from_list(list, opts = {})
|
||||
# @!macro priority_queue_method_from_list
|
||||
#
|
||||
# Create a new priority queue from the given list.
|
||||
#
|
||||
# @param [Enumerable] list the list to build the queue from
|
||||
# @param [Hash] opts the options for creating the queue
|
||||
#
|
||||
# @return [NonConcurrentPriorityQueue] the newly created and populated queue
|
||||
end
|
||||
end
|
||||
end
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
module Concurrent
|
||||
module Collection
|
||||
|
||||
# @!macro priority_queue
|
||||
#
|
||||
# @!visibility private
|
||||
# @!macro internal_implementation_note
|
||||
class RubyNonConcurrentPriorityQueue
|
||||
|
||||
# @!macro priority_queue_method_initialize
|
||||
def initialize(opts = {})
|
||||
order = opts.fetch(:order, :max)
|
||||
@comparator = [:min, :low].include?(order) ? -1 : 1
|
||||
clear
|
||||
end
|
||||
|
||||
# @!macro priority_queue_method_clear
|
||||
def clear
|
||||
@queue = [nil]
|
||||
@length = 0
|
||||
true
|
||||
end
|
||||
|
||||
# @!macro priority_queue_method_delete
|
||||
def delete(item)
|
||||
return false if empty?
|
||||
original_length = @length
|
||||
k = 1
|
||||
while k <= @length
|
||||
if @queue[k] == item
|
||||
swap(k, @length)
|
||||
@length -= 1
|
||||
sink(k) || swim(k)
|
||||
@queue.pop
|
||||
else
|
||||
k += 1
|
||||
end
|
||||
end
|
||||
@length != original_length
|
||||
end
|
||||
|
||||
# @!macro priority_queue_method_empty
|
||||
def empty?
|
||||
size == 0
|
||||
end
|
||||
|
||||
# @!macro priority_queue_method_include
|
||||
def include?(item)
|
||||
@queue.include?(item)
|
||||
end
|
||||
alias_method :has_priority?, :include?
|
||||
|
||||
# @!macro priority_queue_method_length
|
||||
def length
|
||||
@length
|
||||
end
|
||||
alias_method :size, :length
|
||||
|
||||
# @!macro priority_queue_method_peek
|
||||
def peek
|
||||
empty? ? nil : @queue[1]
|
||||
end
|
||||
|
||||
# @!macro priority_queue_method_pop
|
||||
def pop
|
||||
return nil if empty?
|
||||
max = @queue[1]
|
||||
swap(1, @length)
|
||||
@length -= 1
|
||||
sink(1)
|
||||
@queue.pop
|
||||
max
|
||||
end
|
||||
alias_method :deq, :pop
|
||||
alias_method :shift, :pop
|
||||
|
||||
# @!macro priority_queue_method_push
|
||||
def push(item)
|
||||
raise ArgumentError.new('cannot enqueue nil') if item.nil?
|
||||
@length += 1
|
||||
@queue << item
|
||||
swim(@length)
|
||||
true
|
||||
end
|
||||
alias_method :<<, :push
|
||||
alias_method :enq, :push
|
||||
|
||||
# @!macro priority_queue_method_from_list
|
||||
def self.from_list(list, opts = {})
|
||||
queue = new(opts)
|
||||
list.each{|item| queue << item }
|
||||
queue
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Exchange the values at the given indexes within the internal array.
|
||||
#
|
||||
# @param [Integer] x the first index to swap
|
||||
# @param [Integer] y the second index to swap
|
||||
#
|
||||
# @!visibility private
|
||||
def swap(x, y)
|
||||
temp = @queue[x]
|
||||
@queue[x] = @queue[y]
|
||||
@queue[y] = temp
|
||||
end
|
||||
|
||||
# Are the items at the given indexes ordered based on the priority
|
||||
# order specified at construction?
|
||||
#
|
||||
# @param [Integer] x the first index from which to retrieve a comparable value
|
||||
# @param [Integer] y the second index from which to retrieve a comparable value
|
||||
#
|
||||
# @return [Boolean] true if the two elements are in the correct priority order
|
||||
# else false
|
||||
#
|
||||
# @!visibility private
|
||||
def ordered?(x, y)
|
||||
(@queue[x] <=> @queue[y]) == @comparator
|
||||
end
|
||||
|
||||
# Percolate down to maintain heap invariant.
|
||||
#
|
||||
# @param [Integer] k the index at which to start the percolation
|
||||
#
|
||||
# @!visibility private
|
||||
def sink(k)
|
||||
success = false
|
||||
|
||||
while (j = (2 * k)) <= @length do
|
||||
j += 1 if j < @length && ! ordered?(j, j+1)
|
||||
break if ordered?(k, j)
|
||||
swap(k, j)
|
||||
success = true
|
||||
k = j
|
||||
end
|
||||
|
||||
success
|
||||
end
|
||||
|
||||
# Percolate up to maintain heap invariant.
|
||||
#
|
||||
# @param [Integer] k the index at which to start the percolation
|
||||
#
|
||||
# @!visibility private
|
||||
def swim(k)
|
||||
success = false
|
||||
|
||||
while k > 1 && ! ordered?(k/2, k) do
|
||||
swap(k, k/2)
|
||||
k = k/2
|
||||
success = true
|
||||
end
|
||||
|
||||
success
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user