Files
aoc2021/eighteen/snail.go
2021-12-19 00:27:21 +00:00

339 lines
5.2 KiB
Go

package eighteen
import (
"bufio"
"fmt"
"os"
"strconv"
)
type value struct {
p *pair
v int
}
type pair struct {
left value
right value
}
type snail struct {
input []value
}
func (s *snail) load(filename string) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
s.input = append(s.input, parse(scanner.Text()))
}
return nil
}
func (s *snail) largestMagnitude() int {
max := 0
for i := 0; i < len(s.input) - 1; i++ {
for j := i + 1; j < len(s.input); j++ {
// i + j
sum1 := s.input[i].add(s.input[j])
mag := sum1.magnitude()
if mag > max {
max = mag
}
// j + i
sum2 := s.input[j].add(s.input[i])
mag = sum2.magnitude()
if mag > max {
max = mag
}
}
}
return max
}
func (s *snail) sum() value {
v := value{}
for i, n := range s.input {
if i == 0 {
v = n
continue
}
v = v.add(n)
}
return v
}
func (v *value) add(vb value) value {
sum := value{
p: &pair{
left: *v,
right: vb,
},
}
reduced := sum.reduce()
for reduced != nil {
r := reduced.reduce()
if r == nil {
return *reduced
}
reduced = r
}
return sum
}
func (v *value) reduce() *value {
reduced := v.copy()
// Check for nests deeper than 4
if reduced.p != nil {
_, exploded, _, _ := reduced.p.explode(1)
if exploded {
return &reduced
}
}
// Check for values greater than 10
_, split := reduced.split()
if split {
return &reduced
}
return nil
}
func (v *value) magnitude() int {
if v.p == nil {
return v.v
}
return (3 * v.p.left.magnitude()) + (2 * v.p.right.magnitude())
}
func (p *pair) explode(depth int) (int, bool, bool, bool) {
if p == nil || (p.left.p == nil && p.right.p == nil) {
return 0, false, false, false
}
if depth == 4 {
if p.left.p != nil {
if p.right.p != nil {
p.right.p.increaseLeft(p.left.p.right.v, false)
} else {
p.right.v += p.left.p.right.v
}
rightVal := p.left.p.left.v
p.left.v = 0
p.left.p = nil
return rightVal, true, true, false
}
if p.right.p != nil {
if p.left.p != nil {
p.left.p.increaseRight(p.right.p.left.v, false)
} else {
p.left.v += p.right.p.left.v
}
leftVal := p.right.p.right.v
p.right.v = 0
p.right.p = nil
return leftVal, true, false, true
}
return 0, false, false, false
}
// Check if left has nested pair
if p.left.p != nil {
offender, exploded, left, right := p.left.p.explode(depth + 1)
if exploded {
if right {
p.increaseRight(offender, false)
return offender, exploded, left, false
}
return offender, exploded, left, right
}
}
// Check if right has nested pair
if p.right.p != nil {
offender, exploded, left, right := p.right.p.explode(depth + 1)
if exploded {
if left {
p.increaseLeft(offender, false)
return 0, true, false, right
}
return offender, true, left, right
}
}
return 0, false, false, false
}
func (p *pair) increaseLeft(amount int, swapped bool) {
if p.left.p != nil {
if !swapped {
p.left.p.increaseRight(amount, true)
} else {
p.left.p.increaseLeft(amount, true)
}
} else {
p.left.v += amount
}
}
func (p *pair) increaseRight(amount int, swapped bool) {
if p.right.p != nil {
if !swapped {
p.right.p.increaseLeft(amount, true)
} else {
p.right.p.increaseRight(amount, swapped)
}
} else {
p.right.v += amount
}
}
func (v *value) split() (*pair, bool) {
if v.p == nil {
if v.v < 10 {
return nil, false
}
left := v.v / 2
right := v.v / 2
if v.v%2 == 1 {
right++
}
return &pair{
left: value{
v: left,
},
right: value{
v: right,
},
}, true
}
// Check left
newPair, split := v.p.left.split()
if split {
if newPair != nil {
v.p.left.p = newPair
v.p.left.v = 0
}
return nil, split
}
// Check left
newPair, split = v.p.right.split()
if split {
if newPair != nil {
v.p.right.p = newPair
v.p.right.v = 0
}
return nil, split
}
return nil, false
}
func (p *pair) copy() *pair {
if p == nil {
return nil
}
return &pair{
left: p.left.copy(),
right: p.right.copy(),
}
}
func (v *value) copy() value {
return value{
p: v.p.copy(),
v: v.v,
}
}
func (v *value) print() string {
if v == nil {
return "No value to print"
}
if v.p == nil {
return fmt.Sprintf("%d", v.v)
}
s := "["
s += v.p.left.print()
s += ","
s += v.p.right.print()
s += "]"
return s
}
func parse(input string) value {
v := value{}
rawValue := ""
foundPair := false
bracketCount := 0
// Look for the comma in outermost pair
commaIndex := 0
for i, r := range input {
if bracketCount == 0 && r == '[' {
foundPair = true
rawValue = ""
}
switch r {
case '[':
bracketCount++
if bracketCount != 1 {
rawValue += string(r)
}
case ']':
bracketCount--
if bracketCount != 0 {
rawValue += string(r)
}
case ',':
if bracketCount == 1 {
commaIndex = i
}
rawValue += string(r)
default:
rawValue += string(r)
}
}
if foundPair {
v.p = &pair{
left: parse(rawValue[:commaIndex-1]),
right: parse(rawValue[commaIndex:]),
}
} else {
number, err := strconv.Atoi(rawValue)
if err != nil {
panic(err)
}
v.v = number
}
return v
}