diff --git a/day15/main.go b/day15/main.go index f684e56..02d7929 100644 --- a/day15/main.go +++ b/day15/main.go @@ -1,12 +1,83 @@ package main import ( + "container/list" + "errors" "fmt" "io" "os" + "regexp" + "strconv" "strings" ) +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 +} + func main() { if len(os.Args) != 2 && len(os.Args) != 3 { fmt.Fprintf(os.Stderr, "Usage: %s inputfile\n", os.Args[0]) @@ -30,6 +101,7 @@ func main() { inputElements := strings.Split(input, ",") fmt.Printf("Part 1: %d\n", part1(inputElements)) + fmt.Printf("Part 2: %d\n", part2(inputElements)) } func part1(inputElements []string) int { @@ -41,6 +113,39 @@ func part1(inputElements []string) int { return sum } +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) + } + + 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 +} + func hash(s string) int { res := 0 for _, char := range s { @@ -51,3 +156,37 @@ func hash(s string) int { return res } + +// 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") + } +}