├── .gitignore ├── Manifest.txt ├── Gemfile ├── .travis.yml ├── work └── Assembly ├── Index.yml ├── License.txt ├── Contributing.md ├── .index ├── README.md ├── History.md ├── test └── test_pqueue.rb ├── lib └── pqueue.rb └── .gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | .ergo/digest 2 | .yardoc 3 | doc/ 4 | log/ 5 | pkg/ 6 | tmp/ 7 | web/ 8 | *.gem 9 | *.lock 10 | 11 | -------------------------------------------------------------------------------- /Manifest.txt: -------------------------------------------------------------------------------- 1 | #!mast .ruby .yaropts bin lib man qed spec test [A-Z]*.* 2 | lib/pqueue.rb 3 | test/test_pqueue.rb 4 | License.txt 5 | Gemfile.lock 6 | Index.yml 7 | README.md 8 | History.md 9 | Contributing.md 10 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | group :test do 4 | gem "microtest" 5 | gem "ae" 6 | gem "rubytest" 7 | gem "rubytest-cli" 8 | end 9 | 10 | group :build do 11 | gem "indexer" 12 | gem "mast" 13 | end 14 | 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: ruby 3 | script: "bundle exec rubytest -Ilib test/test_*.rb" 4 | rvm: 5 | - 2.1.0 6 | - 2.0.0 7 | - 1.9.3 8 | - 1.8.7 9 | - jruby 10 | - rbx 11 | matrix: 12 | allow_failures: 13 | - rvm: rbx 14 | cache: bundler 15 | -------------------------------------------------------------------------------- /work/Assembly: -------------------------------------------------------------------------------- 1 | --- 2 | #erbside: 3 | # path: lib/pqueue/version.rb 4 | 5 | email: 6 | mailto: 7 | - ruby-talk@ruby-lang.org 8 | - rubyworks-mailinglist@googlegroups.com 9 | 10 | gem: 11 | active: true 12 | 13 | github: 14 | gh_pages: web 15 | 16 | dnote: 17 | title: Developer's Notes 18 | output: log/NOTES.rdoc 19 | 20 | vclog: 21 | output: 22 | - log/history.html 23 | - log/changes.html 24 | 25 | tag: 26 | service: custom 27 | release: | 28 | cmd = %[gemdo news | git tag -a -F - #{project.version}] 29 | puts cmd 30 | sh cmd 31 | 32 | rubytest: 33 | loadpath: lib 34 | files: test/test_*.rb 35 | 36 | #qed: 37 | # files: qed/ 38 | 39 | #qedoc: 40 | # files: qed/ 41 | # title: "PQueue Demonstandum" 42 | # output: QED.rdoc 43 | 44 | -------------------------------------------------------------------------------- /Index.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: 3 | pqueue 4 | 5 | version: 6 | 2.1.0 7 | 8 | title: 9 | PQueue 10 | 11 | summary: 12 | Queue of Prioritized Elements 13 | 14 | description: 15 | A priority queue is like a standard queue, except that each inserted elements 16 | is given a certain priority, based on the result of the comparison block given 17 | at instantiation time. Retrieving an element from the queue will always return 18 | the one with the highest priority. 19 | 20 | resources: 21 | home: http://rubyworks.github.com/pqueue 22 | code: http://github.com/rubyworks/pqueue 23 | bugs: http://github.com/rubyworks/pqueue/issues 24 | 25 | repositories: 26 | upstream: git://github.com/rubyworks/pqueue.git 27 | 28 | authors: 29 | - Trans 30 | - K. Kodama 31 | - Ronald Butler 32 | - Olivier Renaud 33 | - Rick Bradley 34 | 35 | organization: 36 | Rubyworks 37 | 38 | created: 39 | 2001-03-10 40 | 41 | copyrights: 42 | - (c) 2009 Rubyworks (BSD-2) 43 | - (c) 2001 K. Kodama 44 | 45 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | (BSD-2-Clause License) 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, 7 | this list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 14 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 15 | AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 16 | COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 17 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 18 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 20 | OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 21 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 22 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | -------------------------------------------------------------------------------- /Contributing.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUTING 2 | 3 | ## Setup 4 | 5 | Once you've cloned the repo, hit up Bundler. 6 | 7 | ``` 8 | $ bundle 9 | ``` 10 | 11 | ## Testing 12 | 13 | We are using the Microtest framework which is a very simple test 14 | framework built to mimic the original Test::Unit but build on 15 | Ruby Test, the slick meta-testing framework. 16 | 17 | Thanks to the configuration in `etc/test.rb` running test should 18 | be as simple as: 19 | 20 | ``` 21 | $ rubytest 22 | ``` 23 | 24 | ## Releasing 25 | 26 | When releasing a new version there a few things that need to done. 27 | First, of course, make sure the version number is correct by editing 28 | the `Index.yml` file. Then update the canonical `.index` file via: 29 | 30 | ``` 31 | $ index -u Index.yml Gemfile 32 | ``` 33 | 34 | Also, don't forget to add an entry to the `HISTORY.md` file for the 35 | new release. 36 | 37 | Though it is not likely to be needed for this project, ensure the MANIFEST 38 | is up to date: 39 | 40 | ``` 41 | $ mast -u 42 | ``` 43 | 44 | Now build the gem: 45 | 46 | ``` 47 | $ gem build .gemspec 48 | ``` 49 | 50 | To release simply use: 51 | 52 | ``` 53 | $ gem push pqueue-x.x.x.gem 54 | ``` 55 | 56 | Finally, don't forget to add a tag for the release. I always use the 57 | description of the release from the HISTORY file as the tag message 58 | (excluding the changes list). 59 | 60 | ``` 61 | $ gem tag -a x.x.x 62 | ``` 63 | 64 | -------------------------------------------------------------------------------- /.index: -------------------------------------------------------------------------------- 1 | --- 2 | revision: 2013 3 | type: ruby 4 | sources: 5 | - Index.yml 6 | - Gemfile 7 | authors: 8 | - name: Trans 9 | email: transfire@gmail.com 10 | - name: K. Kodama 11 | - name: Ronald Butler 12 | - name: Olivier Renaud 13 | - name: Rick Bradley 14 | organizations: 15 | - name: Rubyworks 16 | requirements: 17 | - groups: 18 | - test 19 | version: '>= 0' 20 | name: microtest 21 | - groups: 22 | - test 23 | version: '>= 0' 24 | name: ae 25 | - groups: 26 | - test 27 | version: '>= 0' 28 | name: rubytest 29 | - groups: 30 | - test 31 | version: '>= 0' 32 | name: rubytest-cli 33 | - groups: 34 | - build 35 | version: '>= 0' 36 | name: indexer 37 | - groups: 38 | - build 39 | version: '>= 0' 40 | name: mast 41 | conflicts: [] 42 | alternatives: [] 43 | resources: 44 | - type: home 45 | uri: http://rubyworks.github.com/pqueue 46 | label: Homepage 47 | - type: code 48 | uri: http://github.com/rubyworks/pqueue 49 | label: Source Code 50 | - type: bugs 51 | uri: http://github.com/rubyworks/pqueue/issues 52 | label: Issue Tracker 53 | repositories: 54 | - name: upstream 55 | scm: git 56 | uri: git://github.com/rubyworks/pqueue.git 57 | categories: [] 58 | copyrights: 59 | - holder: Rubyworks 60 | year: '2009' 61 | license: BSD-2 62 | - holder: K. Kodama 63 | year: '2001' 64 | customs: [] 65 | paths: 66 | lib: 67 | - lib 68 | name: pqueue 69 | title: PQueue 70 | version: 2.1.0 71 | summary: Queue of Prioritized Elements 72 | description: A priority queue is like a standard queue, except that each inserted 73 | elements is given a certain priority, based on the result of the comparison block 74 | given at instantiation time. Retrieving an element from the queue will always return 75 | the one with the highest priority. 76 | created: '2001-03-10' 77 | date: '2015-03-02' 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PQueue 2 | 3 | [![Gem Version](https://badge.fury.io/rb/pqueue.png)](http://badge.fury.io/rb/pqueue) 4 | [![Build Status](https://secure.travis-ci.org/rubyworks/pqueue.png)](http://travis-ci.org/rubyworks/pqueue)     5 | [![Flattr Me](http://api.flattr.com/button/flattr-badge-large.png)](http://flattr.com/thing/324911/Rubyworks-Ruby-Development-Fund) 6 | 7 | [Website](http://rubyworks.github.com/pqueue) · 8 | [YARD API](http://rubydoc.info/gems/pqueue) · 9 | [Report Issue](http://github.com/rubyworks/pqueue/issues) · 10 | [Source Code](http://github.com/rubyworks/pqueue) 11 | 12 | 13 | ## About 14 | 15 | PQueue is a priority queue with array based heap. 16 | A priority queue is like a standard queue, except that each inserted 17 | element is given a certain priority, based on the result of the 18 | comparison block given at instantiation time. Also, retrieving an element 19 | from the queue will always return the one with the highest priority 20 | (see #pop and #top). 21 | 22 | The default is to compare the elements in respect to their #<=> method. 23 | For example, Numeric elements with higher values will have higher 24 | priorities. 25 | 26 | This library is a rewrite of the original PQueue.rb by K. Kodama and 27 | Heap.rb by Ronald Butler. The two libraries were later merged 28 | and generally improved by Olivier Renaud. Then the whole library 29 | rewritten by Trans using the original as a functional reference. 30 | 31 | 32 | ## Usage 33 | 34 | Usage is simple enough. Think of it as an array that takes a block, where 35 | the block decides which element of the array goes first. 36 | 37 | require 'pqueue' 38 | 39 | pq = PQueue.new([2,3,1]){ |a,b| a > b } 40 | 41 | pq.pop #=> 3 42 | 43 | 44 | ## Install 45 | 46 | Using RubyGems: 47 | 48 | gem install pqueue 49 | 50 | 51 | ## Acknowledgements 52 | 53 | Although the library has been completely rewritten since, we still would 54 | like to acknowledge the efforts of the original PQueue authors and 55 | contributors. 56 | 57 | * Olivier Renaud (2007) 58 | * Rick Bradley (2003) 59 | * Ronald Butler (2002) 60 | * K Kodama (2001, original library) 61 | 62 | 63 | ## License & Copyrights 64 | 65 | Copyright (c) 2011 Rubyworks 66 | 67 | PQueue is distributable in accordance with the *BSD-2-Clause* license. 68 | 69 | PQueue is based on the original PQueue library (c) 2001 by K. Kodama. 70 | 71 | See the LICENSE.txt file for details. 72 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | # CHANGE HISTORY 2 | 3 | ## 2.1.0 / 2014-03-03 4 | 5 | Ostatnicky has found a bug! As it turns out both `#deq` and `#shift` were 6 | aliased incorrectly to `push`, when they should have been to `#pop`. 7 | With this release that has been fixes, but we have modified `#shift` 8 | to instead return the lowest priority item, which is a more polymorphic 9 | definition with its use in Array. In addition we have added `#peek` as 10 | an alias for `#top`, and added `bottom`, which it the opposite of `#top`. 11 | 12 | Changes: 13 | 14 | * Fix `#deq` as alias of `#pop`, not `#push`. 15 | * Fix `#shift` to be like `#pop` but opposite priority. 16 | * Add `#peek` as alias of `#top`. 17 | * Add `#bottom` method as opposite of `#top`. 18 | 19 | 20 | ## 2.0.2 / 2011-10-29 21 | 22 | It's been one of those days. I went to to get a wash cloth for the shower, 23 | on my way through the kitchen realized the chilling cookie dough needed to 24 | be put in the oven, but I forgot to put flower in the batter, so they burnt 25 | up in minutes and made a mess that took a half-hour to clean-up. Then I discover 26 | the shower still running, hot water was all but gone, and my bedroom felt like 27 | a suana. To top it all off, it was at this moment I realized PQueue's binary 28 | search algrithm didn't work. Sigh. And fuck if my grandmother won't stop making 29 | me food I don't want to eat. Yea, one of those days! 30 | 31 | Changes: 32 | 33 | * Fixed `#reheap` search algorithm. 34 | 35 | 36 | ## 2.0.1 / 2011-10-29 37 | 38 | Quick fix to remove old legacy library that was supposed to be 39 | removed in previous release. No big deal, it just confused the 40 | YARD documentation. 41 | 42 | Changes: 43 | 44 | * Remove legacy version of library. 45 | 46 | 47 | ## 2.0.0 / 2011-10-29 48 | 49 | This is a complete rewrite to simplify the design and use more 50 | of Ruby's internal methods. Overall performance should be markedly 51 | improved. A few method names have changed to be more consistent with 52 | Ruby's other data structure. Note that the internal heap is now in reverse 53 | order from the previous version. If using #to_a be aware that the priority 54 | order will be reversed. This release also switches the library to 55 | distribution under the BSD-2-Clause license. 56 | 57 | Changes: 58 | 59 | * Rewrite library. 60 | * Modernize build configuration. 61 | * Switch to BSD-2-Clause license. 62 | 63 | 64 | ## 1.0.0 / 2009-07-05 65 | 66 | This is the initial standalone release of PQueue, spun-off from the 67 | Ruby Facets and originally written by K. Komada. 68 | 69 | Changes: 70 | 71 | * Happy New Birthday! 72 | 73 | -------------------------------------------------------------------------------- /test/test_pqueue.rb: -------------------------------------------------------------------------------- 1 | require 'microtest' 2 | require 'ae' 3 | require 'ae/legacy' 4 | 5 | require 'pqueue' 6 | 7 | class PQueueTest < MicroTest::TestCase 8 | include AE::Legacy::Assertions 9 | 10 | ARY_TEST = [2,6,1,3,8,15,0,-4,7,8,10] 11 | ARY_TEST_2 = [25,10,5,13,16,9,16,12] 12 | 13 | def test_initialize_empty 14 | PQueue.new 15 | end 16 | 17 | def test_initialize_single_element 18 | PQueue.new([3]) 19 | end 20 | 21 | def test_initialize_multiple_elements 22 | PQueue.new(ARY_TEST) 23 | end 24 | 25 | def test_initialize_with_custom_comparison 26 | PQueue.new {|a,b| b<=>a} 27 | PQueue.new([3]) {|a,b| b<=>a} 28 | PQueue.new(ARY_TEST) {|a,b| b<=>a} 29 | end 30 | 31 | def test_top 32 | assert_equal(ARY_TEST.max, PQueue.new(ARY_TEST).top) 33 | assert_nil(PQueue.new.top) 34 | end 35 | 36 | def test_pop 37 | sorted_ary = ARY_TEST.sort 38 | q = PQueue.new(ARY_TEST) 39 | ARY_TEST.size.times do 40 | assert_equal(sorted_ary.pop, q.pop) 41 | end 42 | assert_equal(0, q.size) 43 | assert_nil(PQueue.new.pop) 44 | end 45 | 46 | def test_insertion 47 | q = PQueue.new(ARY_TEST) 48 | assert_equal(ARY_TEST.size, q.size) 49 | 50 | ret = q.push(24) 51 | assert_equal(q, ret) 52 | assert_equal(ARY_TEST.size+1, q.size) 53 | end 54 | 55 | def test_concat 56 | q = PQueue.new(ARY_TEST) 57 | 58 | ret = q.concat(ARY_TEST_2) 59 | assert_equal(q, ret) 60 | assert_equal(ARY_TEST.size+ARY_TEST_2.size, q.size) 61 | 62 | q = PQueue.new(ARY_TEST) 63 | r = PQueue.new(ARY_TEST_2) 64 | q.concat(r) 65 | assert_equal(ARY_TEST.size + ARY_TEST_2.size, q.size) 66 | end 67 | 68 | def test_clear 69 | q = PQueue.new(ARY_TEST).clear 70 | assert_equal(q, q.clear) 71 | assert_equal(0, q.size) 72 | end 73 | 74 | def test_replace 75 | q = PQueue.new(ARY_TEST) 76 | q.replace(ARY_TEST_2) 77 | assert_equal(ARY_TEST_2.size, q.size) 78 | 79 | q = PQueue.new(ARY_TEST) 80 | q.replace(PQueue.new(ARY_TEST_2)) 81 | assert_equal(ARY_TEST_2.size, q.size) 82 | end 83 | 84 | def test_inspect 85 | assert_equal("", 86 | PQueue.new(ARY_TEST).inspect) 87 | end 88 | 89 | def test_to_a 90 | q = PQueue.new(ARY_TEST) 91 | assert_equal(ARY_TEST.sort, q.to_a) 92 | q = PQueue.new(0..4) 93 | assert_equal([0,1,2,3,4], q.to_a) 94 | end 95 | 96 | def pop_array 97 | q = PQueue.new(ARY_TEST) 98 | assert_equal(ARY_TEST.sort.reverse[0..5], q.pop_array(5)) 99 | q = PQueue.new(ARY_TEST) 100 | assert_equal(ARY_TEST.sort.reverse, q.pop_array) 101 | end 102 | 103 | def test_include 104 | q = PQueue.new(ARY_TEST + [21] + ARY_TEST_2) 105 | assert_equal(true, q.include?(21)) 106 | 107 | q = PQueue.new(ARY_TEST - [15]) 108 | assert_equal(false, q.include?(15)) 109 | end 110 | 111 | def test_equal 112 | assert_equal(PQueue.new, PQueue.new) 113 | assert_equal(PQueue.new(ARY_TEST), PQueue.new(ARY_TEST.sort_by{rand})) 114 | end 115 | 116 | def test_swap 117 | q = PQueue.new 118 | assert_nil(q.swap(6)) 119 | assert_equal(6, q.top) 120 | 121 | q = PQueue.new(ARY_TEST) 122 | h = PQueue.new(ARY_TEST) 123 | q.pop; q.push(11) 124 | h.swap(11) 125 | assert_equal(q, h) 126 | end 127 | 128 | def test_dup 129 | q = PQueue.new(ARY_TEST) 130 | assert_equal(q, q.dup) 131 | end 132 | 133 | def test_array_copied 134 | ary = ARY_TEST.dup 135 | q = PQueue.new(ary) 136 | q.pop 137 | assert_equal(ARY_TEST, ary) 138 | 139 | ary = ARY_TEST.dup 140 | q = PQueue.new 141 | q.replace(ary) 142 | q.pop 143 | assert_equal(ARY_TEST, ary) 144 | 145 | ary = ARY_TEST.dup 146 | q = PQueue.new([1]) 147 | q.concat(ary) 148 | q.pop 149 | assert_equal(ARY_TEST, ary) 150 | 151 | q = PQueue.new(ARY_TEST) 152 | r = q.dup 153 | q.pop 154 | assert_not_equal(q, r) 155 | end 156 | 157 | def test_reheap 158 | q = PQueue.new([2,4,5]) 159 | q << 6 160 | q.to_a.assert == [2,4,5,6] 161 | q << 1 162 | q.to_a.assert == [1,2,4,5,6] 163 | q << 3 164 | q.to_a.assert == [1,2,3,4,5,6] 165 | 166 | q = PQueue.new([100,5,25]) 167 | q.to_a.assert == [5,25,100] 168 | q << 17 169 | q.to_a.assert == [5,17,25,100] 170 | q << 0 171 | q.to_a.assert == [0,5,17,25,100] 172 | q << -5 173 | q.to_a.assert == [-5,0,5,17,25,100] 174 | q << 100 175 | q.to_a.assert == [-5,0,5,17,25,100,100] 176 | end 177 | 178 | end 179 | -------------------------------------------------------------------------------- /lib/pqueue.rb: -------------------------------------------------------------------------------- 1 | # Priority queue with array based heap. 2 | # 3 | # A priority queue is like a standard queue, except that each inserted 4 | # elements is given a certain priority, based on the result of the 5 | # comparison block given at instantiation time. Also, retrieving an element 6 | # from the queue will always return the one with the highest priority 7 | # (see #pop and #top). 8 | # 9 | # The default is to compare the elements in repect to their #<=> method. 10 | # For example, Numeric elements with higher values will have higher 11 | # priorities. 12 | # 13 | # Note that as of version 2.0 the internal queue is kept in the reverse order 14 | # from how it was kept in previous version. If you had used #to_a in the 15 | # past then be sure to adjust for the priorities to be ordered back-to-front 16 | # instead of the oterh way around. 17 | # 18 | class PQueue 19 | 20 | # 21 | VERSION = "2.1.0" #:erb: VERSION = "<%= version %>" 22 | 23 | # 24 | # Returns a new priority queue. 25 | # 26 | # If elements are given, build the priority queue with these initial 27 | # values. The elements object must respond to #to_a. 28 | # 29 | # If a block is given, it will be used to determine the priority between 30 | # the elements. The block must must take two arguments and return `1`, `0`, 31 | # or `-1` or `true`, `nil` or `false. It should return `0` or `nil` if the 32 | # two arguments are considered equal, return `1` or `true` if the first 33 | # argument is considered greater than the later, and `-1` or `false` if 34 | # the later is considred to be greater than the first. 35 | # 36 | # By default, the priority queue retrieves maximum elements first 37 | # using the #<=> method. 38 | # 39 | def initialize(elements=nil, &block) # :yields: a, b 40 | @que = [] 41 | @cmp = block || lambda{ |a,b| a <=> b } 42 | replace(elements) if elements 43 | end 44 | 45 | protected 46 | 47 | # 48 | # The underlying heap. 49 | # 50 | attr_reader :que #:nodoc: 51 | 52 | public 53 | 54 | # 55 | # Priority comparison procedure. 56 | # 57 | attr_reader :cmp 58 | 59 | # 60 | # Returns the size of the queue. 61 | # 62 | def size 63 | @que.size 64 | end 65 | 66 | # 67 | # Alias of size. 68 | # 69 | alias length size 70 | 71 | # 72 | # Add an element in the priority queue. 73 | # 74 | def push(v) 75 | @que << v 76 | reheap(@que.size-1) 77 | self 78 | end 79 | 80 | # 81 | # Traditional alias for #push. 82 | # 83 | alias enq push 84 | 85 | # 86 | # Alias of #push. 87 | # 88 | alias :<< :push 89 | 90 | # 91 | # Get the element with the highest priority and remove it from 92 | # the queue. 93 | # 94 | # The highest priority is determined by the block given at instantiation 95 | # time. 96 | # 97 | # The deletion time is O(log n), with n is the size of the queue. 98 | # 99 | # Return nil if the queue is empty. 100 | # 101 | def pop 102 | return nil if empty? 103 | @que.pop 104 | end 105 | 106 | # 107 | # Traditional alias for #pop. 108 | # 109 | alias deq pop 110 | 111 | # Get the element with the lowest priority and remove it from 112 | # the queue. 113 | # 114 | # The lowest priority is determined by the block given at instantiation 115 | # time. 116 | # 117 | # The deletion time is O(log n), with n is the size of the queue. 118 | # 119 | # Return nil if the queue is empty. 120 | # 121 | def shift 122 | return nil if empty? 123 | @que.shift 124 | end 125 | 126 | # 127 | # Returns the element with the highest priority, but 128 | # does not remove it from the queue. 129 | # 130 | def top 131 | return nil if empty? 132 | return @que.last 133 | end 134 | 135 | # 136 | # Traditional alias for #top. 137 | # 138 | alias peek top 139 | 140 | # 141 | # Returns the element with the lowest priority, but 142 | # does not remove it from the queue. 143 | # 144 | def bottom 145 | return nil if empty? 146 | return @que.first 147 | end 148 | 149 | # 150 | # Add more than one element at the same time. See #push. 151 | # 152 | # The elements object must respond to #to_a, or be a PQueue itself. 153 | # 154 | def concat(elements) 155 | if empty? 156 | if elements.kind_of?(PQueue) 157 | initialize_copy(elements) 158 | else 159 | replace(elements) 160 | end 161 | else 162 | if elements.kind_of?(PQueue) 163 | @que.concat(elements.que) 164 | sort! 165 | else 166 | @que.concat(elements.to_a) 167 | sort! 168 | end 169 | end 170 | return self 171 | end 172 | 173 | # 174 | # Alias for #concat. 175 | # 176 | alias :merge! :concat 177 | 178 | # 179 | # Return top n-element as a sorted array. 180 | # 181 | def take(n=@size) 182 | a = [] 183 | n.times{a.push(pop)} 184 | a 185 | end 186 | 187 | # 188 | # Returns true if there is no more elements left in the queue. 189 | # 190 | def empty? 191 | @que.empty? 192 | end 193 | 194 | # 195 | # Remove all elements from the priority queue. 196 | # 197 | def clear 198 | @que.clear 199 | self 200 | end 201 | 202 | # 203 | # Replace the content of the heap by the new elements. 204 | # 205 | # The elements object must respond to #to_a, or to be 206 | # a PQueue itself. 207 | # 208 | def replace(elements) 209 | if elements.kind_of?(PQueue) 210 | initialize_copy(elements) 211 | else 212 | @que.replace(elements.to_a) 213 | sort! 214 | end 215 | self 216 | end 217 | 218 | # 219 | # Return a sorted array, with highest priority first. 220 | # 221 | def to_a 222 | @que.dup 223 | end 224 | 225 | # 226 | # Return true if the given object is present in the queue. 227 | # 228 | def include?(element) 229 | @que.include?(element) 230 | end 231 | 232 | # 233 | # Push element onto queue while popping off and returning the next element. 234 | # This is qquivalent to successively calling #pop and #push(v). 235 | # 236 | def swap(v) 237 | r = pop 238 | push(v) 239 | r 240 | end 241 | 242 | # 243 | # Iterate over the ordered elements, destructively. 244 | # 245 | def each_pop #:yields: popped 246 | until empty? 247 | yield pop 248 | end 249 | nil 250 | end 251 | 252 | # 253 | # Pretty inspection string. 254 | # 255 | def inspect 256 | "<#{self.class}: size=#{size}, top=#{top || "nil"}>" 257 | end 258 | 259 | # 260 | # Return true if the queues contain equal elements. 261 | # 262 | def ==(other) 263 | size == other.size && to_a == other.to_a 264 | end 265 | 266 | private 267 | 268 | # 269 | # 270 | # 271 | def initialize_copy(other) 272 | @cmp = other.cmp 273 | @que = other.que.dup 274 | sort! 275 | end 276 | 277 | # 278 | # The element at index k will be repositioned to its proper place. 279 | # 280 | # This, of course, assumes the queue is already sorted. 281 | # 282 | def reheap(k) 283 | return self if size <= 1 284 | 285 | que = @que.dup 286 | 287 | v = que.delete_at(k) 288 | i = binary_index(que, v) 289 | 290 | que.insert(i, v) 291 | 292 | @que = que 293 | 294 | return self 295 | end 296 | 297 | # 298 | # Sort the queue in accorance to the given comparison procedure. 299 | # 300 | def sort! 301 | @que.sort! do |a,b| 302 | case @cmp.call(a,b) 303 | when 0, nil then 0 304 | when 1, true then 1 305 | when -1, false then -1 306 | else 307 | warn "bad comparison procedure in #{self.inspect}" 308 | 0 309 | end 310 | end 311 | self 312 | end 313 | 314 | # 315 | # Alias of #sort! 316 | # 317 | alias heapify sort! 318 | 319 | # 320 | def binary_index(que, target) 321 | upper = que.size - 1 322 | lower = 0 323 | 324 | while(upper >= lower) do 325 | idx = lower + (upper - lower) / 2 326 | comp = @cmp.call(target, que[idx]) 327 | 328 | case comp 329 | when 0, nil 330 | return idx 331 | when 1, true 332 | lower = idx + 1 333 | when -1, false 334 | upper = idx - 1 335 | else 336 | end 337 | end 338 | lower 339 | end 340 | 341 | end # class PQueue 342 | -------------------------------------------------------------------------------- /.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'yaml' 4 | require 'pathname' 5 | 6 | module Indexer 7 | 8 | # Convert index data into a gemspec. 9 | # 10 | # Notes: 11 | # * Assumes all executables are in bin/. 12 | # * Does not yet handle default_executable setting. 13 | # * Does not yet handle platform setting. 14 | # * Does not yet handle required_ruby_version. 15 | # * Support for rdoc entries is weak. 16 | # 17 | class GemspecExporter 18 | 19 | # File globs to include in package --unless a manifest file exists. 20 | FILES = ".index .yardopts alt bin data demo ext features lib man spec test try* [A-Z]*.*" unless defined?(FILES) 21 | 22 | # File globs to omit from FILES. 23 | OMIT = "Config.rb" unless defined?(OMIT) 24 | 25 | # Standard file patterns. 26 | PATTERNS = { 27 | :root => '{.index,Gemfile}', 28 | :bin => 'bin/*', 29 | :lib => 'lib/{**/}*', #.rb', 30 | :ext => 'ext/{**/}extconf.rb', 31 | :doc => '*.{txt,rdoc,md,markdown,tt,textile}', 32 | :test => '{test,spec}/{**/}*.rb' 33 | } unless defined?(PATTERNS) 34 | 35 | # For which revision of indexer spec is this converter intended? 36 | REVISION = 2013 unless defined?(REVISION) 37 | 38 | # 39 | def self.gemspec 40 | new.to_gemspec 41 | end 42 | 43 | # 44 | attr :metadata 45 | 46 | # 47 | def initialize(metadata=nil) 48 | @root_check = false 49 | 50 | if metadata 51 | root_dir = metadata.delete(:root) 52 | if root_dir 53 | @root = root_dir 54 | @root_check = true 55 | end 56 | metadata = nil if metadata.empty? 57 | end 58 | 59 | @metadata = metadata || YAML.load_file(root + '.index') 60 | 61 | if @metadata['revision'].to_i != REVISION 62 | warn "This gemspec exporter was not designed for this revision of index metadata." 63 | end 64 | end 65 | 66 | # 67 | def has_root? 68 | root ? true : false 69 | end 70 | 71 | # 72 | def root 73 | return @root if @root || @root_check 74 | @root_check = true 75 | @root = find_root 76 | end 77 | 78 | # 79 | def manifest 80 | return nil unless root 81 | @manifest ||= Dir.glob(root + 'manifest{,.txt}', File::FNM_CASEFOLD).first 82 | end 83 | 84 | # 85 | def scm 86 | return nil unless root 87 | @scm ||= %w{git hg}.find{ |m| (root + ".#{m}").directory? }.to_sym 88 | end 89 | 90 | # 91 | def files 92 | return [] unless root 93 | @files ||= \ 94 | if manifest 95 | File.readlines(manifest). 96 | map{ |line| line.strip }. 97 | reject{ |line| line.empty? || line[0,1] == '#' } 98 | else 99 | list = [] 100 | Dir.chdir(root) do 101 | FILES.split(/\s+/).each do |pattern| 102 | list.concat(glob(pattern)) 103 | end 104 | OMIT.split(/\s+/).each do |pattern| 105 | list = list - glob(pattern) 106 | end 107 | end 108 | list 109 | end.select{ |path| File.file?(path) }.uniq 110 | end 111 | 112 | # 113 | def glob_files(pattern) 114 | return [] unless root 115 | Dir.chdir(root) do 116 | Dir.glob(pattern).select do |path| 117 | File.file?(path) && files.include?(path) 118 | end 119 | end 120 | end 121 | 122 | def patterns 123 | PATTERNS 124 | end 125 | 126 | def executables 127 | @executables ||= \ 128 | glob_files(patterns[:bin]).map do |path| 129 | File.basename(path) 130 | end 131 | end 132 | 133 | def extensions 134 | @extensions ||= \ 135 | glob_files(patterns[:ext]).map do |path| 136 | File.basename(path) 137 | end 138 | end 139 | 140 | def name 141 | metadata['name'] || metadata['title'].downcase.gsub(/\W+/,'_') 142 | end 143 | 144 | def homepage 145 | page = ( 146 | metadata['resources'].find{ |r| r['type'] =~ /^home/i } || 147 | metadata['resources'].find{ |r| r['name'] =~ /^home/i } || 148 | metadata['resources'].find{ |r| r['name'] =~ /^web/i } 149 | ) 150 | page ? page['uri'] : false 151 | end 152 | 153 | def licenses 154 | metadata['copyrights'].map{ |c| c['license'] }.compact 155 | end 156 | 157 | def require_paths 158 | paths = metadata['paths'] || {} 159 | paths['load'] || ['lib'] 160 | end 161 | 162 | # 163 | # Convert to gemnspec. 164 | # 165 | def to_gemspec 166 | if has_root? 167 | Gem::Specification.new do |gemspec| 168 | to_gemspec_data(gemspec) 169 | to_gemspec_paths(gemspec) 170 | end 171 | else 172 | Gem::Specification.new do |gemspec| 173 | to_gemspec_data(gemspec) 174 | to_gemspec_paths(gemspec) 175 | end 176 | end 177 | end 178 | 179 | # 180 | # Convert pure data settings. 181 | # 182 | def to_gemspec_data(gemspec) 183 | gemspec.name = name 184 | gemspec.version = metadata['version'] 185 | gemspec.summary = metadata['summary'] 186 | gemspec.description = metadata['description'] 187 | 188 | metadata['authors'].each do |author| 189 | gemspec.authors << author['name'] 190 | 191 | if author.has_key?('email') 192 | if gemspec.email 193 | gemspec.email << author['email'] 194 | else 195 | gemspec.email = [author['email']] 196 | end 197 | end 198 | end 199 | 200 | gemspec.licenses = licenses 201 | 202 | requirements = metadata['requirements'] || [] 203 | requirements.each do |req| 204 | next if req['optional'] 205 | next if req['external'] 206 | 207 | name = req['name'] 208 | groups = req['groups'] || [] 209 | 210 | version = gemify_version(req['version']) 211 | 212 | if groups.empty? or groups.include?('runtime') 213 | # populate runtime dependencies 214 | if gemspec.respond_to?(:add_runtime_dependency) 215 | gemspec.add_runtime_dependency(name,*version) 216 | else 217 | gemspec.add_dependency(name,*version) 218 | end 219 | else 220 | # populate development dependencies 221 | if gemspec.respond_to?(:add_development_dependency) 222 | gemspec.add_development_dependency(name,*version) 223 | else 224 | gemspec.add_dependency(name,*version) 225 | end 226 | end 227 | end 228 | 229 | # convert external dependencies into gemspec requirements 230 | requirements.each do |req| 231 | next unless req['external'] 232 | gemspec.requirements << ("%s-%s" % req.values_at('name', 'version')) 233 | end 234 | 235 | gemspec.homepage = homepage 236 | gemspec.require_paths = require_paths 237 | gemspec.post_install_message = metadata['install_message'] 238 | end 239 | 240 | # 241 | # Set gemspec settings that require a root directory path. 242 | # 243 | def to_gemspec_paths(gemspec) 244 | gemspec.files = files 245 | gemspec.extensions = extensions 246 | gemspec.executables = executables 247 | 248 | if Gem::VERSION < '1.7.' 249 | gemspec.default_executable = gemspec.executables.first 250 | end 251 | 252 | gemspec.test_files = glob_files(patterns[:test]) 253 | 254 | unless gemspec.files.include?('.document') 255 | gemspec.extra_rdoc_files = glob_files(patterns[:doc]) 256 | end 257 | end 258 | 259 | # 260 | # Return a copy of this file. This is used to generate a local 261 | # .gemspec file that can automatically read the index file. 262 | # 263 | def self.source_code 264 | File.read(__FILE__) 265 | end 266 | 267 | private 268 | 269 | def find_root 270 | root_files = patterns[:root] 271 | if Dir.glob(root_files).first 272 | Pathname.new(Dir.pwd) 273 | elsif Dir.glob("../#{root_files}").first 274 | Pathname.new(Dir.pwd).parent 275 | else 276 | #raise "Can't find root of project containing `#{root_files}'." 277 | warn "Can't find root of project containing `#{root_files}'." 278 | nil 279 | end 280 | end 281 | 282 | def glob(pattern) 283 | if File.directory?(pattern) 284 | Dir.glob(File.join(pattern, '**', '*')) 285 | else 286 | Dir.glob(pattern) 287 | end 288 | end 289 | 290 | def gemify_version(version) 291 | case version 292 | when /^(.*?)\+$/ 293 | ">= #{$1}" 294 | when /^(.*?)\-$/ 295 | "< #{$1}" 296 | when /^(.*?)\~$/ 297 | "~> #{$1}" 298 | else 299 | version 300 | end 301 | end 302 | 303 | end 304 | 305 | end 306 | 307 | Indexer::GemspecExporter.gemspec --------------------------------------------------------------------------------