Day 18 solution

This commit is contained in:
2021-12-19 00:27:21 +00:00
parent 56b9b82e60
commit 34c3b2da7e
11 changed files with 850 additions and 0 deletions

View File

@@ -182,3 +182,12 @@ The solution for "seventeen" is:
The max height is 35511
There are 3282 initial velocities that will hit the target
```
### Day eighteen
```sh
$ ./aoc2021 --eighteen
The solution for "eighteen" is:
The magnitude of the final sum is 4202
The largest magnitude of any two snail numbers is 4779
```

100
eighteen/input.txt Normal file
View File

@@ -0,0 +1,100 @@
[[[0,[4,4]],6],[[[7,6],6],[[5,3],[3,2]]]]
[[[[4,6],[1,7]],[5,8]],[[9,7],[9,6]]]
[[[2,[7,1]],[[8,2],[9,3]]],3]
[[[[2,1],6],2],4]
[[[[0,3],0],6],[[9,[0,8]],[[2,1],[0,2]]]]
[[[5,1],[[0,5],1]],[[[9,9],[8,7]],7]]
[[[[0,2],8],8],[0,[7,[2,7]]]]
[[[[3,8],[6,4]],[[2,0],2]],3]
[[[[1,5],3],[[5,3],[5,4]]],[[0,1],[[1,2],8]]]
[[[1,1],[[9,3],9]],[[9,[6,5]],[2,6]]]
[[[9,3],[6,[1,5]]],[[3,8],[[4,6],[8,0]]]]
[[3,[6,7]],[[3,0],[5,[3,4]]]]
[1,[2,[[4,1],[2,3]]]]
[[6,[7,8]],[[0,[0,3]],[6,7]]]
[[8,[[0,0],[9,3]]],[[2,6],[[9,1],[4,9]]]]
[[3,0],[[8,[7,1]],4]]
[[[1,0],[[9,7],[7,8]]],[[[0,0],5],[[4,9],4]]]
[[[[4,2],7],[[4,0],0]],[[[5,4],[6,7]],[0,[1,2]]]]
[[[4,[4,3]],[[1,4],[1,1]]],6]
[[[0,[5,9]],[[7,4],2]],[[9,1],[4,7]]]
[[[[5,5],[7,0]],[8,[5,3]]],[[0,[0,2]],[[1,3],[5,8]]]]
[[9,[[9,9],2]],[[9,6],[[4,7],5]]]
[[[[8,7],[5,3]],9],[3,[6,9]]]
[[3,[0,3]],[2,6]]
[[[2,[7,0]],[6,6]],[[7,0],[[3,8],[8,5]]]]
[[[2,6],[2,7]],[[3,6],[0,[9,5]]]]
[[[5,4],1],[5,[[4,9],5]]]
[[[6,3],6],[[[6,0],0],[[4,0],7]]]
[[[[4,1],2],[3,[9,0]]],[0,8]]
[[[2,[3,9]],[[8,3],8]],[[1,[2,2]],[8,[6,4]]]]
[[[[4,3],[5,2]],0],[9,[5,[7,5]]]]
[[[3,2],5],[[[6,3],9],[[2,0],[6,7]]]]
[[[3,9],[[0,6],[0,7]]],[6,[3,2]]]
[0,0]
[[[[0,3],9],[8,[3,9]]],[[0,2],[[0,1],[3,7]]]]
[[0,[4,[3,0]]],[[7,9],[5,[8,7]]]]
[[2,9],[[0,[2,2]],1]]
[[[[5,4],[1,7]],6],[2,[[5,3],[7,7]]]]
[[[[0,4],4],[[6,6],[1,4]]],4]
[[[[4,8],5],[[6,4],[2,3]]],[9,[[8,6],[4,0]]]]
[[1,[6,[1,9]]],[3,[[4,2],[1,8]]]]
[[[[3,7],[5,9]],[[3,8],[3,3]]],[[[7,8],3],[7,3]]]
[[[[0,4],5],[4,[9,0]]],[3,[[4,1],6]]]
[[[7,[2,1]],[[1,9],1]],[[[3,4],[8,6]],6]]
[[[4,1],[5,[8,2]]],[[[1,6],9],[[4,4],2]]]
[[[7,[6,4]],[[0,1],4]],[[5,2],[[9,5],[9,3]]]]
[[[4,2],[1,8]],2]
[[[1,6],5],[8,[2,[2,3]]]]
[[[[0,2],[5,0]],[7,[0,0]]],[[6,[5,9]],5]]
[[[7,6],[9,[2,4]]],[[5,[2,6]],2]]
[[[6,2],4],[[2,9],[[3,0],[4,3]]]]
[[8,[[6,4],[0,2]]],1]
[[[4,1],[7,5]],[9,[[2,4],4]]]
[[[[4,8],[7,5]],[1,[8,5]]],[[3,5],[[9,9],[4,2]]]]
[[[7,[8,4]],[4,[5,8]]],5]
[[7,9],[2,[[9,1],[7,1]]]]
[3,[[[5,8],[4,8]],[5,4]]]
[[[0,[5,5]],[[5,4],[5,4]]],[[9,6],[[9,4],[6,5]]]]
[[7,2],[1,[8,[1,7]]]]
[2,[9,[2,[2,3]]]]
[[[3,[5,1]],[8,[6,4]]],[[2,8],[[2,2],8]]]
[[[7,3],[0,4]],[[4,0],[6,[3,4]]]]
[[4,[2,[2,8]]],[4,[[7,1],9]]]
[[8,[[6,1],2]],[1,[[1,5],9]]]
[[0,[2,[9,4]]],[[[7,4],7],8]]
[[[2,[7,0]],5],[3,[4,4]]]
[[7,[4,[6,0]]],[[4,7],[[3,7],5]]]
[[2,[[8,0],[6,1]]],[[6,[6,1]],[3,9]]]
[[9,0],[[[3,7],0],[[5,8],4]]]
[6,[[[5,8],8],[3,[4,1]]]]
[[8,[[9,3],[8,4]]],[4,[8,2]]]
[[[[8,0],8],[3,7]],[[7,[4,3]],0]]
[[[7,[2,6]],[8,0]],[4,[[1,3],[4,1]]]]
[1,[[[4,9],[4,9]],[[7,0],[6,6]]]]
[9,4]
[[6,7],4]
[[2,[5,2]],[[2,4],[[4,6],[5,5]]]]
[[[5,2],[[5,5],[8,1]]],[[9,[1,6]],3]]
[[[[4,3],1],[8,9]],6]
[[[[3,2],[4,5]],4],[[[4,3],[0,0]],[[3,0],1]]]
[[6,7],[[8,5],[[7,2],4]]]
[[[[8,1],[5,8]],7],[[[5,2],[4,3]],1]]
[[2,[[4,9],5]],[1,1]]
[[9,1],[[[0,8],[1,8]],7]]
[[9,3],[6,4]]
[[8,[4,2]],[[7,[7,4]],[[0,9],[6,1]]]]
[[[[0,5],7],[[7,7],2]],[[2,[5,8]],[9,6]]]
[[[2,1],[7,[1,3]]],[[2,[7,1]],0]]
[[[8,[8,4]],[2,[4,3]]],[[2,[5,6]],[[2,0],[7,3]]]]
[[4,[[4,3],[5,2]]],[1,3]]
[[5,[5,0]],9]
[[[2,[7,6]],[1,8]],[[[5,2],2],0]]
[[2,[2,3]],[[9,8],[[0,1],[3,5]]]]
[[7,[[3,7],3]],[[[7,6],[4,8]],[[1,7],[8,6]]]]
[[[0,0],[[6,1],5]],[5,[5,4]]]
[[2,3],[4,[3,5]]]
[[8,[7,7]],[8,[4,[8,1]]]]
[[[[4,0],3],[[0,0],[0,0]]],[[[6,0],4],[[1,7],0]]]
[[[[6,4],[3,1]],[[2,8],[1,2]]],[4,[[6,5],4]]]
[[[[5,3],7],[4,[2,6]]],[[6,[4,5]],[1,[9,0]]]]

26
eighteen/main.go Normal file
View File

@@ -0,0 +1,26 @@
package eighteen
import "fmt"
type Eighteen struct {
snail snail
}
func Init(filepath string) *Eighteen {
eighteen := &Eighteen{
snail: snail{},
}
eighteen.snail.load(filepath)
return eighteen
}
func (d *Eighteen) Answer() string {
sum := d.snail.sum()
return fmt.Sprintf("The magnitude of the final sum is %d", sum.magnitude())
}
func (d *Eighteen) FollowUp() string {
return fmt.Sprintf("The largest magnitude of any two snail numbers is %d", d.snail.largestMagnitude())
}

338
eighteen/snail.go Normal file
View File

@@ -0,0 +1,338 @@
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
}

339
eighteen/snail_test.go Normal file
View File

@@ -0,0 +1,339 @@
package eighteen
import (
"fmt"
"testing"
)
func Test_read(t *testing.T) {
s := snail{}
if err := s.load("test_input.txt"); err != nil {
t.Log(err)
t.FailNow()
}
if len(s.input) != 10 {
t.Logf("Expected 10 inputs, found %d", len(s.input))
t.Fail()
}
}
func Test_magnitude(t *testing.T) {
s := snail{}
s.load("test_input.txt")
sum := s.sum()
fmt.Println(sum.print())
mag := sum.magnitude()
if mag != 4140 {
t.Logf("Expected a magnitude of 4140, found %d", mag)
t.Fail()
}
}
func Test_magnitude2(t *testing.T) {
v := parse("[[[[0,7],4],[[7,8],[6,0]]],[8,1]]")
expected := 1384
mag := v.magnitude()
if mag != expected {
t.Logf("Expected magnitude %d, found %d", expected, mag)
t.Fail()
}
}
func Test_magnitude3(t *testing.T) {
v := parse("[[[[6,6],[7,6]],[[7,7],[7,0]]],[[[7,7],[7,7]],[[7,8],[9,9]]]]")
expected := 4140
mag := v.magnitude()
if mag != expected {
t.Logf("Expected magnitude %d, found %d", expected, mag)
t.Fail()
}
}
func Test_largestMagnitude(t *testing.T) {
s := snail{}
s.load("test_input.txt")
mag := s.largestMagnitude()
if mag != 3993 {
t.Logf("Expected largest magnitude of 3993, found %d", mag)
t.Fail()
}
}
func Test_sum(t *testing.T) {
s := snail{}
s.load("test_input2.txt")
sum := s.sum()
output := sum.print()
expected := "[[[[1,1],[2,2]],[3,3]],[4,4]]"
if expected != output {
t.Logf("Expected %q, found %q", expected, output)
t.Fail()
}
}
func Test_sum2(t *testing.T) {
s := snail{}
s.load("test_input3.txt")
sum := s.sum()
output := sum.print()
expected := "[[[[3,0],[5,3]],[4,4]],[5,5]]"
if expected != output {
t.Logf("Expected %q, found %q", expected, output)
t.Fail()
}
}
func Test_sum3(t *testing.T) {
s := snail{}
s.load("test_input4.txt")
sum := s.sum()
output := sum.print()
expected := "[[[[5,0],[7,4]],[5,5]],[6,6]]"
if expected != output {
t.Logf("Expected %q, found %q", expected, output)
t.Fail()
}
}
func Test_sum4(t *testing.T) {
s := snail{}
s.load("test_input5.txt")
sum := s.sum()
output := sum.print()
expected := "[[[[8,7],[7,7]],[[8,6],[7,7]]],[[[0,7],[6,6]],[8,7]]]"
if expected != output {
t.Logf("Expected %q, found %q", expected, output)
t.Fail()
}
}
func Test_sum5(t *testing.T) {
v1 := parse("[[[0,[4,5]],[0,0]],[[[4,5],[2,6]],[9,5]]]")
v2 := parse("[7,[[[3,7],[4,3]],[[6,3],[8,8]]]]")
r := v1.add(v2)
expected := "[[[[4,0],[5,4]],[[7,7],[6,0]]],[[8,[7,7]],[[7,9],[5,0]]]]"
output := r.print()
if expected != output {
t.Logf("Expected %q, found %q", expected, output)
t.Fail()
}
}
func Test_reduce(t *testing.T) {
v := parse("[[[[[9,8],1],2],3],4]")
r := v.reduce()
if r == nil {
t.Logf("Expected a reduction, didn't get one")
t.FailNow()
}
r = r.reduce()
if r != nil {
t.Logf("Only expected a single reduction, found %s", r.print())
t.Fail()
}
}
func Test_reduce2(t *testing.T) {
v := parse("[7,[6,[5,[4,[3,2]]]]]")
r := v.reduce()
if r == nil {
t.Logf("Expected a reduction, didn't get one")
t.FailNow()
}
r = r.reduce()
if r != nil {
t.Logf("Only expected a single reduction, found %s", r.print())
t.Fail()
}
}
func Test_reduce3(t *testing.T) {
v := parse("[[6,[5,[4,[3,2]]]],1]")
r := v.reduce()
if r == nil {
t.Logf("Expected a reduction, didn't get one")
t.FailNow()
}
r = r.reduce()
if r != nil {
t.Logf("Only expected a single reduction, found %s", r.print())
t.Fail()
}
}
func Test_reduce4(t *testing.T) {
v := parse("[[3,[2,[1,[7,3]]]],[6,[5,[4,[3,2]]]]]")
r := v.reduce()
if r == nil {
t.Logf("Expected a reduction, didn't get one")
t.FailNow()
}
r = r.reduce()
if r == nil {
t.Logf("Expected a reduction, didn't get one")
t.Fail()
}
r = r.reduce()
if r != nil {
t.Logf("Only expected two reductions, found %s", r.print())
t.Fail()
}
}
func Test_reduce5(t *testing.T) {
v := parse("[[[[[4,3],4],4],[7,[[8,4],9]]],[1,1]]")
r := v.reduce()
if r == nil {
t.Logf("Expected a reduction, didn't get one")
t.FailNow()
}
r = r.reduce()
if r == nil {
t.Logf("Expected a reduction, didn't get one")
t.Fail()
}
r = r.reduce()
if r == nil {
t.Logf("Expected a reduction, didn't get one")
t.Fail()
}
r = r.reduce()
if r == nil {
t.Logf("Expected a reduction, didn't get one")
t.Fail()
}
r = r.reduce()
if r == nil {
t.Logf("Expected a reduction, didn't get one")
t.Fail()
}
r = r.reduce()
if r != nil {
t.Logf("Only expected five reductions, found %s", r.print())
t.Fail()
}
}
func Test_add(t *testing.T) {
first := parse("[1,2]")
second := parse("[[3,4],5]")
sum := first.add(second)
// Check that it equals [[1,2],[[3,4],5]]
if sum.p == nil || sum.p.left.p.left.v != 1 || sum.p.left.p.right.v != 2 {
t.Logf("Expected [1,2], found %s", sum.p.left.print())
t.Fail()
}
// Check that it equals [[1,2],[[3,4],5]]
if sum.p == nil || sum.p.right.p.right.v != 5 {
t.Logf("Expected [[3,4],5], found %s", sum.p.left.print())
t.Fail()
}
}
func Test_parse(t *testing.T) {
s := "[1,2]"
v := parse(s)
if v.p == nil {
t.Logf("Expected a pair, found nothing")
t.FailNow()
}
if v.p.left.v != 1 {
t.Logf("Expected 1, found %d", v.p.left.v)
t.Fail()
}
if v.p.right.v != 2 {
t.Logf("Expected 2, found %d", v.p.right.v)
t.FailNow()
}
}
func Test_parse3(t *testing.T) {
s := "[9,[8,7]]"
v := parse(s)
if v.p == nil {
t.Logf("Expected a pair, found nothing")
t.FailNow()
}
if v.p.right.p.left.v != 8 {
t.Logf("Expected 8, found %d", v.p.right.p.left.v)
t.Fail()
}
if v.p.right.p.right.v != 7 {
t.Logf("Expected 7, found %d", v.p.right.p.right.v)
t.Fail()
}
if v.p.left.v != 9 {
t.Logf("Expected 9, found %d", v.p.left.v)
t.FailNow()
}
}
func Test_parse2(t *testing.T) {
s := "[[1,2],3]"
v := parse(s)
if v.p == nil {
t.Logf("Expected a pair, found nothing")
t.FailNow()
}
if v.p.left.p.left.v != 1 {
t.Logf("Expected 1, found %d", v.p.left.p.left.v)
t.Fail()
}
if v.p.left.p.right.v != 2 {
t.Logf("Expected 2, found %d", v.p.left.p.right.v)
t.Fail()
}
if v.p.right.v != 3 {
t.Logf("Expected 3, found %d", v.p.right.v)
t.FailNow()
}
}

10
eighteen/test_input.txt Normal file
View File

@@ -0,0 +1,10 @@
[[[0,[5,8]],[[1,7],[9,6]]],[[4,[1,2]],[[1,4],2]]]
[[[5,[2,8]],4],[5,[[9,9],0]]]
[6,[[[6,2],[5,6]],[[7,6],[4,7]]]]
[[[6,[0,7]],[0,9]],[4,[9,[9,0]]]]
[[[7,[6,4]],[3,[1,3]]],[[[5,5],1],9]]
[[6,[[7,3],[3,2]]],[[[3,8],[5,7]],4]]
[[[[5,4],[7,7]],8],[[8,3],8]]
[[9,3],[[9,9],[6,[4,9]]]]
[[2,[[7,7],7]],[[5,8],[[9,3],[0,2]]]]
[[[[5,2],5],[8,[3,7]]],[[5,[7,5]],[4,4]]]

4
eighteen/test_input2.txt Normal file
View File

@@ -0,0 +1,4 @@
[1,1]
[2,2]
[3,3]
[4,4]

5
eighteen/test_input3.txt Normal file
View File

@@ -0,0 +1,5 @@
[1,1]
[2,2]
[3,3]
[4,4]
[5,5]

6
eighteen/test_input4.txt Normal file
View File

@@ -0,0 +1,6 @@
[1,1]
[2,2]
[3,3]
[4,4]
[5,5]
[6,6]

10
eighteen/test_input5.txt Normal file
View File

@@ -0,0 +1,10 @@
[[[0,[4,5]],[0,0]],[[[4,5],[2,6]],[9,5]]]
[7,[[[3,7],[4,3]],[[6,3],[8,8]]]]
[[2,[[0,8],[3,4]]],[[[6,7],1],[7,[1,6]]]]
[[[[2,4],7],[6,[0,5]]],[[[6,8],[2,8]],[[2,1],[4,5]]]]
[7,[5,[[3,8],[1,4]]]]
[[2,[2,2]],[8,[8,1]]]
[2,9]
[1,[[[9,3],9],[[9,0],[0,7]]]]
[[[5,[7,4]],7],1]
[[[[4,2],2],6],[8,7]]

View File

@@ -6,6 +6,7 @@ import (
"strings"
"unsupervised.ca/aoc2021/eight"
"unsupervised.ca/aoc2021/eighteen"
"unsupervised.ca/aoc2021/eleven"
"unsupervised.ca/aoc2021/fifteen"
"unsupervised.ca/aoc2021/five"
@@ -73,6 +74,8 @@ func main() {
day = sixteen.Init("sixteen/input.txt")
case "seventeen":
day = seventeen.Init("seventeen/input.txt")
case "eighteen":
day = eighteen.Init("eighteen/input.txt")
default:
fmt.Printf("%q does not have a solution.\n", flagParts[1])
help()