From: Tanaka Akira Date: 2009-09-08T01:27:44+09:00 Subject: [ruby-dev:39248] pdeque - Double-Ended Priority Queue 優先順位つきキューとして、このメールにつけてある pdeque.rb を添付するのはどうでしょう? 優先順位つきキューが欲しい状況としては、 * 最短経路問題を解くダイクストラ法 (や、その拡張の A* 探索) * ソートされた多数のストリームをマージ * 上位 n個を取り出す * スケジューラ などがあります。 pdeque.rb の一番単純な使いかたとしては、PDeque.new で生成し て、PDeque#insert で要素を入れて、PDeque#delete_min で最小の 要素から取り出すことができます。 pd = PDeque.new pd.insert "durian" pd.insert "banana" p pd.delete_min #=> "banana" pd.insert "orange" pd.insert "apple" pd.insert "melon" p pd.delete_min #=> "apple" p pd.delete_min #=> "durian" p pd.delete_min #=> "melon" p pd.delete_min #=> "orange" p pd.delete_min #=> nil この単純な形では要素の <=> で比較が行われますが、要素の優先 順位を別に指定することもできます。 pdeque.rb は * 優先順位が小さいほうからでも大きいほうからでも取り出せる (現状では切替え時に O(n) かかります。そのうちかかりすぎな いように直すかも) * 同じ優先順位のものは後から入れたものが大きいとみなされる (つまり stable) * 優先順位の変更をサポート (ダイクストラ法などではすでに queue に入っている要素の優先 順位を変更できないといけない) * 内部は binary heap で実現 * PDeque.{nlargest,nsmallest,merge} を提供 (参考: Python の heapq) という特徴があります。 既存の優先順位つきキューとしては、 児玉さんの pqueue.rb と Brian Schroeder さんの priority-queue が見つかりますが、 pdeque.rb と比較すると 前者は decrease-key をサポートしてなくてダイクストラ法に使えず、 後者は stable でないなどの違いがあります。 優先順位の意味としては、前者は大きいものから取り出され、後者 は小さいものから取り出されます。スケジューラなどでは前者が自 然で、ダイクストラ法などでは後者が自然です。これはどっちがい いともいえないので、両方サポートすることにしてみました。それ で、double-ended queue つまり deque というわけです。現状では delete_min を使ったときに (すでにそうなっていなければ) min-heap を構成し、delete_max を使ったときに (すでにそうなっ ていなければ) max-heap を構成します。なので、delete_min を使っ た後に delete_max を使うと max-heap を構成するのに O(n) かか ります。min-max-heap にしてその再構成を不要にすることもでき ますが、どうも delete_min と delete_max を両方使うアプリケー ションは少ないようなのと、min-max-heap よりも min-heap や max-heap のほうが速いのでこうしてあります。将来的には、両方 使った時点で min-max-heap にスイッチするのはありうるかもしれ ません。 stable でないのはアプリケーション側で stable にすることもで きますが、スケジューラなど stable でないと困るアプリケーショ ンはたしかに存在するのと、heap の性質から決まる順序に依存さ れても困るのでそうしてあります。あと、最初からつけておいたほ うがメモり効率も良くなります。最初から組み込んでおけば 1word の増加で済みますが、後からつけようとすると、オブジェクトが必 要なので少なくとも 5word の増加が必要になります。 優先順位の変更をサポートしているのは、ダイクストラ法などで必 要だからです。このために、insert したときにその要素を指し示 す PDeque::Locator オブジェクトを返します。このオブジェクト の update メソッドで優先順位 (と要素自体) を変えることができ ます。ちなみに、この操作は論文では decrease-key/increase-key と呼ばれている標準的な操作です。優先順位を大きくするメソッド と小さくするメソッドに分ける意味は感じられなかったので、ひと つにまとめてあります。 内部が binary heap なのはメモり効率のためです。1要素あたり 8word で済むようにしてあります。この 8word には PDeque::Locator オブジェクトの 5word も含んでいて、 decrease-key/increase-key をサポートして stable にするという 条件ではかなり限界に近いのではないかと思っています。 hash を使うと、st_table_entry だけで 6word かかりますし、そ れに加えてオブジェクトひとつ 5word 使えば 11word になります から、そういう方向の実装に比べるとかなりコンパクトになってい ます。 PDeque.{nlargest,nsmallest,merge} は Python の heapq の真似 ですが、そのうち、merge はまさに [ruby-dev:38973] で述べた、 優先順位つきキューが欲しい理由です。nlargest と nsmallest は ついでといえばついでですが、[ruby-list:40949] のような前例も あります。 あと、あまりまじめには探していないのですが、優先順位つきキュー が標準で欲しいという要求はこの前の [ruby-dev:38973] の他に [ruby-dev:23427] が見つかりました。この前 Wikiばなで本人に尋 ねたら忘れてましたが。 どうでしょう? Index: lib/pdeque.rb =================================================================== --- lib/pdeque.rb (revision 0) +++ lib/pdeque.rb (revision 0) @@ -0,0 +1,1505 @@ +# PDeque - Feature Rich Double-Ended Priority Queue. +# +# = Features +# +# * queue - you can insert and delete values +# * priority - you can get a value with minimum priority +# * double-ended - you can get a value with maximum priority too +# * stable - you don't need to maintain timestamps yourself +# * update priority - usable for Dijkstra's shortest path algorithm and various graph algorithms +# * implicit binary heap - most operations are O(log n) at worst +# +# = Introduction +# +# == Simple Insertion/Deletion +# +# You can insert values into a PDeque object. +# You can deletes the values from the object from ascending/descending order. +# delete_min deletes the minimum value. +# It is used for ascending order. +# +# pd = PDeque.new +# pd.insert "durian" +# pd.insert "banana" +# p pd.delete_min #=> "banana" +# pd.insert "orange" +# pd.insert "apple" +# pd.insert "melon" +# p pd.delete_min #=> "apple" +# p pd.delete_min #=> "durian" +# p pd.delete_min #=> "melon" +# p pd.delete_min #=> "orange" +# p pd.delete_min #=> nil +# +# delete_max is similar to delete_min except it deletes maximum element +# instead of minimum. +# It is used for descending order. +# +# == The Order +# +# The order is defined by the priorities corresnponds to the values and +# comparison operator specified for the queue. +# +# pd = PDeque.new(:casecmp) # use casecmp instead of <=>. +# pd.inesrt 1, "Foo" # specify the priority for 1 as "Foo" +# pd.insert 2, "bar" +# pd.insert 3, "Baz" +# p pd.delete_min #=> 2 # "bar" is minimum +# p pd.delete_min #=> 3 +# p pd.delete_min #=> 1 # "Foo" is maximum +# p pd.delete_min #=> nil +# +# If there are multiple values with same priority, subpriority is used to compare them. +# subpriority is an integer which can be specified by 3rd argument of insert. +# If it is not specified, total number of inserted elements is used. +# So PDeque is "stable" with delete_min. +# The element inserted first is minimum and deleted first. +# +# pd = PDeque.new +# pd.insert "a", 1 # "a", "c" and "e" has same priority: 1 +# pd.insert "b", 0 # "b", "d" and "f" has same priority: 0 +# pd.insert "c", 1 +# pd.insert "d", 0 +# pd.insert "e", 1 +# pd.insert "f", 0 +# p pd.delete_min #=> "b" first element with priority 0 +# p pd.delete_min #=> "d" +# p pd.delete_min #=> "f" last element with priority 0 +# p pd.delete_min #=> "a" first element with priority 1 +# p pd.delete_min #=> "c" +# p pd.delete_min #=> "e" last element with priority 1 +# +# Note that delete_max deletes the maximum element. +# So it deletes last element if there are multiple values with same priority. +# +# == Update Element +# +# An inserted element can be modified and/or deleted. +# This is done using PDeque::Locator object. +# It is returned by insert, find_min_locator, etc. +# +# pd = PDeque.new +# d = pd.insert "durian", 1 +# m = pd.insert "mangosteen", 2 +# c = pd.insert "cherry", 3 +# p m #=> # +# p m.value #=> "mangosteen" +# p m.priority #=> 2 +# p pd.find_min #=> "durian" +# p pd.find_min_locator #=> # +# m.update("mangosteen", 0) +# p pd.find_min #=> "mangosteen" +# p pd.find_min_locator #=> # +# pd.delete_element d +# p pd.delete_min #=> "mangosteen" +# p pd.delete_min #=> "cherry" +# p pd.delete_min #=> nil +# +# For example, this feature can be used for graph algorithms +# such as Dijkstra's shortest path finding algorithm, +# A* search algorithm, etc. +# +# def dijkstra_shortest_path(start, edges) +# h = {} +# edges.each {|v1, v2, w| +# (h[v1] ||= []) << [v2, w] +# } +# h.default = [] +# q = PDeque.new +# visited = {start => q.insert([start], 0)} +# until q.empty? +# path, w1 = q.delete_min_priority +# v1 = path.last +# h[v1].each {|v2, w2| +# if !visited[v2] +# visited[v2] = q.insert(path+[v2], w1 + w2) +# elsif w1 + w2 < visited[v2].priority +# visited[v2].update(path+[v2], w1 + w2) # update val/prio +# end +# } +# end +# result = [] +# visited.each_value {|loc| +# result << [loc.value, loc.priority] +# } +# result +# end +# +# E = [ +# ['A', 'B', 2], +# ['A', 'C', 4], +# ['B', 'C', 1], +# ['C', 'B', 2], +# ['B', 'D', 3], +# ['C', 'D', 1], +# ] +# p dijkstra_shortest_path('A', E) +# #=> [[["A"], 0], +# # [["A", "B"], 2], +# # [["A", "B", "C"], 3], +# # [["A", "B", "C", "D"], 4]] +# +# = Internal Heap Algorithm and Performance Tips +# +# PDeque uses min-heap or max-heap internally. +# When delete_min is used, min-heap is constructed and max-heap is destructed. +# When delete_max is used, max-heap is constructed and min-heap is destructed. +# So mixing delete_min and delete_max causes bad performance. +# In future, min-max-heap may be implemented to avoid this problem. +# min-max-heap will be used when delete_min and delete_max is used both. +# (Because min-max-heap is slower than min-heap/max-heap.) +# +class PDeque + include Enumerable + + Locator = Struct.new(:value, :pdeque_or_subpriority, :index_or_priority) + class Locator + + # if pdeque_or_subpriority is PDeque + # pdeque_or_subpriority is pdeque + # index_or_priority is index + # else + # pdeque_or_subpriority is subpriority + # index_or_priority is priority + # end + # + # only 3 fields for memory efficiency. + + private :value= + private :pdeque_or_subpriority + private :pdeque_or_subpriority= + private :index_or_priority + private :index_or_priority= + + private :to_a + private :values + private :size + private :length + private :each + private :each_pair + private :[] + private :[]= + private :values_at + private :members + private :select + + Enumerable.instance_methods.each {|m| + private m + } + + define_method(:==, Object.instance_method(:eql?)) + define_method(:eql?, Object.instance_method(:eql?)) + define_method(:hash, Object.instance_method(:hash)) + + include Comparable + + # Create a PDeque::Locator object. + def initialize(value, priority=value, subpriority=nil) + super value, subpriority, priority + end + + def inspect + prio = self.priority + if self.value == prio + s = self.value.inspect + else + s = "#{self.value.inspect}:#{prio.inspect}" + end + if in_queue? + "\#<#{self.class}: #{s}>" + else + "\#<#{self.class}: #{s} (no queue)>" + end + end + alias to_s inspect + + def initialize_copy(obj) # :nodoc: + raise TypeError, "can not duplicated" + end + + # returns true if the locator is in a queue. + def in_queue? + pdeque_or_subpriority().kind_of? PDeque + end + + # returns the queue. + # + # nil is returned if the locator is not in a pdeque. + def pdeque + in_queue? ? pdeque_or_subpriority() : nil + end + alias queue pdeque + + def index + in_queue? ? index_or_priority() : nil + end + private :index + + def index=(i) + if !in_queue? + raise ArgumentError, "not in queue" + end + self.index_or_priority = i + end + private :index= + + # returns the priority. + def priority + if in_queue? + pd = pdeque_or_subpriority() + priority, subpriority = pd.send(:internal_get_priority, self) + priority + else + index_or_priority() + end + end + + # returns the subpriority. + def subpriority + if in_queue? + pd = pdeque_or_subpriority() + priority, subpriority = pd.send(:internal_get_priority, self) + subpriority + else + pdeque_or_subpriority() + end + end + + # compare to another locator. + # + # This method consider priority and subpriority. + # + # Note that, locators can be compared only in a queue, + # because comparison method is defined by a queue. + # So nil is returned if a locator is not in a queue. + def <=>(other) + return nil unless other.kind_of? PDeque::Locator + pd1 = self.pdeque + pd2 = other.pdeque + return nil if !pd1 || !pd2 || pd1 != pd2 + priority1, subpriority1 = pd1.send(:internal_get_priority, self) + priority2, subpriority2 = pd1.send(:internal_get_priority, other) + pd1.send(:compare, priority1, subpriority1, priority2, subpriority2) + end + + # update the value, priority and subpriority. + # + # subpriority cannot be nil if the locator is in a queue. + # So subpriority is not changed if subpriority is not specified or nil for a locator in a queue. + # subpriority is set to nil if subpriority is not specified or nil for a locator not in a queue. + # + # pd = PDeque.new + # loc1 = pd.insert 1, 2, 3 + # p [loc1.value, loc1.priority, loc1.subpriority] #=> [1, 2, 3] + # loc1.update(11, 12) + # p [loc1.value, loc1.priority, loc1.subpriority] #=> [11, 12, 3] + # + # loc2 = PDeque::Locator.new(4, 5, 6) + # p [loc2.value, loc2.priority, loc2.subpriority] #=> [4, 5, 6] + # loc2.update(14, 15) + # p [loc2.value, loc2.priority, loc2.subpriority] #=> [14, 15, nil] + # + # This feature is called as decrease-key/increase-key in + # Computer Science terminology. + def update(value, priority=value, subpriority=nil) + subpriority = Integer(subpriority) if subpriority != nil + if in_queue? + pd = pdeque_or_subpriority() + if subpriority == nil + subpriority = self.subpriority + else + subpriority = Integer(subpriority) + end + pd.send(:internal_set_priority, self, priority, subpriority) + else + self.index_or_priority = priority + self.pdeque_or_subpriority = subpriority + end + self.value = value + nil + end + + # update the value. + # + # This method doesn't change the priority and subpriority. + # + # pd = PDeque.new + # loc = pd.insert 1, 2, 3 + # p [loc.value, loc.priority, loc.subpriority] #=> [1, 2, 3] + # loc.update_value 10 + # p [loc.value, loc.priority, loc.subpriority] #=> [10, 2, 3] + # + def update_value(value) + update(value, self.priority, self.subpriority) + end + + # update the priority and subpriority. + # + # This method doesn't change the value. + # + # pd = PDeque.new + # loc = pd.insert 1, 2, 3 + # p [loc.value, loc.priority, loc.subpriority] #=> [1, 2, 3] + # loc.update_priority 10 + # p [loc.value, loc.priority, loc.subpriority] #=> [1, 10, 3] + # loc.update_priority 20, 30 + # p [loc.value, loc.priority, loc.subpriority] #=> [1, 20, 30] + # + def update_priority(priority, subpriority=nil) + update(self.value, priority, subpriority) + end + + def internal_inserted(pdeque, index) + raise ArgumentError, "already inserted" if in_queue? + priority = index_or_priority() + self.pdeque_or_subpriority = pdeque + self.index_or_priority = index + priority + end + private :internal_inserted + + def internal_deleted(priority, subpriority) + raise ArgumentError, "not inserted" if !in_queue? + self.index_or_priority = priority + self.pdeque_or_subpriority = subpriority + end + private :internal_deleted + + end + + # Create a PDeque object. + # + # The optional argument, cmp, specify the method to compare priorities. + # It should be a symbol or a Proc which takes two arguments. + # If it is omitted, :<=> is used. + # + # pd = PDeque.new + # pd.insert "Foo" + # pd.insert "bar" + # p pd.delete_min #=> "Foo" + # p pd.delete_min #=> "bar" + # + # pd = PDeque.new(:casecmp) + # pd.insert "Foo" + # pd.insert "bar" + # p pd.delete_min #=> "bar" + # p pd.delete_min #=> "Foo" + # + # pd = PDeque.new(lambda {|a,b| a.casecmp(b) }) + # pd.insert "Foo" + # pd.insert "bar" + # p pd.delete_min #=> "bar" + # p pd.delete_min #=> "Foo" + # + def initialize(cmp = :<=>) + @cmp = cmp + @ary = [] + @heapsize = 0 + @mode = nil + @totalcount = 0 + #@subpriority_generator = nil + end + + # :stopdoc: + ARY_SLICE_SIZE = 3 + # :startdoc: + + def get_entry(i) + locator = @ary[i*ARY_SLICE_SIZE+0] + priority = @ary[i*ARY_SLICE_SIZE+1] + subpriority = @ary[i*ARY_SLICE_SIZE+2] + [locator, priority, subpriority] + end + private :get_entry + + def set_entry(i, locator, priority, subpriority) + tmp = Array.new(ARY_SLICE_SIZE) + tmp[0] = locator + tmp[1] = priority + tmp[2] = subpriority + @ary[i*ARY_SLICE_SIZE, ARY_SLICE_SIZE] = tmp + end + private :set_entry + + def delete_entry(i) + locator, priority, subpriority = @ary[i*ARY_SLICE_SIZE, ARY_SLICE_SIZE] + @ary[i*ARY_SLICE_SIZE, ARY_SLICE_SIZE] = [] + [locator, priority, subpriority] + end + private :delete_entry + + def each_entry + 0.upto(self.size-1) {|i| + ei = @ary[i*ARY_SLICE_SIZE+0] + pi = @ary[i*ARY_SLICE_SIZE+1] + si = @ary[i*ARY_SLICE_SIZE+2] + yield ei, pi, si + } + end + private :each_entry + + def min_mode + if @mode != MinHeap + @mode = MinHeap + @heapsize = @mode.heapify(self, @ary) + elsif @heapsize < self.size + @heapsize = @mode.heapify(self, @ary, @heapsize) + end + end + private :min_mode + + def max_mode + if @mode != MaxHeap + @mode = MaxHeap + @heapsize = @mode.heapify(self, @ary) + elsif @heapsize < self.size + @heapsize = @mode.heapify(self, @ary, @heapsize) + end + end + private :max_mode + + def mode_heapify + if @mode + @heapsize = @mode.heapify(self, @ary) + end + end + private :mode_heapify + + def check_locator(loc) + if !self.equal?(loc.pdeque) || + !get_entry(loc.send(:index))[0].equal?(loc) + raise ArgumentError, "unexpected locator" + end + end + private :check_locator + + def default_subpriority + #return @subpriority_generator.call if @subpriority_generator + self.totalcount + end + private :default_subpriority + + def compare(priority1, subpriority1, priority2, subpriority2) + compare_priority(priority1, priority2).nonzero? or + (subpriority1 <=> subpriority2) + end + private :compare + + def initialize_copy(obj) # :nodoc: + if defined? @ary + @ary = @ary.dup + 0.step(@ary.length-1, ARY_SLICE_SIZE) {|k| + i = k / ARY_SLICE_SIZE + loc1 = @ary[k] + priority = @ary[k+1] + loc2 = PDeque::Locator.new(loc1.value, priority) + loc2.send(:internal_inserted, self, i) + @ary[k] = loc2 + } + end + end + + def inspect # :nodoc: + unless defined? @cmp + return "\#<#{self.class}: uninitialized>" + end + a = [] + each_entry {|loc, priority| + value = loc.value + s = value.inspect + if value != priority + s << ":" << priority.inspect + end + a << s + } + "\#<#{self.class}: #{a.join(' ')}>" + end + + def pretty_print(q) # :nodoc: + q.object_group(self) { + each_entry {|loc, priority| + q.breakable + value = loc.value + q.pp value + if value != priority + q.text ':' + q.pp priority + end + } + } + end + + # compare priority1 and priority2. + # + # pd = PDeque.new + # p pd.compare_priority("a", "b") #=> -1 + # p pd.compare_priority("a", "a") #=> 0 + # p pd.compare_priority("b", "a") #=> 1 + # + def compare_priority(priority1, priority2) + if @cmp.kind_of? Symbol + priority1.__send__(@cmp, priority2) + else + @cmp.call(priority1, priority2) + end + end + + # returns true if the queue is empty. + # + # pd = PDeque.new + # p pd.empty? #=> true + # pd.insert 1 + # p pd.empty? #=> false + # pd.delete_max + # p pd.empty? #=> true + # + def empty? + @ary.empty? + end + + # returns the number of elements in the queue. + # + # pd = PDeque.new + # p pd.size #=> 0 + # pd.insert 1 + # p pd.size #=> 1 + # pd.insert 1 + # p pd.size #=> 2 + # pd.delete_min + # p pd.size #=> 1 + # pd.delete_min + # p pd.size #=> 0 + # + def size + @ary.size / ARY_SLICE_SIZE + end + alias length size + + # returns the total number of elements inserted for the queue, ever. + # + # The result is monotonically increased. + # + # pd = PDeque.new + # p [pd.size, pd.totalcount] #=> [0, 0] + # pd.insert 1 + # p [pd.size, pd.totalcount] #=> [1, 1] + # pd.insert 2 + # p [pd.size, pd.totalcount] #=> [2, 2] + # pd.delete_min + # p [pd.size, pd.totalcount] #=> [1, 2] + # pd.insert 4 + # p [pd.size, pd.totalcount] #=> [2, 3] + # pd.insert 3 + # p [pd.size, pd.totalcount] #=> [3, 4] + # pd.insert 0 + # p [pd.size, pd.totalcount] #=> [4, 5] + # pd.delete_min + # p [pd.size, pd.totalcount] #=> [3, 5] + # pd.insert 2 + # p [pd.size, pd.totalcount] #=> [4, 6] + # + def totalcount + @totalcount + end + + # make the queue empty. + # + # Note that totalcount is not changed. + # + # pd = PDeque.new + # pd.insert 1 + # pd.insert 1 + # p pd.size #=> 2 + # p pd.totalcount #=> 2 + # pd.clear + # p pd.size #=> 0 + # p pd.totalcount #=> 2 + # p pd.find_min #=> nil + # + def clear + @ary.clear + @heapsize = 0 + @mode = nil + end + + def internal_get_priority(loc) + check_locator(loc) + locator, priority, subpriority = get_entry(loc.send(:index)) + [priority, subpriority] + end + private :internal_get_priority + + def internal_set_priority(loc, priority, subpriority) + check_locator(loc) + if @heapsize <= loc.send(:index) + set_entry(loc.send(:index), loc, priority, subpriority) + else + mode_heapify + @mode.update_priority(self, @ary, loc, priority, subpriority) + end + end + private :internal_set_priority + + # insert the locator to the queue. + # + # If loc.subpriority is nil, totalcount is used for stability. + # + # The locator should not already be inserted in a queue. + # + # pd = PDeque.new + # loc = PDeque::Locator.new(1) + # pd.insert_locator loc + # p pd.delete_min #=> 1 + # + def insert_locator(loc) + subpriority = loc.subpriority || default_subpriority + i = self.size + priority = loc.send(:internal_inserted, self, i) + set_entry(i, loc, priority, subpriority) + @totalcount += 1 + loc + end + + # insert the value to the queue. + # + # If priority is omitted, the value itself is used as the priority. + # + # If subpriority is omitted or nil, totalcount is used for stability. + # + # pd = PDeque.new + # pd.insert 3 + # pd.insert 1 + # pd.insert 2 + # p pd.delete_min #=> 1 + # p pd.delete_min #=> 2 + # p pd.delete_min #=> 3 + # + # pd = PDeque.new + # pd.insert 3, 10 + # pd.insert 1, 20 + # pd.insert 2, 30 + # p pd.delete_min #=> 3 + # p pd.delete_min #=> 1 + # p pd.delete_min #=> 2 + # + # This method returns a locator which locates the inserted element. + # It can be used to update the value and priority, or delete the element. + # + # pd = PDeque.new + # pd.insert 3 + # loc1 = pd.insert 1 + # loc2 = pd.insert 2 + # pd.insert 4 + # p pd.delete_max #=> 4 + # pd.delete_locator loc1 + # loc2.update 8 + # p pd.delete_max #=> 8 + # p pd.delete_max #=> 3 + # p pd.delete_max #=> nil + # + def insert(value, priority=value, subpriority=nil) + loc = Locator.new(value, priority, subpriority) + insert_locator(loc) + end + alias add insert + alias put insert + alias enqueue insert + alias enq insert + alias << insert + + # insert all values in iter. + # + # The argument, iter, should have each method. + # + # This method returns nil. + # + # pd = PDeque.new + # pd.insert_all [3,1,2] + # p pd.delete_min #=> 1 + # p pd.delete_min #=> 2 + # p pd.delete_min #=> 3 + # p pd.delete_min #=> nil + # + def insert_all(iter) + iter.each {|v| + insert v + } + nil + end + + # return the locator for the minimum element. + # This method returns nil if the queue is empty. + # + # This method doesn't delete the element from the queue. + # + # pd = PDeque.new + # p pd.find_min_locator #=> nil + # pd.insert 3 + # pd.insert 1 + # pd.insert 2 + # p pd.find_min_locator #=> # + # + def find_min_locator + return nil if empty? + min_mode + @mode.find_min_locator(@ary) + end + + # return the minimum value with its priority. + # This method returns nil if the queue is empty. + # + # This method doesn't delete the element from the queue. + # + # pd = PDeque.new + # p pd.find_min_priority #=> nil + # pd.insert "durian", 1 + # pd.insert "banana", 3 + # pd.insert "melon", 2 + # p pd.find_min_priority #=> ["durian", 1] + # pd.clear + # p pd.find_min_priority #=> nil + # + def find_min_priority + loc = find_min_locator and [loc.value, loc.priority] + end + + # return the minimum value. + # This method returns nil if the queue is empty. + # + # This method doesn't delete the element from the queue. + # + # pd = PDeque.new + # p pd.find_min #=> nil + # pd.insert 3 + # pd.insert 1 + # pd.insert 2 + # p pd.find_min #=> 1 + # + def find_min + loc = find_min_locator and loc.value + end + alias min find_min + alias first find_min + + # return the locator for the maximum element. + # This method returns nil if the queue is empty. + # + # This method doesn't delete the element from the queue. + # + # pd = PDeque.new + # p pd.find_max_locator #=> nil + # pd.insert 3 + # pd.insert 1 + # pd.insert 2 + # p pd.find_max_locator #=> # + # + def find_max_locator + return nil if empty? + max_mode + @mode.find_max_locator(@ary) + end + + # return the maximum value with its priority. + # This method returns nil if the queue is empty. + # + # This method doesn't delete the element from the queue. + # + # pd = PDeque.new + # p pd.find_max_priority #=> nil + # pd.insert "durian", 1 + # pd.insert "banana", 3 + # pd.insert "melon", 2 + # p pd.find_max_priority #=> ["banana", 3] + # pd.clear + # p pd.find_max_priority #=> nil + # + def find_max_priority + loc = find_max_locator and [loc.value, loc.priority] + end + + # returns the maximum value. + # This method returns nil if the queue is empty. + # + # This method doesn't delete the element from the queue. + # + # pd = PDeque.new + # p pd.find_max #=> nil + # pd.insert 3 + # pd.insert 1 + # pd.insert 2 + # p pd.find_max #=> 3 + # + def find_max + loc = find_max_locator and loc.value + end + alias max find_max + alias last find_max + + # returns the locators for the minimum and maximum element as a two-element array. + # If the queue is empty, [nil, nil] is returned. + # + # pd = PDeque.new + # p pd.find_minmax_locator #=> [nil, nil] + # pd.insert 3 + # pd.insert 1 + # pd.insert 2 + # p pd.find_minmax_locator #=> [#, #] + # + def find_minmax_locator + return [nil, nil] if empty? + case @mode + when :min + loc1 = find_min_locator + loc2 = nil + self.each_locator {|loc| + if loc2 == nil || loc2 < loc + loc2 = loc + end + } + when :max + loc2 = find_max_locator + loc1 = nil + self.each_locator {|loc| + if loc1 == nil || loc1 > loc + loc1 = loc + end + } + else + loc1 = loc2 = nil + self.each_locator {|loc| + if loc1 == nil || loc1 > loc + loc1 = loc + end + if loc2 == nil || loc2 < loc + loc2 = loc + end + } + end + [loc1, loc2] + end + + # returns the minimum and maximum value as a two-element array. + # If the queue is empty, [nil, nil] is returned. + # + # pd = PDeque.new + # p pd.find_minmax #=> [nil, nil] + # pd.insert 3 + # pd.insert 1 + # pd.insert 2 + # p pd.find_minmax #=> [1, 3] + # + def find_minmax + loc1, loc2 = self.find_minmax_locator + [loc1 && loc1.value, loc2 && loc2.value] + end + alias minmax find_minmax + + # delete the element specified by the locator. + # + # pd = PDeque.new + # pd.insert 3 + # loc = pd.insert 2 + # pd.insert 1 + # pd.delete_locator loc + # p pd.delete_min #=> 1 + # p pd.delete_min #=> 3 + # p pd.delete_min #=> nil + # + def delete_locator(loc) + check_locator(loc) + if @heapsize <= loc.send(:index) + _, priority, subpriority = delete_entry(loc.send(:index)) + loc.send(:index).upto(self.size-1) {|i| + loc2, _ = get_entry(i) + loc2.send(:index=, i) + } + loc.send(:internal_deleted, priority, subpriority) + loc + else + mode_heapify + @heapsize = @mode.delete_locator(self, @ary, loc) + loc + end + end + + # delete the minimum element in the queue and returns the locator. + # + # This method returns the locator for the deleted element. + # nil is returned if the queue is empty. + # + # pd = PDeque.new + # pd.insert 2 + # pd.insert 1 + # pd.insert 3 + # p pd.delete_min_locator #=> # + # p pd.delete_min_locator #=> # + # p pd.delete_min_locator #=> # + # p pd.delete_min_locator #=> nil + # + def delete_min_locator + return nil if empty? + min_mode + loc = @mode.find_min_locator(@ary) + @heapsize = @mode.delete_locator(self, @ary, loc) + loc + end + + # delete the minimum element in the queue and returns the value and its priority. + # + # This method returns an array which contains the value and its priority + # of the deleted element. + # nil is returned if the queue is empty. + # + # pd = PDeque.new + # pd.insert "durian", 1 + # pd.insert "banana", 3 + # pd.insert "melon", 2 + # p pd.delete_min_priority #=> ["durian", 1] + # p pd.delete_min_priority #=> ["melon", 2] + # p pd.delete_min_priority #=> ["banana", 3] + # p pd.delete_min_priority #=> nil + # + def delete_min_priority + loc = delete_min_locator + return nil unless loc + [loc.value, loc.priority] + end + + # delete the minimum element in the queue and returns the value. + # + # This method returns the value of the deleted element. + # nil is returned if the queue is empty. + # + # pd = PDeque.new + # pd.insert 3 + # pd.insert 1 + # pd.insert 2 + # p pd.delete_min #=> 1 + # p pd.delete_min #=> 2 + # p pd.delete_min #=> 3 + # p pd.delete_min #=> nil + # + def delete_min + loc = delete_min_locator + loc and loc.value + end + alias shift delete_min + alias dequeue delete_min + alias deq delete_min + + # delete the maximum element in the queue and returns the locator. + # + # This method returns the locator for the deleted element. + # nil is returned if the queue is empty. + # + # pd = PDeque.new + # pd.insert 2 + # pd.insert 1 + # pd.insert 3 + # p pd.delete_max_locator #=> # + # p pd.delete_max_locator #=> # + # p pd.delete_max_locator #=> # + # p pd.delete_max_locator #=> nil + # + def delete_max_locator + return nil if empty? + max_mode + loc = @mode.find_max_locator(@ary) + @heapsize = @mode.delete_locator(self, @ary, loc) + loc + end + + # delete the maximum element in the queue and returns the value and its priority. + # + # This method returns an array which contains the value and its priority + # of the deleted element. + # nil is returned if the queue is empty. + # + # pd = PDeque.new + # pd.insert "durian", 1 + # pd.insert "banana", 3 + # pd.insert "melon", 2 + # p pd.delete_max_priority #=> ["banana", 3] + # p pd.delete_max_priority #=> ["melon", 2] + # p pd.delete_max_priority #=> ["durian", 1] + # p pd.delete_max_priority #=> nil + # + def delete_max_priority + loc = delete_max_locator + return nil unless loc + [loc.value, loc.priority] + end + + # delete the maximum element in the queue and returns the value. + # + # This method returns the value of the deleted element. + # nil is returned if the queue is empty. + # + # pd = PDeque.new + # pd.insert 3 + # pd.insert 1 + # pd.insert 2 + # p pd.delete_max #=> 3 + # p pd.delete_max #=> 2 + # p pd.delete_max #=> 1 + # p pd.delete_max #=> nil + # + def delete_max + loc = delete_max_locator + loc and loc.value + end + alias pop delete_max + + # delete an element in the queue and returns the locator. + # The element is choosen for fast deletion. + # + # This method returns the locator for the deleted element. + # nil is returned if the queue is empty. + # + # pd = PDeque.new + # pd.insert 1 + # pd.insert 4 + # pd.insert 3 + # p pd.delete_unspecified_locator #=> # + # p pd.delete_unspecified_locator #=> # + # p pd.delete_unspecified_locator #=> # + # p pd.delete_unspecified_locator #=> nil + # + def delete_unspecified_locator + return nil if empty? + loc, _ = get_entry(self.size-1) + delete_locator(loc) + end + + # delete an element in the queue and returns the value and its priority. + # The element is choosen for fast deletion. + # + # This method returns an array which contains the value and its priority + # of the deleted element. + # nil is returned if the queue is empty. + # + # pd = PDeque.new + # pd.insert "durian", 1 + # pd.insert "banana", 3 + # pd.insert "melon", 2 + # p pd.delete_unspecified_priority #=> ["melon", 2] + # p pd.delete_unspecified_priority #=> ["banana", 3] + # p pd.delete_unspecified_priority #=> ["durian", 1] + # p pd.delete_unspecified_priority #=> nil + # + def delete_unspecified_priority + loc = delete_unspecified_locator + return nil unless loc + [loc.value, loc.priority] + end + + # delete an element in the queue and returns the value. + # The element is choosen for fast deletion. + # + # This method returns the value of the deleted element. + # nil is returned if the queue is empty. + # + # pd = PDeque.new + # pd.insert 1 + # pd.insert 4 + # pd.insert 3 + # p pd.delete_unspecified #=> 3 + # p pd.delete_unspecified #=> 4 + # p pd.delete_unspecified #=> 1 + # p pd.delete_unspecified #=> nil + # + def delete_unspecified + loc = delete_unspecified_locator + return nil unless loc + loc.value + end + + # iterate over the locators in the queue. + # + # The iteration order is unspecified. + # + # pd = PDeque.new + # pd.insert 3 + # pd.insert 1 + # pd.insert 2 + # p pd.delete_min #=> 1 + # pd.each_locator {|v| + # p v #=> #, # + # } + # + def each_locator # :yield: locator + each_entry {|locator, priority| + yield locator + } + nil + end + + # iterate over the values and priorities in the queue. + # + # pd = PDeque.new + # pd.insert "durian", 1 + # pd.insert "banana", 3 + # pd.insert "melon", 2 + # pd.each_with_priority {|val, priority| + # p [val, priority] + # } + # #=> ["durian", 1] + # # ["banana", 3] + # # ["melon", 2] + # + def each_with_priority # :yield: value, priority + each_entry {|locator, priority| + yield locator.value, priority + } + nil + end + + # iterate over the values in the queue. + # + # The iteration order is unspecified. + # + # pd = PDeque.new + # pd.insert 3 + # pd.insert 1 + # pd.insert 2 + # p pd.delete_min #=> 1 + # pd.each {|v| + # p v #=> 2, 3 + # } + # + def each # :yield: value + each_entry {|locator, priority| + yield locator.value + } + nil + end + + # returns the largest n elements in iter as an array. + # + # The result array is ordered from the minimum element. + # + # p PDeque.nlargest(3, [5, 2, 3, 1, 4, 6, 7]) #=> [5, 6, 7] + # + def PDeque.nlargest(n, iter) + limit = (n * Math.log(1+n)).ceil + limit = 1024 if limit < 1024 + pd = PDeque.new + threshold = nil + iter.each {|v| + if pd.size < n + if pd.size == 0 + threshold = v + else + threshold = v if (v <=> threshold) < 0 + end + pd.insert v + else + if (v <=> threshold) > 0 + pd.insert v + if limit < pd.size + tmp = [] + n.times { tmp << pd.delete_max } + pd.clear + pd.insert_all tmp + threshold = tmp.last + end + end + end + } + n = pd.size if pd.size < n + a = [] + n.times { a << pd.delete_max } + a.reverse! + a + end + + # returns the smallest n elements in iter as an array. + # + # The result array is ordered from the minimum element. + # + # p PDeque.nsmallest(5, [5, 2, 3, 1, 4, 6, 7]) #=> [1, 2, 3, 4, 5] + # + def PDeque.nsmallest(n, iter) + limit = (n * Math.log(1+n)).ceil + limit = 1024 if limit < 1024 + pd = PDeque.new + threshold = nil + iter.each {|v| + if pd.size < n + if pd.size == 0 + threshold = v + else + threshold = v if (v <=> threshold) > 0 + end + pd.insert v + else + if (v <=> threshold) < 0 + pd.insert v + if limit < pd.size + tmp = [] + n.times { tmp << pd.delete_min } + pd.clear + pd.insert_all tmp + threshold = tmp.last + end + end + end + } + n = pd.size if pd.size < n + a = [] + n.times { + a << pd.delete_min + } + a + end + + # iterates over iterators specified by arguments. + # + # The iteration order is sorted, from minimum to maximum, + # if all the arugment iterators are sorted. + # + # PDeque.merge(1..4, 3..6) {|v| p v } + # #=> 1 + # # 2 + # # 3 + # # 3 + # # 4 + # # 4 + # # 5 + # # 6 + # + def PDeque.merge(*iters, &b) + pd = PDeque.new + iters.each {|enum| + enum = enum.to_enum unless enum.kind_of? Enumerator + begin + val = enum.next + rescue StopIteration + next + end + pd.insert enum, val + } + loop = lambda {|y| + until pd.empty? + loc = pd.find_min_locator + enum = loc.value + val = loc.priority + y.yield val + begin + val = enum.next + rescue StopIteration + pd.delete_locator loc + next + end + loc.update enum, val + end + } + if block_given? + loop.call(b) + else + Enumerator.new {|y| + loop.call(y) + } + end + end + + # :stopdoc: + + module SimpleHeap + + def size(ary) + return ary.size / ARY_SLICE_SIZE + end + + def get_entry(ary, i) + locator = ary[i*ARY_SLICE_SIZE+0] + priority = ary[i*ARY_SLICE_SIZE+1] + subpriority = ary[i*ARY_SLICE_SIZE+2] + [locator, priority, subpriority] + end + + def set_entry(ary, i, locator, priority, subpriority) + tmp = Array.new(ARY_SLICE_SIZE) + tmp[0] = locator + tmp[1] = priority + tmp[2] = subpriority + ary[i*ARY_SLICE_SIZE, ARY_SLICE_SIZE] = tmp + end + + def delete_entry(ary, i) + locator, priority, subpriority = ary[i*ARY_SLICE_SIZE, ARY_SLICE_SIZE] + ary[i*ARY_SLICE_SIZE, ARY_SLICE_SIZE] = [] + [locator, priority, subpriority] + end + + def each_entry(ary) + 0.upto(self.size-1) {|i| + ei = ary[i*ARY_SLICE_SIZE+0] + pi = ary[i*ARY_SLICE_SIZE+1] + si = ary[i*ARY_SLICE_SIZE+2] + yield ei, pi, si + } + end + + def swap(ary, i, j) + ei, pi, si = get_entry(ary, i) + ej, pj, sj = get_entry(ary, j) + set_entry(ary, i, ej, pj, sj) + set_entry(ary, j, ei, pi, si) + ei.send(:index=, j) + ej.send(:index=, i) + end + + def upheap(pd, ary, j) + while true + return if j <= 0 + i = (j-1) >> 1 + return if upper?(pd, ary, i, j) + swap(ary, j, i) + j = i + end + end + + def downheap(pd, ary, i) + while true + j = i*2+1 + k = i*2+2 + return if size(ary) <= j + if size(ary) == k + return if upper?(pd, ary, i, j) + swap(ary, i, j) + i = j + else + return if upper?(pd, ary, i, j) && upper?(pd, ary, i, k) + loc = upper?(pd, ary, j, k) ? j : k + swap(ary, i, loc) + i = loc + end + end + end + + def find_top_locator(ary) + loc, _ = get_entry(ary, 0) + loc + end + + def delete_locator(pd, ary, loc) + i = loc.send(:index) + _, priority, subpriority = get_entry(ary, i) + last = size(ary) - 1 + loc.send(:internal_deleted, priority, subpriority) + el, pl, sl = delete_entry(ary, last) + if i != last + set_entry(ary, i, el, pl, sl) + el.send(:index=, i) + downheap(pd, ary, i) + end + size(ary) + end + + def heapify(pd, ary, heapsize=0) + # compare number of data movements in worst case. + # choose a way for less data movements. + # + # total size = ary.size / ARY_SLICE_SIZE = n + # addition size = n - heapsize = m + # heap tree height = Math.log2(n+1) = h + # + # worst data movements using downheap: + # - bottom elements cannot move. + # - elements above can move 1 time. + # - ... + # - top element can move h-1 times. + # + # 1*2**(h-2) + 2*2**(h-3) + 3*2**(h-4) + ... + (h-1)*2**0 + # = sum i*2**(h-1-i), i=1 to h-1 + # = 2**h - h - 1 + # = n - h + # + # worst data movements using upheap + # - bottom elements can move h-1 times. + # - n/2 elements are placed at bottom. + # - approximate all elements are at bottom... + # + # (h-1) * m + # + # condition to use downheap: + # + # n - h < (h-1) * m + # n - 1 < (h-1) * (m+1) + # + totalsize = size(ary) + h = Math.log2(totalsize+1) + if totalsize - 1 < (h - 1) * (totalsize - heapsize + 1) + n = (totalsize - 2) / 2 + n.downto(0) {|i| + downheap(pd, ary, i) + } + else + heapsize.upto(totalsize-1) {|i| + upheap(pd, ary, i) + } + end + totalsize + end + end + + module MinHeap + end + class << MinHeap + include SimpleHeap + + def upper?(pd, ary, i, j) + ei, pi, si = get_entry(ary, i) + ej, pj, sj = get_entry(ary, j) + ei <= ej + end + + def update_priority(pd, ary, loc, priority, subpriority) + i = loc.send(:index) + ei, pi, si = get_entry(ary, i) + cmp = pd.send(:compare, pi, si, priority, subpriority) + set_entry(ary, i, ei, priority, subpriority) + if cmp < 0 + # loc.priority < priority + downheap(pd, ary, i) + elsif cmp > 0 + # loc.priority > priority + upheap(pd, ary, i) + end + end + + alias find_min_locator find_top_locator + end + + module MaxHeap + end + class << MaxHeap + include SimpleHeap + + def upper?(pd, ary, i, j) + ei, pi, si = get_entry(ary, i) + ej, pj, sj = get_entry(ary, j) + ei >= ej + end + + def update_priority(pd, ary, loc, priority, subpriority) + i = loc.send(:index) + ei, pi, si = get_entry(ary, i) + subpriority ||= si + cmp = pd.send(:compare, pi, si, priority, subpriority) + set_entry(ary, i, ei, priority, subpriority) + if cmp < 0 + # loc.priority < priority + upheap(pd, ary, i) + elsif cmp > 0 + # loc.priority > priority + downheap(pd, ary, i) + end + end + + alias find_max_locator find_top_locator + end + + # :startdoc: +end Index: test/test-pdeque.rb =================================================================== --- test/test-pdeque.rb (revision 0) +++ test/test-pdeque.rb (revision 0) @@ -0,0 +1,767 @@ +require 'pdeque' +require 'test/unit' + +class PDeque + module SimpleHeap + def validation(pd, ary) + 0.upto(size(ary)-1) {|i| + _, x = get_entry(ary, i) + j = i*2+1 + k = i*2+2 + if j < size(ary) && !upper?(pd, ary, i, j) + _, y = get_entry(ary, j) + raise "wrong binary heap: pri[#{i}]=#{x.inspect} > #{y.inspect}=pri[#{j}]" + end + if k < size(ary) && !upper?(pd, ary, i, k) + _, z = get_entry(ary, k) + raise "wrong binary heap: pri[#{i}]=#{x.inspect} > #{z.inspect}=pri[#{k}]" + end + } + end + end + + def validation + @mode.validation(self, @ary) if @mode + if @ary.length % ARY_SLICE_SIZE != 0 + raise "wrong length" + end + i = 0 + each_entry {|loc, priority| + if !loc.in_queue? + raise "wrongly deleted" + end + if loc.send(:index) != i + raise "index mismatch" + end + unless self.equal? loc.pdeque + raise "pdeque mismatch" + end + i += 1 + } + end +end + +class TestPDeque < Test::Unit::TestCase + def random_test(mode, incremental) + case mode + when :min + find = :find_min + delete = :delete_min + cmp = lambda {|a, b| a <=> b } + when :max + find = :find_max + delete = :delete_max + cmp = lambda {|a, b| b <=> a } + else + raise "wrong mode" + end + pd = PDeque.new + n = 10 + a1 = [] + n.times { + r = rand(n) + a1 << r + pd.insert(r) + if incremental + pd.send find + pd.validation + end + } + a1.sort!(&cmp) + a2 = [] + n.times { + a2 << pd.send(delete) + pd.validation + } + assert_equal(a1, a2) + end + + def test_random100_minmode + 100.times { random_test(:min, false) } + 100.times { random_test(:min, true) } + 100.times { random_test(:max, false) } + 100.times { random_test(:max, true) } + end + + def perm_test(ary, incremental) + a0 = ary.to_a.sort + a0.permutation {|a1| + pd = PDeque.new + a1.each {|v| + pd.insert v + if incremental + pd.find_min + pd.validation + end + } + pd.find_min + pd.validation + a0.each {|v| + assert_equal(v, pd.delete_min) + } + } + end + + def test_permutation + 5.times {|n| + perm_test(0..n, true) + perm_test(0..n, false) + } + end + + def perm_test2(ary) + a0 = ary.to_a.sort + a0.permutation {|a1| + 0.upto(2**(a1.length-1)-1) {|n| + #log = []; p [:n, n, 2**(a1.length-1)-1] + pd = PDeque.new + a1.each_with_index {|v,i| + pd.insert v + #log << v + if n[i] != 0 + pd.find_min + pd.validation + #log << :find_min + end + } + #p log + a0.each {|v| + assert_equal(v, pd.delete_min) + } + } + } + end + + def test_permutation2 + 5.times {|n| + perm_test2(0..n) + } + end + + def test_stable_min + pd = PDeque.new + pd.insert "a", 0 + pd.insert "b", 0 + pd.insert "c", 0 + assert_equal("a", pd.delete_min) + assert_equal("b", pd.delete_min) + assert_equal("c", pd.delete_min) + end + + def test_stable_max + pd = PDeque.new + pd.insert "a", 0 + pd.insert "b", 0 + pd.insert "c", 0 + assert_equal("c", pd.delete_max) + assert_equal("b", pd.delete_max) + assert_equal("a", pd.delete_max) + end + + def test_locator_new + pd = PDeque.new + loc1 = PDeque::Locator.new(1) + loc2 = PDeque::Locator.new(2, 3) + assert_equal(1, loc1.value) + assert_equal(1, loc1.priority) + assert_equal(2, loc2.value) + assert_equal(3, loc2.priority) + pd.insert_locator loc1 + pd.insert_locator loc2 + assert_equal(loc1, pd.delete_min_locator) + assert_equal(loc2, pd.delete_min_locator) + end + + def test_locator_eql + loc1 = PDeque::Locator.new(1,2,3) + loc2 = PDeque::Locator.new(1,2,3) + assert(!loc1.eql?(loc2)) + end + + def test_locator_priority + pd = PDeque.new + loc2 = pd.insert(Object.new, 2) + loc1 = pd.insert(Object.new, 1) + loc3 = pd.insert(Object.new, 3) + assert_equal(1, loc1.priority) + assert_equal(2, loc2.priority) + assert_equal(3, loc3.priority) + pd.delete_locator(loc1) + assert_equal(1, loc1.priority) + end + + def test_locator_update_min + pd = PDeque.new + a = pd.insert("a", 2) + b = pd.insert("b", 1) + c = pd.insert("c", 3) + assert_equal(b, pd.find_min_locator) + a.update("d", 0) + assert_equal("d", a.value) + assert_equal(a, pd.find_min_locator) + a.update("e", 10) + assert_equal("b", pd.delete_min) + assert_equal("c", pd.delete_min) + assert_equal("e", pd.delete_min) + a.update "z", 20 + assert_equal("z", a.value) + assert_equal(20, a.priority) + end + + def test_locator_update_subpriority_min + pd = PDeque.new + a = pd.insert("a", 1, 0) + b = pd.insert("b", 2, 1) + c = pd.insert("c", 1, 2) + d = pd.insert("d", 2, 3) + e = pd.insert("e", 1, 4) + f = pd.insert("f", 2, 5) + assert_equal(a, pd.find_min_locator) + a.update("A", 1, 10) + assert_equal(c, pd.find_min_locator) + a.update("aa", 1, 1) + assert_equal(a, pd.find_min_locator) + pd.delete_locator a + assert_equal(c, pd.find_min_locator) + a.update("aaa", 10, 20) + assert_equal("aaa", a.value) + assert_equal(10, a.priority) + assert_equal(20, a.subpriority) + end + + def test_locator_update_subpriority_max + pd = PDeque.new + a = pd.insert("a", 1, 0) + b = pd.insert("b", 2, 1) + c = pd.insert("c", 1, 2) + d = pd.insert("d", 2, 3) + e = pd.insert("e", 1, 4) + f = pd.insert("f", 2, 5) + assert_equal(f, pd.find_max_locator) + f.update("F", 2, 0) + assert_equal(d, pd.find_max_locator) + f.update("ff", 2, 10) + assert_equal(f, pd.find_max_locator) + pd.delete_locator f + assert_equal(d, pd.find_max_locator) + f.update("fff", 10, 20) + assert_equal("fff", f.value) + assert_equal(10, f.priority) + assert_equal(20, f.subpriority) + end + + def test_locator_update_max + pd = PDeque.new + a = pd.insert("a", 2) + b = pd.insert("b", 1) + c = pd.insert("c", 3) + assert_equal(c, pd.find_max_locator) + b.update("d", 10) + assert_equal("d", b.value) + assert_equal(b, pd.find_max_locator) + b.update("e", 0) + assert_equal("c", pd.delete_max) + assert_equal("a", pd.delete_max) + assert_equal("e", pd.delete_max) + end + + def test_locator_update_value + pd = PDeque.new + loc = pd.insert 1, 2, 3 + assert_equal(1, loc.value) + assert_equal(2, loc.priority) + assert_equal(3, loc.subpriority) + loc.update_value 10 + assert_equal(10, loc.value) + assert_equal(2, loc.priority) + assert_equal(3, loc.subpriority) + end + + def test_locator_update_priority + pd = PDeque.new + loc = pd.insert 1, 2, 3 + assert_equal(1, loc.value) + assert_equal(2, loc.priority) + assert_equal(3, loc.subpriority) + loc.update_priority 10 + assert_equal(1, loc.value) + assert_equal(10, loc.priority) + assert_equal(3, loc.subpriority) + loc.update_priority 20, 30 + assert_equal(1, loc.value) + assert_equal(20, loc.priority) + assert_equal(30, loc.subpriority) + loc.update_priority 40, nil + assert_equal(1, loc.value) + assert_equal(40, loc.priority) + assert_equal(30, loc.subpriority) + pd.delete_min + assert_equal(1, loc.value) + assert_equal(40, loc.priority) + assert_equal(30, loc.subpriority) + loc.update_priority 50, nil + assert_equal(1, loc.value) + assert_equal(50, loc.priority) + assert_equal(nil, loc.subpriority) + end + + def test_locator_subpriority + pd = PDeque.new + loc1 = pd.insert(Object.new, 1, 11) + loc2 = pd.insert(Object.new, 2, 12) + loc3 = pd.insert(Object.new, 3, 13) + assert_equal(1, loc1.priority) + assert_equal(11, loc1.subpriority) + assert_equal(2, loc2.priority) + assert_equal(12, loc2.subpriority) + assert_equal(3, loc3.priority) + assert_equal(13, loc3.subpriority) + pd.delete_locator(loc1) + assert_equal(11, loc1.subpriority) + end + + def test_locator_cmp + pd = PDeque.new + loc1 = pd.insert 1 + loc21 = pd.insert 2 + loc3 = pd.insert 3 + loc22 = pd.insert 2 + [loc1, loc21, loc22, loc3].each_with_index {|a, ai| + [loc1, loc21, loc22, loc3].each_with_index {|b, bi| + cmp0 = ai <=> bi + cmp0 = -1 if cmp0 < 0 + cmp0 = 1 if cmp0 > 0 + cmp1 = a <=> b + cmp1 = -1 if cmp1 < 0 + cmp1 = 1 if cmp1 > 0 + assert_equal(cmp0, cmp1) + } + } + pd.delete_min + assert_equal(nil, loc1 <=> loc3) + end + + def test_new + pd = PDeque.new + pd.insert "Foo" + pd.insert "bar" + assert_equal("Foo", pd.delete_min) + assert_equal("bar", pd.delete_min) + + pd = PDeque.new(:casecmp) + pd.insert "Foo" + pd.insert "bar" + assert_equal("bar", pd.delete_min) + assert_equal("Foo", pd.delete_min) + + pd = PDeque.new(lambda {|a,b| a.casecmp(b) }) + pd.insert "Foo" + pd.insert "bar" + assert_equal("bar", pd.delete_min) + assert_equal("Foo", pd.delete_min) + end + + def test_dup + pd = PDeque.new + pd.insert 1 + pd2 = pd.dup + pd.validation + pd2.validation + pd.insert 2 + pd2.validation + assert_equal(1, pd2.delete_min) + pd.validation + pd2.validation + assert_equal(nil, pd2.delete_min) + pd.validation + pd2.validation + assert_equal(1, pd.delete_min) + pd.validation + pd2.validation + assert_equal(2, pd.delete_min) + assert_equal(nil, pd.delete_min) + end + + def test_marshal + pd = PDeque.new + pd.insert 1 + pd2 = Marshal.load(Marshal.dump(pd)) + pd.validation + pd2.validation + pd.insert 2 + pd2.validation + assert_equal(1, pd2.delete_min) + pd.validation + pd2.validation + assert_equal(nil, pd2.delete_min) + pd.validation + pd2.validation + assert_equal(1, pd.delete_min) + pd.validation + pd2.validation + assert_equal(2, pd.delete_min) + assert_equal(nil, pd.delete_min) + end + + def test_compare_priority + pd = PDeque.new + assert_operator(pd.compare_priority("a", "b"), :<, 0) + assert_operator(pd.compare_priority("a", "a"), :==, 0) + assert_operator(pd.compare_priority("b", "a"), :>, 0) + pd = PDeque.new(:casecmp) + assert_operator(pd.compare_priority("a", "b"), :<, 0) + assert_operator(pd.compare_priority("a", "B"), :<, 0) + assert_operator(pd.compare_priority("A", "b"), :<, 0) + assert_operator(pd.compare_priority("A", "B"), :<, 0) + assert_operator(pd.compare_priority("a", "a"), :==, 0) + assert_operator(pd.compare_priority("a", "A"), :==, 0) + assert_operator(pd.compare_priority("A", "a"), :==, 0) + assert_operator(pd.compare_priority("A", "A"), :==, 0) + assert_operator(pd.compare_priority("b", "a"), :>, 0) + assert_operator(pd.compare_priority("b", "A"), :>, 0) + assert_operator(pd.compare_priority("B", "a"), :>, 0) + assert_operator(pd.compare_priority("B", "A"), :>, 0) + pd = PDeque.new(lambda {|a,b| [a[1],a[0]] <=> [b[1],b[0]]}) + assert_operator(pd.compare_priority([0,0], [0,0]), :==, 0) + assert_operator(pd.compare_priority([0,0], [0,1]), :<, 0) + assert_operator(pd.compare_priority([0,0], [1,0]), :<, 0) + assert_operator(pd.compare_priority([0,0], [1,1]), :<, 0) + assert_operator(pd.compare_priority([0,1], [0,0]), :>, 0) + assert_operator(pd.compare_priority([0,1], [0,1]), :==, 0) + assert_operator(pd.compare_priority([0,1], [1,0]), :>, 0) + assert_operator(pd.compare_priority([0,1], [1,1]), :<, 0) + assert_operator(pd.compare_priority([1,0], [0,0]), :>, 0) + assert_operator(pd.compare_priority([1,0], [0,1]), :<, 0) + assert_operator(pd.compare_priority([1,0], [1,0]), :==, 0) + assert_operator(pd.compare_priority([1,0], [1,1]), :<, 0) + assert_operator(pd.compare_priority([1,1], [0,0]), :>, 0) + assert_operator(pd.compare_priority([1,1], [0,1]), :>, 0) + assert_operator(pd.compare_priority([1,1], [1,0]), :>, 0) + assert_operator(pd.compare_priority([1,1], [1,1]), :==, 0) + end + + def test_empty? + pd = PDeque.new + assert(pd.empty?) + pd.insert 1 + assert(!pd.empty?) + end + + def test_size + pd = PDeque.new + pd.insert 1 + assert_equal(1, pd.size) + pd.insert 10 + assert_equal(2, pd.size) + pd.insert 2 + assert_equal(3, pd.size) + pd.delete_max + assert_equal(2, pd.size) + end + + def test_totalcount + pd = PDeque.new + assert_equal(0, pd.totalcount) + pd.insert 1 + assert_equal(1, pd.totalcount) + pd.insert 2 + assert_equal(2, pd.totalcount) + pd.delete_min + assert_equal(2, pd.totalcount) + pd.insert 4 + assert_equal(3, pd.totalcount) + pd.insert 3 + assert_equal(4, pd.totalcount) + pd.insert 0 + assert_equal(5, pd.totalcount) + pd.delete_min + assert_equal(5, pd.totalcount) + pd.insert 2 + assert_equal(6, pd.totalcount) + end + + def test_clear + pd = PDeque.new + pd.insert 1 + assert(!pd.empty?) + pd.clear + assert(pd.empty?) + end + + def test_insert + pd = PDeque.new + pd.insert 1 + pd.insert 10 + pd.insert 2 + assert_equal(1, pd.delete_min) + assert_equal(2, pd.delete_min) + assert_equal(10, pd.delete_min) + end + + def test_insert_all + pd = PDeque.new + pd.insert_all [3,1,2] + assert_equal(1, pd.delete_min) + assert_equal(2, pd.delete_min) + assert_equal(3, pd.delete_min) + assert_equal(nil, pd.delete_min) + end + + def test_find_min_locator + pd = PDeque.new + pd.insert 1 + loc = pd.find_min_locator + assert_equal(1, loc.value) + assert_equal(1, loc.priority) + assert_equal(pd, loc.pdeque) + assert_equal(1, pd.delete_min) + assert_equal(nil, loc.pdeque) + end + + def test_find_min + pd = PDeque.new + pd.insert 1 + assert_equal(1, pd.find_min) + end + + def test_find_min_priority + pd = PDeque.new + pd.insert "a", 1 + assert_equal(["a", 1], pd.find_min_priority) + pd.delete_min + assert_equal(nil, pd.find_min_priority) + end + + def test_find_max_locator + pd = PDeque.new + pd.insert 1 + loc = pd.find_max_locator + assert_equal(1, loc.value) + assert_equal(1, loc.priority) + assert_equal(pd, loc.pdeque) + end + + def test_find_max + pd = PDeque.new + pd.insert 1 + assert_equal(1, pd.find_max) + end + + def test_find_max_priority + pd = PDeque.new + pd.insert "a", 1 + assert_equal(["a", 1], pd.find_max_priority) + pd.delete_max + assert_equal(nil, pd.find_max_priority) + end + + def test_find_minmax_locator + pd = PDeque.new + assert_equal([nil, nil], pd.find_minmax_locator) + loc3 = pd.insert 3 + loc1 = pd.insert 1 + pd.insert 2 + res = pd.find_minmax_locator + assert_equal([loc1, loc3], res) + end + + def test_find_minmax + pd = PDeque.new + assert_equal([nil, nil], pd.find_minmax) + pd.insert 3 + pd.insert 1 + pd.insert 2 + res = pd.find_minmax + assert_equal([1, 3], res) + end + + def test_delete_locator + pd = PDeque.new + loc = pd.insert 1 + pd.delete_locator loc + assert(pd.empty?) + pd = PDeque.new + loc = pd.insert 2 + pd.insert 3 + pd.insert 1 + assert_equal(1, pd.find_min) + pd.delete_locator(loc) + assert_equal(1, pd.delete_min) + assert_equal(3, pd.delete_min) + end + + def test_delete_min + pd = PDeque.new + pd.insert 1 + pd.insert 2 + pd.insert 0 + assert_equal(0, pd.delete_min) + assert_equal(1, pd.delete_min) + assert_equal(2, pd.delete_min) + assert_equal(nil, pd.delete_min) + end + + def test_delete_min_locator + pd = PDeque.new + loc1 = pd.insert 1 + loc2 = pd.insert 2 + loc0 = pd.insert 0 + assert_equal(loc0, pd.delete_min_locator) + assert_equal(loc1, pd.delete_min_locator) + assert_equal(loc2, pd.delete_min_locator) + assert_equal(nil, pd.delete_min_locator) + end + + def test_delete_max + pd = PDeque.new + pd.insert 1 + pd.insert 2 + pd.insert 0 + assert_equal(2, pd.delete_max) + assert_equal(1, pd.delete_max) + assert_equal(0, pd.delete_max) + assert_equal(nil, pd.delete_max) + end + + def test_delete_max_locator + pd = PDeque.new + loc1 = pd.insert 1 + loc2 = pd.insert 2 + loc0 = pd.insert 0 + assert_equal(loc2, pd.delete_max_locator) + assert_equal(loc1, pd.delete_max_locator) + assert_equal(loc0, pd.delete_max_locator) + assert_equal(nil, pd.delete_max) + end + + def test_delete_unspecified + pd = PDeque.new + a1 = [1,2,0] + a1.each {|v| + pd.insert v + } + a2 = [] + a1.length.times { + a2 << pd.delete_unspecified + } + assert_equal(a1.sort, a2.sort) + assert_equal(nil, pd.delete_unspecified_locator) + end + + def test_delete_unspecified_priority + pd = PDeque.new + a1 = [[1,8],[2,3],[0,5]] + a1.each {|val, priority| + pd.insert val, priority + } + a2 = [] + a1.length.times { + a2 << pd.delete_unspecified_priority + } + assert_equal(a1.sort, a2.sort) + assert_equal(nil, pd.delete_unspecified_locator) + end + + def test_delete_unspecified_locator + pd = PDeque.new + a1 = [1,2,0] + a1.each {|v| + pd.insert v + } + a2 = [] + a1.length.times { + a2 << pd.delete_unspecified_locator.value + } + assert_equal(a1.sort, a2.sort) + assert_equal(nil, pd.delete_unspecified_locator) + end + + def test_each + pd = PDeque.new + a = [1,2,0] + a.each {|v| + pd.insert v + } + pd.each {|v| + assert(a.include? v) + } + end + + def test_each_with_priority + pd = PDeque.new + h = {} + h["durian"] = 1 + h["banana"] = 3 + h["melon"] = 2 + h.each {|val, prio| + pd.insert val, prio + } + pd.each_with_priority {|val, prio| + assert_equal(h[val], prio) + } + end + + def test_each_locator + pd = PDeque.new + a = [1,2,0] + a.each {|v| + pd.insert v + } + pd.each_locator {|loc| + assert(a.include? loc.value) + } + end + + def test_nlargest + a = PDeque.nlargest(3, [5, 1, 3, 2, 4, 6, 7]) + assert_equal([5, 6, 7], a) + + assert_equal([1,2], PDeque.nlargest(3, [1,2])) + + a = [] + 2000.times { a << rand } + b = a.sort + assert_equal(b[-30..-1], PDeque.nlargest(30, a)) + end + + def test_nsmallest + a = PDeque.nsmallest(5, [5, 2, 3, 1, 4, 6, 7]) + assert_equal([1, 2, 3, 4, 5], a) + + assert_equal([1,2], PDeque.nsmallest(3, [1,2])) + + a = [] + 2000.times { a << rand } + b = a.sort + assert_equal(b[0, 30], PDeque.nsmallest(30, a)) + end + + def test_merge + a = [] + PDeque.merge(1..4, 3..6) {|v| a << v } + assert_equal([1,2,3,3,4,4,5,6], a) + end + + def test_merge_enumerator + loc = PDeque.merge(1..4, 3..6) + assert_equal(1, loc.next) + assert_equal(2, loc.next) + assert_equal(3, loc.next) + assert_equal(3, loc.next) + assert_equal(4, loc.next) + assert_equal(4, loc.next) + assert_equal(5, loc.next) + assert_equal(6, loc.next) + assert_raise(StopIteration) { loc.next } + end + + def test_merge_enumerator2 + loc = PDeque.merge(1..4, 3..6) + a = [] + loc.each_slice(2) {|x| + a << x + } + assert_equal([[1,2],[3,3],[4,4],[5,6]], a) + end + +end -- [田中 哲][たなか あきら][Tanaka Akira]