├── .gitignore ├── ext └── fast_containers │ ├── extconf.rb │ ├── fc_pq.h │ ├── fc_pq.cpp │ └── FastContainers.cpp ├── Gemfile ├── Gemfile.lock ├── Rakefile ├── priority_queue_cxx.gemspec ├── LICENCE ├── .bundle └── install.log ├── lib └── fc.rb ├── test ├── performance │ ├── test_fc_vs_pqueue.rb │ ├── test_fc_vs_priority_queue.rb │ ├── test_fc_vs_em_priority_queue.rb │ ├── test_fc_vs_algorithms.rb │ └── test_fc_vs_cpriority_queue.rb └── test_fast_containers.rb └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | doc 2 | Makefile 3 | tmp 4 | lib/fast_containers 5 | *.gem -------------------------------------------------------------------------------- /ext/fast_containers/extconf.rb: -------------------------------------------------------------------------------- 1 | require 'mkmf' 2 | 3 | extension_name = 'fast_containers' 4 | 5 | create_makefile(extension_name) -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'algorithms' 4 | gem 'priority_queue' 5 | #gem 'em-priority-queue' 6 | gem 'pqueue' 7 | # gem 'PriorityQueue' 8 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | algorithms (0.6.1) 5 | pqueue (2.0.2) 6 | priority_queue (0.2.0) 7 | 8 | PLATFORMS 9 | ruby 10 | 11 | DEPENDENCIES 12 | algorithms 13 | pqueue 14 | priority_queue 15 | 16 | BUNDLED WITH 17 | 2.3.11 18 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "rubygems" 2 | require "rake/extensiontask" 3 | require 'rdoc/task' 4 | require 'rake/testtask' 5 | 6 | Rake::Task[:test].prerequisites << :compile 7 | Rake::TestTask.new do |t| 8 | t.test_files = FileList['test/test*.rb'] 9 | end 10 | 11 | Rake::ExtensionTask.new "fast_containers" do |ext| 12 | ext.lib_dir = "lib/fast_containers" 13 | end 14 | 15 | # -------------------------------------------------------------------------------- 16 | # RDOC 17 | # -------------------------------------------------------------------------------- 18 | 19 | RDOC_FILES = FileList["README.md", "ext/fast_containers/FastContainers.cpp", "lib/fc.rb"] 20 | 21 | Rake::RDocTask.new do |rd| 22 | rd.main = "README.md" 23 | rd.rdoc_dir = "doc/site/api" 24 | rd.rdoc_files.include(RDOC_FILES) 25 | end 26 | 27 | Rake::RDocTask.new(:ri) do |rd| 28 | rd.main = "README.md" 29 | rd.rdoc_dir = "doc/ri" 30 | rd.options << "--ri-system" 31 | rd.rdoc_files.include(RDOC_FILES) 32 | end 33 | -------------------------------------------------------------------------------- /priority_queue_cxx.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = "priority_queue_cxx" 3 | s.version = "0.3.7" 4 | s.summary = "Fast (c++ wrapper) priority queue implementation for ruby." 5 | s.date = "2014-03-25" 6 | s.description = "Fast priority queue implementation (c++ wrapper, see README.md for a comparison with other libraries)" 7 | s.authors = ["Roberto Esposito"] 8 | s.email = ["boborbt@gmail.com"] 9 | s.homepage = "https://github.com/boborbt/priority_queue_cxx" 10 | s.files = [ "lib/fc.rb", 11 | "ext/fast_containers/FastContainers.cpp", 12 | "ext/fast_containers/fc_pq.cpp", 13 | "ext/fast_containers/fc_pq.h", 14 | "test/test_fast_containers.rb" ] + 15 | Dir.glob("test/performance/*.rb") 16 | s.rdoc_options << "--main" << "README.md" 17 | s.extra_rdoc_files = ["README.md"] 18 | s.extensions << "ext/fast_containers/extconf.rb" 19 | s.licenses = ['MIT'] 20 | 21 | s.add_development_dependency 'rake-compiler', '~>0' 22 | end 23 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Roberto Esposito 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /.bundle/install.log: -------------------------------------------------------------------------------- 1 | # Logfile created on 2014-03-14 15:58:34 +0100 by logger.rb/44203 2 | I, [2014-03-14T15:58:34.952136 #16070] INFO -- : 0: PriorityQueue (0.1.2) from /Users/esposito/.rvm/gems/ruby-2.1.0/specifications/PriorityQueue-0.1.2.gemspec 3 | I, [2014-03-14T15:58:34.974982 #16070] INFO -- : 0: algorithms (0.6.1) from /Users/esposito/.rvm/gems/ruby-2.1.0/specifications/algorithms-0.6.1.gemspec 4 | I, [2014-03-14T15:58:34.975437 #16070] INFO -- : 0: eventmachine (1.0.3) from /Users/esposito/.rvm/gems/ruby-2.1.0/specifications/eventmachine-1.0.3.gemspec 5 | I, [2014-03-14T15:58:34.975891 #16070] INFO -- : 0: stakach-algorithms (1.0.8) from /Users/esposito/.rvm/gems/ruby-2.1.0/specifications/stakach-algorithms-1.0.8.gemspec 6 | I, [2014-03-14T15:58:34.976330 #16070] INFO -- : 0: em-priority-queue (1.1.2) from /Users/esposito/.rvm/gems/ruby-2.1.0/specifications/em-priority-queue-1.1.2.gemspec 7 | I, [2014-03-14T15:58:34.976775 #16070] INFO -- : 0: pqueue (2.0.2) from /Users/esposito/.rvm/gems/ruby-2.1.0/specifications/pqueue-2.0.2.gemspec 8 | I, [2014-03-14T15:58:34.977164 #16070] INFO -- : 0: priority_queue (0.2.0) from /Users/esposito/.rvm/gems/ruby-2.1.0/specifications/priority_queue-0.2.0.gemspec 9 | I, [2014-03-14T15:58:34.977495 #16070] INFO -- : 0: bundler (1.5.1) from /Users/esposito/.rvm/gems/ruby-2.1.0@global/specifications/bundler-1.5.1.gemspec 10 | -------------------------------------------------------------------------------- /lib/fc.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Roberto Esposito 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining 4 | # a copy of this software and associated documentation files (the 5 | # "Software"), to deal in the Software without restriction, including 6 | # without limitation the rights to use, copy, modify, merge, publish, 7 | # distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so, subject to 9 | # the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be 12 | # included in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | require 'fast_containers' 22 | 23 | module FastContainers 24 | VERSION = "0.3.4" 25 | 26 | class PriorityQueue 27 | include Enumerable 28 | 29 | alias_method :next, :top 30 | alias_method :next_key, :top_key 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/performance/test_fc_vs_pqueue.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Roberto Esposito 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining 4 | # a copy of this software and associated documentation files (the 5 | # "Software"), to deal in the Software without restriction, including 6 | # without limitation the rights to use, copy, modify, merge, publish, 7 | # distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so, subject to 9 | # the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be 12 | # included in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | require 'fc' 22 | require 'pqueue' 23 | require 'benchmark' 24 | 25 | # Performs 50.000 pushes and pops in priority queues using the fc and 26 | # algorithms implementations and reports the time spent. 27 | 28 | N = 100_000 29 | pq_pq = PQueue.new { |x,y| x[1] <=> y[1] } 30 | fc_pq = FastContainers::PriorityQueue.new(:min) 31 | 32 | Benchmark.bm do |bm| 33 | bm.report('pq:push') { N.times { |n| pq_pq.push([n,rand]) } } 34 | bm.report('fc:push') { N.times { |n| fc_pq.push(n.to_s, rand) } } 35 | bm.report('pq:pop') { N.times { pq_pq.pop } } 36 | bm.report('fc:pop') { N.times { fc_pq.pop } } 37 | end -------------------------------------------------------------------------------- /test/performance/test_fc_vs_priority_queue.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Roberto Esposito 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining 4 | # a copy of this software and associated documentation files (the 5 | # "Software"), to deal in the Software without restriction, including 6 | # without limitation the rights to use, copy, modify, merge, publish, 7 | # distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so, subject to 9 | # the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be 12 | # included in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | require 'fc' 22 | require 'priority_queue' 23 | require 'benchmark' 24 | 25 | # Performs 50.000 pushes and pops in priority queues using the fc and 26 | # algorithms implementations and reports the time spent. 27 | 28 | N = 50_000 29 | pq_pq = PriorityQueue.new 30 | fc_pq = FastContainers::PriorityQueue.new(:min) 31 | 32 | Benchmark.bm do |bm| 33 | bm.report('pq:push') { N.times { |n| pq_pq[rand] << n.to_s } } 34 | bm.report('fc:push') { N.times { |n| fc_pq.push(n.to_s, rand) } } 35 | bm.report('pq:pop') { N.times { pq_pq.shift } } 36 | bm.report('fc:pop') { N.times { fc_pq.pop } } 37 | end -------------------------------------------------------------------------------- /test/performance/test_fc_vs_em_priority_queue.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Roberto Esposito 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining 4 | # a copy of this software and associated documentation files (the 5 | # "Software"), to deal in the Software without restriction, including 6 | # without limitation the rights to use, copy, modify, merge, publish, 7 | # distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so, subject to 9 | # the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be 12 | # included in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | require 'fc' 22 | require 'em-priority-queue' 23 | require 'benchmark' 24 | 25 | # Performs 50.000 pushes and pops in priority queues using the fc and 26 | # algorithms implementations and reports the time spent. 27 | 28 | N = 500_000 29 | em_pq = EM::PriorityQueue.new 30 | fc_pq = FastContainers::PriorityQueue.new(:min) 31 | 32 | Benchmark.bm do |bm| 33 | bm.report('em:push') { N.times { |n| em_pq.push(n.to_s, rand) } } 34 | bm.report('fc:push') { N.times { |n| fc_pq.push(n.to_s, rand) } } 35 | bm.report('em:pop') { N.times { em_pq.pop {} } } 36 | bm.report('fc:pop') { N.times { fc_pq.pop } } 37 | end -------------------------------------------------------------------------------- /test/performance/test_fc_vs_algorithms.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Roberto Esposito 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining 4 | # a copy of this software and associated documentation files (the 5 | # "Software"), to deal in the Software without restriction, including 6 | # without limitation the rights to use, copy, modify, merge, publish, 7 | # distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so, subject to 9 | # the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be 12 | # included in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | require 'fc' 22 | require 'algorithms' 23 | require 'benchmark' 24 | 25 | # Performs 50.000 pushes and pops in priority queues using the fc and 26 | # algorithms implementations and reports the time spent. 27 | 28 | N = 50_000 29 | algo_pq = Containers::PriorityQueue.new 30 | fc_pq = FastContainers::PriorityQueue.new(:min) 31 | 32 | Benchmark.bm do |bm| 33 | bm.report('algo:push') { N.times { |n| algo_pq.push(n.to_s, rand) } } 34 | bm.report('fc:push') { N.times { |n| fc_pq.push(n.to_s, rand) } } 35 | bm.report('algo:pop') { N.times { algo_pq.pop } } 36 | bm.report('fc:pop') { N.times { fc_pq.pop } } 37 | end -------------------------------------------------------------------------------- /test/performance/test_fc_vs_cpriority_queue.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Roberto Esposito 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining 4 | # a copy of this software and associated documentation files (the 5 | # "Software"), to deal in the Software without restriction, including 6 | # without limitation the rights to use, copy, modify, merge, publish, 7 | # distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so, subject to 9 | # the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be 12 | # included in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | require 'fc' 22 | require 'priority_queue' 23 | require 'benchmark' 24 | 25 | # Performs 5,000,000 pushes and pops in priority queues using the fc and 26 | # algorithms implementations and reports the time spent. 27 | 28 | N = 5_000_000 29 | pq_pq = CPriorityQueue.new 30 | fc_pq = FastContainers::PriorityQueue.new(:min) 31 | 32 | Benchmark.bm do |bm| 33 | bm.report('pq:push') { N.times { |n| pq_pq.push(n.to_s,rand) } } 34 | bm.report('fc:push') { N.times { |n| fc_pq.push(n.to_s, rand) } } 35 | bm.report('pq:pop') { N.times { pq_pq.delete_min } } 36 | bm.report('fc:pop') { N.times { fc_pq.pop } } 37 | end -------------------------------------------------------------------------------- /ext/fast_containers/fc_pq.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Roberto Esposito 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining 4 | // a copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to 8 | // permit persons to whom the Software is furnished to do so, subject to 9 | // the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be 12 | // included in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | #ifndef FC_QUEUE_H_KTY6FH1S 22 | #define FC_QUEUE_H_KTY6FH1S 23 | 24 | #include 25 | #include 26 | 27 | namespace fc_pq { 28 | class PQueueException : public std::runtime_error { 29 | public: 30 | PQueueException(const char* msg) : std::runtime_error(msg) {} 31 | }; 32 | 33 | typedef struct _PQueue* PQueue; 34 | typedef struct _PQueueIterator* PQueueIterator; 35 | typedef enum { MIN_QUEUE, MAX_QUEUE } PQueueKind; 36 | 37 | /* Constructor. It defaults to construct a max queue. If true is passed 38 | it construct a min queue.*/ 39 | PQueue create(PQueueKind kind); 40 | 41 | /* Destructor */ 42 | void destroy(PQueue q); 43 | 44 | /* Getting the size of the container */ 45 | unsigned int size(PQueue q); 46 | 47 | /* Adding elements */ 48 | void push(PQueue q, void* value, double priority); 49 | 50 | /* Inspecting the queue top (for values) */ 51 | void* top(PQueue q); 52 | 53 | /* Inspecting the queue top (for priorities) */ 54 | double top_key(PQueue q); 55 | 56 | /* Returns the priority of the next best element */ 57 | double second_best_key(PQueue q); 58 | 59 | /* Removing the queue top */ 60 | void pop(PQueue q); 61 | 62 | /* Returns true if the queue is empty */ 63 | bool empty(PQueue q); 64 | 65 | /* Returns a new iterator object */ 66 | PQueueIterator iterator(PQueue q); 67 | 68 | /* Dispose the iterator */ 69 | void iterator_dispose(PQueueIterator it); 70 | 71 | /* Returns the value of the current element */ 72 | void* iterator_get_value(PQueueIterator it); 73 | 74 | /* Returns the priority of the current element */ 75 | double iterator_get_key(PQueueIterator it); 76 | 77 | /* Moves on to the next element */ 78 | PQueueIterator iterator_next(PQueueIterator it); 79 | 80 | /* Return true if the iterator is already out of the container */ 81 | bool iterator_end(PQueueIterator it); 82 | } 83 | 84 | #endif /* end of include guard: FC_QUEUE_H_KTY6FH1S */ 85 | -------------------------------------------------------------------------------- /ext/fast_containers/fc_pq.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Roberto Esposito 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining 4 | // a copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to 8 | // permit persons to whom the Software is furnished to do so, subject to 9 | // the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be 12 | // included in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | #include "fc_pq.h" 22 | #include 23 | 24 | namespace fc_pq { 25 | 26 | typedef std::pair PQElem; 27 | 28 | class PairsComparator 29 | { 30 | bool reverse; 31 | public: 32 | PairsComparator(PQueueKind kind) {reverse=(kind==MIN_QUEUE);} 33 | bool operator() (const PQElem& lhs, const PQElem& rhs) const 34 | { 35 | if (reverse) return (lhs.second>rhs.second); 36 | else return (lhs.second PQueueStorage; 45 | typedef unsigned int PQueueStorageVersion; 46 | 47 | typedef struct _PQueue { 48 | PQueueStorage storage; 49 | PairsComparator comparator; 50 | PQueueStorageVersion version; 51 | 52 | _PQueue(PQueueKind kind) : comparator(kind), version(0) { } 53 | }* PQueue; 54 | 55 | void destroy(PQueue q){ 56 | delete q; 57 | } 58 | 59 | PQueue create(PQueueKind kind) { 60 | return new _PQueue(kind); 61 | } 62 | 63 | /* Getting the size of the container */ 64 | unsigned int size(PQueue q) { 65 | return q->storage.size(); 66 | } 67 | 68 | 69 | void push(PQueue q, void* value, double priority) { 70 | q->version++; 71 | q->storage.push_back(PQElem(value, priority)); 72 | push_heap(q->storage.begin(), q->storage.end(), q->comparator); 73 | } 74 | 75 | void* top(PQueue q) { 76 | return q->storage.at(0).first; 77 | } 78 | 79 | double top_key(PQueue q) { 80 | return q->storage.at(0).second; 81 | } 82 | 83 | double second_best_key(PQueue q) { 84 | // elements in storage are not necessarily sorted, but the 85 | // second best key is bound to be in one of the first two elements 86 | if(q->storage.size()==2) 87 | return q->storage.at(1).second; 88 | 89 | double key1 = q->storage.at(1).second; 90 | double key2 = q->storage.at(2).second; 91 | 92 | if( q->comparator(q->storage.at(1), q->storage.at(2)) ) { 93 | return key2; 94 | } else { 95 | return key1; 96 | } 97 | } 98 | 99 | void pop(PQueue q) { 100 | q->version++; 101 | pop_heap(q->storage.begin(), q->storage.end(), q->comparator); 102 | q->storage.pop_back(); 103 | } 104 | 105 | bool empty(PQueue q) { 106 | return q->storage.empty(); 107 | } 108 | 109 | // -------------------------------------- 110 | // Iterator 111 | // -------------------------------------- 112 | 113 | 114 | typedef struct _PQueueIterator { 115 | PQueueStorage::const_iterator iterator; 116 | PQueue pqueue; 117 | PQueueStorage* storage; 118 | PQueueStorageVersion version; 119 | 120 | _PQueueIterator(PQueue q) : iterator(q->storage.begin()), pqueue(q), storage(&q->storage), version(q->version) 121 | { } 122 | 123 | void checkVersion() { 124 | if(version != pqueue->version) { 125 | throw PQueueException("FastContainers::PriorityQueue - a change in the priority queue invalidated the current iterator."); 126 | } 127 | } 128 | } PQueueImplIterator; 129 | #define QIT(it) ((PQueueImplIterator*)(it)) 130 | 131 | /* Returns a new iterator object */ 132 | PQueueIterator iterator(PQueue q) { 133 | PQueueImplIterator* it = new PQueueImplIterator(q); 134 | return it; 135 | } 136 | 137 | void iterator_dispose(PQueueIterator it) { 138 | delete it; 139 | } 140 | 141 | /* Returns the value of the current element */ 142 | void* iterator_get_value(PQueueIterator it) { 143 | return it->iterator->first; 144 | } 145 | 146 | /* Returns the priority of the current element */ 147 | double iterator_get_key(PQueueIterator it) { 148 | return it->iterator->second; 149 | } 150 | 151 | /* Moves on to the next element */ 152 | PQueueIterator iterator_next(PQueueIterator it) { 153 | it->checkVersion(); 154 | it->iterator++; 155 | return it; 156 | } 157 | 158 | bool iterator_end(PQueueIterator it) { 159 | return it->iterator == it->storage->end(); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /test/test_fast_containers.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014 Roberto Esposito 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining 4 | # a copy of this software and associated documentation files (the 5 | # "Software"), to deal in the Software without restriction, including 6 | # without limitation the rights to use, copy, modify, merge, publish, 7 | # distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so, subject to 9 | # the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be 12 | # included in all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | require "minitest/autorun" 22 | require 'minitest/spec' 23 | require 'minitest/autorun' 24 | require "fc" 25 | 26 | class TestFastContainers < Minitest::Test 27 | def test_new_object_creation 28 | assert !FastContainers::PriorityQueue.new(:max).nil? 29 | end 30 | 31 | def test_new_handling_of_bad_parameter 32 | assert_raises(TypeError) do 33 | FastContainers::PriorityQueue.new(true) 34 | end 35 | end 36 | 37 | def test_push_returns_self 38 | pq = FastContainers::PriorityQueue.new(:max) 39 | assert_equal pq, pq.push("test",10) 40 | end 41 | 42 | def test_top_on_a_single_element_queue_returns_that_element 43 | pq = FastContainers::PriorityQueue.new(:max); 44 | pq.push("test",10) 45 | assert_equal "test", pq.top 46 | end 47 | 48 | def test_next_on_a_single_element_queue_returns_that_element 49 | pq = FastContainers::PriorityQueue.new(:max); 50 | pq.push("test",10) 51 | assert_equal "test", pq.next 52 | end 53 | 54 | 55 | def test_top_returns_the_maximal_element_in_a_max_queue 56 | pq = FastContainers::PriorityQueue.new(:max) # this is a max queue 57 | pq.push("10", 10) 58 | pq.push("30", 30) 59 | pq.push("20", 20) 60 | assert_equal "30", pq.top 61 | end 62 | 63 | def test_next_returns_the_maximal_element_in_a_max_queue 64 | pq = FastContainers::PriorityQueue.new(:max) # this is a max queue 65 | pq.push("10", 10) 66 | pq.push("30", 30) 67 | pq.push("20", 20) 68 | assert_equal "30", pq.next 69 | end 70 | 71 | 72 | def test_top_returns_the_minimal_element_in_a_min_queue 73 | pq = FastContainers::PriorityQueue.new(:min) # this is a max queue 74 | pq.push("10", 10) 75 | pq.push("30", 30) 76 | pq.push("20", 20) 77 | assert_equal "10", pq.top 78 | end 79 | 80 | def test_next_returns_the_minimal_element_in_a_min_queue 81 | pq = FastContainers::PriorityQueue.new(:min) # this is a max queue 82 | pq.push("10", 10) 83 | pq.push("30", 30) 84 | pq.push("20", 20) 85 | assert_equal "10", pq.next 86 | end 87 | 88 | def test_top_key_returns_the_top_priority 89 | pq = FastContainers::PriorityQueue.new(:max) # this is a max queue 90 | pq.push("10", 10) 91 | pq.push("30", 30) 92 | pq.push("20", 20) 93 | assert_equal 30, pq.top_key 94 | end 95 | 96 | def test_next_key_returns_the_top_priority 97 | pq = FastContainers::PriorityQueue.new(:max) # this is a max queue 98 | pq.push("10", 10) 99 | pq.push("30", 30) 100 | pq.push("20", 20) 101 | assert_equal 30, pq.next_key 102 | end 103 | 104 | 105 | def test_pop_removes_an_element_from_the_top 106 | pq = FastContainers::PriorityQueue.new(:max) # this is a max queue 107 | pq.push("10", 10) 108 | pq.push("30", 30) 109 | pq.push("20", 20) 110 | extracted = pq.pop 111 | 112 | assert_equal 20, pq.top_key 113 | assert_equal "30", extracted 114 | end 115 | 116 | def test_pop_raises_an_exception_if_the_queue_is_empty 117 | pq = FastContainers::PriorityQueue.new(:max) # this is a max queue 118 | pq.push("10", 10) 119 | pq.pop 120 | assert_raises(RuntimeError) { 121 | pq.pop 122 | } 123 | end 124 | 125 | def test_empty_returns_false_if_the_queue_is_not_empty 126 | pq = FastContainers::PriorityQueue.new(:max) # this is a max queue 127 | pq.push("10", 10) 128 | assert !pq.empty? 129 | end 130 | 131 | def test_empty_returns_true_if_the_queue_is_empty 132 | pq = FastContainers::PriorityQueue.new(:max) # this is a max queue 133 | assert pq.empty? 134 | end 135 | 136 | def test_size_on_empty_queue 137 | pq = FastContainers::PriorityQueue.new(:max) 138 | assert_equal 0, pq.size 139 | end 140 | 141 | def test_size_on_non_empty_queue 142 | pq = FastContainers::PriorityQueue.new(:max) 143 | pq.push("x",10); 144 | pq.push("y",20); 145 | pq.push("z",30); 146 | assert_equal 3, pq.size 147 | end 148 | 149 | def test_enumerable 150 | pq = FastContainers::PriorityQueue.new(:max) 151 | pq.push(1,10); 152 | pq.push(2,20); 153 | pq.push(3,30); 154 | sum_o = 0 155 | sum_p = 0 156 | pq.map { |o,p| sum_o+=o; sum_p+=p } 157 | assert_equal 6, sum_o 158 | assert_equal 60, sum_p 159 | end 160 | 161 | def test_top_key_on_empty_queues 162 | pq = FastContainers::PriorityQueue.new(:max) 163 | assert_nil pq.top_key 164 | end 165 | 166 | def test_top_on_empty_queues 167 | pq = FastContainers::PriorityQueue.new(:max) 168 | assert_nil pq.top 169 | end 170 | 171 | def test_second_best_key 172 | pq = FastContainers::PriorityQueue.new(:max) 173 | pq.push("x", 100); 174 | pq.push("y", 80); 175 | pq.push("z", 40); 176 | pq.push("w", 60); 177 | pq.push("i", 90); 178 | pq.push("j", 95); 179 | 180 | assert_equal pq.second_best_key, 95 181 | end 182 | 183 | def test_second_best_key_on_min_queues 184 | pq = FastContainers::PriorityQueue.new(:min) 185 | pq.push("a",2) 186 | pq.push("b",2) 187 | pq.push("c",3) 188 | 189 | assert_equal pq.second_best_key, 2 190 | end 191 | 192 | def test_second_best_key_on_empty_pq 193 | pq = FastContainers::PriorityQueue.new(:max) 194 | assert_nil pq.second_best_key 195 | end 196 | 197 | def test_second_best_key_on_size_1_pq 198 | pq = FastContainers::PriorityQueue.new(:max) 199 | pq.push("x", 100) 200 | assert_nil pq.second_best_key 201 | end 202 | 203 | def test_second_best_key_on_size_2_pq 204 | pq = FastContainers::PriorityQueue.new(:max) 205 | pq.push("x", 100) 206 | pq.push("x", 80) 207 | 208 | assert_equal 80, pq.second_best_key 209 | end 210 | 211 | def test_each_will_iterate_over_all_elements 212 | pq = FastContainers::PriorityQueue.new(:max); 213 | pq.push("x", 100); 214 | pq.push("y", 80); 215 | pq.push("z", 40); 216 | pq.push("w", 60); 217 | pq.push("i", 90); 218 | pq.push("j", 95); 219 | 220 | objects = Set.new 221 | 222 | result = pq.each do |obj, priority| 223 | objects << obj 224 | end 225 | 226 | assert_equal objects, Set.new(["x","y","z","w","i","j"]) 227 | assert_equal pq, result 228 | end 229 | 230 | def test_each_will_abort_and_return_nil_if_queue_changes 231 | pq = FastContainers::PriorityQueue.new(:max); 232 | pq.push("x", 100); 233 | pq.push("y", 80); 234 | pq.push("z", 40); 235 | pq.push("w", 60); 236 | pq.push("i", 90); 237 | pq.push("j", 95); 238 | 239 | objects = Set.new 240 | 241 | exception = assert_raises RuntimeError do 242 | count = 0 243 | pq.each do |obj, priority| 244 | if count==3 245 | pq.push("no way!", 90) 246 | end 247 | 248 | count+=1 249 | objects << obj 250 | end 251 | end 252 | 253 | assert_match (/a change in the priority queue invalidated the current iterator/), exception.message 254 | assert_equal 4, objects.size 255 | end 256 | end 257 | -------------------------------------------------------------------------------- /ext/fast_containers/FastContainers.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Roberto Esposito 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining 4 | // a copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to 8 | // permit persons to whom the Software is furnished to do so, subject to 9 | // the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be 12 | // included in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | #include "ruby.h" 22 | #include "fc_pq.h" 23 | 24 | // Defining a space for information and references about the module to be stored internally 25 | static VALUE FastContainers = Qnil; 26 | static VALUE PriorityQueue = Qnil; 27 | 28 | // -------------------------------------------------------------------------------- 29 | // UTILITIES 30 | // -------------------------------------------------------------------------------- 31 | 32 | static fc_pq::PQueue pq_from_self(VALUE self) { 33 | fc_pq::PQueue queue; 34 | Data_Get_Struct(self, struct fc_pq::_PQueue, queue); 35 | 36 | return queue; 37 | } 38 | 39 | static void pq_mark(void *ptr) { 40 | if(ptr==NULL) 41 | return; 42 | 43 | fc_pq::PQueueIterator it = fc_pq::iterator((fc_pq::PQueue)ptr); 44 | while( !fc_pq::iterator_end(it) ) { 45 | rb_gc_mark( (VALUE) fc_pq::iterator_get_value(it) ); 46 | it = fc_pq::iterator_next(it); 47 | } 48 | fc_pq::iterator_dispose(it); 49 | } 50 | 51 | // -------------------------------------------------------------------------------- 52 | // METHODS 53 | // -------------------------------------------------------------------------------- 54 | 55 | /* 56 | * call-seq: 57 | * PriorityQueue.new(queue_kind) 58 | * 59 | * Create a new priority queue and returns it. 60 | * +queue_kind+ specifies whether to build a :min or a :max queue. 61 | */ 62 | static VALUE pq_new(VALUE klass, VALUE queue_kind) { 63 | if( TYPE(queue_kind) != T_SYMBOL ) { 64 | rb_raise(rb_eTypeError, "queue_kind parameter must be a symbol"); 65 | } 66 | 67 | fc_pq::PQueueKind kind; 68 | 69 | if( rb_intern("max") == rb_to_id(queue_kind) ) 70 | kind = fc_pq::MAX_QUEUE; 71 | else if( rb_intern("min") == rb_to_id(queue_kind) ) 72 | kind = fc_pq::MIN_QUEUE; 73 | else rb_raise(rb_eTypeError, "queue_kind parameter must be either :max or :min"); 74 | 75 | fc_pq::PQueue queue = fc_pq::create(kind); 76 | VALUE data = Data_Wrap_Struct(klass, pq_mark, fc_pq::destroy, queue); 77 | rb_obj_call_init(data, 0, NULL); 78 | return data; 79 | } 80 | 81 | /* 82 | * call-seq: 83 | * size -> num 84 | * 85 | * Returns the size of the priority queue 86 | */ 87 | 88 | static VALUE pq_size(VALUE self) { 89 | return INT2NUM(fc_pq::size(pq_from_self(self))); 90 | } 91 | 92 | /* 93 | * call-seq: 94 | * push(obj,priority) -> self 95 | * 96 | * Push the +obj+/+priority+ pair into the queue and returns self. 97 | */ 98 | static VALUE pq_push(VALUE self, VALUE obj, VALUE priority) { 99 | fc_pq::push(pq_from_self(self), (void*)obj, NUM2DBL(priority)); 100 | return self; 101 | } 102 | 103 | /* 104 | * call-seq: 105 | * top -> obj 106 | * 107 | * Returns the object at the top of the priority queue. 108 | */ 109 | static VALUE pq_top(VALUE self) { 110 | fc_pq::PQueue queue = pq_from_self(self); 111 | if( fc_pq::empty(queue) ) { 112 | return Qnil; 113 | } 114 | 115 | return (VALUE) fc_pq::top( queue ); 116 | } 117 | 118 | /* 119 | * call-seq: 120 | * top_key -> float 121 | * 122 | * Returns the priority of the object at the top of the priority queue. 123 | */ 124 | static VALUE pq_top_key(VALUE self) { 125 | fc_pq::PQueue queue = pq_from_self(self); 126 | if(fc_pq::empty(queue)) 127 | return Qnil; 128 | 129 | double priority = fc_pq::top_key(queue); 130 | return DBL2NUM(priority); 131 | } 132 | 133 | /* 134 | * call-seq: 135 | * second_best_key -> float 136 | * 137 | * Returns the priority of the second best element in the priority queue. 138 | */ 139 | static VALUE pq_second_best_key(VALUE self) { 140 | fc_pq::PQueue queue = pq_from_self(self); 141 | if(fc_pq::size(queue) < 2) 142 | return Qnil; 143 | 144 | double priority = fc_pq::second_best_key(queue); 145 | return DBL2NUM(priority); 146 | } 147 | 148 | /* 149 | * call-seq: 150 | * pop -> obj 151 | * 152 | * Pops the top most element from the priority queue. 153 | * Returns the top object (before the pop). 154 | */ 155 | static VALUE pq_pop(VALUE self) { 156 | fc_pq::PQueue queue = pq_from_self(self); 157 | 158 | if( fc_pq::empty(queue) ) 159 | rb_raise(rb_eRuntimeError, "Pop called on an empty queue"); 160 | 161 | VALUE top = (VALUE) fc_pq::top( queue ); 162 | fc_pq::pop(queue); 163 | 164 | return top; 165 | } 166 | 167 | /* 168 | * call-seq: 169 | * empty? 170 | * 171 | * Returns true if the queue is empty 172 | */ 173 | 174 | static VALUE pq_empty(VALUE self) { 175 | if( fc_pq::empty(pq_from_self(self)) ) 176 | return Qtrue; 177 | else 178 | return Qfalse; 179 | } 180 | 181 | 182 | /* 183 | * call-seq: 184 | * each { |obj,priority| ... } -> self 185 | * 186 | * Iterates through the priority queue yielding each element to the given block. 187 | * The order of the yielded elements is not defined. The given block *must not* change 188 | * the queue elements order. In case it does the iteration will be aborted and the 189 | * method will return nil. 190 | * 191 | * Returns self. 192 | */ 193 | 194 | static VALUE pq_each(VALUE self) { 195 | fc_pq::PQueue queue = pq_from_self(self); 196 | fc_pq::PQueueIterator iterator; 197 | 198 | try { 199 | iterator = fc_pq::iterator(queue); 200 | while( !fc_pq::iterator_end(iterator) ) { 201 | VALUE value = (VALUE) fc_pq::iterator_get_value(iterator); 202 | VALUE num = DBL2NUM(fc_pq::iterator_get_key(iterator)); 203 | rb_yield_values( 2,value, num ); 204 | fc_pq::iterator_next(iterator); 205 | } 206 | fc_pq::iterator_dispose(iterator); 207 | } catch (fc_pq::PQueueException& exception) { 208 | fc_pq::iterator_dispose(iterator); 209 | rb_raise(rb_eRuntimeError, "%s", exception.what()); 210 | return Qnil; 211 | } 212 | 213 | return self; 214 | } 215 | 216 | /* 217 | * call-seq: 218 | * pop_each { |obj, priority| ... } -> self 219 | * 220 | * Iterates through the priority queue popping the top element and 221 | * yielding it to the block. The order of yielded elements is guaranteed 222 | * to be the priority order. 223 | * Returns self. 224 | */ 225 | 226 | static VALUE pq_pop_each(VALUE self) { 227 | fc_pq::PQueue queue= pq_from_self(self); 228 | while( !fc_pq::empty(queue) ) { 229 | VALUE value = (VALUE) fc_pq::top(queue); 230 | double key = fc_pq::top_key(queue); 231 | fc_pq::pop(queue); 232 | rb_yield_values(2, value, DBL2NUM(key)); 233 | } 234 | 235 | return self; 236 | } 237 | 238 | // -------------------------------------------------------------------------------- 239 | // INITIALIZATION 240 | // -------------------------------------------------------------------------------- 241 | 242 | /* 243 | * Document-module: FastContainers 244 | * Exposes C++ implementation of some containers not defined in the standard ruby libraries. 245 | */ 246 | 247 | /* 248 | * Document-class: FastContainers::PriorityQueue 249 | * Implements priority queues through a C++ heap (using the standard std::priority_queue class). 250 | * Includes Enumerable, so that standard enumeration based methods (e.g., map, all?, any?, ...) 251 | * can all be used with this container. Notice that Enumerable methods are based on #each, implying 252 | * that the order used to iterate through the container is undefined. 253 | */ 254 | extern "C" { 255 | void Init_fast_containers() { 256 | FastContainers = rb_define_module("FastContainers"); 257 | PriorityQueue = rb_define_class_under(FastContainers, "PriorityQueue", rb_cObject); 258 | rb_undef_alloc_func(PriorityQueue); 259 | rb_global_variable(&FastContainers); 260 | rb_global_variable(&PriorityQueue); 261 | 262 | rb_define_singleton_method(PriorityQueue, "new", RUBY_METHOD_FUNC(pq_new), 1); 263 | rb_define_method(PriorityQueue, "size", RUBY_METHOD_FUNC(pq_size), 0); 264 | rb_define_method(PriorityQueue, "push", RUBY_METHOD_FUNC(pq_push), 2); 265 | rb_define_method(PriorityQueue, "top", RUBY_METHOD_FUNC(pq_top), 0); 266 | rb_define_method(PriorityQueue, "top_key", RUBY_METHOD_FUNC(pq_top_key), 0); 267 | rb_define_method(PriorityQueue, "second_best_key", RUBY_METHOD_FUNC(pq_second_best_key), 0); 268 | rb_define_method(PriorityQueue, "pop", RUBY_METHOD_FUNC(pq_pop), 0); 269 | rb_define_method(PriorityQueue, "empty?", RUBY_METHOD_FUNC(pq_empty), 0); 270 | rb_define_method(PriorityQueue, "each", RUBY_METHOD_FUNC(pq_each), 0); 271 | rb_define_method(PriorityQueue, "pop_each", RUBY_METHOD_FUNC(pq_pop_each), 0); 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PriorityQueueCxx 2 | 3 | [![Gem Version](https://badge.fury.io/rb/priority_queue_cxx.png)](http://badge.fury.io/rb/priority_queue_cxx) 4 | [![priority_queue_cxx API Documentation](https://www.omniref.com/ruby/gems/priority_queue_cxx.png)](https://www.omniref.com/ruby/gems/priority_queue_cxx) 5 | 6 | 7 | *PriorityQueueCxx* provides a fast implementation of priority queues for ruby. Speed is achieved by exposing the c++ standard implementation through a light ruby wrapper. As a bigger project, the library may grow to provide a number of containers that are not in the standard ruby library and are presently only available as pure ruby libraries. Presently, however, the library includes a single class named PriorityQueue. More containers will be added as necessity arises. Contributors and feature requests are most welcome. 8 | 9 | The library exposes a module named ```FastContainers``` (to be required using ```require 'fc'```) which provides the PriorityQueue class. 10 | 11 | ## Installation 12 | 13 | ```ruby 14 | gem install 'priority_queue_cxx' 15 | ``` 16 | 17 | ## Usage Example 18 | 19 | ```ruby 20 | 21 | require 'fc' 22 | q = FastContainers::PriorityQueue.new(:max) 23 | q.push("largest", 10) 24 | q.push("smallest", 5) 25 | q.top # => "largest" 26 | q.top_key # => 10 27 | q.pop 28 | ``` 29 | 30 | ## How fast is it? 31 | 32 | As far as I know, only one other library (the PriorityQueue gem) provides priority queues implemented as a C extension. This implies that the fc::PriorityQueue is a *lot* faster than most current alternatives and, as shown below, it compares favorably with the mentioned C extension as well. 33 | 34 | To get an idea about how fast it is, below I provide a comparison of the time needed to push and pop a large number of elements into a priority queue. Each experiment compares FastContainers with other priority queues implementations. Since timings varies greatly among different implementations, the number of push/pop performed is chosen so to make the experiments to run for (at most) few minutes. 35 | 36 | The following table summarizes the outputs, detailed results can be found in the next few sections. All libraries have been installed through the 'gem' command and executed using ruby v. 2.1.0. 37 | 38 | | library | avg μs per push | avg μs per pop | avg μs per op | 39 | |:--------|---------:|---------:|---------:| 40 | | *priority_queue_cxx* | *0.456* | *1.138* | *0.797* | 41 | | PriorityQueue | 2.09 | 5.186 | 3.638 | 42 | | em-priority-queue | 3.56 | 8.32 | 5.94 | 43 | | pqueue | 669.0 | 0.1 | 334.55| 44 | | algorithms | 2584.6 | 29.6 |1307.1 | 45 | | priority_queue | 1.4 |19134.6 |9568.0 | 46 | 47 | where: results are sorted according to "avg μs per op" (higher in the list is better); μs stands for micro seconds; op stands for any operation (push or pop); the figures for priority_queue_cxx has been calculated with the results of experiments with PriorityQueue (the experiment with the highest number of operations). 48 | 49 | 50 | ### Comparison with [algorithms (0.6.1)](http://rubygems.org/gems/algorithms) (50,000 push/pop) 51 | 52 | ```ruby 53 | 54 | require 'fc' 55 | require 'algorithms' 56 | require 'benchmark' 57 | 58 | N = 50_000 59 | algo_pq = Containers::PriorityQueue.new 60 | fc_pq = FastContainers::PriorityQueue.new(:min) 61 | 62 | Benchmark.bm do |bm| 63 | bm.report('algo:push') { N.times { |n| algo_pq.push(n.to_s, rand) } } 64 | bm.report('fc:push') { N.times { |n| fc_pq.push(n.to_s, rand) } } 65 | bm.report('algo:pop') { N.times { algo_pq.pop } } 66 | bm.report('fc:pop') { N.times { fc_pq.pop } } 67 | end 68 | ``` 69 | 70 | Output (reformatted): 71 | 72 | | | user| system| total| real | 73 | |:--------|---------:|---------:|---------:|-----------:| 74 | |algorithms:push|122.200| 7.030|129.230|(129.173)| 75 | |*fc:push* | *0.020*| *0.000*| *0.020*|*( 0.020)*| 76 | |algorithms:pop | 1.460| 0.020| 1.480|( 1.476)| 77 | |*fc:pop* | *0.030*| *0.000*| *0.030*|*( 0.030)*| 78 | 79 | Summary: FastContainers::PriorityQueues (fc) are *6461.5 times faster* on pushes and *49.3 times faster* on pops. 80 | 81 | 82 | ### Comparison with [priority_queue (0.2.0)](http://rubygems.org/gems/priority_queue) (50,000 push/pop) 83 | 84 | ```ruby 85 | require 'fc' 86 | require 'priority_queue' 87 | require 'benchmark' 88 | 89 | N = 50_000 90 | pq_pq = PriorityQueue.new 91 | fc_pq = FastContainers::PriorityQueue.new(:min) 92 | 93 | Benchmark.bm do |bm| 94 | bm.report('pq:push') { N.times { |n| pq_pq[rand] << n.to_s } } 95 | bm.report('fc:push') { N.times { |n| fc_pq.push(n.to_s, rand) } } 96 | bm.report('pq:pop') { N.times { pq_pq.shift } } 97 | bm.report('fc:pop') { N.times { fc_pq.pop } } 98 | end 99 | ``` 100 | 101 | Output (reformatted): 102 | 103 | | | user| system| total| real | 104 | |:--------|---------:|---------:|---------:|-----------:| 105 | |priority_queue:push | 0.060 | 0.010 | 0.070 |( 0.062593)| 106 | |*fc:push* | *0.020* | *0.000* | *0.020* |*( 0.018866)*| 107 | |priority_queue:pop | 948.440 | 8.290 | 956.730 |(956.676601)| 108 | |*fc:pop* | 0.040 | 0.000 | 0.040 |*( 0.032753)*| 109 | 110 | Summary: FastContainers::PriorityQueues (fc) are *3.5 times faster* on pushes and *23918.25 times faster* on pops. 111 | 112 | ### Comparison with [em-priority-queue (1.1.2)](http://rubygems.org/gems/em-priority-queue) (500,000 push/pop) 113 | 114 | ```ruby 115 | require 'fc' 116 | require 'em-priority-queue' 117 | require 'benchmark' 118 | 119 | N = 500_000 120 | em_pq = EM::PriorityQueue.new 121 | fc_pq = FastContainers::PriorityQueue.new(:min) 122 | 123 | Benchmark.bm do |bm| 124 | bm.report('em:push') { N.times { |n| em_pq.push(n.to_s, rand) } } 125 | bm.report('fc:push') { N.times { |n| fc_pq.push(n.to_s, rand) } } 126 | bm.report('em:pop') { N.times { em_pq.pop {} } } 127 | bm.report('fc:pop') { N.times { fc_pq.pop } } 128 | end 129 | ``` 130 | 131 | Output (reformatted): 132 | 133 | | | user| system| total| real | 134 | |:--------|---------:|---------:|---------:|-----------:| 135 | |em-priority-queue:push |1.650 |0.130 | 1.780 | ( 1.895794) | 136 | |*fc:push* |*0.190* |*0.020* | *0.210* | *( 0.224068)* | 137 | |em-priority-queue:pop |3.980 |0.180 | 4.160 | ( 4.360084) | 138 | |*fc:pop* |*0.380* |*0.000* | *0.380* | *( 0.381250)* | 139 | 140 | Summary: FastContainers::PriorityQueue (fc) are *8.5 times faster* on pushes and *10.9 times faster* on pops. 141 | 142 | ### Comparison with [pqueue (2.0.2)](http://rubygems.org/gems/pqueue) (100,000 push/pop) 143 | 144 | 145 | ```ruby 146 | require 'fc' 147 | require 'pqueue' 148 | require 'benchmark' 149 | 150 | N = 100_000 151 | pq_pq = PQueue.new { |x,y| x[1] <=> y[1] } 152 | fc_pq = FastContainers::PriorityQueue.new(:min) 153 | 154 | Benchmark.bm do |bm| 155 | bm.report('pq:push') { N.times { |n| pq_pq.push([n,rand]) } } 156 | bm.report('fc:push') { N.times { |n| fc_pq.push(n.to_s, rand) } } 157 | bm.report('pq:pop') { N.times { pq_pq.pop } } 158 | bm.report('fc:pop') { N.times { fc_pq.pop } } 159 | end 160 | ``` 161 | 162 | Output (reformatted): 163 | 164 | | | user| system| total| real | 165 | |:--------|---------:|---------:|---------:|-----------:| 166 | |pqueue:push | 25.240|41.660 | 66.900| ( 66.871391)| 167 | |*fc:push* | *0.040* | *0.000* | *0.040*| *( 0.035270)*| 168 | |pqueue:pop | 0.010 | 0.000 | 0.010| ( 0.018718)| 169 | |*fc:pop* | *0.070* | *0.000* | *0.070*| *( 0.061138)*| 170 | 171 | Summary: FastContainers::PriorityQueue (fc) are *1672.5 times faster* on pushes and *7 times slower* on pops. 172 | 173 | ### Comparison with [PriorityQueue (0.1.2)](https://rubygems.org/gems/PriorityQueue) (5,000,000 push/pop) 174 | 175 | 176 | ```ruby 177 | require 'fc' 178 | require 'priority_queue' 179 | require 'benchmark' 180 | 181 | N = 5_000_000 182 | pq_pq = CPriorityQueue.new 183 | fc_pq = FastContainers::PriorityQueue.new(:min) 184 | 185 | Benchmark.bm do |bm| 186 | bm.report('pq:push') { N.times { |n| pq_pq.push(n.to_s,rand) } } 187 | bm.report('fc:push') { N.times { |n| fc_pq.push(n.to_s, rand) } } 188 | bm.report('pq:pop') { N.times { pq_pq.delete_min } } 189 | bm.report('fc:pop') { N.times { fc_pq.pop } } 190 | end 191 | ``` 192 | 193 | Output (reformatted): 194 | 195 | | | user| system| total| real | 196 | |:--------|---------:|---------:|---------:|-----------:| 197 | |PriorityQueue:push | 10.020| 0.430| 10.45|( 10.665449)| 198 | |*fc:push* | *2.110*| *0.170*| *2.28*|*( 2.452529)*| 199 | |PriorityQueue:pop | 25.860| 0.070| 25.93|( 25.949438)| 200 | |*fc:pop* | *5.690*| *0.000*| *5.69*|*( 5.688552)*| 201 | 202 | Summary: FastContainers::PriorityQueue (fc) are *4.58 times faster* on pushes and *4.54 times faster* on pops. 203 | 204 | ## Which is the best priority queue implementation for ruby? 205 | 206 | As it usually happens, the answer is: it depends. The evidence reported above shows that if you are only interested in the speed of push and pop methods, then priority_queue_cxx is a very good candidate. Few other important factors may make other libraries be better suited for your needs. The most glaring one is that priority_queue_cxx implementation (i.e., ```FastContainers::PriorityQueue```) does not support changes of priorities1. If your problem requires this feature, the best candidate appears to be [PriorityQueue (0.1.2)](https://rubygems.org/gems/PriorityQueue) library. Also, in making your choice, you may want to consider the fact that not all the presented libraries appear to be actively maintained (although, no one gave any problem at the time of the writing). 207 | 208 | ## API 209 | 210 | Here it follows a transcription of the RDoc documentation for the library. I'm adding it here because I'm having difficulties in instructing the 'gem' executable to generate the correct files on installation (everything works fine using rdoc from the command line though). Any suggestion about how to solve this problem is *very* welcome. 211 | 212 | ### FastContainers::PriorityQueue 213 | 214 | #### Public Class Methods 215 | 216 | ##### new(queue_kind) 217 | 218 | Create a new priority queue and returns it. queue_kind specifies whether to build a :min or a :max queue. 219 | 220 | Example: 221 | 222 | ```ruby 223 | pq = FastContainers::PriorityQueue.new(:min) 224 | ``` 225 | 226 | #### Public Instance Methods 227 | 228 | ##### each { |obj,priority| ... } → self 229 | 230 | Iterates through the priority queue yielding each element to the given block. The order of the yielded elements is not defined. Returns self. 231 | 232 | Example: 233 | 234 | ```ruby 235 | 236 | pq.each do |obj,priority| 237 | puts "Obj #{obj} has priority #{priority}" 238 | end 239 | ``` 240 | 241 | ##### next 242 | 243 | Alias for: [top](#label-top+%E2%86%92+obj) 244 | 245 | ##### next_key 246 | 247 | Alias for: [top_key](#label-top_key+%E2%86%92+float) 248 | 249 | #### second_best_key → float 250 | 251 | Returns the priority of the second best element in the priority queue. 252 | 253 | ##### empty? 254 | 255 | Returns true if the queue is empty 256 | 257 | ##### pop → self 258 | 259 | Pops the top most element from the priority queue. Returns self. 260 | 261 | ##### pop_each { |obj, priority| ... } → self 262 | 263 | Iterates through the priority queue popping the top element and yielding it to the block. The order of yielded elements is guaranteed to be the priority order. Returns self. 264 | 265 | Example: 266 | 267 | ```ruby 268 | ary = [100, 1, 90, 55, 6] 269 | ary.each { |x| pq.push(x.to_s, x)} 270 | ary.pop_each {|obj, priority| print(priority, ',') } # => 1,6,55,90,100, 271 | ``` 272 | 273 | ##### push(obj,priority) → self 274 | 275 | Push the obj/priority pair into the queue and returns self. 276 | 277 | ##### size → num 278 | 279 | Returns the size of the priority queue 280 | 281 | ##### top → obj 282 | 283 | Returns the object at the top of the priority queue. 284 | 285 | ##### top_key → float 286 | 287 | Returns the priority of the object at the top of the priority queue. 288 | 289 | #### Included Modules 290 | 291 | The class Includes Enumerable, so that standard enumeration based methods (e.g., map, all?, any?, ...) can all be used with this container. Notice that Enumerable methods are based on #each, implying that the order used to iterate through the container is undefined. 292 | 293 | ## Notes 294 | 295 | 1 It is worth mentioning that, due to how priority queue are implemented by the C++ standard library, this implementation can't efficiently support priority changes. In any case, to support this feature would require important changes in the current API. 296 | --------------------------------------------------------------------------------