202 lines
3.9 KiB
Go
202 lines
3.9 KiB
Go
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"os"
|
||
|
|
"strconv"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
func readLines(filename string) []string {
|
||
|
|
raw, err := os.ReadFile(filename)
|
||
|
|
if err != nil {
|
||
|
|
panic(err)
|
||
|
|
}
|
||
|
|
return strings.Split(string(raw), "\n")
|
||
|
|
}
|
||
|
|
|
||
|
|
type id struct {
|
||
|
|
value uint64
|
||
|
|
length uint8
|
||
|
|
}
|
||
|
|
|
||
|
|
func (x id) isSilly() bool {
|
||
|
|
c := uint64(1)
|
||
|
|
for range x.length / 2 {
|
||
|
|
c *= 10
|
||
|
|
}
|
||
|
|
return x.value/c == x.value%c
|
||
|
|
}
|
||
|
|
|
||
|
|
type pattern struct {
|
||
|
|
length uint8
|
||
|
|
repeats uint8
|
||
|
|
}
|
||
|
|
|
||
|
|
var patterns = map[uint8][]pattern{
|
||
|
|
1: {},
|
||
|
|
2: {pattern{length: 1, repeats: 2}},
|
||
|
|
3: {pattern{length: 1, repeats: 3}},
|
||
|
|
4: {pattern{length: 1, repeats: 4}, pattern{length: 2, repeats: 2}},
|
||
|
|
5: {pattern{length: 1, repeats: 5}},
|
||
|
|
6: {pattern{length: 1, repeats: 6}, pattern{length: 2, repeats: 3}, pattern{length: 3, repeats: 2}},
|
||
|
|
7: {pattern{length: 1, repeats: 7}},
|
||
|
|
8: {pattern{length: 1, repeats: 8}, pattern{length: 2, repeats: 4}, pattern{length: 4, repeats: 2}},
|
||
|
|
9: {pattern{length: 1, repeats: 9}, pattern{length: 3, repeats: 3}},
|
||
|
|
10: {pattern{length: 1, repeats: 10}, pattern{length: 5, repeats: 2}, pattern{length: 2, repeats: 5}},
|
||
|
|
11: {pattern{length: 1, repeats: 11}},
|
||
|
|
}
|
||
|
|
|
||
|
|
func (x id) isVerySilly() bool {
|
||
|
|
ps := patterns[x.length]
|
||
|
|
if ps == nil {
|
||
|
|
panic(x.length)
|
||
|
|
}
|
||
|
|
PATTERNS:
|
||
|
|
for _, pattern := range ps {
|
||
|
|
mod := uint64(1)
|
||
|
|
for range pattern.length {
|
||
|
|
mod *= 10
|
||
|
|
}
|
||
|
|
value := x.value
|
||
|
|
candidate := value % mod
|
||
|
|
if candidate == 0 {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
for range pattern.repeats - 1 {
|
||
|
|
value /= mod
|
||
|
|
if value%mod != candidate {
|
||
|
|
continue PATTERNS
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return true
|
||
|
|
}
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
|
||
|
|
type seq struct {
|
||
|
|
start id
|
||
|
|
stop id
|
||
|
|
}
|
||
|
|
|
||
|
|
func parseLine(line string) ([]seq, error) {
|
||
|
|
parts := strings.Split(line, ",")
|
||
|
|
seqs := make([]seq, len(parts))
|
||
|
|
for i, part := range strings.Split(line, ",") {
|
||
|
|
pieces := strings.Split(part, "-")
|
||
|
|
if len(pieces) != 2 {
|
||
|
|
return seqs, fmt.Errorf("invalid part")
|
||
|
|
}
|
||
|
|
var err error
|
||
|
|
s := seq{
|
||
|
|
start: id{length: uint8(len(pieces[0]))},
|
||
|
|
stop: id{length: uint8(len(pieces[1]))},
|
||
|
|
}
|
||
|
|
s.start.value, err = strconv.ParseUint(pieces[0], 10, 64)
|
||
|
|
if err != nil {
|
||
|
|
return seqs, err
|
||
|
|
}
|
||
|
|
s.stop.value, err = strconv.ParseUint(pieces[1], 10, 64)
|
||
|
|
if err != nil {
|
||
|
|
return seqs, err
|
||
|
|
}
|
||
|
|
if s.stop.value < s.start.value {
|
||
|
|
return seqs, fmt.Errorf("invalid range")
|
||
|
|
}
|
||
|
|
if !(s.stop.length == s.start.length || s.stop.length == s.start.length+1) {
|
||
|
|
return seqs, fmt.Errorf("unexpectedly large range %v", s)
|
||
|
|
}
|
||
|
|
seqs[i] = s
|
||
|
|
}
|
||
|
|
return seqs, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func findSillyPatterns(s seq) []uint64 {
|
||
|
|
sillies := []uint64{}
|
||
|
|
oddStart := s.start.length%2 == 1
|
||
|
|
oddStop := s.stop.length%2 == 1
|
||
|
|
if oddStart && oddStop {
|
||
|
|
return sillies
|
||
|
|
}
|
||
|
|
if oddStart {
|
||
|
|
s.start.value = 1
|
||
|
|
s.start.length++
|
||
|
|
for range s.start.length - 1 {
|
||
|
|
s.start.value *= 10
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if oddStop {
|
||
|
|
s.stop.value = 0
|
||
|
|
s.stop.length--
|
||
|
|
c := 1
|
||
|
|
for range s.stop.length {
|
||
|
|
s.stop.value += uint64(9 * c)
|
||
|
|
c *= 10
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if s.stop.value < s.start.value {
|
||
|
|
return sillies
|
||
|
|
}
|
||
|
|
for {
|
||
|
|
if s.start.value > s.stop.value {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
if s.start.isSilly() {
|
||
|
|
sillies = append(sillies, s.start.value)
|
||
|
|
}
|
||
|
|
s.start.value++
|
||
|
|
}
|
||
|
|
return sillies
|
||
|
|
}
|
||
|
|
|
||
|
|
func findVerySillyPatterns(s seq) []uint64 {
|
||
|
|
sillies := []uint64{}
|
||
|
|
pivot := uint64(1)
|
||
|
|
for range s.stop.length - 1 {
|
||
|
|
pivot *= 10
|
||
|
|
}
|
||
|
|
for {
|
||
|
|
if s.start.value > s.stop.value {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
if s.start.isVerySilly() {
|
||
|
|
sillies = append(sillies, s.start.value)
|
||
|
|
}
|
||
|
|
s.start.value++
|
||
|
|
if s.start.value == pivot {
|
||
|
|
s.start.length++
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return sillies
|
||
|
|
}
|
||
|
|
|
||
|
|
func main() {
|
||
|
|
filename := "part0.txt"
|
||
|
|
if len(os.Args) > 1 {
|
||
|
|
filename = os.Args[1]
|
||
|
|
}
|
||
|
|
lines := readLines(filename)
|
||
|
|
if len(lines) != 1 {
|
||
|
|
panic("Invalid lines")
|
||
|
|
}
|
||
|
|
seqs, err := parseLine(lines[0])
|
||
|
|
if err != nil {
|
||
|
|
panic(err)
|
||
|
|
}
|
||
|
|
total := uint64(0)
|
||
|
|
for _, seq := range seqs {
|
||
|
|
for _, value := range findSillyPatterns(seq) {
|
||
|
|
total += value
|
||
|
|
}
|
||
|
|
}
|
||
|
|
fmt.Printf("%v\n", total)
|
||
|
|
|
||
|
|
total = uint64(0)
|
||
|
|
for _, seq := range seqs {
|
||
|
|
for _, value := range findVerySillyPatterns(seq) {
|
||
|
|
total += value
|
||
|
|
}
|
||
|
|
}
|
||
|
|
fmt.Printf("%v\n", total)
|
||
|
|
}
|