mirror of
https://github.com/RussellChamp/advent-of-code-2021.git
synced 2026-10-27 21:52:32 +00:00
day 18 bad attempt
This commit is contained in:
1 parent
586e137f05
commit
0b5bf004f7
4 files changed
+386
-1
No files matched your search
@@ -2,4 +2,4 @@
|
|||||||
* Write more unit tests
|
* Write more unit tests
|
||||||
* Try out clipperhouse/gen library
|
* Try out clipperhouse/gen library
|
||||||
* Try out urfave/cli library
|
* Try out urfave/cli library
|
||||||
* Get the second stars from day 14 and beyond
|
* Day 14 pt2, day 15 pt2, day 16 pt2, day 17 pt1&2
|
||||||
+283
@@ -0,0 +1,283 @@
|
|||||||
|
/* Day 18: Snailfish */
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"AoC2021/utils/args"
|
||||||
|
"AoC2021/utils/log"
|
||||||
|
"AoC2021/utils/timer"
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func check(e error) {
|
||||||
|
if e != nil {
|
||||||
|
log.Println(log.DEBUG, "Oh snap!")
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
argMap := args.Parse(os.Args)
|
||||||
|
log.SetLogLevelFromArgs(argMap)
|
||||||
|
selectedPart, partSpecified := argMap["part"]
|
||||||
|
|
||||||
|
log.Println(log.NORMAL, "--- Day 18: Snailfish ---")
|
||||||
|
timer.Start()
|
||||||
|
|
||||||
|
if !partSpecified || selectedPart == "" || selectedPart == "1" {
|
||||||
|
part1()
|
||||||
|
timer.Tick()
|
||||||
|
log.Println(log.NORMAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !partSpecified || selectedPart == "" || selectedPart == "2" {
|
||||||
|
part2()
|
||||||
|
timer.Tick()
|
||||||
|
log.Println(log.NORMAL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func part1() {
|
||||||
|
log.Println(log.NORMAL, "* Part 1 *")
|
||||||
|
log.Println(log.NORMAL, " Goal: Add up all of the snailfish numbers from the homework assignment in the order they appear.")
|
||||||
|
log.Println(log.NORMAL, " Answer: What is the magnitude of the final sum?")
|
||||||
|
|
||||||
|
input, err := os.Open("./input.txt")
|
||||||
|
check(err)
|
||||||
|
defer input.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(input)
|
||||||
|
lines := 0
|
||||||
|
var s Snum
|
||||||
|
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if lines == 0 {
|
||||||
|
s = ParseSnumStr(line)
|
||||||
|
} else {
|
||||||
|
s = AddSnum(s, ParseSnumStr(line))
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt.Printf("SNum #%d: %s\n", lines+1, s.ToString())
|
||||||
|
|
||||||
|
lines++
|
||||||
|
if lines > 1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf(log.NORMAL, "Read %d lines from input\n", lines)
|
||||||
|
log.Printf(log.NORMAL, "The solution is: %s\n", s.ToString())
|
||||||
|
log.Printf(log.NORMAL, " with a magnitude of %d\n", s.CalcMagnitude())
|
||||||
|
}
|
||||||
|
|
||||||
|
func part2() {
|
||||||
|
log.Println(log.NORMAL, "* Part 2 *")
|
||||||
|
log.Println(log.NORMAL, " Goal: ")
|
||||||
|
log.Println(log.NORMAL, " Answer: ")
|
||||||
|
}
|
||||||
|
|
||||||
|
type Direction int
|
||||||
|
|
||||||
|
const (
|
||||||
|
LEFT Direction = 0
|
||||||
|
RIGHT Direction = 1
|
||||||
|
UP Direction = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// Snailfish number
|
||||||
|
// consists of a pair of numbers
|
||||||
|
// the "left" and "right" side can consist of EITHER a normal value or another pair
|
||||||
|
type Snum struct {
|
||||||
|
lVal int
|
||||||
|
rVal int
|
||||||
|
lPtr *Snum
|
||||||
|
rPtr *Snum
|
||||||
|
parentPtr *Snum
|
||||||
|
dir Direction
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseSnumStr(line string) Snum {
|
||||||
|
var s Snum
|
||||||
|
var sPtr = &s
|
||||||
|
log.Printf(log.DIAGNOSTIC, "Starting to parse snum line \"%s\"\n", line)
|
||||||
|
|
||||||
|
// start on idx 1 and skip the initial open bracket
|
||||||
|
for idx := 1; idx < len(line); idx++ {
|
||||||
|
switch line[idx] {
|
||||||
|
case '[':
|
||||||
|
if sPtr.dir == LEFT {
|
||||||
|
// create a new snum on the left side
|
||||||
|
sPtr.lPtr = &Snum{parentPtr: sPtr}
|
||||||
|
sPtr.dir = RIGHT
|
||||||
|
sPtr = sPtr.lPtr
|
||||||
|
} else {
|
||||||
|
sPtr.rPtr = &Snum{parentPtr: sPtr}
|
||||||
|
sPtr = sPtr.rPtr
|
||||||
|
}
|
||||||
|
case ']':
|
||||||
|
// go up a level
|
||||||
|
sPtr.dir = LEFT
|
||||||
|
sPtr = sPtr.parentPtr
|
||||||
|
case ',':
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
// if it's not a bracket or a comma then it must be a number
|
||||||
|
endIdx := idx + 1
|
||||||
|
for ; line[endIdx] >= '0' && line[endIdx] <= '9'; endIdx++ {
|
||||||
|
}
|
||||||
|
|
||||||
|
strVal := line[idx:endIdx]
|
||||||
|
iVal, err := strconv.Atoi(strVal)
|
||||||
|
check(err)
|
||||||
|
|
||||||
|
if sPtr.dir == LEFT {
|
||||||
|
sPtr.lVal = iVal
|
||||||
|
sPtr.dir = RIGHT
|
||||||
|
} else {
|
||||||
|
sPtr.rVal = iVal
|
||||||
|
}
|
||||||
|
idx = endIdx - 1
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Snum) ToString() string {
|
||||||
|
str := "["
|
||||||
|
if s.lPtr != nil {
|
||||||
|
str += s.lPtr.ToString()
|
||||||
|
} else {
|
||||||
|
str += fmt.Sprintf("%d", s.lVal)
|
||||||
|
}
|
||||||
|
str += ","
|
||||||
|
if s.rPtr != nil {
|
||||||
|
str += s.rPtr.ToString()
|
||||||
|
} else {
|
||||||
|
str += fmt.Sprintf("%d", s.rVal)
|
||||||
|
}
|
||||||
|
str += "]"
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddSnum(s1, s2 Snum) Snum {
|
||||||
|
log.Printf(log.DEBUG, " %s\n+ %s\n", s1.ToString(), s2.ToString())
|
||||||
|
s := Snum{lPtr: &s1, rPtr: &s2}
|
||||||
|
s = s.Simplify()
|
||||||
|
log.Printf(log.DEBUG, "= %s\n", s.ToString())
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s1 Snum) Add(s2 Snum) Snum {
|
||||||
|
log.Printf(log.DEBUG, " %s\n+ %s\n", s1.ToString(), s2.ToString())
|
||||||
|
s := Snum{lPtr: &s1, rPtr: &s2}
|
||||||
|
s = s.Simplify()
|
||||||
|
log.Printf(log.DEBUG, "= %s\n", s.ToString())
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Snum) Simplify() Snum {
|
||||||
|
madeAChange := true
|
||||||
|
for depth := 0; madeAChange; {
|
||||||
|
madeAChange = false
|
||||||
|
for sPtr := &s; sPtr != nil; {
|
||||||
|
switch {
|
||||||
|
case sPtr.ShouldExplode(depth):
|
||||||
|
ExplodeValue(sPtr)
|
||||||
|
madeAChange = true
|
||||||
|
sPtr = nil
|
||||||
|
|
||||||
|
// case sPtr.shouldSplit()
|
||||||
|
|
||||||
|
// travel the left side
|
||||||
|
case sPtr.dir == LEFT:
|
||||||
|
sPtr.dir = RIGHT
|
||||||
|
if sPtr.lPtr != nil {
|
||||||
|
sPtr = sPtr.lPtr
|
||||||
|
depth++
|
||||||
|
}
|
||||||
|
// travel the right side
|
||||||
|
case sPtr.dir == RIGHT:
|
||||||
|
sPtr.dir = UP
|
||||||
|
if sPtr.rPtr != nil {
|
||||||
|
sPtr = sPtr.rPtr
|
||||||
|
depth++
|
||||||
|
}
|
||||||
|
case sPtr.dir == UP:
|
||||||
|
sPtr = sPtr.parentPtr
|
||||||
|
depth--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check to explode
|
||||||
|
// check to split
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Snum) IsNormal() bool {
|
||||||
|
return s.lPtr == nil && s.rPtr == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Snum) ShouldExplode(depth int) bool {
|
||||||
|
return s.IsNormal() && depth > 3
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExplodeValue(at *Snum) {
|
||||||
|
log.Printf(log.DIAGNOSTIC, "Exploding the snum at %s\n", at.ToString())
|
||||||
|
if !at.IsNormal() {
|
||||||
|
panic("tried to explode a non-normal snum")
|
||||||
|
}
|
||||||
|
lVal, rVal := at.lVal, at.rVal
|
||||||
|
var sPtr *Snum
|
||||||
|
// explode left
|
||||||
|
log.Printf(log.NEVER, "Starting at %s\n", at.parentPtr.ToString())
|
||||||
|
for sPtr = at.parentPtr; sPtr.parentPtr != nil && sPtr.lPtr != nil; sPtr = sPtr.parentPtr {
|
||||||
|
log.Printf(log.NEVER, " traveled left to %s\n", sPtr.ToString())
|
||||||
|
}
|
||||||
|
if sPtr != nil {
|
||||||
|
log.Printf(log.NEVER, " added left %d to %d\n", lVal, sPtr.lVal)
|
||||||
|
sPtr.lVal += lVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// explode right
|
||||||
|
for sPtr = at.parentPtr; sPtr.parentPtr != nil && sPtr.rPtr != nil; {
|
||||||
|
log.Printf(log.NEVER, " traveled right to %s\n", sPtr.ToString())
|
||||||
|
sPtr = sPtr.parentPtr
|
||||||
|
}
|
||||||
|
if sPtr != nil {
|
||||||
|
log.Printf(log.NEVER, " added right %d to %d\n", rVal, sPtr.rVal)
|
||||||
|
sPtr.rVal += rVal
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete yourself
|
||||||
|
pPtr := at.parentPtr
|
||||||
|
if pPtr.lPtr == at {
|
||||||
|
pPtr.lPtr = nil
|
||||||
|
} else if pPtr.rPtr == at {
|
||||||
|
pPtr.rPtr = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Snum) ShouldSplit() bool {
|
||||||
|
return s.lVal > 9 || s.rVal > 9
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Snum) CalcMagnitude() int {
|
||||||
|
lVal, rVal := s.lVal, s.rVal
|
||||||
|
if s.lPtr != nil {
|
||||||
|
lVal = s.lPtr.CalcMagnitude()
|
||||||
|
}
|
||||||
|
if s.rPtr != nil {
|
||||||
|
rVal = s.rPtr.CalcMagnitude()
|
||||||
|
}
|
||||||
|
|
||||||
|
return 3*lVal + 2*rVal
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"AoC2021/utils/assert"
|
||||||
|
"AoC2021/utils/log"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDay18_ParseSnumStr(t *testing.T) {
|
||||||
|
s1 := ParseSnumStr("[1,2]")
|
||||||
|
s2 := ParseSnumStr("[11,22]")
|
||||||
|
|
||||||
|
assert.EqualsInt(t, s1.lVal, 1, "should parse left value")
|
||||||
|
assert.EqualsInt(t, s1.rVal, 2, "should parse right value")
|
||||||
|
|
||||||
|
assert.EqualsInt(t, s2.lVal, 11, "should parse larger left value")
|
||||||
|
assert.EqualsInt(t, s2.rVal, 22, "should parse larger right value")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDay18_ToString(t *testing.T) {
|
||||||
|
s := Snum{lVal: 1, rVal: 2}
|
||||||
|
assert.EqualsStr(t, s.ToString(), "[1,2]", "should convert to string")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDay18_AddSnum(t *testing.T) {
|
||||||
|
s1 := Snum{lVal: 1, rVal: 2}
|
||||||
|
s2 := Snum{lVal: 2, rVal: 3}
|
||||||
|
s3 := AddSnum(s1, s2)
|
||||||
|
|
||||||
|
assert.EqualsStr(t, s3.ToString(), "[[1,2],[2,3]]", "should add snums")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDay18_Add(t *testing.T) {
|
||||||
|
log.SetLogLevel(log.DIAGNOSTIC)
|
||||||
|
s := Snum{lVal: 1, rVal: 1}
|
||||||
|
s = s.Add(Snum{lVal: 2, rVal: 2})
|
||||||
|
s = s.Add(Snum{lVal: 3, rVal: 3})
|
||||||
|
s = s.Add(Snum{lVal: 4, rVal: 4})
|
||||||
|
|
||||||
|
assert.EqualsStr(t, s.ToString(), "[[[[1,1],[2,2]],[3,3]],[4,4]]", "should add snums")
|
||||||
|
|
||||||
|
s = s.Add(Snum{lVal: 5, rVal: 5})
|
||||||
|
assert.EqualsStr(t, s.ToString(), "[[[[3,0],[5,3]],[4,4]],[5,5]]", "should simplify after adding snums")
|
||||||
|
|
||||||
|
s = s.Add(Snum{lVal: 6, rVal: 6})
|
||||||
|
assert.EqualsStr(t, s.ToString(), "[[[[5,0],[7,4]],[5,5]],[6,6]]", "should simplify after adding snums")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDay18_Simplify(t *testing.T) {
|
||||||
|
s := ParseSnumStr("[0,[[[1,[2,3]],4],5]]")
|
||||||
|
|
||||||
|
s = s.Simplify()
|
||||||
|
|
||||||
|
assert.EqualsStr(t, s.ToString(), "[0,[[[3,0],7],5]]", "should convert to string")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDay18_IsNormal(t *testing.T) {
|
||||||
|
s1 := ParseSnumStr("[0,1]")
|
||||||
|
s2 := ParseSnumStr("[0,[[[1,[2,3]],4],5]]")
|
||||||
|
|
||||||
|
assert.IsTrue(t, s1.IsNormal(), "should be a normal snum")
|
||||||
|
assert.IsFalse(t, s2.IsNormal(), "should NOT be a normal snum")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDay18_ShouldExplode(t *testing.T) {
|
||||||
|
s1 := ParseSnumStr("[0,1]")
|
||||||
|
s2 := ParseSnumStr("[0,[[[1,[2,3]],4],5]]")
|
||||||
|
|
||||||
|
assert.IsFalse(t, s1.ShouldExplode(3), "should NOT explode a shallow normal snum")
|
||||||
|
assert.IsTrue(t, s1.ShouldExplode(6), "should explode a deep normal snum")
|
||||||
|
assert.IsFalse(t, s2.ShouldExplode(3), "should NOT explode a shallow nested snum")
|
||||||
|
assert.IsFalse(t, s2.ShouldExplode(9), "should NOT explode a deep nested snum")
|
||||||
|
}
|
||||||
|
|
||||||
|
// func TestDay18_ExplodeValue(t *testing.T) {
|
||||||
|
// // skip
|
||||||
|
// }
|
||||||
|
|
||||||
|
func TestDay18_ShouldSplit(t *testing.T) {
|
||||||
|
s1 := ParseSnumStr("[0,1]")
|
||||||
|
s2 := ParseSnumStr("[11,1]")
|
||||||
|
s3 := ParseSnumStr("[[1,2],13]")
|
||||||
|
|
||||||
|
assert.IsFalse(t, s1.ShouldSplit(), "should NOT split a small normal snum")
|
||||||
|
assert.IsTrue(t, s2.ShouldSplit(), "should split a large normal snum")
|
||||||
|
assert.IsTrue(t, s3.ShouldSplit(), "should split a large nested snum")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDay18_CalcMagnitude(t *testing.T) {
|
||||||
|
s1 := ParseSnumStr("[3,1]")
|
||||||
|
s2 := ParseSnumStr("[1,[2,3]]")
|
||||||
|
|
||||||
|
assert.EqualsInt(t, s1.CalcMagnitude(), 11, "should calc normal snum")
|
||||||
|
assert.EqualsInt(t, s2.CalcMagnitude(), 27, "should calc nested snum")
|
||||||
|
}
|
||||||
@@ -9,6 +9,13 @@ func IsTrue(t *testing.T, expr bool, message string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsFalse(t *testing.T, expr bool, message string) {
|
||||||
|
if expr == true {
|
||||||
|
t.Logf("Expected false but got true: %s", message)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func EqualsStr(t *testing.T, actual, expected, message string) {
|
func EqualsStr(t *testing.T, actual, expected, message string) {
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
t.Logf("Expected value '%s' but got '%s': %s", expected, actual, message)
|
t.Logf("Expected value '%s' but got '%s': %s", expected, actual, message)
|
||||||
|
|||||||
Reference in new issue
Block a user