😎 404NotBoring

Sharing things I make—never boring.

updated 2025-01-17

Advent of Code 2024

I decided to participate in Advent of Code this year using Golang so I could get a better grasp on the language.

Github repo

Day 1: Part 1

link to AOC

I split up the input into its columns, threw them into an array, sorted those arrays, and then added the difference between each corresponding value in both arrays to accumulator.

func main() {
	lines := strings.Split(input, "\n")

	var leftList []int
	var rightList []int

	for _, e := range lines {
		fields := strings.Fields(e)

		leftNum, err := strconv.Atoi(fields[0])
		if err != nil {
			log.Panicln("Not a number")
		}

		rightNum, err := strconv.Atoi(fields[1])
		if err != nil {
			log.Panicln("Not a number")
		}

		leftList = append(leftList, leftNum)
		rightList = append(rightList, rightNum)
	}

	sort.Ints(leftList)
	sort.Ints(rightList)

	accumulator := 0

	for index := range leftList {
		difference := leftList[index] - rightList[index]
		if difference > 0 {
			accumulator += difference
		} else {
			accumulator += -difference
		}
	}

	fmt.Println(accumulator)
}

Day 1: Part 2

link to AOC

Pretty easy, just using a hash map for this one.

func main() {
	lines := strings.Split(input, "\n")

	var leftList []int
	var rightList []int

	for _, e := range lines {
		fields := strings.Fields(e)

		leftNum, err := strconv.Atoi(fields[0])
		if err != nil {
			log.Panicln("Not a number")
		}

		rightNum, err := strconv.Atoi(fields[1])
		if err != nil {
			log.Panicln("Not a number")
		}

		leftList = append(leftList, leftNum)
		rightList = append(rightList, rightNum)
	}

	rightListCountMap := make(map[int]int)

	for _, v := range rightList {
		rightListCountMap[v]++
	}

	similarityScore := 0

	for _, v := range leftList {
		num, ok := rightListCountMap[v]
		if ok {
			similarityScore += v * num
		}
	}

	fmt.Println(similarityScore)
}

Day 2: Part 1

link to AOC

Forgetting to check the first pair was a silly mistake. :)

func main() {
	reports := strings.Split(input, "\n")
	safeCount := 0
	for _, report := range reports {
		levels := strings.Split(report, " ")

		isSafe := true
		isIncreasing := true

		for i := 1; i < len(levels); i++ {
			previousNum, err := strconv.Atoi(levels[i-1])
			if err != nil {
				log.Panicln("not a number")
			}
			currentNum, err := strconv.Atoi(levels[i])
			if err != nil {
				log.Panicln("not a number")
			}

			delta := currentNum - previousNum

			// has to be changing at all
			if delta == 0 {
				isSafe = false
				break
			}

			// special case
			if i == 1 {
				// set isIncreasing based on first pair
				if delta > 0 {
				} else {
					isIncreasing = false
				}
			}

			// need to be going in the same direction
			goingInSameDirection := isIncreasing == (delta > 0)
			if !goingInSameDirection {
				isSafe = false
				break
			}

			// 1 <= delta <= 3
			deltaSign := 1
			if delta < 0 {
				deltaSign = -1
			}
			deltaMagnitude := delta * deltaSign
			if deltaMagnitude < 1 || deltaMagnitude > 3 {
				isSafe = false
				break
			}

		}
		if isSafe {
			safeCount++
		}
	}
	fmt.Println(safeCount)
}

Day 2: Part 2

link to AOC

I started by iterating through each level and setting a flag at the index of the first error I encountered. From there, I checked the array with that index removed, then repeated the process with the previous index. It turned into an edge-case nightmare, so I scrapped that approach. Instead, I decided to loop through the levels, exclude the current index, and if the result checked out, I’d count it and move on.

func checkReport(levels []int) bool {
	isSafe := true
	isIncreasing := true

	for i := 1; i < len(levels); i++ {
		previousNum := levels[i-1]
		currentNum := levels[i]

		delta := currentNum - previousNum

		// special case
		if i == 1 {
			// set isIncreasing based on first pair
			if delta > 0 {
			} else {
				isIncreasing = false
			}
		}

		// has to be changing at all
		if delta == 0 {
			isSafe = false
			break
		}

		// need to be going in the same direction
		goingInSameDirection := isIncreasing == (delta > 0)
		if !goingInSameDirection {
			isSafe = false
			break
		}

		// 1 <= delta <= 3
		deltaSign := 1
		if delta < 0 {
			deltaSign = -1
		}
		deltaMagnitude := delta * deltaSign
		if deltaMagnitude < 1 || deltaMagnitude > 3 {
			isSafe = false
			break
		}

	}
	if isSafe {
		return isSafe
	} else {
		return isSafe
	}
}
func main() {
	reports := strings.Split(input, "\n")
	safeCount := 0
	for _, report := range reports {
		levels := strings.Split(report, " ")

		var levelsIntsSlice []int

		for _, v := range levels {
			num, err := strconv.Atoi(v)
			if err != nil {
				log.Panicln("not a number")
			}
			levelsIntsSlice = append(levelsIntsSlice, num)
		}

		for index := range levelsIntsSlice {
			cpy := append([]int(nil), levelsIntsSlice...)
			excludedSlice := append(cpy[:index], cpy[index+1:]...)

			isSafe := checkReport(excludedSlice)
			if isSafe {
				safeCount++
				break
			}
		}
	}
	fmt.Println(safeCount)
}

Day 3: Part 1

link to AOC

I suspect that part 2 will be a little more complex so I implemented a lexer and parser. Also it’s fun to make them. :-)

package main

import (
	"fmt"
	"strconv"
	"unicode"
)

type TokenType string

const (
	EOF           TokenType = "EOF"
	Illegal       TokenType = "ILLEGAL"
	Number        TokenType = "NUMBER"
	Multiply      TokenType = "MULTIPLY"
	OpenBrackets  TokenType = "OPEN_BRACKETS"
	CloseBrackets TokenType = "CLOSE_BRACKETS"
	Comma         TokenType = "COMMA"
)

type Token struct {
	Type  TokenType
	Value string
}

func main() {
	myLexer := NewLexer(input)
	tokens := myLexer.getTokens()

	myParser := NewParser(tokens)
	fmt.Println(myParser.parse())
}

type Parser struct {
	tokens   []Token
	position int
	token    Token
}

func NewParser(tokens []Token) *Parser {
	parser := &Parser{
		tokens: tokens,
	}
	parser.position = -1
	parser.nextToken() // -1 -> 0

	return parser
}

func (parser *Parser) parse() int {
	sum := 0
	for parser.token.Type != EOF {
		if parser.token.Type == Multiply {
			parser.nextToken()
			leftNum, rightNum, ok := parser.numberPair()
			if ok {
				sum += leftNum * rightNum
			}
		}
		parser.nextToken()
	}
	return sum
}

func (parser *Parser) numberPair() (int, int, bool) {
	var leftNum int
	var rightNum int

	if parser.token.Type != OpenBrackets {
		return 0, 0, false
	}
	parser.nextToken()
	if parser.token.Type != Number {
		return 0, 0, false
	}
	leftNum, err := strconv.Atoi(parser.token.Value)
	if err != nil {
		return 0, 0, false
	}
	parser.nextToken()
	if parser.token.Type != Comma {
		return 0, 0, false
	}
	parser.nextToken()
	if parser.token.Type != Number {
		return 0, 0, false
	}
	rightNum, err = strconv.Atoi(parser.token.Value)
	if err != nil {
		return 0, 0, false
	}
	parser.nextToken()
	if parser.token.Type != CloseBrackets {
		return 0, 0, false
	}
	return leftNum, rightNum, true
}

func (parser *Parser) nextToken() {
	parser.position++
	parser.token = parser.tokens[parser.position]
}

type Lexer struct {
	input    string
	position int
	char     byte
}

func NewLexer(input string) *Lexer {
	lexer := &Lexer{input: input}
	lexer.position = -1
	lexer.nextChar()
	return lexer
}

func (lexer *Lexer) nextChar() {
	lexer.position++

	if lexer.position >= len(lexer.input) {
		lexer.char = 0 // represents EOF
	} else {
		lexer.char = lexer.input[lexer.position]
	}
}

func (lexer *Lexer) readCurrentChar() byte {
	if lexer.position >= len(lexer.input) {
		lexer.char = 0 // represents EOF
		return lexer.char
	} else {
		lexer.char = lexer.input[lexer.position]
		return lexer.char
	}
}

func (lexer *Lexer) peekChar() byte {
	if lexer.position+1 >= len(lexer.input) {
		return 0
	} else {
		return lexer.input[lexer.position+1]
	}
}

func (lexer *Lexer) readWord() string {
	var chars []byte
	for unicode.IsLetter(rune(lexer.char)) {
		chars = append(chars, lexer.char)
		lexer.nextChar()
	}
	return string(chars)
}

func (lexer *Lexer) readNumber() string {
	var chars []byte
	for unicode.IsNumber(rune(lexer.char)) {
		chars = append(chars, lexer.char)
		lexer.nextChar()
	}
	return string(chars)
}

func (lexer *Lexer) getTokens() []Token {
	tokens := []Token{}
	for lexer.char != 0 {
		switch {
		case rune(lexer.char) == 'm':
			word := lexer.readWord()
			if word == "mul" {
				tokens = append(tokens, Token{Type: Multiply, Value: word})
			}
		case rune(lexer.char) == '(':
			tokens = append(tokens, Token{Type: OpenBrackets, Value: string(lexer.char)})
			lexer.nextChar()
		case rune(lexer.char) == ')':
			tokens = append(tokens, Token{Type: CloseBrackets, Value: string(lexer.char)})
			lexer.nextChar()
		case rune(lexer.char) == ',':
			tokens = append(tokens, Token{Type: Comma, Value: string(lexer.char)})
			lexer.nextChar()
		case unicode.IsNumber(rune(lexer.char)):
			number := lexer.readNumber()
			tokens = append(tokens, Token{Type: Number, Value: number})
		default:
			tokens = append(tokens, Token{Type: Illegal, Value: string(lexer.char)})
			lexer.nextChar()
		}
	}

	tokens = append(tokens, Token{Type: EOF, Value: ""})
	return tokens
}

Day 3: Part 2

link to AOC

I’m glad I made a lexer and parser because this part 2 was really easy to implement.

package main

import (
	"fmt"
	"strconv"
	"unicode"
)

type TokenType string

const (
	EOF           TokenType = "EOF"
	Illegal       TokenType = "ILLEGAL"
	Number        TokenType = "NUMBER"
	Multiply      TokenType = "MULTIPLY"
	Do            TokenType = "DO"
	Dont          TokenType = "DONT"
	OpenBrackets  TokenType = "OPEN_BRACKETS"
	CloseBrackets TokenType = "CLOSE_BRACKETS"
	Comma         TokenType = "COMMA"
)

type Token struct {
	Type  TokenType
	Value string
}

func main() {
	myLexer := NewLexer(input)
	tokens := myLexer.getTokens()

	myParser := NewParser(tokens)
	fmt.Println(myParser.parse())
}

type Parser struct {
	tokens   []Token
	position int
	token    Token
}

func NewParser(tokens []Token) *Parser {
	parser := &Parser{
		tokens: tokens,
	}
	parser.position = -1
	parser.nextToken() // -1 -> 0

	return parser
}

func (parser *Parser) parse() int {
	sum := 0
	do := true
	for parser.token.Type != EOF {
		if parser.token.Type == Do {
			do = true
		}
		if parser.token.Type == Dont {
			do = false
		}
		if parser.token.Type == Multiply {
			parser.nextToken()
			leftNum, rightNum, ok := parser.numberPair()
			if ok && do {
				sum += leftNum * rightNum
			}
		}
		parser.nextToken()
	}
	return sum
}

func (parser *Parser) numberPair() (int, int, bool) {
	var leftNum int
	var rightNum int

	if parser.token.Type != OpenBrackets {
		return 0, 0, false
	}
	parser.nextToken()
	if parser.token.Type != Number {
		return 0, 0, false
	}
	leftNum, err := strconv.Atoi(parser.token.Value)
	if err != nil {
		return 0, 0, false
	}
	parser.nextToken()
	if parser.token.Type != Comma {
		return 0, 0, false
	}
	parser.nextToken()
	if parser.token.Type != Number {
		return 0, 0, false
	}
	rightNum, err = strconv.Atoi(parser.token.Value)
	if err != nil {
		return 0, 0, false
	}
	parser.nextToken()
	if parser.token.Type != CloseBrackets {
		return 0, 0, false
	}
	return leftNum, rightNum, true
}

func (parser *Parser) nextToken() {
	parser.position++
	parser.token = parser.tokens[parser.position]
}

type Lexer struct {
	input    string
	position int
	char     byte
}

func NewLexer(input string) *Lexer {
	lexer := &Lexer{input: input}
	lexer.position = -1
	lexer.nextChar()
	return lexer
}

func (lexer *Lexer) nextChar() {
	lexer.position++

	if lexer.position >= len(lexer.input) {
		lexer.char = 0 // represents EOF
	} else {
		lexer.char = lexer.input[lexer.position]
	}
}

func (lexer *Lexer) readCurrentChar() byte {
	if lexer.position >= len(lexer.input) {
		lexer.char = 0 // represents EOF
		return lexer.char
	} else {
		lexer.char = lexer.input[lexer.position]
		return lexer.char
	}
}

func (lexer *Lexer) peekChar() byte {
	if lexer.position+1 >= len(lexer.input) {
		return 0
	} else {
		return lexer.input[lexer.position+1]
	}
}

func (lexer *Lexer) readWord() string {
	var chars []byte
	for unicode.IsLetter(rune(lexer.char)) || rune(lexer.char) == '\'' {
		chars = append(chars, lexer.char)
		lexer.nextChar()
	}
	return string(chars)
}

func (lexer *Lexer) readNumber() string {
	var chars []byte
	for unicode.IsNumber(rune(lexer.char)) {
		chars = append(chars, lexer.char)
		lexer.nextChar()
	}
	return string(chars)
}

func (lexer *Lexer) getTokens() []Token {
	tokens := []Token{}
	for lexer.char != 0 {
		switch {
		case rune(lexer.char) == 'm':
			word := lexer.readWord()
			if word == "mul" {
				tokens = append(tokens, Token{Type: Multiply, Value: word})
			}
		case rune(lexer.char) == 'd':
			word := lexer.readWord()
			if word == "do" {
				tokens = append(tokens, Token{Type: Do, Value: word})
			} else if word == "don't" {
				tokens = append(tokens, Token{Type: Dont, Value: word})
			}
		case rune(lexer.char) == '(':
			tokens = append(tokens, Token{Type: OpenBrackets, Value: string(lexer.char)})
			lexer.nextChar()
		case rune(lexer.char) == ')':
			tokens = append(tokens, Token{Type: CloseBrackets, Value: string(lexer.char)})
			lexer.nextChar()
		case rune(lexer.char) == ',':
			tokens = append(tokens, Token{Type: Comma, Value: string(lexer.char)})
			lexer.nextChar()
		case unicode.IsNumber(rune(lexer.char)):
			number := lexer.readNumber()
			tokens = append(tokens, Token{Type: Number, Value: number})
		default:
			tokens = append(tokens, Token{Type: Illegal, Value: string(lexer.char)})
			lexer.nextChar()
		}
	}

	tokens = append(tokens, Token{Type: EOF, Value: ""})
	return tokens
}

Day 4: Part 1

link to AOC

This one was pretty straightforward. Just iterating over every cell and checking if its orthogonal neighbors have the next character in XMAS. I could optimize this a little by making the findWord() function return false earlier, but who cares.

package main

import (
	"fmt"
	"strings"
)

func main() {
	rows := strings.Split(input, "\n")
	width := len(rows[0])
	height := len(rows)
	fmt.Println(input)
	fmt.Println("")

	var grid = make([][]string, width)
	for i := range grid {
		grid[i] = make([]string, height)
	}

	straightInput := strings.ReplaceAll(input, "\n", "")
	for i, c := range straightInput {
		char := string(c)
		y := i / height
		x := i % width

		grid[x][y] = char
	}

	ans := 0
	for j := 0; j < height; j++ {
		for i := 0; i < width; i++ {
			count := findXMAS(grid, i, j)
			ans += count
			if count > 0 {
				fmt.Print(count)
			} else {
				fmt.Print("_")
			}
		}
		fmt.Print("\n")
	}
	fmt.Println(ans)
}

func query(grid [][]string, x int, y int, match string) bool {
	if x < 0 || x >= len(grid[0]) {
		return false
	}
	if y < 0 || y >= len(grid) {
		return false
	}

	return grid[x][y] == match
}

func findXMAS(grid [][]string, x int, y int) int {
	count := 0

	if findWord(grid, "XMAS", x, y, -1, -1) {
		count++
	}
	if findWord(grid, "XMAS", x, y, 0, -1) {
		count++
	}
	if findWord(grid, "XMAS", x, y, 1, -1) {
		count++
	}
	if findWord(grid, "XMAS", x, y, -1, 0) {
		count++
	}
	if findWord(grid, "XMAS", x, y, 1, 0) {
		count++
	}
	if findWord(grid, "XMAS", x, y, -1, 1) {
		count++
	}
	if findWord(grid, "XMAS", x, y, 0, 1) {
		count++
	}
	if findWord(grid, "XMAS", x, y, 1, 1) {
		count++
	}

	return count
}

func findWord(grid [][]string, word string, x int, y int, dx int, dy int) bool {
	for i, l := range word {
		letter := string(l)
		if !query(grid, x+i*dx, y+i*dy, letter) {
			return false
		}
	}
	return true
}

Day 4: Part 2

link to AOC

I was bashing my head in. Turns out I need to check and remove this pattern from the candidates. Lol, lmao.

M_S
_A_
S_M
package main

import (
	"fmt"
	"strings"
)

func main() {
	rows := strings.Split(input, "\n")
	width := len(rows[0])
	height := len(rows)

	var grid = make([][]string, width)
	for i := range grid {
		grid[i] = make([]string, height)
	}

	straightInput := strings.ReplaceAll(input, "\n", "")
	for i, c := range straightInput {
		char := string(c)
		y := i / height
		x := i % width

		grid[x][y] = char
	}

	ans := 0
	for j := 0; j < height; j++ {
		for i := 0; i < width; i++ {
			currentChar := query(grid, i, j)

			c := findXMAS(grid, i, j)
			if c > 0 {
				fmt.Print("_")
				ans++
			} else {
				fmt.Print(currentChar)
			}
		}
		fmt.Print("\n")
	}
	fmt.Println(ans)
}

func query(grid [][]string, x int, y int) string {
	if x < 0 || x >= len(grid) {
		return ""
	}
	if y < 0 || y >= len(grid[0]) {
		return ""
	}

	return grid[x][y]
}

func findXMAS(grid [][]string, x int, y int) int {
	currentChar := grid[x][y]

	if currentChar == "A" {
		// just add up the tl tr bl br M's and S's
		mCount := 0
		sCount := 0

		tl := query(grid, x-1, y-1)
		if tl == "M" {
			mCount++
		} else if tl == "S" {
			sCount++
		} else {
			return 0
		}

		tr := query(grid, x+1, y-1)
		if tr == "M" {
			mCount++
		} else if tr == "S" {
			sCount++
		} else {
			return 0
		}

		bl := query(grid, x-1, y+1)
		if bl == "M" {
			mCount++
		} else if bl == "S" {
			sCount++
		} else {
			return 0
		}

		br := query(grid, x+1, y+1)
		if br == "M" {
			mCount++
		} else if br == "S" {
			sCount++
		} else {
			return 0
		}

		if mCount == 2 && sCount == 2 && tl != br && tr != bl {
			return 1
		} else {
			return 0
		}
	}
	return 0
}

Day 5: Part 1

link to AOC

This one was a lot easier than day 3. We’ll see how well part 2 goes…

package main

import (
	"fmt"
	"slices"
	"strconv"
	"strings"
)

var rulesHash = make(map[int][]int)

func main() {
	split := strings.Split(input, "\n\n")
	rules := strings.Split(split[0], "\n")
	pages := strings.Split(split[1], "\n")

	for _, v := range rules {
		s := strings.Split(v, "|")

		leftNum, _ := strconv.Atoi(s[0])
		rightNum, _ := strconv.Atoi(s[1])

		rulesHash[leftNum] = append(rulesHash[leftNum], rightNum)
	}

	accumulator := 0
	for _, v := range pages {
		e := strings.Split(v, ",")
		nums := []int{}

		for _, n := range e {
			asdf, _ := strconv.Atoi(n)
			nums = append(nums, asdf)
		}

		good := checkPagesAgainstRules(nums)
		if good {
			accumulator += nums[len(nums)/2]
		}
	}
	fmt.Println(accumulator)
}

func checkPagesAgainstRules(nums []int) bool {
	for currentNumIndex, currentNum := range nums {
		currentNumShouldBeBefore := rulesHash[currentNum]

		for checkNumIndex, checkNum := range nums {
			if checkNumIndex >= currentNumIndex {
				continue
			}

			if slices.Contains(currentNumShouldBeBefore, checkNum) {
				return false
			}
		}
	}
	return true
}

Day 5: Part 2

link to AOC

Not too bad, just implementing a sort of bubble sort I guess. There’s tons of room for optimization on this one.

package main

import (
	"fmt"
	"slices"
	"strconv"
	"strings"
)

var rulesHash = make(map[int][]int)

func main() {
	split := strings.Split(input, "\n\n")
	rules := strings.Split(split[0], "\n")
	pages := strings.Split(split[1], "\n")

	for _, v := range rules {
		s := strings.Split(v, "|")

		leftNum, _ := strconv.Atoi(s[0])
		rightNum, _ := strconv.Atoi(s[1])

		rulesHash[leftNum] = append(rulesHash[leftNum], rightNum)
	}

	accumulator := 0
	for _, v := range pages {
		e := strings.Split(v, ",")
		nums := []int{}

		for _, n := range e {
			asdf, _ := strconv.Atoi(n)
			nums = append(nums, asdf)
		}

		good := checkPagesAgainstRules(nums)
		if good {
			accumulator += nums[len(nums)/2]
		}
	}
	fmt.Println(accumulator)
}

func checkPagesAgainstRules(nums []int) bool {
	for currentNumIndex, currentNum := range nums {
		currentNumShouldBeBefore := rulesHash[currentNum]

		for checkNumIndex, checkNum := range nums {
			if checkNumIndex >= currentNumIndex {
				continue
			}

			if slices.Contains(currentNumShouldBeBefore, checkNum) {
				return false
			}
		}
	}
	return true
}

Day 6: Part 1

link to AOC

I like this one, reminds me of making cellular automata.

package main

import (
	"errors"
	"fmt"
	"strings"
)

func main() {
	rows := strings.Split(input, "\n")

	pathLength := 0

	x, y, dx, dy, err := findGuard(rows)
	fmt.Println(x, y, dx, dy, err)
	for err == nil {
		guardChar, _ := query(x, y, rows)

		// update guard's pos
		upcomingTile, err2 := query(x+dx, y+dy, rows)
		if err2 != nil {
			// guard is at the edge
			pathLength++
			break
		}

		switch {
		case upcomingTile == '#':
			switch {
			case dx == 0 && dy == -1:
				replace(x, y, rows, '>')
			case dx == 1 && dy == 0:
				replace(x, y, rows, 'v')
			case dx == 0 && dy == 1:
				replace(x, y, rows, '<')
			case dx == -1 && dy == 0:
				replace(x, y, rows, '^')
			}
		default:
			// clear
			replace(x+dx, y+dy, rows, guardChar)
			replace(x, y, rows, 'X')
			if upcomingTile != 'X' {
				pathLength++
			}
		}

		x, y, dx, dy, err = findGuard(rows)
	}
	fmt.Println("")
	for _, row := range rows {
		fmt.Println(row)
	}

	fmt.Println(pathLength)
}

func query(x int, y int, rows []string) (byte, error) {
	width := len(rows[0])
	height := len(rows)

	if x < 0 || x >= width || y < 0 || y >= height {
		fmt.Println("oob")
		return 0, errors.New("out of bounds")
	}

	return rows[y][x], nil
}

func replace(x int, y int, rows []string, char byte) (byte, error) {
	replacedChar, err := query(x, y, rows)
	if err != nil {
		return 0, err
	}

	row := rows[y]

	var newRow []byte
	for i, v := range row {
		if i == x {
			newRow = append(newRow, char)
			continue
		}
		newRow = append(newRow, byte(v))
	}
	rows[y] = string(newRow)

	return replacedChar, nil
}

func findGuard(rows []string) (x int, y int, dx int, dy int, err error) {
	width := len(rows[0])
	height := len(rows)

	for searchX := range width {
		for searchY := range height {
			char, _ := query(searchX, searchY, rows)
			switch {
			case char == '^':
				return searchX, searchY, 0, -1, nil
			case char == '>':
				return searchX, searchY, 1, 0, nil
			case char == 'v':
				return searchX, searchY, 0, 1, nil
			case char == '<':
				return searchX, searchY, -1, 0, nil
			default:
				continue
			}
		}
	}

	return 0, 0, 0, 0, errors.New("couldn't find the guard")
}

Day 6: Part 2

link to AOC

TODO: Finish this one, lol

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Day 7: Part 1

link to AOC

After futzing around with a silly iterative way of doing this I just went to recursion and that made this a Hell of a lot easier.

package main

import (
	"fmt"
	"strconv"
	"strings"
)

func main() {
	fmt.Println(input)
	rows := strings.Split(input, "\n")
	passedAccumulation := 0

	for _, row := range rows {
		s := strings.Split(row, ": ")
		result, _ := strconv.Atoi(s[0])

		n := strings.Split(s[1], " ")
		nums := []int{}
		for _, asdf := range n {
			ee, _ := strconv.Atoi(asdf)
			nums = append(nums, ee)
		}

		if test(result, nums, 0, 0) {
			passedAccumulation += result
		}
	}
	fmt.Println("")
	fmt.Println(passedAccumulation, "total passed added together")
}

func test(target int, nums []int, accumulator int, indent int) (ok bool) {
	if len(nums) == 0 {
		return accumulator == target
	} else {
		num := nums[0]
		return test(target, nums[1:], accumulator+num, indent+1) || test(target, nums[1:], accumulator*num, indent+1)
	}
}

Day 7: Part 2

link to AOC

This one was pretty easy to add onto. Part 2 of today was way easier than part 2 of Yesterday.

package main

import (
	"fmt"
	"strconv"
	"strings"
)

func main() {
	fmt.Println(input)
	rows := strings.Split(input, "\n")
	passedAccumulation := 0

	for _, row := range rows {
		s := strings.Split(row, ": ")
		result, _ := strconv.Atoi(s[0])

		n := strings.Split(s[1], " ")
		nums := []int{}
		for _, asdf := range n {
			ee, _ := strconv.Atoi(asdf)
			nums = append(nums, ee)
		}

		if test(result, nums, 0, 0) {
			passedAccumulation += result
		}
	}
	fmt.Println("")
	fmt.Println(passedAccumulation, "total passed added together")
}

func test(target int, nums []int, accumulator int, indent int) (ok bool) {
	if len(nums) == 0 {
		return accumulator == target
	} else {
		num := nums[0]
		joinedNum := concat(accumulator, num)
		return test(target, nums[1:], joinedNum, indent+1) || test(target, nums[1:], accumulator+num, indent+1) || test(target, nums[1:], accumulator*num, indent+1)
	}
}

func concat(leftNum int, rightNum int) int {
	leftString := strconv.Itoa(leftNum)
	rightString := strconv.Itoa(rightNum)
	joinedString := strings.Join([]string{leftString, rightString}, "")

	joinedNum, _ := strconv.Atoi(joinedString)

	return joinedNum
}

Day 8: Part 1

link to AOC

This one was nice and simple.

package main

import (
	"fmt"
	"strings"
	"unicode"
)

type Coordinates struct {
	x int
	y int
}

type Node struct {
	pos  Coordinates
	kind rune
}

func main() {
	r := strings.Split(input, "\n")
	fmt.Println(input)

	nodes := []Node{}

	for y, row := range r {
		for x, char := range row {
			if unicode.IsLetter(char) || unicode.IsNumber(char) {
				nodes = append(nodes, Node{pos: Coordinates{x, y}, kind: char})
			}
		}
	}

	antinodes := make(map[Coordinates]bool)

	height := len(r)
	width := len(r[0])

	for _, node := range nodes {
		for _, otherNode := range nodes {
			if node == otherNode {
				continue
			}
			if node.kind != otherNode.kind {
				continue
			}
			// node and otherNode are not the same and also share the same kind

			dx := otherNode.pos.x - node.pos.x
			dy := otherNode.pos.y - node.pos.y
			antinodePos := Coordinates{node.pos.x + 2*dx, node.pos.y + 2*dy}
			if antinodePos.x >= 0 && antinodePos.x < width && antinodePos.y >= 0 && antinodePos.y < height {
				antinodes[antinodePos] = true
			}
		}
	}

	fmt.Println("")
	for y := 0; y < height; y++ {
		for x := 0; x < width; x++ {
			if antinodes[Coordinates{x, y}] == true {
				fmt.Print("x")
			} else {
				fmt.Print(".")
			}
		}
		fmt.Println("")
	}
	fmt.Println(len(antinodes))
}

Day 8: Part 2

link to AOC

I shamelessly copy-pasted the Euclidean algorithm because I just did not feel like futzing around with it, lol.

package main

import (
	"fmt"
	"strings"
	"unicode"
)

type Coordinates struct {
	x int
	y int
}

type Node struct {
	pos  Coordinates
	kind rune
}

func main() {
	r := strings.Split(input, "\n")
	fmt.Println(input)

	nodes := []Node{}

	for y, row := range r {
		for x, char := range row {
			if unicode.IsLetter(char) || unicode.IsNumber(char) {
				nodes = append(nodes, Node{pos: Coordinates{x, y}, kind: char})
			}
		}
	}

	antinodes := make(map[Coordinates]bool)

	height := len(r)
	width := len(r[0])

	for _, node := range nodes {
		for _, otherNode := range nodes {
			if node == otherNode {
				continue
			}
			if node.kind != otherNode.kind {
				continue
			}
			// node and otherNode are not the same and also share the same kind

			dx := otherNode.pos.x - node.pos.x
			dy := otherNode.pos.y - node.pos.y
			gcd := gcd(dx, dy)

			xStep := dx / gcd
			yStep := dy / gcd

			i := 0
			antinodePos := Coordinates{node.pos.x + i*xStep, node.pos.y + i*yStep}

			// forward
			for antinodePos.x >= 0 && antinodePos.x < width && antinodePos.y >= 0 && antinodePos.y < height {
				antinodes[antinodePos] = true

				i++
				antinodePos = Coordinates{node.pos.x + i*xStep, node.pos.y + i*yStep}
			}

			i = 0
			antinodePos = Coordinates{node.pos.x + -i*xStep, node.pos.y + -i*yStep}

			// backwards
			for antinodePos.x >= 0 && antinodePos.x < width && antinodePos.y >= 0 && antinodePos.y < height {
				antinodes[antinodePos] = true

				i++
				antinodePos = Coordinates{node.pos.x + -i*xStep, node.pos.y + -i*yStep}
			}
		}
	}

	fmt.Println("")
	for y := 0; y < height; y++ {
		for x := 0; x < width; x++ {
			if antinodes[Coordinates{x, y}] == true {
				fmt.Print("x")
			} else {
				fmt.Print(".")
			}
		}
		fmt.Println("")
	}
	fmt.Println(len(antinodes))
}

func gcd(a, b int) int {
	// euclidean algo
	for b != 0 {
		a, b = b, a%b
	}
	return a
}

Day 9: Part 1

link to AOC

This one wasn’t bad at all. There’s definitely a way to do this in one loop though.

package main

import (
	"fmt"
	"strconv"
)

func main() {
	nums := []int{}

	for _, char := range input {
		n, _ := strconv.Atoi(string(char))
		nums = append(nums, n)
	}

	expanded := []int{}

	free := true
	id := 0
	for _, num := range nums {
		if free {
			for i := 0; i < num; i++ {
				expanded = append(expanded, id)
			}
			id++
		} else {
			for i := 0; i < num; i++ {
				expanded = append(expanded, -1)
			}
		}
		free = !free
	}

	fmt.Println(nums)
	fmt.Println(expanded)

	rightIndex := len(expanded) - 1
	leftIndex := 0

	for {
		// first, go to the next indicies
		// skip numbered on left
		for expanded[leftIndex] != -1 {
			leftIndex++
			if leftIndex >= len(expanded) {
				break
			}
		}
		// skip un-numbered on the right
		for expanded[rightIndex] == -1 {
			rightIndex--
			if rightIndex < 0 {
				break
			}
		}
		if leftIndex >= rightIndex || leftIndex >= len(expanded) || rightIndex < 0 {
			break
		}

		// great, now do stuff

		expanded[leftIndex] = expanded[rightIndex]
		expanded[rightIndex] = -1

		leftIndex++
		rightIndex--

	}

	fmt.Println(expanded)

	checkSum := 0

	for i, num := range expanded {
		if num == -1 {
			break
		} else {
			checkSum += i * num
		}
	}
	fmt.Println(checkSum)
}

Day 9: Part 2

link to AOC

TODO: do this lol

TODO

Day 10: Part 1

link to AOC

Thought I was smart looking in the reverse order, but I don’t think it actually saves any time.

package main

import (
	"fmt"
	"strconv"
	"strings"
)

func main() {
	var grid [][]int

	fmt.Println(input)
	for i, row := range strings.Split(input, "\n") {
		grid = append(grid, []int{})
		for _, col := range row {
			num, err := strconv.Atoi(string(col))
			if err != nil {
				grid[i] = append(grid[i], -1)
			} else {
				grid[i] = append(grid[i], num)
			}
		}
	}

	width := len(grid[0])
	height := len(grid)

	var trailheads [][2]int

	for x := 0; x < width; x++ {
		for y := 0; y < height; y++ {
			current := query(x, y, grid)
			if current == 9 {
				trailheads = append(trailheads, [2]int{x, y})
			}
		}
	}

	score := 0
	for _, trailhead := range trailheads {
		x := trailhead[0]
		y := trailhead[1]

		found := make(map[Coordinates]bool)

		trail(x, y, &score, grid, found)
	}
	fmt.Println(score)
}

type Coordinates struct {
	x int
	y int
}

func query(x int, y int, grid [][]int) int {
	if x < 0 || x >= len(grid[0]) || y < 0 || y >= len(grid) {
		return -100
	} else {
		return grid[y][x]
	}

}

func trail(x int, y int, score *int, grid [][]int, found map[Coordinates]bool) {
	current := query(x, y, grid)
	if current == 0 {
		coords := Coordinates{x: x, y: y}
		if !found[coords] {
			*score++
			found[coords] = true
		}
		return
	}

	l := query(x-1, y, grid)
	if l-current == -1 {
		trail(x-1, y, score, grid, found)
	}
	r := query(x+1, y, grid)
	if r-current == -1 {
		trail(x+1, y, score, grid, found)
	}
	u := query(x, y-1, grid)
	if u-current == -1 {
		trail(x, y-1, score, grid, found)
	}
	d := query(x, y+1, grid)
	if d-current == -1 {
		trail(x, y+1, score, grid, found)
	}

}

Day 10: Part 2

link to AOC

Funny thing is, I actually solved part 2 while doing part 1 because I read it wrong. Very epic.

package main

import (
	"fmt"
	"strconv"
	"strings"
)

func main() {
	var grid [][]int

	fmt.Println(input)
	for i, row := range strings.Split(input, "\n") {
		grid = append(grid, []int{})
		for _, col := range row {
			num, err := strconv.Atoi(string(col))
			if err != nil {
				grid[i] = append(grid[i], -1)
			} else {
				grid[i] = append(grid[i], num)
			}
		}
	}

	width := len(grid[0])
	height := len(grid)

	var trailheads [][2]int

	for x := 0; x < width; x++ {
		for y := 0; y < height; y++ {
			current := query(x, y, grid)
			if current == 9 {
				trailheads = append(trailheads, [2]int{x, y})
			}
		}
	}

	score := 0
	for _, trailhead := range trailheads {
		x := trailhead[0]
		y := trailhead[1]

		found := make(map[Coordinates]bool)

		trail(x, y, &score, grid, found)
	}
	fmt.Println(score)
}

type Coordinates struct {
	x int
	y int
}

func query(x int, y int, grid [][]int) int {
	if x < 0 || x >= len(grid[0]) || y < 0 || y >= len(grid) {
		return -100
	} else {
		return grid[y][x]
	}

}

func trail(x int, y int, score *int, grid [][]int, found map[Coordinates]bool) {
	current := query(x, y, grid)
	if current == 0 {
		*score++
		return
	}

	l := query(x-1, y, grid)
	if l-current == -1 {
		trail(x-1, y, score, grid, found)
	}
	r := query(x+1, y, grid)
	if r-current == -1 {
		trail(x+1, y, score, grid, found)
	}
	u := query(x, y-1, grid)
	if u-current == -1 {
		trail(x, y-1, score, grid, found)
	}
	d := query(x, y+1, grid)
	if d-current == -1 {
		trail(x, y+1, score, grid, found)
	}

}

Day 11: Part 1

link to AOC

Nice and simple, I like this one.

package main

import (
	"fmt"
	"strconv"
	"strings"
)

func main() {
	e := strings.Split(input, " ")
	nums := []int{}
	for _, numStr := range e {
		num, _ := strconv.Atoi(numStr)
		nums = append(nums, num)
	}
	fmt.Println(nums)
	for i := 0; i < 25; i++ {
		nums = rules(nums)
	}
	fmt.Println(len(nums))
}

func rules(nums []int) (result []int) {
	var tmp [][]int

	for _, num := range nums {

		var numTurnsInto []int
		if num == 0 {
			numTurnsInto = []int{1}
		} else if hasEvenDigits(num) {
			numStr := strconv.Itoa(num)
			length := len(numStr)
			half := length / 2

			left := numStr[:half]
			right := numStr[half:]

			leftNum, _ := strconv.Atoi(left)
			rightNum, _ := strconv.Atoi(right)

			numTurnsInto = []int{leftNum, rightNum}

		} else {
			numTurnsInto = []int{num * 2024}
		}

		tmp = append(tmp, numTurnsInto)
	}

	for _, t := range tmp {
		result = append(result, t...)
	}

	return result
}

func hasEvenDigits(n int) bool {
	numStr := strconv.Itoa(n)
	return len(numStr)%2 == 0
}

Day 11: Part 2

link to AOC

I was really racking my brain trying to make some sort of amalgamation of an iterative approach with a recursive one. After sleeping on it I thought of the solution. Many such cases.

package main

import (
	"fmt"
	"strconv"
	"strings"
)

func main() {
	limit := 75
	e := strings.Split(input, " ")
	nums := []int{}
	for _, numStr := range e {
		num, _ := strconv.Atoi(numStr)
		nums = append(nums, num)
	}

	answer := 0
	for _, num := range nums {
		answer += solve(num, limit)
	}

	fmt.Println(answer)
}

type Pair struct {
	num, blinks int
}

var cached = make(map[Pair]int)

func solve(num int, blinks int) int {
	var val int

	if blinks == 0 {
		return 1
	}

	checkPair := Pair{num, blinks}
	hit, ok := cached[checkPair]
	if ok {
		val = hit
	} else if num == 0 {
		val = solve(1, blinks-1)
	} else if hasEvenDigits(num) {
		numStr := strconv.Itoa(num)
		length := len(numStr)
		half := length / 2

		left := numStr[:half]
		right := numStr[half:]

		leftNum, _ := strconv.Atoi(left)
		rightNum, _ := strconv.Atoi(right)

		val = solve(leftNum, blinks-1) + solve(rightNum, blinks-1)
	} else {
		val = solve(num*2024, blinks-1)
	}
	cached[checkPair] = val
	return val
}

func hasEvenDigits(n int) bool {
	numStr := strconv.Itoa(n)
	return len(numStr)%2 == 0
}

Day 12: Part 1

link to AOC

This was pretty simple, I wonder what part two is going to throw at me.

package main

import (
	"fmt"
	"strings"
)

type Coordinates struct {
	x, y int
}

var alreadyVisited = make(map[Coordinates]bool)

var grid = strings.Split(input, "\n")

func main() {
	fmt.Println(input)

	cost := 0
	for y, row := range grid {
		for x := range row {
			currentCoordinates := Coordinates{x, y}
			if !alreadyVisited[currentCoordinates] {
				accumulatedArea := 0
				accumulatedPerimeter := 0
				solve(query(x, y), x, y, &accumulatedArea, &accumulatedPerimeter)
				fmt.Println("area", string(query(x, y)), "cost:", accumulatedArea, "x", accumulatedPerimeter, "=", accumulatedArea*accumulatedPerimeter)
				cost += accumulatedArea * accumulatedPerimeter
			}
		}
	}
	fmt.Println(cost)
}

func solve(char rune, x int, y int, accumulatedArea *int, accumulatedPerimeter *int) {
	t := query(x, y-1) == char
	b := query(x, y+1) == char
	l := query(x-1, y) == char
	r := query(x+1, y) == char

	neighbors := 0
	if t {
		neighbors++
	}
	if b {
		neighbors++
	}
	if l {
		neighbors++
	}
	if r {
		neighbors++
	}

	*accumulatedArea++
	*accumulatedPerimeter += 4 - neighbors

	alreadyVisited[Coordinates{x, y}] = true

	if t && !alreadyVisited[Coordinates{x, y - 1}] {
		solve(char, x, y-1, accumulatedArea, accumulatedPerimeter)
	}
	if b && !alreadyVisited[Coordinates{x, y + 1}] {
		solve(char, x, y+1, accumulatedArea, accumulatedPerimeter)
	}
	if l && !alreadyVisited[Coordinates{x - 1, y}] {
		solve(char, x-1, y, accumulatedArea, accumulatedPerimeter)
	}
	if r && !alreadyVisited[Coordinates{x + 1, y}] {
		solve(char, x+1, y, accumulatedArea, accumulatedPerimeter)
	}
}

func query(x int, y int) rune {
	width := len(grid[0])
	height := len(grid)

	if x < 0 || x >= width || y < 0 || y >= height {
		return '_'
	} else {

		return rune(grid[y][x])
	}
}

Day 12: Part 2

link to AOC

Pretty hefty chunk of code. After some thought I figured that the number of corners also equals the number of sides. Gathering the plots of land into a hashmap and throwing all those into a corner detecting algorithm did wonders. There were a few edge cases that were annoying to stumble upon but I figured it out eventually.

package main

import (
	"fmt"
	"strings"
)

type Coordinates struct {
	x, y int
}

var alreadyVisited = make(map[Coordinates]bool)

var grid = strings.Split(input, "\n")

func main() {
	fmt.Println(input)

	cost := 0
	for y, row := range grid {
		for x := range row {
			currentCoordinates := Coordinates{x, y}
			if !alreadyVisited[currentCoordinates] {
				char := query(currentCoordinates)
				accumulatedArea := 0
				points := make(map[Coordinates]bool)
				getArea(char, currentCoordinates, &accumulatedArea, points)

				sides := sides(points)

				fmt.Println("area", string(char), "costs", accumulatedArea, "area x", sides, "sides =", accumulatedArea*sides)
				fmt.Println()
				cost += accumulatedArea * sides
			}
		}
	}
	fmt.Println(cost)
}

func sides(points map[Coordinates]bool) int {

	s := 0

	minX := 100000
	maxX := -100000
	minY := 100000
	maxY := -100000

	for key := range points {
		if key.x < minX {
			minX = key.x
		}
		if key.y < minY {
			minY = key.y
		}
		if key.x > maxX {
			maxX = key.x
		}
		if key.y > maxY {
			maxY = key.y
		}
	}

	for y := minY; y <= maxY; y++ {
		for x := minX; x <= maxX; x++ {
			fmt.Print("\033[0m")
			c := Coordinates{x, y}
			if checkConvexCorner(&s, c, points) {
				fmt.Print("\033[31m")
			}
			if checkConcaveCorner(&s, c, points) {
				fmt.Print("\033[40m")
			}
			if points[c] {
				fmt.Print(string(query(c)))
			} else {

				fmt.Print(" ")
			}
			fmt.Print("\033[0m")
		}
		fmt.Print("\033[0m")
		fmt.Println()
	}

	return s
}

func checkConvexCorner(sides *int, c Coordinates, points map[Coordinates]bool) bool {
	sidesBefore := *sides
	if !points[c] {
		return false
	} else {
		t := points[Coordinates{c.x, c.y - 1}]
		b := points[Coordinates{c.x, c.y + 1}]
		l := points[Coordinates{c.x - 1, c.y}]
		r := points[Coordinates{c.x + 1, c.y}]
		if !t && !l {
			*sides++
		}
		if !t && !r {
			*sides++
		}
		if !b && !l {
			*sides++
		}
		if !b && !r {
			*sides++
		}

		if sidesBefore != *sides {
			return true
		} else {
			return false
		}
	}
}

func checkConcaveCorner(sides *int, c Coordinates, points map[Coordinates]bool) bool {
	sidesBefore := *sides
	if points[c] {
		return false
	} else {
		t := points[Coordinates{c.x, c.y - 1}]
		tr := points[Coordinates{c.x + 1, c.y - 1}]
		tl := points[Coordinates{c.x - 1, c.y - 1}]
		b := points[Coordinates{c.x, c.y + 1}]
		br := points[Coordinates{c.x + 1, c.y + 1}]
		bl := points[Coordinates{c.x - 1, c.y + 1}]
		l := points[Coordinates{c.x - 1, c.y}]
		r := points[Coordinates{c.x + 1, c.y}]
		if t && l && tl {
			*sides++
		}
		if t && r && tr {
			*sides++
		}
		if b && l && bl {
			*sides++
		}
		if b && r && br {
			*sides++
		}

		if sidesBefore != *sides {
			return true
		} else {
			return false
		}
	}
}

func getArea(char rune, c Coordinates, accumulatedArea *int, points map[Coordinates]bool) {
	t := query(Coordinates{c.x, c.y - 1}) == char
	b := query(Coordinates{c.x, c.y + 1}) == char
	l := query(Coordinates{c.x - 1, c.y}) == char
	r := query(Coordinates{c.x + 1, c.y}) == char

	neighbors := 0
	if t {
		neighbors++
	}
	if b {
		neighbors++
	}
	if l {
		neighbors++
	}
	if r {
		neighbors++
	}

	*accumulatedArea++

	alreadyVisited[c] = true
	points[c] = true

	if t && !alreadyVisited[Coordinates{c.x, c.y - 1}] {
		getArea(char, Coordinates{c.x, c.y - 1}, accumulatedArea, points)
	}
	if b && !alreadyVisited[Coordinates{c.x, c.y + 1}] {
		getArea(char, Coordinates{c.x, c.y + 1}, accumulatedArea, points)
	}
	if l && !alreadyVisited[Coordinates{c.x - 1, c.y}] {
		getArea(char, Coordinates{c.x - 1, c.y}, accumulatedArea, points)
	}
	if r && !alreadyVisited[Coordinates{c.x + 1, c.y}] {
		getArea(char, Coordinates{c.x + 1, c.y}, accumulatedArea, points)
	}
}

func query(c Coordinates) rune {
	width := len(grid[0])
	height := len(grid)

	if c.x < 0 || c.x >= width || c.y < 0 || c.y >= height {
		return '_'
	} else {

		return rune(grid[c.y][c.x])
	}
}

Day 13: Part 1

link to AOC

Day 13: Part 2

link to AOC

— random.txt