
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.
Day 1: Part 1
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
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
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
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
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
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
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
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
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
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
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
TODO: Finish this one, lol
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Day 7: Part 1
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
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
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
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
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
TODO: do this lol
TODO
Day 10: Part 1
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
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
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
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
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
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])
}
}