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 }