2021-12-29 21:54:04 -05:00
|
|
|
/* Day 18: Snailfish */
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"AoC2021/utils/args"
|
|
|
|
|
"AoC2021/utils/log"
|
|
|
|
|
"AoC2021/utils/timer"
|
|
|
|
|
"bufio"
|
|
|
|
|
"fmt"
|
2021-12-30 00:45:17 -05:00
|
|
|
"math"
|
2021-12-29 21:54:04 -05:00
|
|
|
"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()
|
2021-12-30 00:45:17 -05:00
|
|
|
log.Printf(log.DEBUG, "Line #%3d: ", lines+1)
|
2021-12-29 21:54:04 -05:00
|
|
|
if lines == 0 {
|
2021-12-30 00:45:17 -05:00
|
|
|
log.Printf(log.DEBUG, "%s\n", line)
|
2021-12-29 21:54:04 -05:00
|
|
|
s = ParseSnumStr(line)
|
|
|
|
|
} else {
|
2021-12-30 00:45:17 -05:00
|
|
|
log.Printf(log.DEBUG, "+ %s\n", line)
|
2021-12-29 21:54:04 -05:00
|
|
|
s = AddSnum(s, ParseSnumStr(line))
|
2021-12-30 00:45:17 -05:00
|
|
|
log.Printf(log.DEBUG, "%10s = %s\n", "", s.ToString())
|
2021-12-29 21:54:04 -05:00
|
|
|
}
|
|
|
|
|
lines++
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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: ")
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-30 00:45:17 -05:00
|
|
|
// Snailfish number
|
|
|
|
|
type Snum []int
|
2021-12-29 21:54:04 -05:00
|
|
|
|
|
|
|
|
const (
|
2021-12-30 00:45:17 -05:00
|
|
|
LBracket int = -1
|
|
|
|
|
RBracket int = -2
|
|
|
|
|
Comma int = -3
|
2021-12-29 21:54:04 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func ParseSnumStr(line string) Snum {
|
|
|
|
|
var s Snum
|
|
|
|
|
log.Printf(log.DIAGNOSTIC, "Starting to parse snum line \"%s\"\n", line)
|
|
|
|
|
|
2021-12-30 00:45:17 -05:00
|
|
|
for idx := 0; idx < len(line); idx++ {
|
2021-12-29 21:54:04 -05:00
|
|
|
switch line[idx] {
|
|
|
|
|
case '[':
|
2021-12-30 00:45:17 -05:00
|
|
|
s = append(s, LBracket)
|
2021-12-29 21:54:04 -05:00
|
|
|
case ']':
|
2021-12-30 00:45:17 -05:00
|
|
|
s = append(s, RBracket)
|
2021-12-29 21:54:04 -05:00
|
|
|
case ',':
|
2021-12-30 00:45:17 -05:00
|
|
|
s = append(s, Comma)
|
2021-12-29 21:54:04 -05:00
|
|
|
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)
|
2021-12-30 00:45:17 -05:00
|
|
|
s = append(s, iVal)
|
2021-12-29 21:54:04 -05:00
|
|
|
|
|
|
|
|
idx = endIdx - 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return s
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s Snum) ToString() string {
|
2021-12-30 00:45:17 -05:00
|
|
|
str := ""
|
|
|
|
|
|
|
|
|
|
for _, i := range s {
|
|
|
|
|
switch i {
|
|
|
|
|
case LBracket:
|
|
|
|
|
str += "["
|
|
|
|
|
case RBracket:
|
|
|
|
|
str += "]"
|
|
|
|
|
case Comma:
|
|
|
|
|
str += ","
|
|
|
|
|
default: // must be a number
|
|
|
|
|
str += fmt.Sprintf("%d", i)
|
|
|
|
|
}
|
2021-12-29 21:54:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return str
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func AddSnum(s1, s2 Snum) Snum {
|
2021-12-30 00:45:17 -05:00
|
|
|
log.Printf(log.DIAGNOSTIC, " %s\n+ %s\n", s1.ToString(), s2.ToString())
|
|
|
|
|
|
|
|
|
|
s := append(Snum{LBracket}, s1...)
|
|
|
|
|
s = append(s, Comma)
|
|
|
|
|
s = append(s, s2...)
|
|
|
|
|
s = append(s, RBracket)
|
|
|
|
|
|
2021-12-29 21:54:04 -05:00
|
|
|
s = s.Simplify()
|
2021-12-30 00:45:17 -05:00
|
|
|
log.Printf(log.DIAGNOSTIC, "= %s\n", s.ToString())
|
2021-12-29 21:54:04 -05:00
|
|
|
|
|
|
|
|
return s
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s1 Snum) Add(s2 Snum) Snum {
|
2021-12-30 00:45:17 -05:00
|
|
|
log.Printf(log.DIAGNOSTIC, " %s\n+ %s\n", s1.ToString(), s2.ToString())
|
|
|
|
|
|
|
|
|
|
s := append(Snum{LBracket}, s1...)
|
|
|
|
|
s = append(s, Comma)
|
|
|
|
|
s = append(s, s2...)
|
|
|
|
|
s = append(s, RBracket)
|
|
|
|
|
|
2021-12-29 21:54:04 -05:00
|
|
|
s = s.Simplify()
|
2021-12-30 00:45:17 -05:00
|
|
|
log.Printf(log.DIAGNOSTIC, "= %s\n", s.ToString())
|
2021-12-29 21:54:04 -05:00
|
|
|
|
|
|
|
|
return s
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s Snum) Simplify() Snum {
|
2021-12-30 00:45:17 -05:00
|
|
|
depth := 0
|
2021-12-29 21:54:04 -05:00
|
|
|
|
2021-12-30 00:45:17 -05:00
|
|
|
for idx := 0; idx < len(s); idx++ {
|
|
|
|
|
switch s[idx] {
|
|
|
|
|
case LBracket:
|
|
|
|
|
depth++
|
|
|
|
|
if s.ShouldExplodeAt(idx) {
|
|
|
|
|
s.ExplodeAt(idx)
|
|
|
|
|
idx = 0
|
|
|
|
|
}
|
|
|
|
|
case RBracket:
|
|
|
|
|
depth--
|
|
|
|
|
case Comma:
|
|
|
|
|
continue
|
|
|
|
|
default: // a number
|
|
|
|
|
if s.ShouldSplitAt(idx) {
|
|
|
|
|
s.SplitAt(idx)
|
|
|
|
|
idx = 0
|
2021-12-29 21:54:04 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return s
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-30 00:45:17 -05:00
|
|
|
func (s Snum) DepthAt(pos int) int {
|
|
|
|
|
depth := 0
|
|
|
|
|
for _, i := range s[:pos+1] {
|
|
|
|
|
if i == LBracket {
|
|
|
|
|
depth++
|
|
|
|
|
} else if i == RBracket {
|
|
|
|
|
depth--
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return depth
|
2021-12-29 21:54:04 -05:00
|
|
|
}
|
|
|
|
|
|
2021-12-30 00:45:17 -05:00
|
|
|
func (s Snum) IsNormalAt(pos int) bool {
|
|
|
|
|
// the next few symbols should be "[#,#]"
|
|
|
|
|
return pos+4 < len(s) && s[pos] == LBracket && s[pos+1] >= 0 && s[pos+3] >= 0
|
2021-12-29 21:54:04 -05:00
|
|
|
}
|
|
|
|
|
|
2021-12-30 00:45:17 -05:00
|
|
|
// the current read position and depth (for ease of calculation)
|
|
|
|
|
func (s Snum) ShouldExplodeAt(pos int) bool {
|
|
|
|
|
return s.IsNormalAt(pos) && s.DepthAt(pos) > 4
|
2021-12-29 21:54:04 -05:00
|
|
|
}
|
|
|
|
|
|
2021-12-30 00:45:17 -05:00
|
|
|
func (s *Snum) ExplodeAt(pos int) {
|
|
|
|
|
log.Printf(log.DIAGNOSTIC, "Exploding the snum at %d: %s\n", pos, s.ToString())
|
|
|
|
|
|
|
|
|
|
lVal, rVal := (*s)[pos+1], (*s)[pos+3]
|
|
|
|
|
// explode left!
|
|
|
|
|
for idx := pos; idx > 0; idx-- {
|
|
|
|
|
if (*s)[idx] >= 0 {
|
|
|
|
|
(*s)[idx] += lVal
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// explode right!
|
|
|
|
|
for idx := pos + 4; idx < len(*s); idx++ {
|
|
|
|
|
if (*s)[idx] >= 0 {
|
|
|
|
|
(*s)[idx] += rVal
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// replace the current snum with 0!
|
|
|
|
|
*s = append((*s)[0:pos], append(Snum{0}, (*s)[pos+5:]...)...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s Snum) ShouldSplitAt(pos int) bool {
|
|
|
|
|
return s[pos] > 9
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Snum) SplitAt(pos int) {
|
|
|
|
|
lVal, rVal := int(math.Floor(float64((*s)[pos])/2)), int(math.Ceil(float64((*s)[pos])/2))
|
|
|
|
|
log.Printf(log.DIAGNOSTIC, "Splitting the snum at %d: %d, adding values %d and %d\n", pos, (*s)[pos], lVal, rVal)
|
|
|
|
|
|
|
|
|
|
// Add a few extra values into the slice and then set the values
|
|
|
|
|
*s = append((*s)[:pos+4], (*s)[pos:]...)
|
|
|
|
|
(*s)[pos] = LBracket
|
|
|
|
|
(*s)[pos+1] = lVal
|
|
|
|
|
(*s)[pos+2] = Comma
|
|
|
|
|
(*s)[pos+3] = rVal
|
|
|
|
|
(*s)[pos+4] = RBracket
|
2021-12-29 21:54:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s Snum) CalcMagnitude() int {
|
2021-12-30 00:45:17 -05:00
|
|
|
for idx := 0; idx < len(s) && len(s) > 1; idx++ {
|
|
|
|
|
if s.IsNormalAt(idx) {
|
|
|
|
|
mVal := 3*s[idx+1] + 2*s[idx+3]
|
|
|
|
|
log.Printf(log.DIAGNOSTIC, "Replacing %s with %d: ", s[idx:idx+5].ToString(), mVal)
|
|
|
|
|
s = append(s[:idx+1], s[idx+5:]...)
|
|
|
|
|
s[idx] = mVal
|
|
|
|
|
idx = 0
|
|
|
|
|
log.Printf(log.DIAGNOSTIC, "%s\n", s.ToString())
|
|
|
|
|
}
|
2021-12-29 21:54:04 -05:00
|
|
|
}
|
2021-12-30 00:45:17 -05:00
|
|
|
// when there's only one value left, that's the answer
|
|
|
|
|
return 3*s[1] + 2*s[3]
|
2021-12-29 21:54:04 -05:00
|
|
|
}
|