Add and use a heaphash.

This commit is contained in:
Danila Fedorin 2020-12-07 01:20:32 -08:00
parent cfd4d1447a
commit d1fc0c3241
3 changed files with 108 additions and 14 deletions

View File

@ -1,39 +1,45 @@
require "./heaphash.cr"
class Graph(A)
def initialize
@edges = {} of A => Set({A, Int32})
@nodes = Set(A).new
end
def add_node(n)
@edges[n] = Set({A, Int32}).new
@nodes << n
end
def add_edge(f, t, c = 1)
@edges[f] ||= Set({A, Int32}).new
@edges[f] << {t, c}
@nodes << f << t
end
def add_biedge(f, t, c = 1)
add_edge(f, t, c)
add_edge(t, f, c)
end
def edges?(n)
@edges[n]? || Set({A, Int32}).new
end
def find_path(f, t)
visited = Set(A).new
candidates = Set { f }
distances = {f => 0}
distances = HeapHash { f => 0 }
prev = {} of A => A
dist = nil
while !candidates.empty?
candidate = candidates.min_by { |c| distances[c] }
while !distances.empty?
candidate, dist = distances.pop
break if candidate == t
visited << candidate
candidates.delete candidate
dist = distances[candidate]
@edges.fetch(candidate, Set({A, Int32}).new).each do |e|
edges?(candidate).each do |e|
node, cost = e
new_dist = dist + cost
candidates << node unless visited.includes? node
next if visited.includes? node
next if (old_dist = distances[node]?) && old_dist < new_dist
distances[node] = new_dist
prev[node] = candidate
@ -47,6 +53,29 @@ class Graph(A)
path << prev_bt
backtrack = prev_bt
end
{path.reverse!, distances[t]}
{path.reverse!, dist.not_nil!}
end
def topol
edge_counts = HeapHash(A, Int32).new
@nodes.each do |k|
edge_counts[k] = 0
end
@edges.each do |k, v|
v.each do |e|
edge_counts[e[0]] -= 1
end
end
order = [] of A
while !edge_counts.empty?
k, count = edge_counts.pop
raise "cycle in graph!" unless count == 0
order << k
edges?(k).each do |e|
edge_counts[e[0]] += 1
end
end
order
end
end

View File

@ -4,7 +4,7 @@ class Array(T)
while i != 0
j = (i-1)//2
break if yield self[i], self[j]
self[i], self[j] = self[j], self[i]
swap(i,j)
i = j
end
end
@ -15,10 +15,10 @@ class Array(T)
v1 = self[j1]
v2 = self[j2]?
if v2 && (yield v1, v2) && (yield self[i], v2)
self[j2], self[i] = self[i], v2
swap(i, j2)
i = j2
elsif yield self[i], v1
self[j1], self[i] = self[i], v1
swap(i, j1)
i = j1
else
break
@ -51,7 +51,7 @@ class Array(T)
end
def heap_pop(&cmp : T,T -> Bool)
self[0], self[size-1] = self[size-1], self[0]
swap(0, size-1)
v = pop
percalate_down(0, &cmp)
v

65
src/advent/heaphash.cr Normal file
View File

@ -0,0 +1,65 @@
require "./heap.cr"
# Inspired by Python's heapdict
class HeapHash(K,V)
private class Wrapper(K,V)
property key : K
property value : V
property index : Int32
def initialize(@key, @value, @index)
end
end
private class InternalHeap(K, V) < Array(Wrapper(K,V))
def swap(i,j)
super(i,j)
self[i].index = i
self[j].index = j
end
end
def initialize
@hash = {} of K => Wrapper(K,V)
@heap = InternalHeap(K,V).new
end
def delete(k)
wrapper = @hash[k]
@heap.bubble_up(wrapper.index) do |i, j|
false
end
pop
end
def []=(k : K, v : V)
if @hash.has_key? k
delete k
end
wrapper = Wrapper(K,V).new(k, v, @heap.size)
@hash[k] = wrapper
@heap.heap_push(wrapper) do |i, j|
i.value < j.value
end
end
def [](k)
wrapper = @hash[k]
wrapper.value
end
def []?(k)
return nil unless wrapper = @hash[k]?
wrapper.value
end
def pop
wrapper = @heap.heap_pop do |l, r|
l.value < r.value
end
@hash.delete wrapper.key
{wrapper.key, wrapper.value}
end
delegate size, empty?, to: @heap
end