diff --git a/2018/day07-1.go b/2018/day07-1.go new file mode 100644 index 0000000..5e8cbdb --- /dev/null +++ b/2018/day07-1.go @@ -0,0 +1,29 @@ +package main + +import ( + "fmt" + "strings" + + "internal/tree" + "internal/util" +) + +func main() { + data := util.ReadInput() + + // Build a tree of dependencies. + root := tree.BuildDependencyTree(data) + + // Walk the tree and determine the correct ordering. + order := tree.FindOrder(root) + + output := strings.Builder{} + for _, node := range order { + if node.Name == 0 { + continue + } + + output.WriteRune(node.Name) + } + fmt.Println(output.String()) +} diff --git a/2018/internal/tree/breadthfirst.go b/2018/internal/tree/breadthfirst.go new file mode 100644 index 0000000..85da356 --- /dev/null +++ b/2018/internal/tree/breadthfirst.go @@ -0,0 +1,44 @@ +// BreadthFirstOrder and its companion functions ended up not giving the +// right solution, but I'm leaving them here as a useful generic tree +// algorithm. +package tree + +func BreadthFirstOrder(root *Node) []*Node { + seen := make(map[rune]bool) + return breadthFirstOrderR(root, seen) +} + +func findTreeRoot(depMap map[rune]*Node) *Node { + for _, node := range depMap { + if len(node.Parents) == 0 { + return node + } + } + + return nil +} + +func breadthFirstOrderR(node *Node, seen map[rune]bool) []*Node { + order := []*Node{} + + // handle root + if len(node.Parents) == 0 { + order = append(order, node) + seen[node.Name] = true + } + + // add children we haven't seen before + for _, child := range node.Children { + if _, ok := seen[child.Name]; !ok { + order = append(order, child) + seen[child.Name] = true + } + } + + // recurse + for _, child := range node.Children { + order = append(order, breadthFirstOrderR(child, seen)...) + } + + return order +} diff --git a/2018/internal/tree/debug.go b/2018/internal/tree/debug.go new file mode 100644 index 0000000..fea494e --- /dev/null +++ b/2018/internal/tree/debug.go @@ -0,0 +1,33 @@ +package tree + +import ( + "fmt" +) + +func DebugPrintTree(root *Node) { + debugPrintTreeR(root, 0) +} + +func debugPrintTreeR(node *Node, level int) { + for i := 0; i < level; i++ { + fmt.Printf(" ") + } + + DebugPrintNode(node) + + for _, child := range node.Children { + debugPrintTreeR(child, level+1) + } +} + +func DebugPrintNode(node *Node) { + fmt.Printf("%c @ %p. Children: ", node.Name, node) + for _, child := range node.Children { + fmt.Printf("%c ", child.Name) + } + fmt.Printf("Parents: ") + for _, parent := range node.Parents { + fmt.Printf("%c ", parent.Name) + } + fmt.Printf("\n") +} diff --git a/2018/internal/tree/findorder.go b/2018/internal/tree/findorder.go new file mode 100644 index 0000000..03f027e --- /dev/null +++ b/2018/internal/tree/findorder.go @@ -0,0 +1,51 @@ +package tree + +import "fmt" // debug + +// FindOrder determines the correct order according to these rules: +// 1. A node cannot come before all of its parents have been included. +// 2. At any given time, the lowest-lettered step must be executed. +func FindOrder(root *Node) []*Node { + seen := make(map[rune]bool) + return findOrderR(root, seen) +} + +func findOrderR(node *Node, seen map[rune]bool) []*Node { + order := []*Node{} + + // debug + fmt.Printf("In node: %c | Children: ", node.Name) + for _, child := range node.Children { + fmt.Printf("%c ", child.Name) + } + fmt.Printf("| Parents: ") + for _, parent := range node.Parents { + fmt.Printf("%c ", parent.Name) + } + fmt.Println() + // end debug + + // add the node, but only if all its parents have been seen + if len(node.Parents) == 0 { + seen[node.Name] = true + } else { + for _, parent := range node.Parents { + if _, ok := seen[parent.Name]; !ok { + return order + } + } + + if _, ok := seen[node.Name]; !ok { + order = append(order, node) + fmt.Printf("Added node: %c\n", node.Name) + seen[node.Name] = true + } + } + + // recurse + for _, child := range node.Children { + order = append(order, findOrderR(child, seen)...) + } + + return order +} diff --git a/2018/internal/tree/tree.go b/2018/internal/tree/tree.go new file mode 100644 index 0000000..9c97033 --- /dev/null +++ b/2018/internal/tree/tree.go @@ -0,0 +1,67 @@ +package tree + +import ( + "regexp" + "sort" +) + +type Node struct { + Name rune + Parents []*Node + Children []*Node +} + +// BuildDependencyTree parses the input and returns the root node. +func BuildDependencyTree(data []string) *Node { + depMap := make(map[rune]*Node) + parentRegex := regexp.MustCompile("Step ([A-Z]) must") + childRegex := regexp.MustCompile("step ([A-Z]) can begin") + + for _, line := range data { + parent := rune(parentRegex.FindSubmatch([]byte(line))[1][0]) + child := rune(childRegex.FindSubmatch([]byte(line))[1][0]) + + // Create the nodes if they don't exist + if depMap[parent] == nil { + depMap[parent] = &Node{Name: parent} + } + if depMap[child] == nil { + depMap[child] = &Node{Name: child} + } + + // Get each node and add the dependency info. + pNode := depMap[parent] + cNode := depMap[child] + + pNode.Children = append(pNode.Children, cNode) + cNode.Parents = append(cNode.Parents, pNode) + } + + // sort the children in each node + for _, node := range depMap { + sortChildren(node) + } + + return BuildTreeRoot(depMap) +} + +func sortChildren(node *Node) { + children := node.Children + sort.Slice(children[:], func(i, j int) bool { + return children[i].Name < children[j].Name + }) +} + +func BuildTreeRoot(depMap map[rune]*Node) *Node { + root := &Node{Name: 0} + + for _, node := range depMap { + if len(node.Parents) == 0 { + root.Children = append(root.Children, node) + node.Parents = append(node.Parents, root) + } + } + + sortChildren(root) + return root +} diff --git a/2018/internal/util/strings.go b/2018/internal/util/strings.go new file mode 100644 index 0000000..0c50832 --- /dev/null +++ b/2018/internal/util/strings.go @@ -0,0 +1,14 @@ +package util + +func ReverseString(s string) string { + runes := []rune(s) + + i := 0 + j := len(runes) - 1 + for i < j { + runes[i], runes[j] = runes[j], runes[i] + i++ + j-- + } + return string(runes) +}