advent-of-code-2023/day15/main.go

197 lines
4.4 KiB
Go
Raw Normal View History

2023-12-18 01:50:50 +00:00
package main
import (
2023-12-18 03:29:31 +00:00
"container/list"
"errors"
2023-12-18 01:50:50 +00:00
"fmt"
"io"
"os"
2023-12-18 03:29:31 +00:00
"regexp"
"strconv"
2023-12-18 01:50:50 +00:00
"strings"
)
2023-12-18 03:29:31 +00:00
type HashMap[V any] []*list.List
type HashMapItem[V any] struct {
key string
value V
}
// Operation is some sort of operation we can perform on a hashmap
type Operation[V any] func(HashMap[V])
// makeSetOperation makes an Operation that will set the given key to the given value in the hashmap
func makeSetOperation[V any](key string, value V) Operation[V] {
return func(hm HashMap[V]) {
keyHash := hash(key)
element := findInList(hm[keyHash], func(item HashMapItem[V]) bool {
return item.key == key
})
if element == nil {
hm[keyHash].PushBack(HashMapItem[V]{key: key, value: value})
} else {
element.Value = HashMapItem[V]{value: value, key: key}
}
}
}
// makeDeleteOperation makes an Operation that will delete the given key from the hashmap
func makeDeleteOperation[V any](key string) Operation[V] {
return func(hm HashMap[V]) {
keyHash := hash(key)
element := findInList(hm[keyHash], func(item HashMapItem[V]) bool {
return item.key == key
})
if element != nil {
hm[keyHash].Remove(element)
}
}
}
// NewHashMap makes a new hash map with the given value type
func NewHashMap[V any]() HashMap[V] {
m := make(HashMap[V], 256)
for i := range m {
m[i] = list.New().Init()
}
return m
}
// EntriesInBox gets all the entries in the hash map box with the given index
func (hm HashMap[V]) EntriesInBox(idx int) ([]HashMapItem[V], error) {
if idx > len(hm) {
return nil, errors.New("out of bounds box id")
}
entryList := hm[idx]
res := make([]HashMapItem[V], 0, entryList.Len())
for cursor := entryList.Front(); cursor != nil; cursor = cursor.Next() {
value := cursor.Value.(HashMapItem[V])
res = append(res, value)
}
return res, nil
}
2023-12-18 01:50:50 +00:00
func main() {
if len(os.Args) != 2 && len(os.Args) != 3 {
fmt.Fprintf(os.Stderr, "Usage: %s inputfile\n", os.Args[0])
os.Exit(1)
}
inputFilename := os.Args[1]
inputFile, err := os.Open(inputFilename)
if err != nil {
panic(fmt.Sprintf("could not open input file: %s", err))
}
defer inputFile.Close()
inputBytes, err := io.ReadAll(inputFile)
if err != nil {
panic(fmt.Sprintf("could not read input file: %s", err))
}
input := strings.TrimSpace(string(inputBytes))
inputElements := strings.Split(input, ",")
fmt.Printf("Part 1: %d\n", part1(inputElements))
2023-12-18 03:29:31 +00:00
fmt.Printf("Part 2: %d\n", part2(inputElements))
2023-12-18 01:50:50 +00:00
}
func part1(inputElements []string) int {
sum := 0
for _, element := range inputElements {
sum += hash(element)
}
return sum
}
2023-12-18 03:29:31 +00:00
func part2(inputElements []string) int {
operations := make([]Operation[int], 0, len(inputElements))
for _, element := range inputElements {
operation, err := parseOperation(element)
if err != nil {
panic(fmt.Sprintf("could not parse operation %q: %s", element, err))
}
operations = append(operations, operation)
}
hm := NewHashMap[int]()
for _, operation := range operations {
operation(hm)
}
2023-12-18 03:48:24 +00:00
return calculatePower(hm)
}
func hash(s string) int {
res := 0
for _, char := range s {
res += int(char)
res *= 17
res %= 256
}
return res
}
func calculatePower(hm HashMap[int]) int {
2023-12-18 03:29:31 +00:00
power := 0
for i := range hm {
boxEntries, err := hm.EntriesInBox(i)
if err != nil {
// Can't happen since we're iterating over the known array
panic(err)
}
boxNumber := i + 1
for j, item := range boxEntries {
power += boxNumber * (j + 1) * item.value
}
}
return power
}
// findInList finds the given element in the linked list. The element *MUST* be of the type V, or this will panic
func findInList[V any](l *list.List, isEqual func(V) bool) *list.Element {
for cursor := l.Front(); cursor != nil; cursor = cursor.Next() {
if isEqual(cursor.Value.(V)) {
return cursor
}
}
return nil
}
func parseOperation(s string) (Operation[int], error) {
setPattern := regexp.MustCompile(`^([a-z]+)=(\d+)`)
deletePattern := regexp.MustCompile(`^([a-z]+)-$`)
setMatches := setPattern.FindStringSubmatch(s)
deleteMatches := deletePattern.FindStringSubmatch(s)
if setMatches != nil {
key := setMatches[1]
value, err := strconv.Atoi(setMatches[2])
if err != nil {
// cannot happen, since the pattern says this is a number
panic(err)
}
return makeSetOperation(key, value), nil
} else if deleteMatches != nil {
key := deleteMatches[1]
return makeDeleteOperation[int](key), nil
} else {
return nil, errors.New("invalid operation string")
}
}