I decided to participate in Advent of Code this year using Golang so I could get a better grasp on the language.
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)
}
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)
}
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)
}
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)
}
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
}
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
}
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
}
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
}
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
}
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
}
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")
}
TODO: Finish this one, lol
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
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)
}
}
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
}
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))
}
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
}
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)
}
TODO: do this lol
TODO
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)
}
}
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)
}
}
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
}
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
}
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])
}
}
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])
}
}
— random.txt