This commit is contained in:
2022-12-23 19:45:56 -08:00
commit 8792e5275a
77 changed files with 31154 additions and 0 deletions

59
solutions/day1/day1.go Normal file
View File

@@ -0,0 +1,59 @@
package day1
import (
"bufio"
"fmt"
"os"
"sort"
"strconv"
)
func Run(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
var elf_totals []int
var current_total int
var max_total int
max_total = 0
for scanner.Scan() {
num, err := strconv.Atoi(scanner.Text())
if err == nil {
current_total += num
} else {
// this means we got a newline, I'd guess
elf_totals = append(elf_totals, current_total)
if current_total > max_total {
max_total = current_total
}
current_total = 0
}
}
if current_total > 0 {
elf_totals = append(elf_totals, current_total)
if current_total > max_total {
max_total = current_total
}
}
file.Close()
fmt.Println("Maximum food value is", max_total)
sort.Slice(elf_totals, func(i, j int) bool {
return elf_totals[i] > elf_totals[j]
})
fmt.Println("Top three items are", elf_totals[0:3])
fmt.Println("which adds to", elf_totals[0]+elf_totals[1]+elf_totals[2])
}

98
solutions/day10/day10.go Normal file
View File

@@ -0,0 +1,98 @@
package day10
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func clearLine(line *[]rune) {
for idx := range *line {
(*line)[idx] = '.'
}
}
func printLine(line *[]rune) {
for _, c := range *line {
fmt.Printf("%c", c)
}
fmt.Println()
}
func registerCycle(cycle int, x int, line *[]rune) int {
result := 0
var position int
for position = cycle - 1; position > 40; position -= 40 {
}
if cycle == 20 || (cycle > 20 && ((cycle-20)%40 == 0)) {
result = x * cycle
//fmt.Println("register cycle", cycle, "value of x is", x, "signal strength is", result)
}
//if position == 0 {
// fmt.Println("position is", position, "and x is", x)
//}
if position >= x-1 && position <= x+1 {
(*line)[position] = '#'
}
if position == 40 {
printLine(line)
clearLine(line)
}
return result
}
func Run(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
cycle := 1
x := 1
signalStrengthSum := 0
currentLine := []rune{'.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.', '.'}
if len(currentLine) != 40 {
fmt.Println("line length", len(currentLine))
return
}
registerCycle(cycle, x, &currentLine)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "addx ") {
cycle += 1
signalStrengthSum += registerCycle(cycle, x, &currentLine)
cycle += 1
mod, err := strconv.Atoi(string(line[5:]))
if err != nil {
fmt.Println("Invalid x modifier", err, "having read", string(line[5:]))
return
}
x += mod
signalStrengthSum += registerCycle(cycle, x, &currentLine)
} else if strings.HasPrefix(line, "noop") {
cycle += 1
signalStrengthSum += registerCycle(cycle, x, &currentLine)
} else {
fmt.Println("PANIC: Unknown instruction:", line)
return
}
}
file.Close()
//printLine(line)
fmt.Println("Signal strength fun", signalStrengthSum)
}

226
solutions/day11/day11.go Normal file
View File

@@ -0,0 +1,226 @@
package day11
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
type Operation int
const (
Times Operation = iota
Plus
Square
)
type Monkey struct {
items []int
operation Operation
operation_by int
divisible_by int
if_true int
if_false int
}
func printMonkeys(monkeys []*Monkey) {
for monkey_no, monkey := range monkeys {
if monkey != nil {
fmt.Printf("Monkey #%d: ", monkey_no)
var items []int = (*monkey).items
for idx, item := range items {
fmt.Printf("%d", item)
if idx != len(items)-1 {
fmt.Printf(", ")
}
}
fmt.Println()
}
}
}
func lcm(items []int) int {
worstCase := 1
smallest := items[0]
for _, val := range items {
worstCase *= val
if val < smallest {
smallest = val
}
}
retval := worstCase
for candidate := worstCase; candidate > 2; candidate -= smallest {
works := true
for _, val := range items {
works = works && (candidate%val == 0)
}
if works {
retval = candidate
}
}
return retval
}
func runRound(monkeys *[]*Monkey, counts *[]int, ceiling int) {
//three := big.NewInt(3)
for monkey_no, monkey := range *monkeys {
if monkey == nil {
break
}
//fmt.Printf("Monkey %d:\n", monkey_no)
items := (*monkey).items
(*monkey).items = []int{}
for _, item := range items {
(*counts)[monkey_no] += 1
//fmt.Printf(" Monkey inspects an item with a worry level of %d.\n", item)
var result int = item
switch monkey.operation {
case Times:
result = item * monkey.operation_by
//fmt.Printf(" Worry level is multiplied by %d to %d.\n", monkey.operation_by, result)
case Square:
result = item * item
//fmt.Printf(" Worry level is squared to %d.\n", result)
case Plus:
result = item + monkey.operation_by
//fmt.Printf(" Worry level is increased by %d to %d.\n", monkey.operation_by, result)
default:
//fmt.Printf("PANIC: Don't understand operation\n", monkey.operation_by)
return
}
result %= ceiling
//result = result.Div(result, three)
//fmt.Printf(" Monkey gets bored with item. Worry level is divided by 3 to %d.\n", result)
var targetMonkey int
if result%monkey.divisible_by == 0 {
//fmt.Printf(" Current worry level is divisible by %d.\n", monkey.divisible_by)
targetMonkey = monkey.if_true
} else {
//fmt.Printf(" Current worry level is not divisible by %d.\n", monkey.divisible_by)
targetMonkey = monkey.if_false
}
//fmt.Printf(" Item with worry level %d is thrown to monkey %d.\n", result, targetMonkey)
(*monkeys)[targetMonkey].items = append((*monkeys)[targetMonkey].items, result)
}
}
}
func Run(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
monkeys := []*Monkey{nil, nil, nil, nil, nil, nil, nil, nil}
inspectionCounts := []int{0, 0, 0, 0, 0, 0, 0, 0}
divisors := []int{}
var currentMonkey *Monkey = nil
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "Monkey ") {
number, err := strconv.Atoi(line[7 : len(line)-1])
if err != nil {
fmt.Println("Error parsing monkey number", line[7:len(line)-1])
return
}
newMonkey := Monkey{[]int{}, Times, 0, 0, 0, 0}
monkeys[number] = &newMonkey
currentMonkey = &newMonkey
} else if strings.HasPrefix(line, " Starting items: ") {
list := line[18:]
splits := strings.Split(list, ", ")
if currentMonkey == nil {
fmt.Println("Item list with no monkey")
return
}
for _, split := range splits {
splitInt, err := strconv.Atoi(split)
if err != nil {
fmt.Println("Error parsing monkey's item", split)
return
}
(*currentMonkey).items = append((*&currentMonkey).items, splitInt)
}
} else if strings.HasPrefix(line, " Operation: new = old * ") {
amtString := line[25:]
if amtString == "old" {
(*currentMonkey).operation = Square
} else {
amt, err := strconv.Atoi(amtString)
if err != nil {
fmt.Println("Error parsing operation number", amtString)
return
}
(*&currentMonkey).operation = Times
(*&currentMonkey).operation_by = amt
}
} else if strings.HasPrefix(line, " Operation: new = old + ") {
amtString := line[25:]
amt, err := strconv.Atoi(amtString)
if err != nil {
fmt.Println("Error parsing plus operation number", amtString)
return
}
(*&currentMonkey).operation = Plus
(*&currentMonkey).operation_by = amt
} else if strings.HasPrefix(line, " Test: divisible by ") {
amtString := line[21:]
amt, err := strconv.Atoi(amtString)
if err != nil {
fmt.Println("Error parsing divisible by number", amtString)
return
}
(*&currentMonkey).divisible_by = amt
divisors = append(divisors, amt)
} else if strings.HasPrefix(line, " If true: throw to monkey ") {
amtString := line[29:]
amt, err := strconv.Atoi(amtString)
if err != nil {
fmt.Println("Error parsing if true number", amtString)
return
}
(*&currentMonkey).if_true = amt
} else if strings.HasPrefix(line, " If false: throw to monkey ") {
amtString := line[30:]
amt, err := strconv.Atoi(amtString)
if err != nil {
fmt.Println("Error parsing if false number", amtString)
return
}
(*&currentMonkey).if_false = amt
} else if len(line) == 0 {
} else {
fmt.Printf("Invalid line: '%s'\n", line)
return
}
}
ceiling := lcm(divisors)
printMonkeys(monkeys)
for i := 0; i < 10000; i++ {
if i%100 == 0 {
fmt.Println("Running round", i)
}
runRound(&monkeys, &inspectionCounts, ceiling)
//printMonkeys(monkeys)
}
for monkey_no, count := range inspectionCounts {
fmt.Printf("Monkey %d inspected items %d times\n", monkey_no, count)
}
file.Close()
}

174
solutions/day12/day12.go Normal file
View File

@@ -0,0 +1,174 @@
package day12
import (
"bufio"
"fmt"
"os"
)
type Point struct {
x int
y int
}
func to_level(r rune) int {
return int(r - 'a')
}
func from_level(l int) rune {
return rune('a' + l)
}
func validMove(p1 Point, p2 Point, grid [][]int) bool {
if p2.x < 0 || p2.y < 0 || p2.x >= len(grid[0]) || p2.y >= len(grid) {
return false
}
diff := grid[p2.y][p2.x] - grid[p1.y][p1.x]
return (diff <= 1)
}
func updateDistances(grid [][]int, distances [][]int) bool {
y := 0
updatedSomething := false
for _, row := range distances {
for x := range row {
best := distances[y][x]
nexts := nextSteps(Point{x, y}, grid)
for _, next := range nexts {
if distances[next.y][next.x] != -1 {
if best == -1 || distances[next.y][next.x]+1 < best {
fmt.Println("Found a live one; at", Point{x, y}, "with score", distances[y][x], "going to", next, "with score", distances[next.y][next.x])
best = distances[next.y][next.x] + 1
updatedSomething = true
}
}
}
distances[y][x] = best
}
y += 1
}
return updatedSomething
}
func nextSteps(p Point, grid [][]int) []Point {
retval := []Point{}
// point above
above := Point{p.x, p.y - 1}
if validMove(p, above, grid) {
retval = append(retval, above)
}
// point below
below := Point{p.x, p.y + 1}
if validMove(p, below, grid) {
retval = append(retval, below)
}
// point left
left := Point{p.x - 1, p.y}
if validMove(p, left, grid) {
retval = append(retval, left)
}
// point right
right := Point{p.x + 1, p.y}
if validMove(p, right, grid) {
retval = append(retval, right)
}
return retval
}
func printMap(grid [][]int) {
for _, line := range grid {
for _, v := range line {
fmt.Printf("%c", from_level(v))
}
fmt.Println()
}
}
func printDistances(grid [][]int, distances [][]int) {
for y, line := range distances {
for x, v := range line {
fmt.Printf("%5d/%c", v, from_level(grid[y][x]))
}
fmt.Println()
}
}
func Run(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
grid := [][]int{}
distances := [][]int{}
var start Point
var end Point
for scanner.Scan() {
line := scanner.Text()
row := []int{}
drow := []int{}
y := len(grid)
for x, r := range []rune(line) {
if r == 'S' {
r = 'a'
start = Point{x, y}
}
if r == 'E' {
r = 'z'
end = Point{x, y}
}
row = append(row, to_level(r))
drow = append(drow, -1)
}
grid = append(grid, row)
distances = append(distances, drow)
}
file.Close()
distances[end.y][end.x] = 0
printMap(grid)
printDistances(grid, distances)
fmt.Printf("Start position is (%d, %d)\n", start.x, start.y)
fmt.Printf("End position is (%d, %d)\n", end.x, end.y)
fmt.Println("Valid first steps:", nextSteps(start, grid))
iteration := 0
keepGoing := true
for keepGoing {
fmt.Println("Running iteration", iteration)
printDistances(grid, distances)
keepGoing = updateDistances(grid, distances)
iteration += 1
}
fmt.Printf("Best path is %d steps long.\n", distances[start.y][start.x])
closestAIs := -1
for y, line := range grid {
for x, v := range line {
if from_level(v) == 'a' {
distance := distances[y][x]
fmt.Printf("Point (%d, %d) is level `a`, and is %d steps from end.\n", x, y, distance)
if distance != -1 {
if closestAIs == -1 || distance < closestAIs {
closestAIs = distance
}
}
}
}
}
fmt.Println("The closest 'a' is", closestAIs, "steps away.")
}

208
solutions/day13/day13.go Normal file
View File

@@ -0,0 +1,208 @@
package day13
import (
"bufio"
"fmt"
"os"
"sort"
"strings"
)
type Element struct {
isNumber bool
number int
sublist []Element
}
func parseElement(line string) (retval Element, remainder string, err error) {
line = strings.TrimSpace(line)
//fmt.Printf("Working on line: '%s'\n", line)
if line[0] == '[' {
sublist := []Element{}
if line[1] == ']' {
retval = Element{false, 0, sublist}
remainder = line[2:]
return
}
work := line[1:]
for {
element, rest, suberr := parseElement(work)
//fmt.Println("Back from subcall with element", element, "rest", rest, "suberr", suberr)
if suberr != nil {
err = fmt.Errorf("suberror: %s", suberr)
return
}
sublist = append(sublist, element)
if rest[0] == ',' {
work = rest[1:]
} else if rest[0] == ']' {
work = rest[1:]
break
} else {
err = fmt.Errorf("don't know how to deal with list that ends with '%s'", rest)
return
}
}
retval = Element{false, 0, sublist}
remainder = work
return
} else if line[0] >= '0' && line[0] <= '9' {
var computed int
for computed = 0; line[0] >= '0' && line[0] <= '9'; line = line[1:] {
computed = (computed * 10) + int(line[0]-'0')
}
retval = Element{true, computed, nil}
remainder = line
return
} else {
err = fmt.Errorf("panic: Don't know how to parse line element '%s'", line)
return
}
}
func renderElement(element Element) string {
if element.isNumber {
return fmt.Sprintf("%d", element.number)
} else {
retval := "["
if len(element.sublist) > 0 {
retval += renderElement(element.sublist[0])
}
for i := 1; i < len(element.sublist); i++ {
retval += fmt.Sprintf(",%s", renderElement(element.sublist[i]))
}
retval += "]"
return retval
}
}
type OrderResult int
const (
ProperlyOrdered OrderResult = iota
ImproperlyOrdered
Unknown
)
func compareElements(a Element, b Element) OrderResult {
if a.isNumber && b.isNumber {
return compareNumbers(a.number, b.number)
} else if a.isNumber && !b.isNumber {
return compareLists([]Element{{true, a.number, nil}}, b.sublist)
} else if !a.isNumber && b.isNumber {
return compareLists(a.sublist, []Element{{true, b.number, nil}})
} else {
return compareLists(a.sublist, b.sublist)
}
}
func compareNumbers(a int, b int) OrderResult {
if a < b {
return ProperlyOrdered
}
if a > b {
return ImproperlyOrdered
}
return Unknown
}
func compareLists(a []Element, b []Element) OrderResult {
if len(a) > 0 && len(b) > 0 {
headResult := compareElements(a[0], b[0])
if headResult != Unknown {
return headResult
}
return compareLists(a[1:], b[1:])
}
if len(a) == 0 && len(b) == 0 {
return Unknown
}
if len(a) == 0 {
return ProperlyOrdered
}
return ImproperlyOrdered
}
func Run(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
elements := []Element{}
for scanner.Scan() {
line := scanner.Text()
if len(line) == 0 {
continue
}
element, remainder, err := parseElement(line)
if err != nil {
fmt.Println(err)
return
}
if len(remainder) > 0 {
fmt.Printf("Unexpected remainder in line: '%s'\n", remainder)
return
}
fmt.Println("Parsed element", renderElement(element))
elements = append(elements, element)
}
total := 0
for i := 0; i < len(elements); i += 2 {
index := i/2 + 1
switch compareElements(elements[i], elements[i+1]) {
case ProperlyOrdered:
fmt.Printf("Pair #%d is properly ordered\n", index)
total += index
case ImproperlyOrdered:
fmt.Printf("Pair #%d is NOT properly ordered\n", index)
case Unknown:
fmt.Printf("Pair #%d is UNKNOWN\n", index)
default:
fmt.Printf("PANIC PANIC PANIC: Unknown ordering?!\n")
}
}
fmt.Println("\nTotal proper index value is", total)
divider2, _, _ := parseElement("[[2]]")
divider6, _, _ := parseElement("[[6]]")
elements = append(elements, divider2, divider6)
sort.SliceStable(elements, func(i, j int) bool {
return compareElements(elements[i], elements[j]) == ProperlyOrdered
})
fmt.Println()
fmt.Println("Sorted order:")
decoderKey := 1
for i := 0; i < len(elements); i++ {
fmt.Println(renderElement(elements[i]))
if compareElements(elements[i], divider2) == Unknown || compareElements(elements[i], divider6) == Unknown {
decoderKey *= i + 1
}
}
fmt.Println("Decoder key is", decoderKey)
file.Close()
}

295
solutions/day14/day14.go Normal file
View File

@@ -0,0 +1,295 @@
package day14
import (
"bufio"
"errors"
"fmt"
"os"
"strconv"
"strings"
)
type Point struct {
x int
y int
}
func parsePoint(v string) (result Point, err error) {
var parts []string = strings.Split(v, ",")
if len(parts) != 2 {
err = errors.New("Weird number of parts in point")
return
}
x, xerr := strconv.Atoi(parts[0])
if xerr != nil {
err = xerr
return
}
y, yerr := strconv.Atoi(parts[1])
if yerr != nil {
err = yerr
return
}
result = Point{x, y}
return
}
type Segment struct {
start Point
end Point
}
func parseSegments(line string) (result []Segment, err error) {
parts := strings.Split(line, " -> ")
result = []Segment{}
var left *Point = nil
for _, rightString := range parts {
right, rerr := parsePoint(rightString)
if rerr != nil {
err = rerr
return
}
if left != nil {
result = append(result, Segment{*left, right})
}
left = &right
}
return
}
func segmentPoints(segments []Segment) []Point {
retval := []Point{}
for _, segment := range segments {
retval = append(retval, segment.start, segment.end)
}
return retval
}
func findExtents(segments []Segment) (leftmost int, rightmost int, bottom int) {
leftmost = int(^uint(0) >> 1)
rightmost = 0
bottom = 0
for _, point := range segmentPoints(segments) {
if point.x < leftmost {
leftmost = point.x
}
if point.x > rightmost {
rightmost = point.x
}
if point.y > bottom {
bottom = point.y
}
}
return
}
type Cell int
const (
Air Cell = iota
Stone
Sand
Start
)
func drawGraph(graph [][]Cell) {
for _, line := range graph {
for _, v := range line {
switch v {
case Air:
fmt.Printf(".")
case Stone:
fmt.Printf("#")
case Sand:
fmt.Printf("O")
case Start:
fmt.Printf("+")
default:
fmt.Printf("?")
}
}
fmt.Println()
}
fmt.Println()
}
type State int
const (
Good State = iota
Stop
Done
)
func nextStep(curX int, curY int, graph [][]Cell) (nextX int, nextY int, state State) {
if curY+1 >= len(graph) {
state = Done
return
}
if graph[curY+1][curX] == Air {
nextX = curX
nextY = curY + 1
state = Good
return
}
if curX == 0 {
state = Done
return
}
if graph[curY+1][curX-1] == Air {
nextX = curX - 1
nextY = curY + 1
state = Good
return
}
if curX+1 >= len(graph[0]) {
state = Done
return
}
if graph[curY+1][curX+1] == Air {
nextX = curX + 1
nextY = curY + 1
state = Good
return
}
state = Stop
return
}
func dropGrain(startX int, graph [][]Cell) bool {
curX := startX
curY := 0
for true {
nextX, nextY, result := nextStep(curX, curY, graph)
switch result {
case Done:
return true
case Stop:
result := graph[curY][curX] == Start
graph[curY][curX] = Sand
return result
case Good:
curX = nextX
curY = nextY
}
}
return true
}
func Run(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
segments := []Segment{}
for scanner.Scan() {
line := scanner.Text()
newSegments, err := parseSegments(line)
if err != nil {
fmt.Println("PANIC", err)
return
}
for _, segment := range newSegments {
segments = append(segments, segment)
}
}
file.Close()
fmt.Println(segments)
leftmostX, rightmostX, bottomY := findExtents(segments)
fmt.Println("Leftmost X value is", leftmostX)
fmt.Println("Rightmost X value is", rightmostX)
fmt.Println("Bottom Y value is", bottomY)
leftmostX = 0
rightmostX += 500
width := rightmostX - leftmostX
graph := [][]Cell{}
for y := 0; y <= bottomY+2; y++ {
line := []Cell{}
for x := 0; x <= width; x++ {
line = append(line, Air)
}
graph = append(graph, line)
}
segments = append(segments, Segment{Point{leftmostX, bottomY + 2}, Point{rightmostX, bottomY + 2}})
for _, segment := range segments {
if segment.start.x == segment.end.x {
var top int
var bottom int
if segment.start.y < segment.end.y {
top = segment.start.y
bottom = segment.end.y
} else {
top = segment.end.y
bottom = segment.start.y
}
for current := top; current <= bottom; current++ {
graph[current][segment.start.x-leftmostX] = Stone
}
continue
}
if segment.start.y == segment.end.y {
var left int
var right int
if segment.start.x < segment.end.x {
left = segment.start.x
right = segment.end.x
} else {
left = segment.end.x
right = segment.start.x
}
for current := left; current <= right; current++ {
graph[segment.start.y][current-leftmostX] = Stone
}
continue
}
fmt.Println("PANIC: Diagonal line!?")
return
}
graph[0][500-leftmostX] = Start
drawGraph(graph)
count := 0
for !dropGrain(500-leftmostX, graph) {
count += 1
}
drawGraph(graph)
fmt.Println("Dropped", count, "grains of sand")
if graph[0][500-leftmostX] != Sand {
fmt.Println("PANIC: NOT WIDE ENOUGH")
}
}

182
solutions/day15/day15.go Normal file
View File

@@ -0,0 +1,182 @@
package day15
import (
"bufio"
"fmt"
"os"
"strconv"
)
type Point struct {
x int
y int
}
func pointDistance(a Point, b Point) int {
return diff(a.x, b.x) + diff(a.y, b.y)
}
func diff(a int, b int) int {
if a > b {
return a - b
} else {
return b - a
}
}
type Sensor struct {
location Point
distance int
}
func parseLine(line string) (sensor Sensor, beacon Point) {
var sensorX int
var sensorY int
var beaconX int
var beaconY int
fmt.Sscanf(line, "Sensor at x=%d, y=%d: closest beacon is at x=%d, y=%d", &sensorX, &sensorY, &beaconX, &beaconY)
sensorLoc := Point{sensorX, sensorY}
beacon = Point{beaconX, beaconY}
distance := pointDistance(sensorLoc, beacon)
sensor = Sensor{sensorLoc, distance}
return
}
func isBeacon(x int, y int, beacons []Point) bool {
for _, beacon := range beacons {
if beacon.x == x && beacon.y == y {
return true
}
}
return false
}
func inSensorRange(x int, y int, sensors []Sensor) *Sensor {
point := Point{x, y}
for _, sensor := range sensors {
distance := pointDistance(point, sensor.location)
if distance <= sensor.distance {
return &sensor
}
}
return nil
}
func leftmostPoint(sensors []Sensor) int {
leftmost := 0
for _, sensor := range sensors {
candidate := sensor.location.x - sensor.distance
if candidate < leftmost {
leftmost = candidate
}
}
return leftmost
}
func rightmostPoint(sensors []Sensor) int {
rightmost := 0
for _, sensor := range sensors {
candidate := sensor.location.x + sensor.distance
if candidate > rightmost {
rightmost = candidate
}
}
return rightmost
}
func addLinePoints(pointSet *[]Point, start Point, end Point) {
offsetX := 1
offsetY := 1
if end.x < start.x {
offsetX = -1
}
if end.y < start.y {
offsetY = -1
}
for x, y := start.x, start.y; y != end.y; x, y = x+offsetX, y+offsetY {
*pointSet = append(*pointSet, Point{x, y})
}
}
func addEdgePoints(pointSet *[]Point, sensor Sensor) {
distance := sensor.distance + 1
top := Point{sensor.location.x, sensor.location.y - distance}
right := Point{sensor.location.x + distance, sensor.location.y}
bottom := Point{sensor.location.x, sensor.location.y + distance}
left := Point{sensor.location.x - distance, sensor.location.y}
addLinePoints(pointSet, top, right)
addLinePoints(pointSet, right, bottom)
addLinePoints(pointSet, bottom, left)
addLinePoints(pointSet, left, top)
}
func Run(filename string, yStr string, maxCoordStr string) {
y, err := strconv.Atoi(yStr)
if err != nil {
fmt.Println("PANIC: bad y argument", yStr)
return
}
maxCoord, err := strconv.Atoi(maxCoordStr)
if err != nil {
fmt.Println("PANIC: bad y argument", maxCoordStr)
return
}
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
sensors := []Sensor{}
beacons := []Point{}
for scanner.Scan() {
line := scanner.Text()
sensor, beacon := parseLine(line)
sensors = append(sensors, sensor)
beacons = append(beacons, beacon)
}
file.Close()
fmt.Println(sensors)
count := 0
for x := leftmostPoint(sensors); x <= rightmostPoint(sensors); x++ {
if isBeacon(x, y, beacons) {
continue
}
if inSensorRange(x, y, sensors) != nil {
count += 1
}
}
fmt.Println("Beacons couldn't be in", count, "places on line", y)
candidates := []Point{}
for _, sensor := range sensors {
addEdgePoints(&candidates, sensor)
}
for _, candidate := range candidates {
if candidate.x >= 0 && candidate.y >= 0 && candidate.x <= maxCoord && candidate.y <= maxCoord {
closestSensor := inSensorRange(candidate.x, candidate.y, sensors)
if closestSensor == nil {
fmt.Printf("Found possible beacon location at (%d, %d). Frequency value is %d\n", candidate.x, candidate.y, candidate.x*4000000+candidate.y)
}
}
}
}

185
solutions/day16/day16.go Normal file
View File

@@ -0,0 +1,185 @@
package day16
import (
"bufio"
"fmt"
"os"
"strings"
"unicode"
)
type Valve struct {
flowRate int
leadsTo []string
}
type SimulationPoint struct {
time int
imAt string
elephantAt string
opened string
}
type MovePair struct {
me string
elephant string
}
func newMovePair(useElephant bool, a string, b string) MovePair {
if useElephant && a != "**" && b != "**" && b < a {
return MovePair{b, a}
} else {
return MovePair{a, b}
}
}
func addMovePair(pair MovePair, list *[]MovePair) {
for _, existing := range *list {
if existing.me == pair.me && existing.elephant == pair.elephant {
return
}
}
*list = append(*list, pair)
}
func alreadyVisited(place string, point SimulationPoint) bool {
for i := 0; i < len(point.opened); i += 2 {
if place[0] == point.opened[i] && place[1] == point.opened[i+1] {
return true
}
}
return false
}
func addPlace(place string, opened string) string {
injectionPoint := 0
for injectionPoint < len(opened) && (opened[injectionPoint] < place[0] || (opened[injectionPoint] == place[0] && opened[injectionPoint+1] < place[1])) {
injectionPoint += 2
}
return opened[0:injectionPoint] + place + opened[injectionPoint:]
}
var everythingOpened string
var simulatedItems map[SimulationPoint]int
func runSimulations(useElephant bool, time int, myPlace string, elephantAt string, opened string, valves map[string]Valve) int {
maxTime := 30
if useElephant {
maxTime = 26
}
if time == maxTime || everythingOpened == opened {
return 0
}
point := SimulationPoint{time, myPlace, elephantAt, opened}
if previousValue, ok := simulatedItems[point]; ok {
return previousValue
}
bestResult := 0
myMoves := []string{}
if !alreadyVisited(myPlace, point) && valves[myPlace].flowRate != 0 {
myMoves = append(myMoves, "**")
}
myMoves = append(myMoves, valves[myPlace].leadsTo...)
elephantMoves := []string{"AA"}
if useElephant {
elephantMoves = []string{}
if !alreadyVisited(elephantAt, point) && valves[elephantAt].flowRate != 0 && myPlace != elephantAt {
elephantMoves = append(elephantMoves, "**")
}
elephantMoves = append(elephantMoves, valves[elephantAt].leadsTo...)
}
moves := []MovePair{}
for _, myMove := range myMoves {
for _, elephantMove := range elephantMoves {
pair := newMovePair(useElephant, myMove, elephantMove)
addMovePair(pair, &moves)
}
}
for _, pair := range moves {
myMove := pair.me
elephantMove := pair.elephant
openReleases := 0
myNext := myPlace
elephantNext := elephantAt
nowOpen := opened
if myMove == "**" {
openReleases += valves[myPlace].flowRate * (maxTime - (time + 1))
nowOpen = addPlace(myPlace, nowOpen)
} else {
myNext = myMove
}
if elephantMove == "**" {
openReleases += valves[elephantAt].flowRate * (maxTime - (time + 1))
nowOpen = addPlace(elephantAt, nowOpen)
} else {
elephantNext = elephantMove
}
candidate := openReleases + runSimulations(useElephant, time+1, myNext, elephantNext, nowOpen, valves)
if candidate > bestResult {
bestResult = candidate
}
}
simulatedItems[point] = bestResult
return bestResult
}
func Run(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
valveMap := map[string]Valve{}
everythingOpened = ""
for scanner.Scan() {
line := scanner.Text()
var valve string
var rate int
fmt.Sscanf(line, "Valve %s has flow rate=%d", &valve, &rate)
_, after_semi, found_semi := strings.Cut(line, ";")
if !found_semi {
fmt.Println("PANIC: Couldn't find semi colon in line.")
return
}
after_semi = strings.TrimLeftFunc(after_semi, func(r rune) bool {
return !unicode.IsUpper(r)
})
targets := strings.Split(after_semi, ", ")
if rate > 0 {
everythingOpened = addPlace(valve, everythingOpened)
}
valveMap[valve] = Valve{rate, targets}
}
simulatedItems = map[SimulationPoint]int{}
bestRelease1 := runSimulations(false, 0, "AA", "AA", "", valveMap)
fmt.Println("Working on my own, best pressure release found is", bestRelease1)
simulatedItems = map[SimulationPoint]int{}
bestRelease2 := runSimulations(true, 0, "AA", "AA", "", valveMap)
fmt.Println("Working with an elephant, best pressure release found is", bestRelease2)
file.Close()
}

301
solutions/day17/day17.go Normal file
View File

@@ -0,0 +1,301 @@
package day17
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
type Piece []string
var horizontalBar Piece = Piece{"@@@@"}
var plus Piece = Piece{".@.", "@@@", ".@."}
var backwardsL Piece = Piece{"..@", "..@", "@@@"}
var verticalBar Piece = Piece{"@", "@", "@", "@"}
var block Piece = Piece{"@@", "@@"}
var pieces []Piece = []Piece{horizontalBar, plus, backwardsL, verticalBar, block}
type Board [][]rune
func newBoard() Board {
return Board{}
}
func pieceToRows(piece Piece) Board {
retval := Board{}
for i := range piece {
newRow := append([]rune(".."), []rune(piece[i])...)
for len(newRow) < 7 {
newRow = append(newRow, '.')
}
retval = append(retval, newRow)
}
return retval
}
func rowEmpty(row int, board Board) bool {
if row >= len(board) {
return false
}
for i := 0; i < 7; i++ {
if board[row][i] != '.' {
return false
}
}
return true
}
func addPiece(piece Piece, board *Board) {
rows := pieceToRows(piece)
for !(rowEmpty(0, *board) && rowEmpty(1, *board) && rowEmpty(2, *board)) {
*board = append(Board{[]rune(".......")}, *board...)
}
for len(*board) >= 3 && rowEmpty(3, *board) {
*board = (*board)[1:]
}
*board = append(rows, *board...)
}
func printBoard(board *Board) {
for _, row := range *board {
fmt.Printf("|")
for _, r := range row {
fmt.Printf("%c", r)
}
fmt.Printf("|\n")
}
fmt.Printf("+-------+\n")
}
func copyBoard(clearPiece bool, currentBoard *Board, newBoard *Board) {
for len(*newBoard) < len(*currentBoard) {
*newBoard = append(Board{[]rune(".......")}, *newBoard...)
}
for len(*newBoard) > len(*currentBoard) {
*newBoard = (*newBoard)[1:]
}
for y, row := range *currentBoard {
for x, value := range row {
if clearPiece && value == '@' {
(*newBoard)[y][x] = '.'
} else {
(*newBoard)[y][x] = (*currentBoard)[y][x]
}
}
}
}
func movePiece(direction rune, currentBoard *Board, newBoard *Board) bool {
/// match the values
copyBoard(true, currentBoard, newBoard)
for y, row := range *currentBoard {
for x, value := range row {
if value == '@' {
newX := x
newY := y
switch direction {
case '<':
newX = x - 1
case '>':
newX = x + 1
case 'v':
newY = y + 1
default:
fmt.Printf("PANIC: Invalid direction %c\n", direction)
return false
}
if newX < 0 || newX >= 7 || newY >= len(*currentBoard) {
return false
}
if (*currentBoard)[newY][newX] == '#' {
return false
}
(*newBoard)[newY][newX] = '@'
}
}
}
return true
}
func freezeBoard(board *Board) int {
for _, row := range *board {
for x, value := range row {
if value == '@' {
row[x] = '#'
}
}
}
for y := 0; y < len(*board)-3; y++ {
allCovered := true
for x := 0; x < 7; x++ {
allCovered = allCovered && ((*board)[y][x] == '#' || (*board)[y+1][x] == '#' || (*board)[y+2][x] == '#' || (*board)[y+3][x] == '#')
}
if allCovered {
originalLength := len(*board)
//fmt.Println("--- original board ---")
//printBoard(board)
*board = (*board)[0 : y+4]
//fmt.Println("--- cleaned board ---")
//printBoard(board)
//fmt.Println("---", originalLength-len(*board), "rows removed ---")
removedRows := originalLength - len(*board)
//if removedRows > 0 {
// fmt.Println("Removed", removedRows, "rows.")
//}
return removedRows
}
}
return 0
}
func sameBoard(board1 Board, board2 Board) bool {
if len(board1) != len(board2) {
return false
}
for y, row := range board1 {
for x := range row {
if board1[y][x] != board2[y][x] {
return false
}
}
}
return true
}
func duplicateBoard(board Board) Board {
newBoard := Board{}
for _, row := range board {
newRow := []rune(".......")
copy(newRow, row)
newBoard = append(newBoard, newRow)
}
return newBoard
}
type PreviousState struct {
moveIndex int
pieceIndex int
}
type PreviousLocation struct {
dropNo int
removedRows int
board Board
}
func Run(filename string, targetStr string) {
targetPieces, err := strconv.Atoi(targetStr)
if err != nil {
fmt.Println("PANIC: Don't understand target number")
return
}
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
instructions := []rune{}
for scanner.Scan() {
line := scanner.Text()
line = strings.TrimFunc(line, func(r rune) bool {
return r != '>' && r != '<'
})
instructions = append(instructions, []rune(line)...)
}
file.Close()
board1 := newBoard()
board2 := newBoard()
moveIndex := 0
pieceIndex := 0
previousStates := map[PreviousState][]PreviousLocation{}
removedRows := 0
skippedAhead := false
for dropNo := 1; dropNo <= targetPieces; dropNo++ {
//fmt.Println("Dropping piece", dropNo, "with piece", pieceIndex, "and move", moveIndex)
if !skippedAhead {
state := PreviousState{moveIndex, pieceIndex}
previousLocations, previousStateExisted := previousStates[state]
if previousStateExisted {
for _, location := range previousLocations {
if sameBoard(board1, location.board) {
dropDifference := dropNo - location.dropNo
removedRowsDifference := removedRows - location.removedRows
fmt.Println("Got it, current drop", dropNo, "previous version", location.dropNo, "removed row diff", removedRowsDifference)
repeats := (targetPieces - dropNo) / dropDifference
fmt.Println("Drop difference of", dropDifference, "meaning we can do this gap", repeats, "more times")
fmt.Println("removed rows", removedRows, "+", repeats*removedRowsDifference, "=", removedRows+(repeats*removedRowsDifference))
removedRows += repeats * removedRowsDifference
fmt.Println("Drop number", dropNo, "+", repeats*dropDifference, "=", dropNo+(repeats*dropDifference))
dropNo += repeats*dropDifference - 1
skippedAhead = true
break
}
}
if skippedAhead {
continue
} else {
previousStates[state] = append(previousStates[state], PreviousLocation{dropNo, removedRows, duplicateBoard(board1)})
}
} else {
previousStates[state] = []PreviousLocation{{dropNo, removedRows, duplicateBoard(board1)}}
}
}
addPiece(pieces[pieceIndex], &board1)
for {
//fmt.Println("Move", moveIndex, "is", string(instructions[moveIndex]))
if !movePiece(instructions[moveIndex], &board1, &board2) {
copyBoard(false, &board1, &board2)
}
moveIndex = (moveIndex + 1) % len(instructions)
if !movePiece('v', &board2, &board1) {
copyBoard(false, &board2, &board1)
break
}
}
removedRows += freezeBoard(&board1)
pieceIndex = (pieceIndex + 1) % len(pieces)
}
//printBoard(&board1)
fmt.Println("Removed", removedRows, "rows.")
rowsWithRocks := removedRows
for i := 0; i < len(board1); i++ {
if !rowEmpty(i, board1) {
rowsWithRocks += 1
}
}
fmt.Println("Rocks are", rowsWithRocks, "rows tall.")
}

148
solutions/day18/day18.go Normal file
View File

@@ -0,0 +1,148 @@
package day18
import (
"bufio"
"fmt"
"os"
)
type Point struct {
x int
y int
z int
}
const (
Lava int = iota
Air
Steam
)
func surroundingPoints(point Point) []Point {
return []Point{
{point.x - 1, point.y, point.z},
{point.x + 1, point.y, point.z},
{point.x, point.y - 1, point.z},
{point.x, point.y + 1, point.z},
{point.x, point.y, point.z - 1},
{point.x, point.y, point.z + 1},
}
}
func countBlankSides(points map[Point]int, against int) int {
blankSides := 0
for point, original := range points {
if original == Lava {
pointBlankSides := 0
for _, surrounding := range surroundingPoints(point) {
if points[surrounding] == against {
pointBlankSides += 1
}
}
//fmt.Println("Point", point, "had", pointBlankSides, "blank sides.")
blankSides += pointBlankSides
}
}
return blankSides
}
func inUniverse(point Point, min Point, max Point) bool {
return point.x >= min.x && point.x <= max.x &&
point.y >= min.y && point.y <= max.y &&
point.z >= min.z && point.z <= max.x
}
func floodSteam(minPoint Point, maxPoint Point, points map[Point]int) {
stack := []Point{minPoint}
for len(stack) > 0 {
nextIdx := len(stack) - 1
next := stack[nextIdx]
stack = stack[0:nextIdx]
if inUniverse(next, minPoint, maxPoint) && points[next] == Air {
points[next] = Steam
stack = append(stack, surroundingPoints(next)...)
}
}
}
func findExtendedBoundingBox(points map[Point]int) (minPoint Point, maxPoint Point) {
minPoint = Point{1000000, 100000, 100000}
maxPoint = Point{0, 0, 0}
for point := range points {
if point.x < minPoint.x {
minPoint.x = point.x
}
if point.x > maxPoint.x {
maxPoint.x = point.x
}
if point.y < minPoint.y {
minPoint.y = point.y
}
if point.y > maxPoint.y {
maxPoint.y = point.y
}
if point.z < minPoint.z {
minPoint.z = point.z
}
if point.z > maxPoint.z {
maxPoint.z = point.z
}
}
fmt.Println("Min point", minPoint)
fmt.Println("Max point", maxPoint)
minPoint.x -= 1
minPoint.y -= 1
minPoint.z -= 1
maxPoint.x += 1
maxPoint.y += 1
maxPoint.z += 1
return
}
func Run(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
points := map[Point]int{}
for scanner.Scan() {
line := scanner.Text()
var x int
var y int
var z int
fmt.Sscanf(line, "%d,%d,%d", &x, &y, &z)
point := Point{x, y, z}
points[point] = Lava
}
file.Close()
minPoint, maxPoint := findExtendedBoundingBox(points)
for x := minPoint.x; x <= maxPoint.x; x++ {
for y := minPoint.y; y <= maxPoint.y; y++ {
for z := minPoint.z; z <= maxPoint.z; z++ {
point := Point{x, y, z}
_, exists := points[point]
if !exists {
points[point] = Air
}
}
}
}
fmt.Println("Found", countBlankSides(points, Air), "blank sides in base solid.")
floodSteam(minPoint, maxPoint, points)
fmt.Println("Found", countBlankSides(points, Steam), "blank sides after filling interior.")
}

171
solutions/day19/day19.go Normal file
View File

@@ -0,0 +1,171 @@
package day19
import (
"bufio"
"fmt"
"os"
)
type Blueprint struct {
blueprintNumber int
oreRobotOreCost int
clayRobotOreCost int
obsRobotOreCost int
obsRobotClayCost int
geodeRobotOreCost int
geodeRobotObsCost int
}
type State struct {
minute int
ore int
clay int
obsidian int
geodes int
oreRobots int
clayRobots int
obsidianRobots int
geodeRobots int
}
var initialState State = State{0, 0, 0, 0, 0, 1, 0, 0, 0}
func copyState(state State) State {
return State{
state.minute,
state.ore,
state.clay,
state.obsidian,
state.geodes,
state.oreRobots,
state.clayRobots,
state.obsidianRobots,
state.geodeRobots,
}
}
func nextStates(current State, minutes int, blueprint Blueprint) []State {
if current.minute == minutes {
return []State{}
}
buyNothing := State{
current.minute + 1,
current.ore + current.oreRobots,
current.clay + current.clayRobots,
current.obsidian + current.obsidianRobots,
current.geodes + current.geodeRobots,
current.oreRobots,
current.clayRobots,
current.obsidianRobots,
current.geodeRobots,
}
retval := []State{buyNothing}
if current.ore >= blueprint.oreRobotOreCost {
buyOre := copyState(buyNothing)
buyOre.ore -= blueprint.oreRobotOreCost
buyOre.oreRobots += 1
retval = append(retval, buyOre)
}
if current.ore >= blueprint.clayRobotOreCost {
buyClay := copyState(buyNothing)
buyClay.ore -= blueprint.clayRobotOreCost
buyClay.clayRobots += 1
retval = append(retval, buyClay)
}
if current.ore >= blueprint.obsRobotOreCost && current.clay >= blueprint.obsRobotClayCost {
buyObsidian := copyState(buyNothing)
buyObsidian.ore -= blueprint.obsRobotOreCost
buyObsidian.clay -= blueprint.obsRobotClayCost
buyObsidian.obsidianRobots += 1
retval = append(retval, buyObsidian)
}
if current.ore >= blueprint.geodeRobotOreCost && current.obsidian >= blueprint.geodeRobotObsCost {
buyGeode := copyState(buyNothing)
buyGeode.ore -= blueprint.geodeRobotOreCost
buyGeode.obsidian -= blueprint.geodeRobotObsCost
buyGeode.geodeRobots += 1
retval = append(retval, buyGeode)
}
return retval
}
func runSimulation(blueprint Blueprint, minutes int) int {
visited := map[State]bool{}
queue := []State{initialState}
bestResult := 0
for len(queue) > 0 {
nextState := queue[len(queue)-1]
queue = queue[0 : len(queue)-1]
_, alreadyDone := visited[nextState]
if alreadyDone {
continue
}
visited[nextState] = true
minutesLeft := minutes - nextState.minute
bestPossibleFuture := nextState.geodes + (minutesLeft*(2*nextState.geodeRobots+(minutesLeft-1)))/2
if bestPossibleFuture < bestResult {
continue
}
newStates := nextStates(nextState, minutes, blueprint)
if len(newStates) == 0 {
if nextState.geodes > bestResult {
bestResult = nextState.geodes
}
}
queue = append(queue, newStates...)
}
return bestResult
}
func Run(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
blueprints := []Blueprint{}
for scanner.Scan() {
line := scanner.Text()
var blueprintNumber, oreRobotOre, clayRobotOre, obsRobotOre, obsRobotClay, geodeRobotOre, geodeRobotObs int
fmt.Sscanf(line, "Blueprint %d: Each ore robot costs %d ore. Each clay robot costs %d ore. Each obsidian robot costs %d ore and %d clay. Each geode robot costs %d ore and %d obsidian.", &blueprintNumber, &oreRobotOre, &clayRobotOre, &obsRobotOre, &obsRobotClay, &geodeRobotOre, &geodeRobotObs)
blueprints = append(blueprints, Blueprint{blueprintNumber, oreRobotOre, clayRobotOre, obsRobotOre, obsRobotClay, geodeRobotOre, geodeRobotObs})
}
file.Close()
fmt.Println("Blueprints", blueprints)
totalQualityLevel := 0
for _, blueprint := range blueprints {
geodesHarvested := runSimulation(blueprint, 24)
fmt.Println("Found", geodesHarvested, "geodes for blueprint", blueprint.blueprintNumber)
totalQualityLevel += geodesHarvested * blueprint.blueprintNumber
}
fmt.Println("Total quality level of all blueprints at 24 minutes is", totalQualityLevel)
multipliedGeodes := 1
if len(blueprints) > 3 {
blueprints = blueprints[0:3]
}
for _, blueprint := range blueprints {
geodesHarvested := runSimulation(blueprint, 32)
fmt.Println("Found", geodesHarvested, "geodes for blueprint", blueprint.blueprintNumber)
multipliedGeodes *= geodesHarvested
}
fmt.Println("Multiplied number of geodes of first three blueprints at 32 minutes is", multipliedGeodes)
}

166
solutions/day2/day2.go Normal file
View File

@@ -0,0 +1,166 @@
package day2
import (
"bufio"
"fmt"
"os"
"golang.org/x/exp/utf8string"
)
type Move = int
const (
Rock Move = 1
Paper = 2
Scissors = 3
)
type Result = int
const (
Loss Result = 0
Tie = 3
Win = 6
)
func score(opponent Move, us Move) int {
switch opponent {
case Rock:
switch us {
case Rock:
return Rock + Tie
case Paper:
return Paper + Win
case Scissors:
return Scissors + Loss
}
case Paper:
switch us {
case Rock:
return Rock + Loss
case Paper:
return Paper + Tie
case Scissors:
return Scissors + Win
}
case Scissors:
switch us {
case Rock:
return Rock + Win
case Paper:
return Paper + Loss
case Scissors:
return Scissors + Tie
}
}
fmt.Println("PANIC: Unknown combo: them", opponent, "us", us)
return 0
}
func compute_move(opponent Move, goal Result) Move {
switch opponent {
case Rock:
switch goal {
case Loss:
return Scissors
case Tie:
return Rock
case Win:
return Paper
}
case Paper:
switch goal {
case Loss:
return Rock
case Tie:
return Paper
case Win:
return Scissors
}
case Scissors:
switch goal {
case Loss:
return Paper
case Tie:
return Scissors
case Win:
return Rock
}
}
fmt.Println("PANIC: Unknown combo: them", opponent, "goal", goal)
return 0
}
func move(r rune) Move {
switch r {
case 'A':
return Rock
case 'B':
return Paper
case 'C':
return Scissors
case 'X':
return Rock
case 'Y':
return Paper
case 'Z':
return Scissors
}
fmt.Println("PANIC: Unknown character", r)
return 0
}
func result(r rune) Result {
switch r {
case 'X':
return Loss
case 'Y':
return Tie
case 'Z':
return Win
}
fmt.Println("PANIC: Unknown character for result", r)
return 99
}
func Run(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
var total_score_base int = 0
var total_score_extended int = 0
for scanner.Scan() {
line := scanner.Text()
utf8 := utf8string.NewString(line)
opponent := move(utf8.At(0))
us := move(utf8.At(2))
total_score_base += score(opponent, us)
target := result(utf8.At(2))
computed_move := compute_move(opponent, target)
total_score_extended += score(opponent, computed_move)
}
file.Close()
fmt.Println("Total initial score is", total_score_base)
fmt.Println("Total score with back compute is", total_score_extended)
}

144
solutions/day20/day20.go Normal file
View File

@@ -0,0 +1,144 @@
package day20
import (
"bufio"
"fmt"
"os"
"strconv"
)
func printNumbers(numbers []int, nexts []int) {
current := 0
for nexts[current] == -1 {
current += 1
}
first := numbers[current]
fmt.Printf("%d", first)
current = nexts[current]
for numbers[current] != first {
fmt.Printf(" %d", numbers[current])
current = nexts[current]
}
fmt.Println()
}
func removeIndex(idx int, prevs []int, nexts []int) (before int, after int) {
before = prevs[idx]
after = nexts[idx]
nexts[before] = after
prevs[after] = before
prevs[idx] = -1
nexts[idx] = -1
return
}
func insertAfter(idx int, after int, prevs []int, nexts []int) {
afterNext := nexts[after]
prevs[idx] = after
nexts[idx] = afterNext
prevs[afterNext] = idx
nexts[after] = idx
}
func insertBefore(idx int, before int, prevs []int, nexts []int) {
beforePrev := prevs[before]
prevs[idx] = beforePrev
nexts[idx] = before
prevs[before] = idx
nexts[beforePrev] = idx
}
func shift(count int, idx int, prevs []int, nexts []int) {
count %= len(prevs) - 1
for count != 0 {
before, after := removeIndex(idx, prevs, nexts)
if count > 0 {
insertAfter(idx, after, prevs, nexts)
count -= 1
} else {
insertBefore(idx, before, prevs, nexts)
count += 1
}
}
}
func indexPast(count int, current int, nexts []int) int {
for count > 0 {
current = nexts[current]
count -= 1
}
return current
}
func Run(filename string, keyString string, countString string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
key, err := strconv.Atoi(keyString)
if err != nil {
fmt.Println("Invalid key value", err)
return
}
roundCount, err := strconv.Atoi(countString)
if err != nil {
fmt.Println("Invalid round count", err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
numbers := []int{}
nexts := []int{}
prevs := []int{}
zeroIndex := -1
for scanner.Scan() {
line := scanner.Text()
x, err := strconv.Atoi(line)
if err != nil {
fmt.Println("Couldn't parse number", line)
return
}
numbers = append(numbers, x*key)
nexts = append(nexts, 0)
prevs = append(prevs, 0)
if x == 0 {
zeroIndex = len(numbers) - 1
}
}
for idx := range nexts {
nexts[idx] = (idx + 1) % len(nexts)
if idx == 0 {
prevs[idx] = len(prevs) - 1
} else {
prevs[idx] = idx - 1
}
}
file.Close()
for round := 0; round < roundCount; round++ {
for idx := range numbers {
fmt.Println("Working round", round+1, "index", idx, "with value/count", numbers[idx])
shift(numbers[idx], idx, prevs, nexts)
//printNumbers(numbers, nexts)
}
}
idx1k := indexPast(1000, zeroIndex, nexts)
idx2k := indexPast(1000, idx1k, nexts)
idx3k := indexPast(1000, idx2k, nexts)
val1k := numbers[idx1k]
val2k := numbers[idx2k]
val3k := numbers[idx3k]
fmt.Println("Value 1k after 0", val1k, "2k", val2k, "3k", val3k, "sum", val1k+val2k+val3k)
}

221
solutions/day21/day21.go Normal file
View File

@@ -0,0 +1,221 @@
package day21
import (
"bufio"
"fmt"
"os"
"strconv"
)
const (
OpAdd int = iota
OpMul
OpSub
OpDiv
OpTarget
)
func opString(v int) string {
switch v {
case OpAdd:
return "+"
case OpMul:
return "*"
case OpSub:
return "-"
case OpDiv:
return "/"
case OpTarget:
return "=="
default:
return "???????"
}
}
type MonkeyState struct {
valueSet bool
value int
operation int
left string
right string
}
func step(monkeys map[string]MonkeyState) bool {
changedSomething := false
for monkey, state := range monkeys {
if !state.valueSet && monkeys[state.left].valueSet && monkeys[state.right].valueSet {
switch state.operation {
case OpAdd:
state.value = monkeys[state.left].value + monkeys[state.right].value
case OpSub:
state.value = monkeys[state.left].value - monkeys[state.right].value
case OpMul:
state.value = monkeys[state.left].value * monkeys[state.right].value
case OpDiv:
state.value = monkeys[state.left].value / monkeys[state.right].value
}
state.valueSet = true
monkeys[monkey] = state
changedSomething = true
}
}
return changedSomething
}
func contains(ls []string, x string) bool {
for _, tester := range ls {
if tester == x {
return true
}
}
return false
}
func Run(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
monkeys := map[string]MonkeyState{}
for scanner.Scan() {
line := scanner.Text()
monkey := line[0:4]
data := line[6:]
value, err := strconv.Atoi(data)
if err == nil {
monkeys[monkey] = MonkeyState{true, value, 0, "", ""}
} else {
left := data[0:4]
right := data[7:]
switch data[5] {
case '+':
monkeys[monkey] = MonkeyState{false, 0, OpAdd, left, right}
case '-':
monkeys[monkey] = MonkeyState{false, 0, OpSub, left, right}
case '*':
monkeys[monkey] = MonkeyState{false, 0, OpMul, left, right}
case '/':
monkeys[monkey] = MonkeyState{false, 0, OpDiv, left, right}
default:
fmt.Println("Bad operation", string(data[5]))
return
}
}
}
file.Close()
for !monkeys["root"].valueSet {
step(monkeys)
}
fmt.Println("Root monkey value, part 1:", monkeys["root"].value)
humn := monkeys["humn"]
humn.valueSet = false
humn.operation = OpTarget
monkeys["humn"] = humn
unwoundMonkeys := []string{"humn"}
updatedSomething := true
for updatedSomething {
updatedSomething = false
for monkey, state := range monkeys {
if contains(unwoundMonkeys, state.left) || contains(unwoundMonkeys, state.right) {
if state.valueSet {
state.valueSet = false
monkeys[monkey] = state
unwoundMonkeys = append(unwoundMonkeys, monkey)
updatedSomething = true
}
}
}
}
fmt.Println("monkeys now", monkeys)
var targetValue int
var nextNode string
if monkeys[monkeys["root"].left].valueSet {
targetValue = monkeys[monkeys["root"].left].value
nextNode = monkeys["root"].right
fmt.Println("Root's right is the unknown one", nextNode)
} else {
targetValue = monkeys[monkeys["root"].right].value
nextNode = monkeys["root"].left
fmt.Println("Root's left is the unknown one", nextNode)
}
for !monkeys["humn"].valueSet {
nextState := monkeys[nextNode]
if nextState.valueSet {
fmt.Println("PANIC: Grounded in a node with the value set")
return
} else if nextState.operation == OpTarget {
nextState.value = targetValue
nextState.valueSet = true
monkeys[nextNode] = nextState
} else if monkeys[nextState.left].valueSet {
nextState.value = targetValue
nextState.valueSet = true
monkeys[nextNode] = nextState
switch nextState.operation {
case OpAdd:
// T = V + X
// T - V = X
targetValue = targetValue - monkeys[nextState.left].value
case OpMul:
// T = V * X
// T / V = X
targetValue = targetValue / monkeys[nextState.left].value
case OpSub:
// T = V - X
// T + X = V
// X = V - T
targetValue = monkeys[nextState.left].value - targetValue
case OpDiv:
// T = V / X
// TX = V
// X = V / T
targetValue = monkeys[nextState.left].value / targetValue
default:
fmt.Println("PANIC: Bad operation in back compute")
}
nextNode = nextState.right
} else if monkeys[nextState.right].valueSet {
nextState.value = targetValue
nextState.valueSet = true
monkeys[nextNode] = nextState
switch nextState.operation {
case OpAdd:
// T = X + V
// T - V = X
targetValue = targetValue - monkeys[nextState.right].value
case OpMul:
// T = X * V
targetValue = targetValue / monkeys[nextState.right].value
case OpSub:
// T = X - V
// T + V = X
targetValue = targetValue + monkeys[nextState.right].value
case OpDiv:
// T = X / V
// TV = X
targetValue = targetValue * monkeys[nextState.right].value
default:
fmt.Println("PANIC: Bad operation in back compute")
}
nextNode = nextState.left
} else {
fmt.Println("PANIC: Trouble with a double split in search")
return
}
}
fmt.Println("Actually, my value should be", monkeys["humn"].value)
}

635
solutions/day22/day22.go Normal file
View File

@@ -0,0 +1,635 @@
package day22
import (
"bufio"
"fmt"
"os"
)
type Command int
type Tile int
type Direction int
type Face int
type Board [][]Tile
type FaceMap map[Face]Board
const (
TurnRight Command = -1
TurnLeft = -2
)
const (
Void Tile = iota
Empty
Wall
)
const (
Right Direction = 0
Down = 1
Left = 2
Up = 3
)
const (
TopFace Face = iota
BottomFace
LeftFace
RightFace
FrontFace
BackFace
)
func printBoard(board Board) {
for _, row := range board {
for _, tile := range row {
switch tile {
case Void:
fmt.Printf(" ")
case Empty:
fmt.Printf(".")
case Wall:
fmt.Printf("#")
}
}
fmt.Println()
}
}
func parseCommands(runes []rune) []Command {
result := []Command{}
accum := 0
for _, r := range runes {
if r >= '0' && r <= '9' {
accum = (accum * 10) + int(r-'0')
} else {
result = append(result, Command(accum))
accum = 0
if r == 'R' {
result = append(result, TurnRight)
} else {
result = append(result, TurnLeft)
}
}
}
if accum > 0 {
result = append(result, Command(accum))
}
return result
}
func parseBoardLine(runes []rune) []Tile {
result := []Tile{}
for _, r := range runes {
switch r {
case ' ':
result = append(result, Void)
case '.':
result = append(result, Empty)
case '#':
result = append(result, Wall)
default:
fmt.Println("PANIC: Illegal character in board map", string(r))
}
}
return result
}
func initialPosition(board Board) (x int, y int) {
for idx, tile := range board[0] {
if tile == Empty {
x = idx
y = 0
return
}
}
fmt.Println("PANIC: Couldn't figure out initial position")
x = 0
y = 0
return
}
func turn(direction Direction, turn Command) Direction {
switch direction {
case Up:
switch turn {
case TurnLeft:
return Left
case TurnRight:
return Right
}
case Right:
switch turn {
case TurnLeft:
return Up
case TurnRight:
return Down
}
case Down:
switch turn {
case TurnLeft:
return Right
case TurnRight:
return Left
}
case Left:
switch turn {
case TurnLeft:
return Down
case TurnRight:
return Up
}
}
fmt.Println("PANIC: turn default WTF")
return Down
}
type State2D struct {
x int
y int
direction Direction
board Board
}
func step2d(count int, state *State2D) {
if count == 0 {
return
}
keepGoing := true
switch state.direction {
case Right:
currentRow := state.board[state.y]
tester := (state.x + 1) % len(currentRow)
for {
if currentRow[tester] == Void {
tester = (tester + 1) % len(currentRow)
continue
} else if currentRow[tester] == Wall {
keepGoing = false
break
} else {
(*state).x = tester
break
}
}
case Left:
currentRow := state.board[state.y]
tester := state.x - 1
for {
if tester < 0 {
tester = len(currentRow) - 1
}
if currentRow[tester] == Void {
tester -= 1
continue
} else if currentRow[tester] == Wall {
keepGoing = false
break
} else {
(*state).x = tester
break
}
}
case Up:
tester := state.y - 1
for {
if tester < 0 {
tester = len(state.board) - 1
}
if state.board[tester][state.x] == Void {
tester -= 1
continue
} else if state.board[tester][state.x] == Wall {
keepGoing = false
break
} else {
(*state).y = tester
break
}
}
case Down:
fmt.Println("Moving down count is", count, "y value", state.y)
tester := (state.y + 1) % len(state.board)
for {
if state.board[tester][state.x] == Void {
tester = (tester + 1) % len(state.board)
continue
} else if state.board[tester][state.x] == Wall {
fmt.Println("Hit wall at tester", tester)
keepGoing = false
break
} else {
(*state).y = tester
break
}
}
default:
fmt.Println("BAD")
}
if keepGoing {
step2d(count-1, state)
}
}
func copyFace(startX int, startY int, faceSize int, board Board) Board {
retval := Board{}
for y := 0; y < faceSize; y++ {
newRow := []Tile{}
for x := 0; x < faceSize; x++ {
newRow = append(newRow, board[y+startY][x+startX])
}
retval = append(retval, newRow)
}
return retval
}
func validPoint(x int, y int, board Board) bool {
return (x >= 0) && (y >= 0) && (x < len(board[0])) && (y < len(board))
}
var faceMoves map[Face]map[Direction]Face = map[Face]map[Direction]Face{
TopFace: map[Direction]Face{
Up: BackFace,
Left: LeftFace,
Right: RightFace,
Down: FrontFace,
},
BottomFace: map[Direction]Face{
Up: FrontFace,
Left: LeftFace,
Right: RightFace,
Down: BackFace,
},
LeftFace: map[Direction]Face{
Up: TopFace,
Left: BackFace,
Right: FrontFace,
Down: BottomFace,
},
RightFace: map[Direction]Face{
Up: TopFace,
Left: FrontFace,
Right: BackFace,
Down: BottomFace,
},
FrontFace: map[Direction]Face{
Up: TopFace,
Left: LeftFace,
Right: RightFace,
Down: BottomFace,
},
BackFace: map[Direction]Face{
Up: BackFace,
Left: RightFace,
Right: FrontFace,
Down: FrontFace,
},
}
var faceName map[Face]string = map[Face]string{
TopFace: "top",
BottomFace: "bottom",
LeftFace: "left",
RightFace: "right",
FrontFace: "front",
BackFace: "back",
}
var dirName map[Direction]string = map[Direction]string{
Up: "up",
Down: "down",
Left: "left",
Right: "right",
}
var tileName map[Tile]string = map[Tile]string{
Void: "void",
Empty: "empty",
Wall: "wall",
}
type State3D struct {
x int
y int
face Face
direction Direction
board FaceMap
}
func translate(x int, y int, faceSize int, face Face, dir Direction) (resx int, resy int, resdir Direction) {
switch face {
case TopFace:
resdir = Down
switch dir {
case Right, Left:
resy = 0
resx = y
case Up, Down:
resy = 0
resx = x
}
case FrontFace:
resdir = dir
switch dir {
case Up:
resy = faceSize - 1
resx = x
case Down:
resy = 0
resx = x
case Left:
resy = y
resx = faceSize - 1
case Right:
resy = y
resx = 0
}
case LeftFace:
switch dir {
case Up:
resx = 0
resy = x
resdir = Right
case Down:
resx = 0
resy = x
resdir = Right
case Left:
resy = y
resx = faceSize - 1
resdir = Left
case Right:
resy = y
resx = 0
resdir = Right
}
case RightFace:
switch dir {
case Up:
resx = faceSize - 1
resy = x
resdir = Left
case Down:
resx = faceSize - 1
resy = x
resdir = Left
case Left:
resx = faceSize - 1
resy = y
resdir = Left
case Right:
resy = y
resx = 0
resdir = Right
}
case BackFace:
switch dir {
case Up:
resx = x
resy = 0
resdir = Down
case Down:
resx = x
resy = faceSize - 1
resdir = Up
case Left:
resx = faceSize - 1
resy = y
resdir = Right
case Right:
resy = y
resx = 0
resdir = Left
}
case BottomFace:
switch dir {
case Up:
resx = x
resy = faceSize - 1
resdir = Up
case Down:
resx = x
resy = faceSize - 1
resdir = Up
case Left:
resx = y
resy = faceSize - 1
resdir = Up
case Right:
resy = faceSize - 1
resx = y
resdir = Up
}
}
return
}
func step3d(count int, state *State3D) {
if count == 0 {
return
}
for i := 0; i < count; i++ {
newX := state.x
newY := state.y
newFace := state.face
newDirection := state.direction
switch state.direction {
case Up:
newY -= 1
case Down:
newY += 1
case Left:
newX -= 1
case Right:
newX += 1
}
if !validPoint(newX, newY, state.board[state.face]) {
newFace = faceMoves[state.face][state.direction]
newX, newY, newDirection = translate(newX, newY, len(state.board[state.face]), state.face, state.direction)
}
fmt.Printf("(%d, %d) going %s on %s ---> (%d, %d) going %s on %s [%s]\n", state.x, state.y, dirName[state.direction], faceName[state.face], newX, newY, dirName[newDirection], faceName[newFace], tileName[state.board[newFace][newY][state.x]])
if state.board[newFace][newY][newX] == Wall {
return
}
(*state).x = newX
(*state).y = newY
(*state).face = newFace
(*state).direction = newDirection
}
}
func Run(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
var commands []Command
initialBoard := Board{}
for scanner.Scan() {
line := scanner.Text()
runes := []rune(line)
if len(runes) > 0 {
if runes[0] >= '0' && runes[0] <= '9' {
commands = parseCommands(runes)
} else {
initialBoard = append(initialBoard, parseBoardLine(runes))
}
}
}
file.Close()
maximumRowLength := 0
for _, row := range initialBoard {
if len(row) > maximumRowLength {
maximumRowLength = len(row)
}
}
board := Board{}
for _, row := range initialBoard {
for len(row) < maximumRowLength {
row = append(row, Void)
}
board = append(board, row)
}
printBoard(board)
startX, startY := initialPosition(board)
x, y := startX, startY
fmt.Println("initial position is", x, y)
state := State2D{x, y, Right, board}
for _, command := range commands {
fmt.Println("x", state.x, "y", state.y, "direction", dirName[state.direction])
fmt.Println("Running", command)
switch command {
case TurnRight:
state.direction = turn(state.direction, TurnRight)
case TurnLeft:
state.direction = turn(state.direction, TurnLeft)
default:
step2d(int(command), &state)
}
}
finalRow := state.y + 1
finalColumn := state.x + 1
finalPassword := 1000*finalRow + 4*finalColumn + int(state.direction)
fmt.Println("Final row is", finalRow, "final column is", finalColumn, "final password is", finalPassword)
narrowestSection := len(board[0])
for _, row := range board {
startIdx := -1
endIdx := -1
for idx, tile := range row {
if tile != Void && startIdx == -1 {
startIdx = idx
}
if tile == Void && (startIdx != -1) && (endIdx == -1) {
endIdx = idx
}
}
if endIdx == -1 {
endIdx = len(row) - 1
}
if (endIdx - startIdx) < narrowestSection {
narrowestSection = endIdx - startIdx
}
}
cubeFaceSize := narrowestSection
fmt.Println("Cube face size is", cubeFaceSize)
faceMap := FaceMap{}
currentFace := TopFace
currentSpineX := startX
currentSpineY := startY
for validPoint(currentSpineX, currentSpineY, board) {
faceMap[currentFace] = copyFace(currentSpineX, currentSpineY, cubeFaceSize, board)
fmt.Printf("Found %s face at (%d,%d)\n", faceName[currentFace], currentSpineX, currentSpineY)
leftwardsFace := faceMoves[currentFace][Left]
for leftX := currentSpineX - cubeFaceSize; validPoint(leftX, currentSpineY, board) && board[currentSpineY][leftX] != Void; leftX -= cubeFaceSize {
faceMap[leftwardsFace] = copyFace(leftX, currentSpineY, cubeFaceSize, board)
fmt.Printf("Found %s face at (%d,%d)\n", faceName[leftwardsFace], leftX, currentSpineY)
fmt.Println("Moving face", faceName[leftwardsFace], "left to", faceName[faceMoves[leftwardsFace][Left]])
leftwardsFace = faceMoves[leftwardsFace][Left]
}
rightwardsFace := faceMoves[currentFace][Right]
for rightX := currentSpineX + cubeFaceSize; validPoint(rightX, currentSpineY, board) && board[currentSpineY][rightX] != Void; rightX += cubeFaceSize {
faceMap[rightwardsFace] = copyFace(rightX, currentSpineY, cubeFaceSize, board)
fmt.Printf("Found %s face at (%d,%d)\n", faceName[rightwardsFace], rightX, currentSpineY)
fmt.Println("Moving face", faceName[rightwardsFace], "right to", faceName[faceMoves[rightwardsFace][Left]])
rightwardsFace = faceMoves[rightwardsFace][Right]
}
currentSpineY += cubeFaceSize
fmt.Println("Moving face", faceName[currentFace], "down to", faceName[faceMoves[currentFace][Down]])
currentFace = faceMoves[currentFace][Down]
}
if len(faceMap) != 6 {
fmt.Println("COULD NOT FIND ALL SIX FACES")
return
}
for face, board := range faceMap {
fmt.Println("Board", faceName[face])
printBoard(board)
}
fmt.Println("initial position is", x, y)
state3 := State3D{0, 0, TopFace, Right, faceMap}
for _, command := range commands {
fmt.Println("x", state3.x, "y", state3.y, "face", faceName[state3.face], "direction", dirName[state3.direction])
fmt.Println("Running", command)
switch command {
case TurnRight:
state3.direction = turn(state3.direction, TurnRight)
case TurnLeft:
state3.direction = turn(state3.direction, TurnLeft)
default:
step3d(int(command), &state3)
}
}
finalRow = state3.y + 1
finalColumn = state3.x + 1
finalPassword = 1000*finalRow + 4*finalColumn + int(state.direction)
fmt.Println("Final row is", finalRow, "final column is", finalColumn, "final password is", finalPassword)
}

237
solutions/day23/day23.go Normal file
View File

@@ -0,0 +1,237 @@
package day23
import (
"bufio"
"fmt"
"os"
)
type Direction int
const (
North Direction = iota
South
East
West
)
type Point struct {
x int
y int
}
type ElfLocations map[Point]bool
func extents(locations ElfLocations) (topLeft Point, bottomRight Point) {
lowestX := 1000000000000000000
highestX := -1000000000000000000
lowestY := 1000000000000000000
highestY := -1000000000000000000
for location := range locations {
if location.x < lowestX {
lowestX = location.x
}
if location.x > highestX {
highestX = location.x
}
if location.y < lowestY {
lowestY = location.y
}
if location.y > highestY {
highestY = location.y
}
}
topLeft = Point{lowestX, lowestY}
bottomRight = Point{highestX, highestY}
return
}
func hasElf(x int, y int, locations ElfLocations) bool {
_, exists := locations[Point{x, y}]
return exists
}
func printMap(locations ElfLocations) {
topLeft, bottomRight := extents(locations)
fmt.Println("top left", topLeft)
fmt.Println("bottom right", bottomRight)
for y := topLeft.y; y <= bottomRight.y; y++ {
for x := topLeft.x; x <= bottomRight.x; x++ {
if hasElf(x, y, locations) {
fmt.Printf("#")
} else {
fmt.Printf(".")
}
}
fmt.Println()
}
}
func directionIsEmpty(direction Direction, x int, y int, locations ElfLocations) bool {
switch direction {
case North:
return !hasElf(x-1, y-1, locations) && !hasElf(x, y-1, locations) && !hasElf(x+1, y-1, locations)
case South:
return !hasElf(x-1, y+1, locations) && !hasElf(x, y+1, locations) && !hasElf(x+1, y+1, locations)
case West:
return !hasElf(x-1, y-1, locations) && !hasElf(x-1, y, locations) && !hasElf(x-1, y+1, locations)
case East:
return !hasElf(x+1, y-1, locations) && !hasElf(x+1, y, locations) && !hasElf(x+1, y+1, locations)
}
return false
}
func showDirection(dir *Direction) string {
if dir == nil {
return "<nil>"
}
switch *dir {
case North:
return "north"
case South:
return "south"
case East:
return "east"
case West:
return "west"
}
return "<???>"
}
func proposeMove(x int, y int, directionPreference []Direction, locations ElfLocations) *Point {
var firstEmpty *Direction = nil
allEmpty := true
for _, dir := range directionPreference {
if directionIsEmpty(dir, x, y, locations) {
if firstEmpty == nil {
firstEmpty = new(Direction)
*firstEmpty = dir
}
} else {
allEmpty = false
}
}
if allEmpty {
return nil
}
if firstEmpty == nil {
return nil
}
switch *firstEmpty {
case North:
return &Point{x, y - 1}
case South:
return &Point{x, y + 1}
case East:
return &Point{x + 1, y}
case West:
return &Point{x - 1, y}
}
fmt.Println("PANIC: fell through?!")
return nil
}
func runRound(locations ElfLocations, preferredDirections []Direction) (newLocations ElfLocations, done bool) {
suggestedMoves := map[Point]Point{}
takenPoints := map[Point]int{}
for location := range locations {
proposed := proposeMove(location.x, location.y, preferredDirections, locations)
if proposed != nil {
fmt.Println("Proposed to move", location, "to", proposed)
suggestedMoves[location] = *proposed
curVal := takenPoints[*proposed]
takenPoints[*proposed] = curVal + 1
}
}
newLocations = ElfLocations{}
done = true
for originalLocation := range locations {
newPlace, hasNewPlace := suggestedMoves[originalLocation]
if hasNewPlace && takenPoints[newPlace] <= 1 {
newLocations[newPlace] = true
done = false
} else {
newLocations[originalLocation] = true
}
}
return
}
func Run(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
elfLocations := ElfLocations{}
y := 0
for scanner.Scan() {
line := []rune(scanner.Text())
for x, r := range line {
if r == '#' {
elfLocations[Point{x, y}] = true
}
}
y += 1
}
file.Close()
preferredDirections := []Direction{North, South, West, East}
for i := 0; i < 10; i++ {
fmt.Println("Round", i)
printMap(elfLocations)
newLocations, done := runRound(elfLocations, preferredDirections)
preferredDirections = append(preferredDirections[1:], preferredDirections[0])
if done {
fmt.Println("No one moves after this round, so we're done.")
break
}
elfLocations = newLocations
}
printMap(elfLocations)
topLeft, bottomRight := extents(elfLocations)
emptyGround := 0
for x := topLeft.x; x <= bottomRight.x; x++ {
for y := topLeft.y; y <= bottomRight.y; y++ {
if !hasElf(x, y, elfLocations) {
emptyGround += 1
}
}
}
fmt.Println(emptyGround, "empty spots in smallest bounding box.")
round := 11
for {
fmt.Println("Starting round", round)
newLocations, done := runRound(elfLocations, preferredDirections)
if done {
fmt.Println("No one moved after round", round)
break
}
elfLocations = newLocations
preferredDirections = append(preferredDirections[1:], preferredDirections[0])
round += 1
}
}

92
solutions/day3/day3.go Normal file
View File

@@ -0,0 +1,92 @@
package day3
import (
"bufio"
"fmt"
"os"
)
func in_set(target rune, set []rune) bool {
for _, char := range set {
if char == target {
return true
}
}
return false
}
func priority(value rune) int {
var base_value int = 0
if value >= 'A' && value <= 'Z' {
base_value = int('A') - 27
}
if value >= 'a' && value <= 'z' {
base_value = int('a') - 1
}
return int(value) - base_value
}
func part1_addition(runes []rune) int {
var total int = 0
midpoint := len(runes) / 2
left_runes := runes[0:midpoint]
right_runes := runes[midpoint:]
for pos, char := range left_runes {
if !in_set(char, left_runes[0:pos]) {
if in_set(char, right_runes) {
total += priority(char)
}
}
}
return total
}
func score_badge(set1 []rune, set2 []rune, set3 []rune) int {
for pos, char := range set1 {
if !in_set(char, set1[0:pos]) {
if in_set(char, set2) && in_set(char, set3) {
return priority(char)
}
}
}
fmt.Println("PANIC: Couldn't find overlapping badge item")
return 0
}
func Run(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
var total_overlap int = 0
var total_badge int = 0
var grouping [][]rune = [][]rune{}
for scanner.Scan() {
line := scanner.Text()
runes := []rune(line)
total_overlap += part1_addition(runes)
grouping = append(grouping, runes)
if len(grouping) == 3 {
total_badge += score_badge(grouping[0], grouping[1], grouping[2])
grouping = [][]rune{}
}
}
file.Close()
fmt.Println("Total initial score is", total_overlap)
fmt.Println("Total badge score is", total_badge)
}

114
solutions/day4/day4.go Normal file
View File

@@ -0,0 +1,114 @@
package day4
import (
"bufio"
"errors"
"fmt"
"os"
"strconv"
"strings"
)
type SectionRange struct {
start int
end int
}
func total_overlap(a SectionRange, b SectionRange) bool {
return ((a.start >= b.start) && (a.end <= b.end)) ||
((b.start >= a.start) && (b.end <= a.end))
}
func partial_overlap(a SectionRange, b SectionRange) bool {
return (a.start >= b.start && a.start <= b.end) ||
(a.end >= b.end && a.end <= b.end) ||
(b.start >= a.start && b.start <= a.end) ||
(b.end >= a.start && b.end <= a.end)
}
func range_from_string(s string) (ret_range SectionRange, err error) {
ret_range = SectionRange{0, 0}
items := strings.Split(s, "-")
if len(items) != 2 {
err = errors.New("invalid range format")
return
}
start, start_err := strconv.Atoi(items[0])
if start_err != nil {
err = start_err
return
}
ret_range.start = start
end, end_err := strconv.Atoi(items[1])
if end_err != nil {
err = end_err
return
}
ret_range.end = end
err = nil
return
}
func line_to_pair(s string) (left SectionRange, right SectionRange, err error) {
items := strings.Split(s, ",")
if len(items) != 2 {
err = errors.New("invalid range format")
return
}
left, left_err := range_from_string(items[0])
if left_err != nil {
err = left_err
return
}
right, right_err := range_from_string(items[1])
if right_err != nil {
err = right_err
return
}
return
}
func Run(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
var total_overlapping int = 0
var partial_overlapping int = 0
for scanner.Scan() {
line := scanner.Text()
left, right, err := line_to_pair(line)
if err != nil {
fmt.Println("ERROR: failed to parse line:", err)
} else {
if total_overlap(left, right) {
total_overlapping += 1
}
if partial_overlap(left, right) {
partial_overlapping += 1
}
}
}
file.Close()
fmt.Println("# of overlapping pairs", total_overlapping)
fmt.Println("# of partially overlapping pairs", partial_overlapping)
}

282
solutions/day5/day5.go Normal file
View File

@@ -0,0 +1,282 @@
package day5
import (
"bufio"
"errors"
"fmt"
"os"
"strconv"
"strings"
"unicode"
)
type Stack struct {
identity rune
then *Stack
}
func newStack() *Stack {
return nil
}
func push(box rune, stack *Stack) *Stack {
var retval *Stack = new(Stack)
retval.identity = box
retval.then = stack
return retval
}
func pushToEnd(box rune, stack *Stack) *Stack {
if stack == nil {
retval := new(Stack)
retval.identity = box
retval.then = nil
return retval
} else {
stack.then = pushToEnd(box, stack.then)
return stack
}
}
func pushSet(front *Stack, stack *Stack) *Stack {
var prev *Stack = nil
current := front
for current != nil {
prev = current
current = current.then
}
if prev == nil {
return stack
} else {
prev.then = stack
}
return front
}
func pop(stack *Stack) (newStack *Stack, value rune, err error) {
if stack == nil {
newStack = nil
value = 'X'
err = errors.New("attempt to pop from empty stack")
return
}
value = stack.identity
newStack = stack.then
return
}
func popSet(amt int, stack *Stack) (newStack *Stack, set *Stack, err error) {
var prev *Stack = nil
var current *Stack = stack
for i := 0; i < amt; i++ {
if current == nil {
err = errors.New("tried to take more items than existed in stack")
return
}
prev = current
current = current.then
}
if prev != nil {
set = stack
prev.then = nil
} else {
set = nil
}
newStack = current
return
}
func stackLength(stack *Stack) int {
if stack == nil {
return 0
} else {
return 1 + stackLength(stack.then)
}
}
type Workspace []*Stack
func addLine(line string, input *Workspace) (err error) {
currentColumn := 0
fmt.Println("Workspace line's length is", len(line))
for len(line) >= 3 {
fmt.Println(" line length now", len(line))
// make sure we've got enough space in the workspace
for currentColumn+1 >= len(*input) {
*input = append(*input, newStack())
}
// see if there's actually something there
if line[0] == '[' && unicode.IsLetter(rune(line[1])) {
fmt.Println("Adding", rune(line[1]), "to column", currentColumn)
(*input)[currentColumn] = pushToEnd(rune(line[1]), (*input)[currentColumn])
} else if line[0] != ' ' {
err = errors.New("doesn't start with square or blank")
return
}
currentColumn += 1
if len(line) == 3 {
return
}
if len(line) > 3 {
line = line[4:]
}
}
return
}
func runMove(problem int, move OperatorMove, workspace *Workspace) error {
switch problem {
case 1:
for i := 0; i < move.count; i++ {
newFrom, value, err := pop((*workspace)[move.from])
if err != nil {
return err
}
(*workspace)[move.from] = newFrom
(*workspace)[move.to] = push(value, (*workspace)[move.to])
}
return nil
case 2:
newFrom, poppedSet, err := popSet(move.count, (*workspace)[move.from])
if err != nil {
return err
}
(*workspace)[move.from] = newFrom
(*workspace)[move.to] = pushSet(poppedSet, (*workspace)[move.to])
return nil
default:
return errors.New("weird problem number found in runMove")
}
}
func printWorkspace(input Workspace) {
maxLength := 0
for _, element := range input {
current := stackLength(element)
if current > maxLength {
maxLength = current
}
}
for _, element := range input {
current := stackLength(element)
blanks := maxLength - current
outputString := ""
for i := 0; i < blanks; i++ {
outputString = outputString + " "
}
node := element
for node != nil {
outputString = outputString + fmt.Sprintf("[%c] ", node.identity)
node = node.then
}
fmt.Println(outputString)
}
}
type OperatorMove struct {
count int
from int
to int
}
func parseMove(line string) (move OperatorMove, err error) {
words := strings.Split(line, " ")
if len(words) != 6 {
err = errors.New("wrong number of words in move line")
return
}
if words[0] != "move" || words[2] != "from" || words[4] != "to" {
err = fmt.Errorf("illegal words in move line: %s/%s/%s", words[0], words[2], words[4])
return
}
count, cerr := strconv.Atoi(words[1])
from, ferr := strconv.Atoi(words[3])
to, terr := strconv.Atoi(words[5])
if cerr != nil || ferr != nil || terr != nil {
err = fmt.Errorf("illegal number found: %s/%s/%s", words[1], words[3], words[5])
}
move = OperatorMove{count, from - 1, to - 1}
return
}
func Run(filename string, problemStr string) {
problem, err := strconv.Atoi(problemStr)
if err != nil || (problem < 1 || problem > 2) {
fmt.Println("Didn't understand problem number", os.Args[2], "should be 1 or 2")
}
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
workspace := new(Workspace)
moves := []OperatorMove{}
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, "[") {
fmt.Println("Adding workspace line")
addLine(line, workspace)
} else {
move, err := parseMove(line)
if err == nil {
fmt.Println("Adding move", move)
moves = append(moves, move)
} else {
fmt.Println("Skipping dead line")
}
}
}
file.Close()
printWorkspace(*workspace)
for count, move := range moves {
fmt.Println("-----", count+1, "-----")
runMove(problem, move, workspace)
printWorkspace(*workspace)
}
resultString := ""
for _, stack := range *workspace {
if stack == nil {
resultString += "*"
} else {
resultString += fmt.Sprintf("%c", rune((*stack).identity))
}
}
fmt.Println("Final string is", resultString)
}

58
solutions/day6/day6.go Normal file
View File

@@ -0,0 +1,58 @@
package day6
import (
"bufio"
"fmt"
"os"
"strconv"
)
func allDifferent(a string) bool {
length := len(a)
for i := 0; i < length; i++ {
for j := i + 1; j < length; j++ {
if a[i] == a[j] {
return false
}
}
}
return true
}
func Run(filename string, problemStr string) {
problem, err := strconv.Atoi(problemStr)
if err != nil || !(problem == 4 || problem == 14) {
fmt.Println("Didn't understand problem number", os.Args[2], "should be 4 or 14")
}
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
line := scanner.Text()
if len(line) > 0 {
var index int
offset := problem - 1
for index = offset; index < len(line); index++ {
if allDifferent(line[index-offset : index+1]) {
break
}
}
fmt.Println("First marker after character", index+1)
}
}
file.Close()
}

159
solutions/day7/day7.go Normal file
View File

@@ -0,0 +1,159 @@
package day7
import (
"bufio"
"errors"
"fmt"
"os"
"strconv"
"strings"
)
type FileSystem struct {
name string
parent *FileSystem
subdirectories []*FileSystem
files []FileInfo
}
func newFileSystem(name string, parent *FileSystem) *FileSystem {
return &FileSystem{name, parent, []*FileSystem{}, []FileInfo{}}
}
func printFileTree(tree *FileSystem, prefix string) {
fmt.Println(prefix, "-", tree.name, fmt.Sprintf("(dir size=%d)", totalSize(tree)))
for _, file := range tree.files {
fmt.Println(prefix, " ", "-", file.name, fmt.Sprintf("(file, size=%d)", file.size))
}
for _, dir := range tree.subdirectories {
printFileTree(dir, prefix+" ")
}
}
func totalSize(tree *FileSystem) int {
total := 0
for _, file := range tree.files {
total += file.size
}
for _, dir := range tree.subdirectories {
total += totalSize(dir)
}
return total
}
func computePart1(tree *FileSystem) int {
result := 0
for _, dir := range tree.subdirectories {
result += computePart1(dir)
}
currentSize := totalSize(tree)
if currentSize <= 100000 {
result += currentSize
}
return result
}
func computePart2(tree *FileSystem, size int) int {
result := 70000000
for _, dir := range tree.subdirectories {
subResult := computePart2(dir, size)
if subResult < result {
result = subResult
}
}
currentSize := totalSize(tree)
if currentSize > size && currentSize < result {
result = currentSize
}
return result
}
type FileInfo struct {
name string
size int
}
func parseFileInfo(line string) (result FileInfo, err error) {
splits := strings.Split(line, " ")
if len(splits) != 2 {
err = errors.New("too many values from file info split")
return
}
size, err := strconv.Atoi(splits[0])
if err != nil {
err = errors.New(fmt.Sprintf("could not parse file size: '%s'", splits[0]))
return
}
result = FileInfo{splits[1], size}
return
}
func Run(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
root := newFileSystem("/", nil)
current := root
for scanner.Scan() {
line := scanner.Text()
fmt.Println("line", line)
if strings.HasPrefix(line, "$ cd /") {
current = root
} else if strings.HasPrefix(line, "$ cd ..") {
current = current.parent
} else if strings.HasPrefix(line, "$ cd ") {
for _, subdir := range current.subdirectories {
if subdir.name == line[5:] {
current = subdir
continue
}
}
} else if strings.HasPrefix(line, "$ ls") {
// don't do anything
} else if strings.HasPrefix(line, "dir ") {
newTree := newFileSystem(line[4:], current)
current.subdirectories = append(current.subdirectories, newTree)
} else {
fileInfo, err := parseFileInfo(line)
if err != nil {
fmt.Println("PANIC:", err)
return
}
current.files = append((*current).files, fileInfo)
}
}
file.Close()
printFileTree(root, "")
fmt.Println("example first part", computePart1(root))
unusedSpace := 70000000 - totalSize(root)
fmt.Println("unused space is currently", unusedSpace)
needToGet := 30000000 - unusedSpace
fmt.Println("need to find a directory at least", needToGet, "bytes big")
fmt.Println("that directory has size", computePart2(root, needToGet))
}

142
solutions/day8/day8.go Normal file
View File

@@ -0,0 +1,142 @@
package day8
import (
"bufio"
"fmt"
"os"
"strconv"
)
func isVisible(treeMap [][]int, x int, y int) bool {
treeHeight := treeMap[y][x]
left_test := true
for trial := 0; trial < x; trial++ {
left_test = left_test && treeMap[y][trial] < treeHeight
}
right_test := true
for trial := x + 1; trial < len(treeMap[y]); trial++ {
right_test = right_test && treeMap[y][trial] < treeHeight
}
up_test := true
for trial := 0; trial < y; trial++ {
up_test = up_test && treeMap[trial][x] < treeHeight
}
down_test := true
for trial := y + 1; trial < len(treeMap); trial++ {
down_test = down_test && treeMap[trial][x] < treeHeight
}
fmt.Println("Test for x", x, "y", y, "which has height", treeHeight, "tests are", left_test, right_test, up_test, down_test)
return (left_test || right_test || up_test || down_test)
}
func score(treeMap [][]int, x int, y int) int {
treeHeight := treeMap[y][x]
if x == 0 || y == 0 {
return 0
}
final := 1
score := 0
for trial := x - 1; trial >= 0; trial-- {
if treeMap[y][trial] >= treeHeight {
score += 1
break
}
score += 1
}
final *= score
score = 0
for trial := x + 1; trial < len(treeMap[y]); trial++ {
if treeMap[y][trial] >= treeHeight {
score += 1
break
}
score += 1
}
final *= score
score = 0
for trial := y - 1; trial >= 0; trial-- {
if treeMap[trial][x] >= treeHeight {
score += 1
break
}
score += 1
}
final *= score
score = 0
for trial := y + 1; trial < len(treeMap); trial++ {
if treeMap[trial][x] >= treeHeight {
score += 1
break
}
score += 1
}
final *= score
return final
}
func Run(filename string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
treeMap := [][]int{}
for scanner.Scan() {
line := scanner.Text()
row := []int{}
for _, c := range line {
value, err := strconv.Atoi(string(c))
if err != nil {
fmt.Println("PANIC: bad character")
return
}
row = append(row, value)
}
treeMap = append(treeMap, row)
}
file.Close()
width := len(treeMap[0])
height := len(treeMap)
visibleTrees := 2*width + 2*height - 4
bestScore := 1
for y := 1; y < (height - 1); y++ {
for x := 1; x < (width - 1); x++ {
if isVisible(treeMap, x, y) {
visibleTrees += 1
//fmt.Printf("*")
} else {
//fmt.Printf("%d", treeMap[y][x])
}
current := score(treeMap, x, y)
if current > bestScore {
bestScore = current
}
}
//fmt.Println()
}
fmt.Println("There are", visibleTrees, "visible trees")
fmt.Println("Best score is", bestScore)
}

153
solutions/day9/day9.go Normal file
View File

@@ -0,0 +1,153 @@
package day9
import (
"bufio"
"fmt"
"os"
"strconv"
)
type Point struct {
x int
y int
}
func makeSnake(length int) []*Point {
snake := []*Point{}
for i := 0; i < length; i++ {
snake = append(snake, &Point{0, 0})
}
return snake
}
func move(direction rune, point *Point) {
switch direction {
case 'U':
point.y -= 1
case 'R':
point.x += 1
case 'D':
point.y += 1
case 'L':
point.x -= 1
default:
fmt.Println("PANIC! Bad direction", string(direction))
}
}
func abs(x int) int {
if x >= 0 {
return x
} else {
return -x
}
}
func areTouching(head *Point, tail *Point) bool {
return abs(head.x-tail.x) <= 1 && abs(head.y-tail.y) <= 1
}
func updateTail(head *Point, tail *Point) {
if areTouching(head, tail) {
return
}
if head.x == tail.x {
if head.y <= tail.y-2 {
tail.y -= 1
return
}
if head.y >= tail.y+2 {
tail.y += 1
return
}
}
if head.y == tail.y {
if head.x <= tail.x-2 {
tail.x -= 1
return
}
if head.x >= tail.x+2 {
tail.x += 1
return
}
}
proposedTails := []Point{
{tail.x - 1, tail.y - 1},
{tail.x - 1, tail.y + 1},
{tail.x + 1, tail.y - 1},
{tail.x + 1, tail.y + 1}}
for _, proposed := range proposedTails {
if areTouching(head, &proposed) {
tail.x = proposed.x
tail.y = proposed.y
return
}
}
fmt.Println("PANIC, couldn't figure out next step for", *head, "and", *tail)
}
func contains(array *[]Point, point Point) bool {
for _, val := range *array {
if val == point {
return true
}
}
return false
}
func runMove(direction rune, amount int, snake []*Point, visitedPoints *[]Point) {
for i := 0; i < amount; i++ {
move(direction, snake[0])
for t := 1; t < len(snake); t++ {
updateTail(snake[t-1], snake[t])
}
lastPoint := snake[len(snake)-1]
if !contains(visitedPoints, *lastPoint) {
*visitedPoints = append(*visitedPoints, *lastPoint)
}
}
}
func Run(filename string, lengthStr string) {
file, err := os.Open(filename)
if err != nil {
fmt.Println("Error opening file:", err)
return
}
snakeLength, err := strconv.Atoi(lengthStr)
if err != nil {
fmt.Println("Error parsing snake length:", err)
return
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
snake := makeSnake(snakeLength)
visitedPoints := []Point{}
for scanner.Scan() {
line := scanner.Text()
amount, err := strconv.Atoi(line[2:])
if err != nil {
fmt.Println("Could not parse argument", line)
return
}
runMove(rune(line[0]), amount, snake, &visitedPoints)
}
fmt.Println("The tail visited", len(visitedPoints), "points")
file.Close()
}