Compare commits

...

12 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
9 changed files with 1274 additions and 3 deletions

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)
}

6
new.sh
View File

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