commit a8363ae1689c43250d785ef97a492c0775a0b915 Author: Danila Fedorin Date: Sat Dec 5 15:32:50 2020 -0800 Write initial version of the library. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0bbd4a9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/docs/ +/lib/ +/bin/ +/.shards/ +*.dwarf + +# Libraries don't need dependency lock +# Dependencies will be locked in applications that use them +/shard.lock diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b771843 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2020 your-name-here + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..67b3f76 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# advent +Collection of common code that may help in solving advent of code +puzzles. + +## Installation + +1. Add the dependency to your `shard.yml`: + + ```yaml + dependencies: + advent: + git: https://dev.danilafe.com/Advent-of-Code/advent + ``` + +2. Run `shards install` + +## Usage + +```crystal +require "advent" +``` diff --git a/shard.yml b/shard.yml new file mode 100644 index 0000000..ab007b4 --- /dev/null +++ b/shard.yml @@ -0,0 +1,9 @@ +name: advent +version: 0.1.0 + +authors: + - Danila Fedorin + +crystal: 0.35.1 + +license: MIT diff --git a/spec/advent_spec.cr b/spec/advent_spec.cr new file mode 100644 index 0000000..0ccaf75 --- /dev/null +++ b/spec/advent_spec.cr @@ -0,0 +1 @@ +require "./spec_helper" diff --git a/spec/graph_spec.cr b/spec/graph_spec.cr new file mode 100644 index 0000000..e4d2b35 --- /dev/null +++ b/spec/graph_spec.cr @@ -0,0 +1,26 @@ +require "./spec_helper" + +describe Graph do + describe "#find_path" do + it "reports no paths in a graph with no edges" do + gr = Graph(String).new + elems = ["a", "b", "c", "d"] + elems.each &->gr.add_node(String) + elems.each do |elem| + elems.each do |other| + next if elem == other + gr.find_path(elem, other).should be_nil + end + end + end + + it "computes the shortest path" do + gr = Graph(String).new + [{"a", "b", 1}, {"a", "c", 1}, {"c", "b", 1}, {"b", "d", 1}].each do |t| + from, to, cost = t + gr.add_edge(from, to, cost) + end + gr.find_path("a", "d").should eq({["a", "b", "d"], 2}) + end + end +end diff --git a/spec/heap_spec.cr b/spec/heap_spec.cr new file mode 100644 index 0000000..4fd5442 --- /dev/null +++ b/spec/heap_spec.cr @@ -0,0 +1,44 @@ +require "./spec_helper" + +describe Array do + describe "#heapify" do + it "preserves the elements in the array" do + a = Array(Int32).new(20) { rand(10) } + a.sort.should eq(a.clone.heapify!.sort) + end + + it "creates an array with proper ordering" do + a = Array(Int32).new(20) { rand(10) } + a.heapify! + a.is_heap?.should be_true + end + + it "maintains heap property while popping" do + a = Array(Int32).new(20) { rand(10) } + a.heapify! + 20.times do |i| + a.heap_pop + a.is_heap?.should be_true + end + end + + it "maintains heap property while pushing" do + a = [] of Int32 + 20.times do + a.heap_push(rand 10) + a.is_heap?.should be_true + end + end + + it "pops numbers in descending order" do + a = Array(Int32).new(20) { rand(10) } + a.heapify! + last = Int32::MAX + 20.times do + popped = a.heap_pop + popped.should be <= last + last = popped + end + end + end +end diff --git a/spec/knapsack_spec.cr b/spec/knapsack_spec.cr new file mode 100644 index 0000000..31227ae --- /dev/null +++ b/spec/knapsack_spec.cr @@ -0,0 +1,18 @@ +describe Array do + describe "#knapsack" do + it "works with costs of one" do + ans = [1,2,3,4,5,6].shuffle.knapsack(3) do |i| + {1, i} + end + ans[1].sort! + ans.should eq({15, [4,5,6]}) + end + + it "works in the non-greedy case" do + ans = [{2, 2}, {2, 2}, {3, 3}].shuffle.knapsack(4) do |i| + i + end + ans.should eq({4, [{2,2},{2,2}]}) + end + end +end diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr new file mode 100644 index 0000000..4be159a --- /dev/null +++ b/spec/spec_helper.cr @@ -0,0 +1,2 @@ +require "spec" +require "../src/advent" diff --git a/src/advent.cr b/src/advent.cr new file mode 100644 index 0000000..b0291cf --- /dev/null +++ b/src/advent.cr @@ -0,0 +1 @@ +require "./advent/*" diff --git a/src/advent/graph.cr b/src/advent/graph.cr new file mode 100644 index 0000000..a064e00 --- /dev/null +++ b/src/advent/graph.cr @@ -0,0 +1,52 @@ +class Graph(A) + def initialize + @edges = {} of A => Set({A, Int32}) + end + + def add_node(n) + @edges[n] = Set({A, Int32}).new + end + + def add_edge(f, t, c = 1) + @edges[f] ||= Set({A, Int32}).new + @edges[f] << {t, c} + end + + def add_biedge(f, t, c = 1) + add_edge(f, t, c) + add_edge(t, f, c) + end + + def find_path(f, t) + visited = Set(A).new + candidates = Set { f } + distances = {f => 0} + prev = {} of A => A + + while !candidates.empty? + candidate = candidates.min_by { |c| distances[c] } + break if candidate == t + visited << candidate + candidates.delete candidate + dist = distances[candidate] + + @edges.fetch(candidate, Set({A, Int32}).new).each do |e| + node, cost = e + new_dist = dist + cost + candidates << node unless visited.includes? node + next if (old_dist = distances[node]?) && old_dist < new_dist + distances[node] = new_dist + prev[node] = candidate + end + end + + backtrack = t + path = [t] of A + while backtrack != f + return nil unless prev_bt = prev[backtrack]? + path << prev_bt + backtrack = prev_bt + end + {path.reverse!, distances[t]} + end +end diff --git a/src/advent/heap.cr b/src/advent/heap.cr new file mode 100644 index 0000000..3c52e50 --- /dev/null +++ b/src/advent/heap.cr @@ -0,0 +1,82 @@ +class Array(T) + def bubble_up(i, &cmp) + return if i >= size + while i != 0 + j = (i-1)//2 + break if yield self[i], self[j] + self[i], self[j] = self[j], self[i] + i = j + end + end + + def percalate_down(i, &cmp) + while i*2+1 < size + j1, j2 = i*2+1, i*2+2 + v1 = self[j1] + v2 = self[j2]? + if v2 && (yield v1, v2) && (yield self[i], v2) + self[j2], self[i] = self[i], v2 + i = j2 + elsif yield self[i], v1 + self[j1], self[i] = self[i], v1 + i = j1 + else + break + end + end + end + + def heapify!(&cmp : T,T -> Bool) + size.times do |i| + bubble_up(i, &cmp) + end + self + end + + def heapify! + heapify! do |i,j| + i < j + end + end + + def heap_push(v, &cmp : T,T -> Bool) + self << v + bubble_up(size - 1, &cmp) + end + + def heap_push(v) + heap_push(v) do |i,j| + i < j + end + end + + def heap_pop(&cmp : T,T -> Bool) + self[0], self[size-1] = self[size-1], self[0] + v = pop + percalate_down(0, &cmp) + v + end + + def heap_pop + heap_pop do |i, j| + i < j + end + end + + def is_heap?(&cmp : T,T -> Bool) + (size-1).times do |i| + i = size - i - 1 + vi = self[i] + vp = self[(i-1)//2] + return false unless (yield self[i], self[(i-1)//2]) || vi == vp + end + return true + end + + def is_heap? + is_heap? do |i,j| + i < j + end + end + +end diff --git a/src/advent/knapsack.cr b/src/advent/knapsack.cr new file mode 100644 index 0000000..8f2d5d1 --- /dev/null +++ b/src/advent/knapsack.cr @@ -0,0 +1,39 @@ +class Array(T) + def knapsack(budget, &cv : T -> {Int32,Int32}) + cost_values = map &cv + + memo = {} of {Int32, Int32} => Int32 + bt = {} of {Int32, Int32} => Bool + compute = uninitialized Int32, Int32 -> Int32 + compute = ->(size : Int32, budget : Int32) { + if m = memo[{size, budget}]? + return m + end + return memo[{size, budget}] = 0 if size == 0 + + cost, value = cost_values[size-1] + no_val = compute.call(size-1, budget) + yes_val = (budget < cost) ? 0 : compute.call(size-1, budget - cost) + value + + if yes_val > no_val + bt[{size, budget}] = true + return yes_val + else + bt[{size, budget}] = false + return no_val + end + } + + value = compute.call(size, budget) + i = size + items = [] of T + while i != 0 + if bt[{i, budget}] + items << self[i-1] + budget -= cost_values[i-1][0] + end + i -= 1 + end + {value, items} + end +end