Skip to content

Commit

Permalink
slightly reimplement heap using new CompleteTree
Browse files Browse the repository at this point in the history
  • Loading branch information
rickhull committed Jun 20, 2024
1 parent a389ea8 commit df5544b
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 111 deletions.
190 changes: 96 additions & 94 deletions lib/compsci/heap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,110 +18,112 @@
# swap nodes at each layer of the tree, and there are log(n, base b) layers
# to the tree.
#
class CompSci::Heap < CompSci::CompleteTree
# * defaults to a MaxHeap, with the largest node at the root
# * specify a minheap with minheap: true or cmp_val: -1
#
def initialize(cmp_val: 1, minheap: false, child_slots: 2)
super(child_slots: child_slots)
cmp_val = -1 if minheap
case cmp_val
when -1, 1
@cmp_val = cmp_val
else
raise(ArgumentError, "unknown comparison value: #{cmp_val}")
module CompSci
class Heap
attr_reader :tree, :cmp_val

# * defaults to a MaxHeap, with the largest node at the root
# * specify a minheap with minheap: true or cmp_val: -1
#
def initialize(cmp_val: 1, minheap: false, child_slots: 2)
@tree = CompleteTree.new(child_slots: child_slots)
cmp_val = -1 if minheap
case cmp_val
when -1, 1
@cmp_val = cmp_val
else
raise(ArgumentError, "unknown comparison value: #{cmp_val}")
end
end
end

# * append to the array
# * sift_up -- O(log n) on heap size
#
def push(node)
@array.push(node)
self.sift_up(@array.size - 1)
end
# * append to the array
# * sift_up -- O(log n) on heap size
#
def push(node)
@tree.push(node)
self.sift_up(@tree.size - 1)
end

# * remove from the front of the array
# * move last node to root
# * sift_down -- O(log n) on heap size
#
def pop
node = @array.shift
replacement = @array.pop
@array.unshift replacement if replacement
self.sift_down(0)
node
end
# * remove from the front of the array
# * move last node to root
# * sift_down -- O(log n) on heap size
#
def pop
node = @tree.shift
replacement = @tree.pop
@tree.unshift replacement if replacement
self.sift_down(0)
node
end

# * return what pop would return (avoid sifting)
#
def peek
@array.first
end
# * return what pop would return (avoid sifting)
#
def peek
@tree.first
end

# * called recursively
# * idx represents the node suspected to violate the heap
# * intended to be O(log n) on heap size (log base child_slots)
#
def sift_up(idx = nil)
idx ||= @array.size - 1
return self if idx <= 0
pidx = self.class.parent_idx(idx, @child_slots)
if !self.heapish?(pidx, idx)
@array[idx], @array[pidx] = @array[pidx], @array[idx] # swap
self.sift_up(pidx)
# * called recursively
# * idx represents the node suspected to violate the heap
# * intended to be O(log n) on heap size (log base child_slots)
#
def sift_up(idx = @tree.size - 1)
return self if idx <= 0
pidx = @tree.class.parent_idx(idx, @tree.child_slots)
if !heapish?(pidx, idx)
@tree.swap(idx, pidx)
sift_up(pidx)
end
self
end
self
end

# * called recursively
# * idx represents the node suspected to violate the heap
# * intended to be O(log n) on heap size (log base child_slots)
# * slower than sift_up because one parent vs multiple children
#
def sift_down(idx = nil)
idx ||= 0
return self if idx >= @array.size
cidxs = self.class.children_idx(idx, @child_slots)
# promote the heapiest child
cidx = self.heapiest(cidxs)
if !self.heapish?(idx, cidx)
@array[idx], @array[cidx] = @array[cidx], @array[idx] # swap
self.sift_down(cidx)
# * called recursively
# * idx represents the node suspected to violate the heap
# * intended to be O(log n) on heap size (log base child_slots)
# * slower than sift_up because one parent vs multiple children
#
def sift_down(idx = 0)
return self if idx >= @tree.size
cidxs = @tree.class.children_idx(idx, @tree.child_slots)
# promote the heapiest child
cidx = self.heapiest(cidxs)
if !self.heapish?(idx, cidx)
@tree.swap(idx, cidx)
self.sift_down(cidx)
end
self
end
self
end

# are values of parent and child (by index) in accordance with heap property?
#
def heapish?(pidx, cidx)
(@array[pidx] <=> @array[cidx]) != (@cmp_val * -1)
end
# do values of parent and child (by index) meet the heap test?
#
def heapish?(pidx, cidx)
(@tree[pidx] <=> @tree[cidx]) != (@cmp_val * -1)
end

# return the heapiest idx in cidxs
#
def heapiest(cidxs)
idx = cidxs.first
cidxs.each { |cidx|
idx = cidx if cidx < @array.size and self.heapish?(cidx, idx)
}
idx
end
# return the heapiest idx in cidxs
#
def heapiest(cidxs)
idx = cidxs.first
cidxs.each { |cidx|
idx = cidx if cidx < @tree.size and heapish?(cidx, idx)
}
idx
end

# * not used internally
# * checks that every node satisfies the heap property
# * calls heapish? on idx's children and then heap? on them recursively
#
def heap?(idx: 0)
check_children = []
self.class.children_idx(idx, @child_slots).each { |cidx|
# cidx is arithmetically produced; the corresponding child may not exist
if cidx < @array.size
return false unless self.heapish?(idx, cidx)
check_children << cidx
end
}
check_children.each { |cidx| return false unless self.heap?(idx: cidx) }
true
# * not used internally
# * checks that every node satisfies the heap property
# * calls heapish? on idx's children and then heap? on them recursively
#
def heap?(idx: 0)
check_children = []
@tree.class.children_idx(idx, @tree.child_slots).each { |cidx|
# cidx may not be valid
if cidx < @tree.size
return false unless heapish?(idx, cidx)
check_children << cidx
end
}
check_children.each { |cidx| return false unless self.heap?(idx: cidx) }
true
end
end
end
34 changes: 17 additions & 17 deletions test/heap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@

it "must satisfy the heap property" do
expect(@maxheap.heap?).must_equal true
expect(@maxheap.array).wont_equal @inserts
expect(@maxheap.array).wont_equal @inserts.reverse
expect(@maxheap.tree.array).wont_equal @inserts
expect(@maxheap.tree.array).wont_equal @inserts.reverse
end

it "must recognize heap violations" do
@maxheap.array.unshift 0
@maxheap.tree.unshift 0
expect(@maxheap.heap?).must_equal false
@maxheap.array.shift
@maxheap.tree.shift
expect(@maxheap.heap?).must_equal true

@maxheap.array.push 10
@maxheap.tree.push 10
expect(@maxheap.heap?).must_equal false
@maxheap.sift_up
expect(@maxheap.heap?).must_equal true
Expand All @@ -37,7 +37,7 @@
end

it "must heapish?" do
expect(@maxheap.array[0]).must_be :>, @maxheap.array[1]
expect(@maxheap.tree[0]).must_be :>, @maxheap.tree[1]
expect(@maxheap.heapish?(0, 1)).must_equal true
end

Expand All @@ -57,16 +57,16 @@

it "must satisfy the heap property" do
expect(@minheap.heap?).must_equal true
expect(@minheap.array).must_equal @inserts
expect(@minheap.tree.array).must_equal @inserts
end

it "must recognize heap violations" do
@minheap.array.unshift 10
@minheap.tree.unshift 10
expect(@minheap.heap?).must_equal false
@minheap.array.shift
@minheap.tree.shift
expect(@minheap.heap?).must_equal true

@minheap.array.push 0
@minheap.tree.push 0
expect(@minheap.heap?).must_equal false
@minheap.sift_up
expect(@minheap.heap?).must_equal true
Expand All @@ -79,7 +79,7 @@
end

it "must heapish?" do
expect(@minheap.array[0]).must_be :<, @minheap.array[1]
expect(@minheap.tree[0]).must_be :<, @minheap.tree[1]
expect(@minheap.heapish?(0, 1)).must_equal true
end

Expand All @@ -99,17 +99,17 @@

it "must satisfy the heap property" do
expect(@heap3.heap?).must_equal true
expect(@heap3.array).wont_equal @inserts
expect(@heap3.array).wont_equal @inserts.reverse
expect(@heap3.tree.array).wont_equal @inserts
expect(@heap3.tree.array).wont_equal @inserts.reverse
end

it "must recognize heap violations" do
@heap3.array.unshift 0
@heap3.tree.unshift 0
expect(@heap3.heap?).must_equal false
@heap3.array.shift
@heap3.tree.shift
expect(@heap3.heap?).must_equal true

@heap3.array.push 10
@heap3.tree.push 10
expect(@heap3.heap?).must_equal false
@heap3.sift_up
expect(@heap3.heap?).must_equal true
Expand All @@ -122,7 +122,7 @@
end

it "must heapish?" do
expect(@heap3.array[0]).must_be :>, @heap3.array[1]
expect(@heap3.tree[0]).must_be :>, @heap3.tree[1]
expect(@heap3.heapish?(0, 1)).must_equal true
end

Expand Down

0 comments on commit df5544b

Please sign in to comment.