diff --git a/src/advent/graph.cr b/src/advent/graph.cr index a064e00..575a01c 100644 --- a/src/advent/graph.cr +++ b/src/advent/graph.cr @@ -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 diff --git a/src/advent/heap.cr b/src/advent/heap.cr index 3c52e50..dd24572 100644 --- a/src/advent/heap.cr +++ b/src/advent/heap.cr @@ -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 diff --git a/src/advent/heaphash.cr b/src/advent/heaphash.cr new file mode 100644 index 0000000..78de6f9 --- /dev/null +++ b/src/advent/heaphash.cr @@ -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