2017-day7: unidirectional graph, weird weights

This commit is contained in:
alexchao26
2020-12-14 18:48:36 -05:00
parent eb4f03269d
commit 9eaf35506a
2 changed files with 200 additions and 0 deletions
+159
View File
@@ -0,0 +1,159 @@
package main
import (
"flag"
"fmt"
"strings"
"github.com/alexchao26/advent-of-code-go/mathutil"
"github.com/alexchao26/advent-of-code-go/util"
)
func main() {
var part int
flag.IntVar(&part, "part", 1, "part 1 or 2")
flag.Parse()
fmt.Println("Running part", part)
if part == 1 {
ans := part1(util.ReadFile("./input.txt"))
fmt.Println("Output:", ans)
} else {
ans := part2(util.ReadFile("./input.txt"))
fmt.Println("Output:", ans)
}
}
func part1(input string) string {
graph := parseInput(input)
allNames := map[string]bool{}
for k := range graph {
allNames[k] = true
}
// iterate through all graph edges and remove any dependants from allNames
// map b/c that cannot be at the bottom of the stack
for _, node := range graph {
for _, name := range node.edges {
delete(allNames, name)
}
}
if len(allNames) != 1 {
panic("Expected one name left, got" + mathutil.IntToStr(len(allNames)))
}
// have to iterate over graph to get remaining name
var nameAtBottom string
for name := range allNames {
nameAtBottom = name
}
return nameAtBottom
}
// NOTE this is not a fully generalized solution, there are graphs where this
// could fail. Although I suspect AoC inputs are tailored to make this solution
// sufficient
func part2(input string) int {
graph := parseInput(input)
currentNode := part1(input)
weightCalculator := memoCalcWeight(graph)
var siblings []string
// should not run for more than the number of nodes in the graph
for indexToExit := 0; indexToExit < len(graph); indexToExit++ {
// store dependents which have a particular weight
weightToDependents := map[int][]string{}
for _, dependentName := range graph[currentNode].edges {
weight := weightCalculator(dependentName)
weightToDependents[weight] = append(weightToDependents[weight], dependentName)
}
// one of the dependents has a different weight than the others, dive into it
if len(weightToDependents) > 1 {
// store its siblings b/c IF the next loop finds that all dependents
// are an equal weight, we need to compare the current set of siblings
siblings = graph[currentNode].edges
// find the node to dive into
for _, names := range weightToDependents {
if len(names) == 1 {
currentNode = names[0]
}
}
} else if len(weightToDependents) == 1 {
// if dependents all have the same weight, this node is the problem.
// compare to its siblings to determine what its weight should be
currentWeight := weightCalculator(currentNode)
for _, sib := range siblings {
if sib != currentNode {
desiredWeight := weightCalculator(sib)
// apply diff to current node's weight and return it
return graph[currentNode].weight - (currentWeight - desiredWeight)
}
}
} else {
panic("unhandled case, weightToDependents == 0")
}
}
panic("something's wrong in the loop...")
}
// memoized: calculate the weight, including children, of a given node name
func memoCalcWeight(graph map[string]graphNode) func(string) int {
memo := make(map[string]int, len(graph))
var closureFunc func(string) int
closureFunc = func(rootName string) int {
// return from memo if possible
if wt, ok := memo[rootName]; ok {
return wt
}
// otherwise calculate & set in memo
sum := graph[rootName].weight
for _, dependent := range graph[rootName].edges {
sum += closureFunc(dependent)
}
memo[rootName] = sum
return sum
}
return closureFunc
}
type graphNode struct {
name string // unused but useful for debugging
weight int
edges []string
}
func parseInput(input string) map[string]graphNode {
lines := strings.Split(input, "\n")
graph := make(map[string]graphNode, len(lines))
for _, l := range lines {
parts := strings.Split(l, " -> ")
leftParts := strings.Split(parts[0], " ")
name := leftParts[0]
weight := mathutil.StrToInt(leftParts[1][1 : len(leftParts[1])-1])
var edges []string
if len(parts) == 2 {
edges = strings.Split(parts[1], ", ")
// assumption confirmed that all nodes have zero or >= 2 edges
if len(edges) == 1 {
panic("Assumed no nodes have exactly one dependant, but got 1 for node: " + name)
}
}
graph[name] = graphNode{
name: name,
weight: weight,
edges: edges,
}
}
return graph
}
+41
View File
@@ -0,0 +1,41 @@
package main
import (
"testing"
"github.com/alexchao26/advent-of-code-go/util"
)
func Test_part1(t *testing.T) {
tests := []struct {
name string
input string
want string
}{
{"actual", util.ReadFile("input.txt"), "xegshds"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := part1(tt.input); got != tt.want {
t.Errorf("part1() = %v, want %v", got, tt.want)
}
})
}
}
func Test_part2(t *testing.T) {
tests := []struct {
name string
input string
want int
}{
{"actual", util.ReadFile("input.txt"), 299},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := part2(tt.input); got != tt.want {
t.Errorf("part2() = %v, want %v", got, tt.want)
}
})
}
}