Compare commits

...

28 Commits

Author SHA1 Message Date
3e9fe4153c more idiot-proof new-day script 2020-12-26 15:54:56 -05:00
f1d6903fba and day 25! 2020-12-26 15:41:44 -05:00
16784775c9 catching up, here's day 24 2020-12-26 14:50:36 -05:00
df30e9abc0 add day 23 2020-12-26 13:09:37 -05:00
17ab6d7144 ok fine 2020-12-22 20:06:29 -05:00
42c77627d1 day 21 backfill 2020-12-22 19:52:52 -05:00
f744ac6745 run on input, not test input 2020-12-22 17:10:46 -05:00
44fb58ff14 22 done 2020-12-22 16:58:21 -05:00
010a843471 aargh 20 part 2 is a nightmare 2020-12-21 15:03:39 -05:00
d2b4bc2921 day nineteen checkpoint. Doesn't work yet. 2020-12-19 12:30:01 -05:00
9821836b6c clarify comments 2020-12-19 09:45:31 -05:00
0cd38cfd03 got part one working, part 2 will be more work 2020-12-19 09:40:55 -05:00
d8ed532442 when in doubt, copy and paste. part 2 completes in under a second! 2020-12-17 20:18:28 -05:00
e1d3ca5ea2 add some comments 2020-12-16 19:15:23 -05:00
0883efd35e I did the last part by hand first, then bothered to write out the reduction loop 2020-12-16 19:13:15 -05:00
353d5e92c9 performance improvements; no more string conversions or lookups in the map 2020-12-15 21:41:07 -05:00
9d406c2d22 day fifteen, with a pretty slow part 2 2020-12-15 21:19:23 -05:00
80142a25fb part 2 doesn't work yet 2020-12-15 17:54:04 -05:00
e2ac00420d remove useless for loop 2020-12-13 13:01:44 -05:00
18bccde2f5 and cheated just a little to find out how to skip ahead using coprimes 2020-12-13 13:00:53 -05:00
559d91b9a6 part one works, part two... eh. 2020-12-13 11:46:57 -05:00
6ed45d319b make 180 match the other reassignments 2020-12-12 12:39:56 -05:00
1124022f4f fix typo 2020-12-12 12:23:14 -05:00
f5ec48772e add day 12 2020-12-12 12:22:14 -05:00
55fa312abf add a little scaffold for new days 2020-12-12 11:26:47 -05:00
24b59f71fa add duration output 2020-12-11 21:57:10 -05:00
9244b2e178 day 11 ended up being a _lot_ of code 2020-12-11 21:33:00 -05:00
56cd171588 day ten, part one works, part two doesn't 2020-12-11 17:30:31 -05:00
29 changed files with 2774 additions and 2 deletions

3
.gitignore vendored
View File

@@ -1 +1,2 @@
input
input
testinput

View File

@@ -5,11 +5,16 @@ import (
"fmt"
"os"
"strconv"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
// [...] they need you to find the two entries that sum to 2020 and then multiply those two numbers together.

View File

@@ -6,12 +6,17 @@ import (
"os"
"strconv"
"strings"
"time"
"unicode"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
//To try to debug the problem, they have created a list (your puzzle input) of passwords (according to the corrupted database) and the corporate policy when that password was set.

View File

@@ -5,11 +5,16 @@ import (
"fmt"
"os"
"strings"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
type slope struct {

View File

@@ -6,11 +6,16 @@ import (
"os"
"strconv"
"strings"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
type passport struct {

View File

@@ -5,11 +5,16 @@ import (
"fmt"
"os"
"sort"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
type boardingPass struct {

View File

@@ -5,11 +5,16 @@ import (
"fmt"
"os"
"strings"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
func union(s string) string {

View File

@@ -6,11 +6,16 @@ import (
"os"
"strconv"
"strings"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
// Bag is a bag that contains other bags

View File

@@ -6,11 +6,16 @@ import (
"os"
"strconv"
"strings"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
type opCode struct {

View File

@@ -6,11 +6,16 @@ import (
"os"
"sort"
"strconv"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
func checkNotSum(t int, s []int) bool {

122
10/main.go Normal file
View File

@@ -0,0 +1,122 @@
package main
import (
"bufio"
"fmt"
"os"
"sort"
"strconv"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
// Find a chain that uses all of your adapters to connect the charging outlet to your device's built-in adapter
// and count the joltage differences between the charging outlet, the adapters, and your device.
// What is the number of 1-jolt differences multiplied by the number of 3-jolt differences?
func partOne() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
// wall
adapters := []int{0}
// load
for scanner.Scan() {
line := scanner.Text()
i, _ := strconv.Atoi(line)
adapters = append(adapters, i)
}
// sort
sort.Slice(adapters, func(i, j int) bool {
return adapters[i] < adapters[j]
})
// device
adapters = append(adapters, adapters[len(adapters)-1]+3)
one := 0
three := 0
// count gaps
for i, j := range adapters {
if i < len(adapters)-1 {
if adapters[i+1]-j == 3 {
three = three + 1
} else if adapters[i+1]-j == 1 {
one = one + 1
}
}
}
fmt.Println(three * one)
}
// You glance back down at your bag and try to remember why you brought so many adapters; there must be more than a trillion valid ways to arrange them!
// Surely, there must be an efficient way to count the arrangements.
// What is the total number of distinct ways you can arrange the adapters to connect the charging outlet to your device?
func partTwo() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
// wall
adapters := []int{0}
for scanner.Scan() {
line := scanner.Text()
i, _ := strconv.Atoi(line)
adapters = append(adapters, i)
}
// sort
sort.Slice(adapters, func(i, j int) bool {
return adapters[i] < adapters[j]
})
// device
adapters = append(adapters, adapters[len(adapters)-1]+3)
combinations := 1
ones := 0
three := 0
// count gaps
for i, j := range adapters {
if i < len(adapters)-1 {
if adapters[i+1]-j == 1 {
// small steps...
ones = ones + 1
} else if adapters[i+1]-j == 3 {
combinations = combinations + 1
if ones == 2 {
combinations = combinations * 1
}
if ones == 3 {
combinations = combinations * 2
}
if ones == 4 {
combinations = combinations * 4
}
if ones == 5 {
combinations = combinations * 7
}
ones = 0
three = three + 1
}
}
}
fmt.Println(adapters, combinations)
}

303
11/main.go Normal file
View File

@@ -0,0 +1,303 @@
package main
import (
"bufio"
"fmt"
"os"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
type cell struct {
taken int // 1: someone there; 0: empty
seat bool // t: seat available; f: open floor
}
type waitingRoom struct {
seats [][]cell
maxCol int
maxRow int
}
func (w *waitingRoom) parseRow(s string) {
row := []cell{}
for _, r := range s {
switch {
case r == 'L':
row = append(row, cell{taken: 0, seat: true})
case r == '.':
row = append(row, cell{taken: 0, seat: false})
}
}
w.seats = append(w.seats, row)
}
func (w *waitingRoom) countNeighborsPartOne(row, col int) int {
c := 0
if row-1 > -1 {
c = c + w.seats[row-1][col].taken // north
if col-1 > -1 {
c = c + w.seats[row-1][col-1].taken // northwest
}
if col+1 < w.maxCol {
c = c + w.seats[row-1][col+1].taken // northheast
}
}
if row+1 < w.maxRow {
c = c + w.seats[row+1][col].taken // south
if col+1 < w.maxCol {
c = c + w.seats[row+1][col+1].taken // southeast
}
if col-1 > -1 {
c = c + w.seats[row+1][col-1].taken // southwest
}
}
if col-1 > -1 {
c = c + w.seats[row][col-1].taken // west
}
if col+1 < w.maxCol {
c = c + w.seats[row][col+1].taken // east
}
return c
}
func (w *waitingRoom) makeNextStepPartOne() *waitingRoom {
newRoom := &waitingRoom{
seats: make([][]cell, w.maxRow),
maxCol: w.maxCol,
maxRow: w.maxRow,
}
for r := range w.seats {
newRoom.seats[r] = make([]cell, newRoom.maxCol)
for c := range w.seats[r] {
if w.seats[r][c].seat && w.seats[r][c].taken == 0 && w.countNeighborsPartOne(r, c) == 0 {
newRoom.seats[r][c].seat = w.seats[r][c].seat
newRoom.seats[r][c].taken = 1
} else if w.seats[r][c].seat && w.seats[r][c].taken == 1 && w.countNeighborsPartOne(r, c) >= 4 {
newRoom.seats[r][c].seat = w.seats[r][c].seat
newRoom.seats[r][c].taken = 0
} else {
newRoom.seats[r][c].seat = w.seats[r][c].seat
newRoom.seats[r][c].taken = w.seats[r][c].taken
}
}
}
return newRoom
}
func (w *waitingRoom) countNeighborsPartTwo(row, col int) int {
c := 0
trow := row
tcol := col
// south
for trow < w.maxRow-1 {
trow = trow + 1
if w.seats[trow][col].seat {
c = c + w.seats[trow][col].taken
break
}
}
trow = row
// north
for trow > 0 {
trow = trow - 1
if w.seats[trow][col].seat {
c = c + w.seats[trow][col].taken
break
}
}
trow = row
// west
for tcol > 0 {
tcol = tcol - 1
if w.seats[row][tcol].seat {
c = c + w.seats[row][tcol].taken
break
}
}
tcol = col
// east
for tcol < w.maxCol-1 {
tcol = tcol + 1
if w.seats[row][tcol].seat {
c = c + w.seats[row][tcol].taken
break
}
}
tcol = col
// southeast
for trow < w.maxRow-1 && tcol < w.maxCol-1 {
trow = trow + 1
tcol = tcol + 1
if w.seats[trow][tcol].seat {
c = c + w.seats[trow][tcol].taken
break
}
}
trow = row
tcol = col
// southwest
for trow < w.maxRow-1 && tcol > 0 {
trow = trow + 1
tcol = tcol - 1
if w.seats[trow][tcol].seat {
c = c + w.seats[trow][tcol].taken
break
}
}
trow = row
tcol = col
// northeast
for trow > 0 && tcol < w.maxCol-1 {
trow = trow - 1
tcol = tcol + 1
if w.seats[trow][tcol].seat {
c = c + w.seats[trow][tcol].taken
break
}
}
trow = row
tcol = col
// northwest
for trow > 0 && tcol > 0 {
trow = trow - 1
tcol = tcol - 1
if w.seats[trow][tcol].seat {
c = c + w.seats[trow][tcol].taken
break
}
}
return c
}
func (w *waitingRoom) makeNextStepPartTwo() *waitingRoom {
newRoom := &waitingRoom{
seats: make([][]cell, w.maxRow),
maxCol: w.maxCol,
maxRow: w.maxRow,
}
for r := range w.seats {
newRoom.seats[r] = make([]cell, newRoom.maxCol)
for c := range w.seats[r] {
if w.seats[r][c].seat && w.seats[r][c].taken == 0 && w.countNeighborsPartTwo(r, c) == 0 {
newRoom.seats[r][c].seat = w.seats[r][c].seat
newRoom.seats[r][c].taken = 1
} else if w.seats[r][c].seat && w.seats[r][c].taken == 1 && w.countNeighborsPartTwo(r, c) >= 5 {
newRoom.seats[r][c].seat = w.seats[r][c].seat
newRoom.seats[r][c].taken = 0
} else {
newRoom.seats[r][c].seat = w.seats[r][c].seat
newRoom.seats[r][c].taken = w.seats[r][c].taken
}
}
}
return newRoom
}
func (w *waitingRoom) countFilledSeats() int {
count := 0
for r := range w.seats {
for c := range w.seats[r] {
count = count + w.seats[r][c].taken
}
}
return count
}
func (w *waitingRoom) print() {
for r := range w.seats {
s := ""
for c := range w.seats[r] {
if !w.seats[r][c].seat {
s = s + "."
} else if w.seats[r][c].taken == 0 {
s = s + "L"
} else if w.seats[r][c].taken == 1 {
s = s + "#"
}
}
fmt.Println(s)
}
}
func checkStasis(old, new *waitingRoom) bool {
for r := range old.seats {
for c := range old.seats[r] {
if old.seats[r][c].seat && old.seats[r][c].taken != new.seats[r][c].taken {
return false
}
}
}
return true
}
func partOne() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
oldRoom := &waitingRoom{}
for scanner.Scan() {
line := scanner.Text()
oldRoom.parseRow(line)
}
oldRoom.maxCol = len(oldRoom.seats[0])
oldRoom.maxRow = len(oldRoom.seats)
for {
newRoom := oldRoom.makeNextStepPartOne()
if checkStasis(newRoom, oldRoom) {
fmt.Println(newRoom.countFilledSeats())
return
}
oldRoom = newRoom
}
}
func partTwo() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
oldRoom := &waitingRoom{}
for scanner.Scan() {
line := scanner.Text()
oldRoom.parseRow(line)
}
oldRoom.maxCol = len(oldRoom.seats[0])
oldRoom.maxRow = len(oldRoom.seats)
for {
newRoom := oldRoom.makeNextStepPartTwo()
if checkStasis(newRoom, oldRoom) {
fmt.Println(newRoom.countFilledSeats())
return
}
oldRoom = newRoom
}
}

197
12/main.go Normal file
View File

@@ -0,0 +1,197 @@
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
type ship struct {
horizontal int
vertical int
direction rune // E W N S
}
// both horizontal/vertical are relative to ship
type waypoint struct {
horizontal int
vertical int
}
func parseLine(s string) (rune, int) {
i, _ := strconv.Atoi(s[1:])
return []rune(s)[0], i
}
func movePartOne(s ship, action rune, vector int) ship {
switch action {
case 'R', 'L':
s.direction = rotateShip(s.direction, vector, action)
case 'N':
s.vertical = s.vertical + vector
case 'S':
s.vertical = s.vertical - vector
case 'E':
s.horizontal = s.horizontal + vector
case 'W':
s.horizontal = s.horizontal - vector
case 'F':
s = movePartOne(s, s.direction, vector)
}
return s
}
func movePartTwo(s ship, w waypoint, action rune, vector int) (ship, waypoint) {
switch action {
case 'R', 'L':
w = rotateWaypoint(w, action, vector)
case 'N':
w.vertical = w.vertical + vector
case 'S':
w.vertical = w.vertical - vector
case 'E':
w.horizontal = w.horizontal + vector
case 'W':
w.horizontal = w.horizontal - vector
case 'F':
s.horizontal = s.horizontal + (w.horizontal * vector)
s.vertical = s.vertical + (w.vertical * vector)
}
return s, w
}
// for example E, 90, L => N; E, 180, R => W; E, 90, R => S
func rotateShip(current rune, degrees int, rl rune) rune {
// easy
if degrees == 180 {
switch current {
case 'N':
return 'S'
case 'S':
return 'N'
case 'E':
return 'W'
case 'W':
return 'E'
}
}
// translate L to R
if rl == 'L' {
if degrees == 90 {
degrees = 270
} else {
degrees = 90
}
}
// the R set
if degrees == 90 {
switch current {
case 'N':
return 'E'
case 'S':
return 'W'
case 'E':
return 'S'
case 'W':
return 'N'
}
} else if degrees == 270 {
switch current {
case 'N':
return 'W'
case 'S':
return 'E'
case 'E':
return 'N'
case 'W':
return 'S'
}
}
return current // we're made a terrible mistake somewhere
}
func rotateWaypoint(w waypoint, action rune, vector int) waypoint {
if vector == 180 {
w.horizontal, w.vertical = -w.horizontal, -w.vertical
} else if action == 'R' {
if vector == 90 {
w.horizontal, w.vertical = w.vertical, -w.horizontal
}
if vector == 270 {
w.horizontal, w.vertical = -w.vertical, w.horizontal
}
} else if action == 'L' {
if vector == 90 {
w.horizontal, w.vertical = -w.vertical, w.horizontal
}
if vector == 270 {
w.horizontal, w.vertical = w.vertical, -w.horizontal
}
}
return w
}
func manhattan(i, j int) int {
return abs(i) + abs(j)
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
func partOne() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
s := ship{
horizontal: 0,
vertical: 0,
direction: 'E',
}
for scanner.Scan() {
dir, vec := parseLine(scanner.Text())
s = movePartOne(s, dir, vec)
}
fmt.Println(manhattan(s.horizontal, s.vertical))
}
func partTwo() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
s := ship{
horizontal: 0,
vertical: 0,
// no longer care about ship direction
}
w := waypoint{
horizontal: 10,
vertical: 1,
}
for scanner.Scan() {
dir, vec := parseLine(scanner.Text())
s, w = movePartTwo(s, w, dir, vec)
}
fmt.Println(manhattan(s.horizontal, s.vertical))
}

89
13/main.go Normal file
View File

@@ -0,0 +1,89 @@
package main
import (
"bufio"
"fmt"
"math"
"os"
"strconv"
"strings"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
func partOne() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
busWaitTimes := map[int]int{}
shortestWait := -1
// don't actually need loop logic for a two-line input
scanner.Scan()
ts := scanner.Text()
currentTimestamp, _ := strconv.Atoi(ts)
scanner.Scan()
buses := strings.Split(scanner.Text(), ",")
for _, b := range buses {
if b != "x" {
j, _ := strconv.Atoi(b)
busWaitTimes[j] = (((currentTimestamp / j) + 1) * j) - currentTimestamp
if shortestWait == -1 || busWaitTimes[shortestWait] > busWaitTimes[j] {
shortestWait = j
}
}
}
fmt.Println(busWaitTimes[shortestWait] * shortestWait)
}
type bus struct {
route float64
offset float64
}
func partTwo() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
buses := []bus{}
scanner.Scan() // discard first line
scanner.Scan()
line := strings.Split(scanner.Text(), ",")
for i, b := range line {
if b != "x" {
j, _ := strconv.Atoi(b)
buses = append(buses, bus{
route: float64(j),
offset: float64(i),
})
}
}
t := float64(0)
offset := buses[0].route
loop:
t = t + offset
offset = 1
for i := range buses {
if math.Remainder(t+buses[i].offset, buses[i].route) != 0 {
goto loop
} else {
offset = offset * buses[i].route
}
}
fmt.Println(int(t))
}

148
14/main.go Normal file
View File

@@ -0,0 +1,148 @@
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
type bitset struct {
bits []string
}
func newBitset(i int) bitset {
b := strings.Split(fmt.Sprintf("%036b", i), "")
bs := bitset{
bits: strings.Split("000000000000000000000000000000000000", ""),
}
for i, bt := range b {
if bt != "0" {
bs.bits[i] = bt
}
}
return bs
}
func (b bitset) ToInt() int {
i, _ := strconv.ParseInt(strings.Join(b.bits, ""), 2, 64)
return int(i)
}
func parseMask(s string) string {
return strings.Split(s, " = ")[1]
}
func parseMemoryToAddrAndBitset(s string) (int, bitset) {
a := strings.Split(s, " = ")
addr, _ := strconv.Atoi(strings.TrimFunc(a[0], func(r rune) bool {
if r == 'm' || r == 'e' || r == '[' || r == ']' {
return true
}
return false
}))
i, _ := strconv.Atoi(a[1])
return addr, newBitset(i)
}
func parseMemoryToBitsetAndValue(s string) (bitset, int) {
a := strings.Split(s, " = ")
addr, _ := strconv.Atoi(strings.TrimFunc(a[0], func(r rune) bool {
if r == 'm' || r == 'e' || r == '[' || r == ']' {
return true
}
return false
}))
i, _ := strconv.Atoi(a[1])
return newBitset(addr), i
}
func applyMaskToValue(m string, b bitset) int {
for i, r := range m {
if r == '0' {
b.bits[i] = "0"
}
if r == '1' {
b.bits[i] = "1"
}
}
return b.ToInt()
}
func applyMaskToAddr(prefix []string, m string, attr bitset) []bitset {
if len(m) == 0 {
return []bitset{{bits: prefix}}
}
res := []bitset{}
for _, r := range m {
if r == '0' || r == 'X' {
res = append(res, applyMaskToAddr(append(prefix[:], "0"), m[1:], attr)...)
}
if r == '1' || r == 'X' {
res = append(res, applyMaskToAddr(append(prefix[:], "1"), m[1:], attr)...)
}
}
return res
}
func partOne() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
mem := map[int]int{}
var currentMask string
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "mask") {
currentMask = parseMask(line)
} else {
addr, value := parseMemoryToAddrAndBitset(line)
mem[addr] = applyMaskToValue(currentMask, value)
}
}
sum := 0
for _, v := range mem {
sum = sum + v
}
fmt.Println(sum)
}
func partTwo() {
f, _ := os.Open("testinput")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
mem := map[int]int{}
var currentMask string
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "mask") {
currentMask = parseMask(line)
} else {
addr, value := parseMemoryToBitsetAndValue(line)
for _, b := range applyMaskToAddr([]string{}, currentMask, addr) {
mem[b.ToInt()] = value
}
}
}
sum := 0
for _, v := range mem {
sum = sum + v
}
fmt.Println(sum)
}

85
15/main.go Normal file
View File

@@ -0,0 +1,85 @@
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
func partOne() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
// naive implementation
scanner.Scan()
numbers := strings.Split(scanner.Text(), ",")
for {
i := len(numbers) - 1
lastNumber := numbers[i]
// walk backward through the list, looking for a match
for j := range numbers {
if len(numbers) == 2020 {
fmt.Println(numbers[2019])
return
}
if i-j == 0 {
// made it to the head of the list, no matches; append 0
numbers = append(numbers, "0")
break
} else if numbers[i-j-1] == lastNumber {
// a number matched; insert the difference between the last number and when we last saw it
numbers = append(numbers, fmt.Sprintf("%d", len(numbers)-1-(i-j-1)))
break
}
}
}
}
func partTwo() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
// caching implementation. It's still slow, but it at least finishes...!
scanner.Scan()
numbers := strings.Split(scanner.Text(), ",")
seen := make(map[int]int)
// load in numbers
for i, v := range numbers[:len(numbers)-1] {
j, _ := strconv.Atoi(v)
seen[j] = i
}
i := len(numbers) - 1
lastNumber, _ := strconv.Atoi(numbers[i])
for {
if seen[lastNumber] == 30000000-1 {
fmt.Println(lastNumber)
return
}
if v, ok := seen[lastNumber]; ok {
// a number matched; insert the difference between the last number and when we last saw it
seen[lastNumber] = i
lastNumber = i - v
} else {
// never seen that before
seen[lastNumber] = i
lastNumber = 0
}
i = i + 1
}
}

232
16/main.go Normal file
View File

@@ -0,0 +1,232 @@
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
type valuerange struct {
min int
max int
}
type rule struct {
name string
ranges []valuerange
field int
}
type ticket struct {
values []int
valid bool
}
func parseRule(s string) rule {
q := strings.Split(s, ": ")
ru := rule{
name: q[0],
ranges: make([]valuerange, 0, 2),
}
r := strings.Split(q[1], " or ")
for _, rr := range r {
mm := strings.Split(rr, "-")
min, _ := strconv.Atoi(mm[0])
max, _ := strconv.Atoi(mm[1])
ru.ranges = append(ru.ranges, valuerange{min: min, max: max})
}
return ru
}
func parseTicket(s string) ticket {
t := ticket{
values: []int{},
}
vs := strings.Split(s, ",")
for _, v := range vs {
vi, _ := strconv.Atoi(v)
t.values = append(t.values, vi)
}
return t
}
// return valid/invalid and the invalid value, if found
func validateTicket(r []rule, t ticket) (bool, int) {
for _, ta := range t.values {
ok := false
for _, ru := range r {
outsideRule := (ta < ru.ranges[0].min || (ta > ru.ranges[0].max && ta < ru.ranges[1].min) || ta > ru.ranges[1].max)
if ok || !outsideRule {
ok = true
}
}
if !ok {
return false, ta
}
}
return true, 0
}
// returns a mapping of fields to possible rules
func guessFields(rules []rule, tickets []ticket) map[int][]int {
guesses := map[int][]int{}
for ri, r := range rules {
for i := range tickets[0].values { // loop over the fields on tickets, and...
ok := true
for _, t := range tickets { // loop over all the tickets...
ta := t.values[i] // checking the current field value...
outsideRule := (ta < r.ranges[0].min || (ta > r.ranges[0].max && ta < r.ranges[1].min) || ta > r.ranges[1].max)
if !ok || outsideRule { // checking if any of the tickets fall outside our rule's range.
ok = false
}
}
if ok { // if all tickets pass, we've got a hit
guesses[i] = append(guesses[i], ri)
}
}
}
return guesses
}
func reduce(g map[int][]int) (int, int) {
for i, j := range g {
if len(j) == 1 {
return i, j[0]
}
}
return 0, 0
}
func remove(a []int, rule int) []int {
for i, j := range a {
if j == rule {
a[i] = a[len(a)-1]
a[len(a)-1] = 0
return a[:len(a)-1]
}
}
return a
}
func partOne() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
rules := []rule{}
tickets := []ticket{}
invalidityRate := 0
seenAllRules := false
seenMyTicket := false
for scanner.Scan() {
line := scanner.Text()
// check for end of rules list
if line == "" && !seenAllRules {
seenAllRules = true
scanner.Scan() // clear out the following line
continue
}
// parse rules until we run out
if !seenAllRules {
rules = append(rules, parseRule(line))
continue
}
// parse own ticket, then clear for remaining tickets
if !seenMyTicket {
tickets = append(tickets, parseTicket(scanner.Text()))
seenMyTicket = true
scanner.Scan() // clear out the next two lines
scanner.Scan()
continue
}
// parse and validate other people's tickets
t := parseTicket(line)
ok, i := validateTicket(rules, t)
if !ok {
t.valid = false
invalidityRate = invalidityRate + i
} else {
t.valid = true
tickets = append(tickets, t)
}
}
fmt.Println(invalidityRate)
}
func partTwo() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
rules := []rule{}
myTicket := ticket{}
tickets := []ticket{}
seenAllRules := false
seenMyTicket := false
for scanner.Scan() {
line := scanner.Text()
// check for end of rules list
if line == "" && !seenAllRules {
seenAllRules = true
scanner.Scan() // clear out the following line
continue
}
// parse rules until we run out
if !seenAllRules {
rules = append(rules, parseRule(line))
continue
}
// parse own ticket, then clear for remaining tickets
if !seenMyTicket {
myTicket = parseTicket(scanner.Text())
tickets = append(tickets, myTicket)
seenMyTicket = true
scanner.Scan() // clear out the next two lines
scanner.Scan()
continue
}
// parse and validate other people's tickets
t := parseTicket(line)
ok, _ := validateTicket(rules, t)
if ok {
t.valid = true
tickets = append(tickets, t)
}
}
guesses := guessFields(rules, tickets)
var lastRuleSolved int
// iterate over the guesses list. on every iteration, only one field has only one rule remaining that's valid
// save it off into the rules list, then remove that rule from all fields.
// Luckily, we don't need any higher-order elimination logic.
for i := 1; i < len(guesses); i++ {
for i, g := range guesses {
if len(g) == 1 {
rules[g[0]].field = i
lastRuleSolved = g[0]
}
}
for i := range guesses {
guesses[i] = remove(guesses[i], lastRuleSolved)
}
}
result := 1
// first 6 rules are our "departure" rules
for _, r := range rules[:6] {
result = result * myTicket.values[r.field]
}
fmt.Println(result)
}

235
17/main.go Normal file
View File

@@ -0,0 +1,235 @@
package main
import (
"bufio"
"fmt"
"os"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
type cell struct {
x int // length
y int // width
z int // depth
w int // spissitude
}
type universe struct {
cells map[cell]bool
hyper bool // true: 4-dimensional; false: 3-dimensional
}
func makeNeighborsList(c cell, hyper bool) []cell {
cells := []cell{}
if hyper {
cells = append(cells, []cell{
// outward...?
// above
{x: c.x + 1, y: c.y + 1, z: c.z + 1, w: c.w + 1},
{x: c.x + 1, y: c.y, z: c.z + 1, w: c.w + 1},
{x: c.x + 1, y: c.y - 1, z: c.z + 1, w: c.w + 1},
{x: c.x, y: c.y + 1, z: c.z + 1, w: c.w + 1},
{x: c.x, y: c.y, z: c.z + 1, w: c.w + 1},
{x: c.x, y: c.y - 1, z: c.z + 1, w: c.w + 1},
{x: c.x - 1, y: c.y + 1, z: c.z + 1, w: c.w + 1},
{x: c.x - 1, y: c.y, z: c.z + 1, w: c.w + 1},
{x: c.x - 1, y: c.y - 1, z: c.z + 1, w: c.w + 1},
// same layer
{x: c.x + 1, y: c.y + 1, z: c.z, w: c.w + 1},
{x: c.x + 1, y: c.y, z: c.z, w: c.w + 1},
{x: c.x + 1, y: c.y - 1, z: c.z, w: c.w + 1},
{x: c.x, y: c.y + 1, z: c.z, w: c.w + 1},
{x: c.x, y: c.y, z: c.z, w: c.w + 1},
{x: c.x, y: c.y - 1, z: c.z, w: c.w + 1},
{x: c.x - 1, y: c.y + 1, z: c.z, w: c.w + 1},
{x: c.x - 1, y: c.y, z: c.z, w: c.w + 1},
{x: c.x - 1, y: c.y - 1, z: c.z, w: c.w + 1},
// below
{x: c.x + 1, y: c.y + 1, z: c.z - 1, w: c.w + 1},
{x: c.x + 1, y: c.y, z: c.z - 1, w: c.w + 1},
{x: c.x + 1, y: c.y - 1, z: c.z - 1, w: c.w + 1},
{x: c.x, y: c.y + 1, z: c.z - 1, w: c.w + 1},
{x: c.x, y: c.y, z: c.z - 1, w: c.w + 1},
{x: c.x, y: c.y - 1, z: c.z - 1, w: c.w + 1},
{x: c.x - 1, y: c.y + 1, z: c.z - 1, w: c.w + 1},
{x: c.x - 1, y: c.y, z: c.z - 1, w: c.w + 1},
{x: c.x - 1, y: c.y - 1, z: c.z - 1, w: c.w + 1},
// inward...?
// above
{x: c.x + 1, y: c.y + 1, z: c.z + 1, w: c.w - 1},
{x: c.x + 1, y: c.y, z: c.z + 1, w: c.w - 1},
{x: c.x + 1, y: c.y - 1, z: c.z + 1, w: c.w - 1},
{x: c.x, y: c.y + 1, z: c.z + 1, w: c.w - 1},
{x: c.x, y: c.y, z: c.z + 1, w: c.w - 1},
{x: c.x, y: c.y - 1, z: c.z + 1, w: c.w - 1},
{x: c.x - 1, y: c.y + 1, z: c.z + 1, w: c.w - 1},
{x: c.x - 1, y: c.y, z: c.z + 1, w: c.w - 1},
{x: c.x - 1, y: c.y - 1, z: c.z + 1, w: c.w - 1},
// same layer
{x: c.x + 1, y: c.y + 1, z: c.z, w: c.w - 1},
{x: c.x + 1, y: c.y, z: c.z, w: c.w - 1},
{x: c.x + 1, y: c.y - 1, z: c.z, w: c.w - 1},
{x: c.x, y: c.y + 1, z: c.z, w: c.w - 1},
{x: c.x, y: c.y, z: c.z, w: c.w - 1},
{x: c.x, y: c.y - 1, z: c.z, w: c.w - 1},
{x: c.x - 1, y: c.y + 1, z: c.z, w: c.w - 1},
{x: c.x - 1, y: c.y, z: c.z, w: c.w - 1},
{x: c.x - 1, y: c.y - 1, z: c.z, w: c.w - 1},
// below
{x: c.x + 1, y: c.y + 1, z: c.z - 1, w: c.w - 1},
{x: c.x + 1, y: c.y, z: c.z - 1, w: c.w - 1},
{x: c.x + 1, y: c.y - 1, z: c.z - 1, w: c.w - 1},
{x: c.x, y: c.y + 1, z: c.z - 1, w: c.w - 1},
{x: c.x, y: c.y, z: c.z - 1, w: c.w - 1},
{x: c.x, y: c.y - 1, z: c.z - 1, w: c.w - 1},
{x: c.x - 1, y: c.y + 1, z: c.z - 1, w: c.w - 1},
{x: c.x - 1, y: c.y, z: c.z - 1, w: c.w - 1},
{x: c.x - 1, y: c.y - 1, z: c.z - 1, w: c.w - 1},
}...)
}
// "normal" space
cells = append(cells, []cell{
// above
{x: c.x + 1, y: c.y + 1, z: c.z + 1, w: c.w},
{x: c.x + 1, y: c.y, z: c.z + 1, w: c.w},
{x: c.x + 1, y: c.y - 1, z: c.z + 1, w: c.w},
{x: c.x, y: c.y + 1, z: c.z + 1, w: c.w},
{x: c.x, y: c.y, z: c.z + 1, w: c.w},
{x: c.x, y: c.y - 1, z: c.z + 1, w: c.w},
{x: c.x - 1, y: c.y + 1, z: c.z + 1, w: c.w},
{x: c.x - 1, y: c.y, z: c.z + 1, w: c.w},
{x: c.x - 1, y: c.y - 1, z: c.z + 1, w: c.w},
// same layer
{x: c.x + 1, y: c.y + 1, z: c.z, w: c.w},
{x: c.x + 1, y: c.y, z: c.z, w: c.w},
{x: c.x + 1, y: c.y - 1, z: c.z, w: c.w},
{x: c.x, y: c.y + 1, z: c.z, w: c.w},
// <-- original cell
{x: c.x, y: c.y - 1, z: c.z, w: c.w},
{x: c.x - 1, y: c.y + 1, z: c.z, w: c.w},
{x: c.x - 1, y: c.y, z: c.z, w: c.w},
{x: c.x - 1, y: c.y - 1, z: c.z, w: c.w},
// below
{x: c.x + 1, y: c.y + 1, z: c.z - 1, w: c.w},
{x: c.x + 1, y: c.y, z: c.z - 1, w: c.w},
{x: c.x + 1, y: c.y - 1, z: c.z - 1, w: c.w},
{x: c.x, y: c.y + 1, z: c.z - 1, w: c.w},
{x: c.x, y: c.y, z: c.z - 1, w: c.w},
{x: c.x, y: c.y - 1, z: c.z - 1, w: c.w},
{x: c.x - 1, y: c.y + 1, z: c.z - 1, w: c.w},
{x: c.x - 1, y: c.y, z: c.z - 1, w: c.w},
{x: c.x - 1, y: c.y - 1, z: c.z - 1, w: c.w},
}...)
return cells
}
func checkNeighbors(u universe, c cell) bool {
sum := 0
for _, c := range makeNeighborsList(c, u.hyper) {
if u.cells[c] {
sum = sum + 1
}
}
if sum == 3 {
return true
} else if u.cells[c] && sum == 2 {
return true
}
return false
}
func step(old universe) universe {
new := universe{
cells: make(map[cell]bool, len(old.cells)),
hyper: old.hyper,
}
cellsToCheck := make([]cell, 0, len(old.cells)*80)
for c := range old.cells {
cellsToCheck = append(cellsToCheck, makeNeighborsList(c, old.hyper)...)
}
for _, c := range cellsToCheck {
if checkNeighbors(old, c) {
new.cells[c] = true
}
}
return new
}
func newUniverse(input []string, hyper bool) universe {
u := universe{
cells: make(map[cell]bool, len(input)),
hyper: hyper,
}
for i, r := range input {
for j, c := range r {
if c == '#' {
u.cells[cell{z: 0, x: i, y: j, w: 0}] = true
}
}
}
return u
}
func countActivesInUniverse(u universe) {
fmt.Println(len(u.cells))
}
func partOne() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
var input []string
for scanner.Scan() {
input = append(input, scanner.Text())
}
u := newUniverse(input, false)
u = step(u)
u = step(u)
u = step(u)
u = step(u)
u = step(u)
u = step(u)
countActivesInUniverse(u)
}
func partTwo() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
var input []string
for scanner.Scan() {
input = append(input, scanner.Text())
}
u := newUniverse(input, true)
u = step(u)
u = step(u)
u = step(u)
u = step(u)
u = step(u)
u = step(u)
countActivesInUniverse(u)
}

180
18/main.go Normal file
View File

@@ -0,0 +1,180 @@
package main
import (
"bufio"
"fmt"
"os"
"strings"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
// an expression is a list of expressions and a list of operators that intersperse those expressions, OR a number.
// as a sanity check, the length of the body list should always be one more than operators, unless both are 0,
// in which case number must be set. Luckily, we don't have to worry about 0 (the "empty" state for numbers).
type expression struct {
body []expression
operators []op
number int
}
// op can be one of two things.
type op struct {
plus bool
multiply bool
}
// 1 + 2 should parse to
// expression{
// body:[
// expression{number: 1},
// expression{number: 2},
// ],
// operators:[
// op{plus: true},
// ],
// }
// (2 * 2) + 2 should parse to:
// expression{
// body:[
// expression{
// body: [
// expression{number: 2},
// expression{number: 2},
// ],
// operators: [
// op{multiply: true},
// ],
// },
// expression{number: 2},
// ],
// operators:[
// op{plus:true}
// ]
// }
func parseExpression(e expression, s string) (expression, string) {
for i := 0; i < len(s); {
switch s[i] {
case '(':
sub, remainder := parseExpression(expression{}, s[i+1:])
e.body = append(e.body, sub)
i = len(s) - len(remainder) + 1 // skip everything we just parsed in the subexpression
case ')':
return e, s[i:]
case '*':
e.operators = append(e.operators, op{multiply: true})
i = i + 1
case '+':
e.operators = append(e.operators, op{plus: true})
i = i + 1
case ' ':
i = i + 1
case '1', '2', '3', '4', '5', '6', '7', '8', '9':
// luckily, it's all single-digit numbers and I don't need to track parser state
// an ASCII byte number minus the rune value of 0 equals the integer itself: '1' - '0' == 49 - 48;
// rune math returns a platform-specific int, so cast to generic int.
e.body = append(e.body, expression{number: int(s[i] - '0')})
i = i + 1
}
}
return e, ""
}
func printExpression(e expression) string {
result := ""
for i := 0; i < len(e.body); i = i + 1 {
if e.body[i].number != 0 {
result = result + fmt.Sprintf("%d ", e.body[i].number)
} else {
result = result + "(" + printExpression(e.body[i]) + ") "
}
if i < len(e.operators) {
if e.operators[i].multiply {
result = result + "* "
} else if e.operators[i].plus {
result = result + "+ "
}
}
}
return strings.TrimSpace(result)
}
// evaluation starts at the root then descends into the sub-expressions.
func evalLeftToRight(e expression) int {
var result int
if e.number != 0 {
result = e.number
} else {
result = evalLeftToRight(e.body[0])
}
for i := 0; i < len(e.operators); i = i + 1 {
if e.operators[i].multiply {
result = result * evalLeftToRight(e.body[i+1])
}
if e.operators[i].plus {
result = result + evalLeftToRight(e.body[i+1])
}
}
return result
}
// previous approach might not work...
func evalPlusThenMultiplication(e expression) int {
var result int
if e.number != 0 {
result = e.number
} else {
result = evalPlusThenMultiplication(e.body[0])
}
for i := 0; i < len(e.operators); i = i + 1 {
if e.operators[i].multiply {
result = result * evalPlusThenMultiplication(e.body[i+1])
}
if e.operators[i].plus {
result = result + evalPlusThenMultiplication(e.body[i+1])
}
}
return result
}
func partOne() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
total := 0
for scanner.Scan() {
line := scanner.Text()
e, _ := parseExpression(expression{}, line)
total = total + evalLeftToRight(e)
}
fmt.Println(total)
}
func partTwo() {
f, _ := os.Open("testinput")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
total := 0
for scanner.Scan() {
line := scanner.Text()
e, _ := parseExpression(expression{}, line)
fmt.Println(printExpression(e), "=", evalPlusThenMultiplication(e))
}
fmt.Println(total)
}

175
19/main.go Normal file
View File

@@ -0,0 +1,175 @@
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
// options: 20 => [][20]; 20 10 => [][20, 10]; 20 | 10 => [][20][10]; 20 10 | 39 12 => [][20, 10][39, 12];
// token is only set for the two "bottom" rules of "a" and "b"
// options and token will never both be set
// ints all point to rule numbers in overall rule list
type rule struct {
name int
options [][]int
token rune
}
func parseRule(s string) rule {
r := rule{
options: make([][]int, 0),
}
sp := strings.Split(s, ": ")
r.name, _ = strconv.Atoi(sp[0])
// either it's a token rule...
if sp[1] == `"a"` || sp[1] == `"b"` {
r.token = []rune(strings.Trim(sp[1], `"`))[0]
return r
}
// ...or an options rule.
opts := strings.Split(sp[1], " | ")
for _, o := range opts {
opt := []int{}
for _, i := range strings.Split(o, " ") {
ii, _ := strconv.Atoi(i)
opt = append(opt, ii)
}
r.options = append(r.options, opt)
}
return r
}
func validateString(rules []rule, currentRule int, s string) (bool, string) {
activeRule := rules[currentRule]
// if we're run out of string, the rule obviously cannot match
if len(s) == 0 {
return false, ""
}
// we've reached a leaf; check if the current head of the string matches our expected rune
if len(activeRule.options) == 0 {
return []rune(s)[0] == activeRule.token, s[1:]
}
// if there's only one option, attempt to apply the rules
if len(activeRule.options) == 1 {
option := activeRule.options[0]
if len(option) == 1 {
return validateString(rules, option[0], s)
}
if len(option) == 2 {
ok, remainder := validateString(rules, option[0], s)
if ok {
return validateString(rules, option[1], remainder)
}
}
if len(option) == 3 {
ok, remainder := validateString(rules, option[0], s)
if ok {
ok, remainder = validateString(rules, option[1], remainder)
}
if ok {
return validateString(rules, option[2], remainder)
}
}
}
// otherwise, we need to branch into our options and try and apply their rules
if len(activeRule.options) == 2 {
ok := false
remainder := ""
optionOne := activeRule.options[0]
if len(optionOne) == 1 {
ok, remainder = validateString(rules, optionOne[0], s)
}
if len(optionOne) == 2 {
ok, remainder = validateString(rules, optionOne[0], s)
if ok {
ok, remainder = validateString(rules, optionOne[1], remainder)
}
}
if len(optionOne) == 3 {
ok, remainder = validateString(rules, optionOne[0], s)
if ok {
ok, remainder = validateString(rules, optionOne[1], remainder)
}
if ok {
ok, remainder = validateString(rules, optionOne[2], remainder)
}
}
if ok {
return ok, remainder
}
optionTwo := activeRule.options[1]
if len(optionTwo) == 1 {
ok, _ = validateString(rules, optionTwo[0], s)
}
if len(optionTwo) == 2 {
ok, remainder = validateString(rules, optionTwo[0], s)
if ok {
return validateString(rules, optionTwo[1], remainder)
}
}
if len(optionTwo) == 3 {
ok, remainder = validateString(rules, optionTwo[0], s)
if ok {
ok, remainder = validateString(rules, optionTwo[1], remainder)
}
if ok {
return validateString(rules, optionTwo[2], remainder)
}
}
}
return false, s
}
func partOne() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
rules := make([]rule, 134)
for scanner.Scan() {
line := scanner.Text()
if line == "" {
break
}
rule := parseRule(line)
rules[rule.name] = rule
}
total := 0
for scanner.Scan() {
line := scanner.Text()
if ok, _ := validateString(rules, 0, line); ok {
fmt.Println("VALID:", line)
total = total + 1
} else {
fmt.Println("INVALID:", line)
}
}
fmt.Println(total)
}
func partTwo() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
// line := scanner.Text()
}
}

244
20/main.go Normal file
View File

@@ -0,0 +1,244 @@
package main
import (
"bufio"
"fmt"
"math"
"os"
"strconv"
"strings"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
type tile struct {
id int
neighbors map[int]int // key matches the edge key, so we can skip existing matches if we want
edges map[int]string
image map[int]string
}
// edges are always defined clockwise starting from 0:north
// as long as there's only ever one match for an edge, I don't _think_ I need to keep track of piece orientation...?
func parseTile(header string, body []string) tile {
id, _ := strconv.Atoi(strings.Trim(header, "Tile :"))
t := tile{
id: id,
neighbors: make(map[int]int, 4),
edges: make(map[int]string, 4),
image: make(map[int]string, 8),
}
// make edges and extract picture data...
left := []byte{}
right := []byte{}
for i := range body {
left = append(left, body[i][0])
right = append(right, body[i][9])
// image has edges removed; still want 0-indexed for sanity later!
if i != 0 && i != 9 {
t.image[i-1] = body[i-1][1:9]
}
}
t.edges[0] = body[0]
t.edges[1] = string(right)
t.edges[2] = body[9]
t.edges[3] = string(left)
return t
}
// return is match, inverted
func testEdge(one, two string) (bool, bool) {
if one == two {
return true, false
}
// go doesn't have a built-in "string reversal" method, so we just check it the hard way
// 9 is our magic number for a 10-character string, which we know they all are.
// This might not be 100% sound for unicode (byte != rune), but our string is ASCII so it's... fine.
for i := range one {
if one[i] != two[9-i] {
return false, false
}
}
return true, true
}
// this is wasteful in that it happily checks edges that we've already determined neighbors for,
// but it's performant enough that I don't really care to fix it
func findPartners(id int, tiles map[int]tile) map[int]tile {
currentTile := tiles[id]
for tile := range tiles {
if tile != id {
for edgeID := range tiles[tile].edges {
for i := range currentTile.edges {
if ok, _ := testEdge(tiles[tile].edges[edgeID], currentTile.edges[i]); ok {
tiles[tile].neighbors[edgeID] = tiles[id].id
tiles[id].neighbors[i] = tiles[tile].id
}
}
}
}
}
return tiles
}
// Starting from a corner, we can "walk" through the image by:
// stepping to the next tile in a known direction
// checking where _that_ tile thinks we came from
// then re-orienting ourselves and stepping again.
// For example, if we take a corner and go to its 0 neighbor,
// and that neighbor thinks our previous tile was 3,
// a "straight line" along the edge would be 1. Repeat as necessary until you hit an edge.
// Once we hit an edge, return to the start of the row,
// step to its 90° neighbor, then walk parallel to the initial edge.
// Repeat until we reach the far corner.
// Additionally, we need to "line up" the edges, so there's some alignment checking that has to happen.
func mergeTiles(corner int, tiles map[int]tile) []string {
// just to make the counting a little easier
sideLength := int(math.Sqrt(float64(len(tiles))))
result := make([]string, sideLength*8)
headOfRow := tiles[corner]
currentRowDirection := 0
nextRowDirection := 0
// what an absurd way to "pick" between two keys in a two-key map
for i := range headOfRow.neighbors {
if currentRowDirection == 0 {
currentRowDirection = i
} else if nextRowDirection == 0 {
nextRowDirection = i
}
}
currentTile := tiles[corner]
image := imageFromEdge(currentTile, 0)
for row, s := range image {
result[row] = result[row] + s
}
for i := 0; i < sideLength; i = i + 1 {
nextID := currentTile.neighbors[currentRowDirection]
for e, id := range tiles[nextID].neighbors {
if id == currentTile.id {
_, invert := testEdge(currentTile.edges[currentRowDirection], tiles[nextID].edges[e])
image := imageFromEdge(tiles[nextID], (e+1)%4)
if !invert {
for row, s := range image {
result[row] = result[row] + s
}
} else {
for row, s := range image {
result[7-row] = result[7-row] + s
}
}
currentRowDirection = (e + 2) % 4 // flip 180 around the compass
}
}
currentTile = tiles[nextID]
}
return result
}
// returns the image "rotated" so that the provided edge is on "top" (the 0 position)
func imageFromEdge(t tile, edge int) map[int]string {
if edge == 1 {
newImage := make(map[int]string, 8)
for i := 0; i < 8; i = i + 1 {
b := []byte{t.image[0][7-i], t.image[1][7-i], t.image[2][7-i], t.image[3][7-i], t.image[4][7-i], t.image[5][7-i], t.image[6][7-i], t.image[7][7-i]}
newImage[i] = string(b)
}
return newImage
}
if edge == 2 {
newImage := make(map[int]string, 8)
for i := 0; i < 8; i = i + 1 {
b := []byte{t.image[7-i][7], t.image[7-i][6], t.image[7-i][5], t.image[7-i][4], t.image[7-i][3], t.image[7-i][2], t.image[7-i][1], t.image[7-i][0]}
newImage[i] = string(b)
}
return newImage
}
if edge == 3 {
newImage := make(map[int]string, 8)
for i := 0; i < 8; i = i + 1 {
b := []byte{t.image[7][i], t.image[6][i], t.image[5][i], t.image[4][i], t.image[3][i], t.image[2][i], t.image[1][i], t.image[0][i]}
newImage[i] = string(b)
}
return newImage
}
// edge == 0 does nothing
return t.image
}
func partOne() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
buffer := []string{}
tiles := make(map[int]tile)
for scanner.Scan() {
line := scanner.Text()
if line == "" {
tile := parseTile(buffer[0], buffer[1:])
tiles[tile.id] = tile
buffer = []string{}
} else {
buffer = append(buffer, line)
}
}
// catch that last tile
// weirdly, I'd think the trailing newline in the input would have done this for us
tile := parseTile(buffer[0], buffer[1:])
tiles[tile.id] = tile
corners := 1
for id := range tiles {
tiles = findPartners(id, tiles)
if len(tiles[id].neighbors) == 2 {
corners = corners * id
}
}
fmt.Println(corners)
}
func partTwo() {
f, _ := os.Open("testinput")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
buffer := []string{}
tiles := make(map[int]tile)
for scanner.Scan() {
line := scanner.Text()
if line == "" {
tile := parseTile(buffer[0], buffer[1:])
tiles[tile.id] = tile
buffer = []string{}
} else {
buffer = append(buffer, line)
}
}
// catch that last tile
tile := parseTile(buffer[0], buffer[1:])
tiles[tile.id] = tile
corner := 0
for id := range tiles {
tiles = findPartners(id, tiles)
// grab a corner to start the merge from
if len(tiles[id].neighbors) == 2 && corner == 0 {
corner = id
}
}
for _, line := range mergeTiles(corner, tiles) {
fmt.Println(line)
}
}

140
21/main.go Normal file
View File

@@ -0,0 +1,140 @@
package main
import (
"bufio"
"fmt"
"os"
"strings"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
type food struct {
allergens []string
ingredients []string
}
func intersection(a, b []string) []string {
// special-case starting out
if len(a) == 0 {
return b
} else if len(b) == 0 {
return a
}
result := []string{}
for _, s := range b {
for _, t := range a {
if s == t {
result = append(result, s)
}
}
}
return result
}
func countSafeIngredients(f food, allergens map[string][]string) int {
for c, i := range f.ingredients {
for _, l := range allergens {
for _, a := range l {
if i == a {
f.ingredients[c] = ""
}
}
}
}
result := 0
for _, i := range f.ingredients {
if i != "" {
result = result + 1
}
}
return result
}
func remove(ingredients []string, ingredient string) []string {
for i, j := range ingredients {
if j == ingredient {
ingredients[i] = ingredients[len(ingredients)-1]
ingredients[len(ingredients)-1] = ""
return ingredients[:len(ingredients)-1]
}
}
return ingredients
}
func partOne() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
foods := []food{}
allergens := map[string][]string{}
for scanner.Scan() {
line := strings.Split(scanner.Text(), " (contains ")
f := food{
ingredients: strings.Split(line[0], " "),
allergens: strings.Split(strings.TrimSuffix(line[1], ")"), ", "),
}
foods = append(foods, f)
for _, a := range f.allergens {
allergens[a] = intersection(f.ingredients, allergens[a])
}
}
sum := 0
for _, f := range foods {
sum = sum + countSafeIngredients(f, allergens)
}
fmt.Println(sum)
}
func partTwo() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
foods := []food{}
allergens := map[string][]string{}
for scanner.Scan() {
line := strings.Split(scanner.Text(), " (contains ")
f := food{
ingredients: strings.Split(line[0], " "),
allergens: strings.Split(strings.TrimSuffix(line[1], ")"), ", "),
}
foods = append(foods, f)
for _, a := range f.allergens {
allergens[a] = intersection(f.ingredients, allergens[a])
}
}
// hey, this is the same reduction logic as day 16!
trueAllergens := map[string]string{}
var lastAllergenSolved string
for i := 0; i < len(allergens); i = i + 1 {
for i, a := range allergens {
if len(a) == 1 {
trueAllergens[i] = a[0]
lastAllergenSolved = a[0]
}
}
for i := range allergens {
allergens[i] = remove(allergens[i], lastAllergenSolved)
}
}
formatted := "" // in theory we shouldn't hard-code this allergen order...
for _, a := range []string{"dairy", "eggs", "fish", "nuts", "peanuts", "sesame", "soy", "wheat"} {
formatted = formatted + trueAllergens[a] + ","
}
fmt.Println(strings.TrimSuffix(formatted, ","))
}

155
22/main.go Normal file
View File

@@ -0,0 +1,155 @@
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
type deck struct {
cards []int
}
type cacheKey struct {
one int
two int
}
func countWinningDeck(deck []int) int {
sum := 0
for i := range deck {
sum = sum + ((len(deck) - i) * deck[i])
}
return sum
}
// return values is who won/their score: true for player one, false for player two
func playCombat(one, two []int) (bool, int) {
round := 0
for len(one) > 0 && len(two) > 0 {
round = round + 1
if one[0] > two[0] {
one = append(one[1:], one[0], two[0])
two = two[1:]
} else if one[0] < two[0] {
two = append(two[1:], two[0], one[0])
one = one[1:]
}
}
if len(one) == 0 {
return false, countWinningDeck(two)
}
return true, countWinningDeck(one)
}
func playRecursiveCombat(one, two []int) (bool, int) {
loopCache := make(map[cacheKey]bool, 0)
round := 0
for len(one) > 0 && len(two) > 0 {
// check for loops
if loopCache[cacheKey{one: countWinningDeck(one), two: countWinningDeck(two)}] {
return true, countWinningDeck(one)
}
loopCache[cacheKey{one: countWinningDeck(one), two: countWinningDeck(two)}] = true
// otherwise, play a round
round = round + 1
if len(one[1:]) >= one[0] && len(two[1:]) >= two[0] {
// fuckin' go slice internals
subDeckOne := make([]int, len(one))
subDeckTwo := make([]int, len(two))
copy(subDeckOne, one)
copy(subDeckTwo, two)
winner, _ := playRecursiveCombat(subDeckOne[1:one[0]+1], subDeckTwo[1:two[0]+1])
if winner { // p1 wins
one = append(one[1:], one[0], two[0])
two = two[1:]
} else { // p2 wins
two = append(two[1:], two[0], one[0])
one = one[1:]
}
} else if one[0] > two[0] {
one = append(one[1:], one[0], two[0])
two = two[1:]
} else if one[0] < two[0] {
two = append(two[1:], two[0], one[0])
one = one[1:]
}
}
if len(one) == 0 {
return false, countWinningDeck(two)
}
return true, countWinningDeck(one)
}
func partOne() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
// deal the decks
playerone := deck{}
for scanner.Scan() {
line := scanner.Text()
if line == "" {
break
}
i, err := strconv.Atoi(line)
if err == nil {
playerone.cards = append(playerone.cards, i)
}
}
playertwo := deck{}
for scanner.Scan() {
line := scanner.Text()
i, err := strconv.Atoi(line)
if err == nil {
playertwo.cards = append(playertwo.cards, i)
}
}
// play a game and determine a winner
_, score := playCombat(playerone.cards, playertwo.cards)
fmt.Println(score)
}
func partTwo() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
// deal the decks
playerone := deck{}
for scanner.Scan() {
line := scanner.Text()
if line == "" {
break
}
i, err := strconv.Atoi(line)
if err == nil {
playerone.cards = append(playerone.cards, i)
}
}
playertwo := deck{}
for scanner.Scan() {
line := scanner.Text()
i, err := strconv.Atoi(line)
if err == nil {
playertwo.cards = append(playertwo.cards, i)
}
}
_, score := playRecursiveCombat(playerone.cards, playertwo.cards)
fmt.Println(score)
}

137
23/main.go Normal file
View File

@@ -0,0 +1,137 @@
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
func cupInList(cup int, cups []int) bool {
for _, c := range cups {
if c == cup {
return true
}
}
return false
}
func generateFullCupset(cups []int, totalCups int) []int {
for i := len(cups); i < totalCups; i = i + 1 {
cups = append(cups, i)
}
return cups
}
func findTargetCupIndex(cup int, cups []int) int {
for i, c := range cups {
if c == cup {
return i
}
}
return 0 // uh-oh
}
func step(cups []int) []int {
cupCount := len(cups)
currentCup := cups[0]
removedCups := cups[1:4]
newList := make([]int, 0, cupCount)
newList = append([]int{cups[0]}, cups[4:]...)
var targetCup int
if currentCup == 1 {
targetCup = cupCount
} else {
targetCup = currentCup - 1
}
for cupInList(targetCup, removedCups) {
targetCup = targetCup - 1
if targetCup == 0 {
targetCup = cupCount
}
}
tc := findTargetCupIndex(targetCup, newList)
result := make([]int, 0, cupCount)
result = append(result, newList[1:tc+1]...)
result = append(result, removedCups...)
result = append(result, newList[tc+1:]...)
result = append(result, newList[0])
return result
}
func runabout(cups []int) string {
oneIndex := findTargetCupIndex(1, cups)
result := ""
for i := oneIndex + 1; i < len(cups); i = i + 1 {
result = result + strconv.Itoa(cups[i])
}
for i := 0; i < len(cups[:oneIndex]); i = i + 1 {
result = result + strconv.Itoa(cups[i])
}
return result
}
func nextTwo(cups []int) (int, int) {
oneIndex := findTargetCupIndex(1, cups)
if oneIndex+1 > len(cups) {
return cups[0], cups[1]
}
if oneIndex+2 > len(cups) {
return cups[len(cups)], cups[0]
}
return cups[oneIndex+1], cups[oneIndex+2]
}
func partOne() {
f, _ := os.Open("testinput")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
cups := []int{}
scanner.Scan()
for _, s := range strings.Split(scanner.Text(), "") {
i, _ := strconv.Atoi(s)
cups = append(cups, i)
}
for i := 0; i < 100; i = i + 1 {
cups = step(cups)
}
fmt.Println(runabout(cups))
}
func partTwo() {
f, _ := os.Open("testinput")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
cups := []int{}
scanner.Scan()
for _, s := range strings.Split(scanner.Text(), "") {
i, _ := strconv.Atoi(s)
cups = append(cups, i)
}
allCups := generateFullCupset(cups, 10000000)
for i := 0; i < 10000000; i = i + 1 {
allCups = step(allCups)
fmt.Println(i)
}
fmt.Println(nextTwo(allCups))
}

186
24/main.go Normal file
View File

@@ -0,0 +1,186 @@
package main
import (
"bufio"
"fmt"
"os"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
// Our grid is a hex, so let's represent that as follows:
// x runs east-west, y runs north-south. Yes, these coordinates here are backward.
// 2,-2 2,0 2,2 2,4
// 1,-2 1,-1 1,1 1,2
// 0,-2 0,0 0,2 0,4
// -1,-2 -1,-1 -1,1 -1,2
// -2,-2 -2,0 -2,2 -2,4
// 0,0 -> e -> 0, 2
// -> w -> 0,-2
// -> se -> -1, 1
// -> sw -> -1,-1
// -> ne -> 1, 1
// -> nw -> 1,-1
// 0 == white, 1 == black
type tiles map[coord]int
type coord struct {
x int
y int
}
func walkGrid(directions []string) (int, int) {
var y, x int
for _, d := range directions {
switch d {
case "e":
x = x + 2
case "se":
y = y - 1
x = x + 1
case "sw":
y = y - 1
x = x - 1
case "w":
x = x - 2
case "ne":
y = y + 1
x = x + 1
case "nw":
y = y + 1
x = x - 1
}
}
return y, x
}
func parseLine(s string) []string {
directions := []string{}
for i := 0; i < len(s); {
switch s[i] {
case 'e':
directions = append(directions, "e")
i = i + 1
case 's':
if s[i+1] == 'e' {
directions = append(directions, "se")
i = i + 2
} else if s[i+1] == 'w' {
directions = append(directions, "sw")
i = i + 2
}
case 'w':
directions = append(directions, "w")
i = i + 1
case 'n':
if s[i+1] == 'e' {
directions = append(directions, "ne")
i = i + 2
} else if s[i+1] == 'w' {
directions = append(directions, "nw")
i = i + 2
}
}
}
return directions
}
func generation(t tiles) tiles {
newTiles := tiles{}
seenWhiteTiles := tiles{}
for coord, tile := range t {
// walk black tile list
if tile == 1 {
neighbors := getNeighbors(coord)
bn := 0
for _, n := range neighbors {
if t[n] == 0 {
seenWhiteTiles[n] = seenWhiteTiles[n] + 1
} else {
bn = bn + 1
}
}
// black tiles only survive if they have one or two black neighbors
if bn == 1 || bn == 2 {
newTiles[coord] = 1
}
}
}
// white tiles turn black if they have two black neighbors
for c, n := range seenWhiteTiles {
if n == 2 {
newTiles[c] = 1
}
}
return newTiles
}
func getNeighbors(c coord) []coord {
return []coord{
{x: c.x + 2, y: c.y}, // e
{x: c.x - 2, y: c.y}, // w
{x: c.x + 1, y: c.y + 1}, // ne
{x: c.x + 1, y: c.y - 1}, // nw
{x: c.x - 1, y: c.y + 1}, // se
{x: c.x - 1, y: c.y - 1}, // sw
}
}
func countBlackTiles(t tiles) int {
sum := 0
for _, tile := range t {
sum = sum + tile
}
return sum
}
func partOne() {
f, _ := os.Open("testinput")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
t := tiles{}
for scanner.Scan() {
line := scanner.Text()
x, y := walkGrid(parseLine(line))
if t[coord{x: x, y: y}] == 0 {
t[coord{x: x, y: y}] = 1
} else {
t[coord{x: x, y: y}] = 0
}
}
fmt.Println(countBlackTiles(t))
}
func partTwo() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
t := tiles{}
for scanner.Scan() {
line := scanner.Text()
y, x := walkGrid(parseLine(line))
if t[coord{x: x, y: y}] == 0 {
t[coord{x: x, y: y}] = 1
} else {
t[coord{x: x, y: y}] = 0
}
}
for day := 1; day <= 100; day = day + 1 {
t = generation(t)
}
fmt.Println(countBlackTiles(t))
}

54
25/main.go Normal file
View File

@@ -0,0 +1,54 @@
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
fmt.Printf("p1: %s, p2: n/a\n", duration)
}
func partOne() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
scanner.Scan()
doorPubKey, _ := strconv.Atoi(scanner.Text())
scanner.Scan()
cardPubKey, _ := strconv.Atoi(scanner.Text())
subjectNumber := 7
value := 1
magicPrime := 20201227
loopCount := 1
doorLoopCount := 0
cardLoopCount := 0
for {
value = (value * subjectNumber) % magicPrime
if value == doorPubKey {
doorLoopCount = loopCount
}
if value == cardPubKey {
cardLoopCount = loopCount
}
loopCount = loopCount + 1
if doorLoopCount != 0 && cardLoopCount != 0 {
break
}
}
value = 1
subjectNumber = cardPubKey
for i := 0; i < doorLoopCount; i = i + 1 {
value = (value * subjectNumber) % magicPrime
}
fmt.Println(value)
}

37
main.go.tmpl Normal file
View File

@@ -0,0 +1,37 @@
package main
import (
"bufio"
"fmt"
"os"
"time"
)
func main() {
start := time.Now()
partOne()
duration := time.Since(start)
partTwo()
duration2 := time.Since(start)
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
}
func partOne() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
// line := scanner.Text()
}
}
func partTwo() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
// line := scanner.Text()
}
}

7
new.sh Executable file
View File

@@ -0,0 +1,7 @@
#!/bin/bash
if [ "${1}" != "" ]; then
mkdir -p ${1}
touch ${1}/{input,testinput}
cp -n main.go.tmpl ${1}/main.go
fi

2
run.sh
View File

@@ -1,7 +1,7 @@
#!/bin/bash
if [ "${1}" != "" ]; then
cd ${01}
cd ${1}
go run *.go
cd - > /dev/null
fi