197 lines
4.4 KiB
Go
197 lines
4.4 KiB
Go
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])
|
|
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))
|
|
fmt.Printf("Part 2: %d\n", part2(inputElements))
|
|
}
|
|
|
|
func part1(inputElements []string) int {
|
|
sum := 0
|
|
for _, element := range inputElements {
|
|
sum += hash(element)
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
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 {
|
|
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")
|
|
}
|
|
}
|