diff --git a/day-12/go.mod b/day-12/go.mod new file mode 100644 index 0000000..6aded9f --- /dev/null +++ b/day-12/go.mod @@ -0,0 +1,10 @@ +module github.com/onyx-and-iris/aoc2023/day-12 + +go 1.21.5 + +require github.com/sirupsen/logrus v1.9.3 + +require ( + github.com/stretchr/testify v1.8.4 // indirect + golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect +) diff --git a/day-12/go.sum b/day-12/go.sum new file mode 100644 index 0000000..24d54d0 --- /dev/null +++ b/day-12/go.sum @@ -0,0 +1,17 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/day-12/makefile b/day-12/makefile new file mode 100644 index 0000000..680ef75 --- /dev/null +++ b/day-12/makefile @@ -0,0 +1,10 @@ +TEST="test.txt" +INPUT="input.txt" + +test: + cat $(TEST) | go run . + +run: + cat $(INPUT) | go run . + +all: test \ No newline at end of file diff --git a/day-12/one.go b/day-12/one.go new file mode 100644 index 0000000..40609b4 --- /dev/null +++ b/day-12/one.go @@ -0,0 +1,113 @@ +package main + +import ( + "fmt" + + log "github.com/sirupsen/logrus" +) + +const ( + OPERATIONAL = iota + DAMAGED + UNKNOWN +) + +var records []record + +type record struct { + format []int + springs []spring +} + +func newRecord() record { + return record{} +} + +// raw returns the state of all springs for this record as int array +func (r record) raw() []int { + vals := make([]int, len(r.springs)) + for j, spring := range r.springs { + vals[j] = spring.state + } + return vals +} + +type spring struct { + state int +} + +func newSpring(state int) spring { + return spring{state: state} +} + +// tracker keeps track of results for function calls +type tracker struct { + checked map[string]int +} + +func newTracker() tracker { + return tracker{checked: make(map[string]int)} +} + +func (t tracker) add(key string, res int) { + t.checked[key] = res +} + +func validate(springs, format []int) bool { + return !contains(springs[:format[0]], OPERATIONAL) && (format[0] == len(springs) || springs[format[0]] != DAMAGED) +} + +// count returns the number of arrangements possible for a set of springs +func count(springs, format []int) int { + if len(springs) == 0 { + if len(format) == 0 { + return 1 + } + return 0 + } + if len(format) == 0 { + if contains(springs, DAMAGED) { + return 0 + } + return 1 + } + + identifier := fmt.Sprintf("%v%v", format, springs) + if res, ok := fnTracker.checked[fmt.Sprintf("%v%v", format, springs)]; ok { + log.Debug("returning cached value for ", identifier) + return res + } + + result := 0 + + if springs[0] == OPERATIONAL || springs[0] == UNKNOWN { + result += count(springs[1:], format) + } + + if springs[0] == DAMAGED || springs[0] == UNKNOWN { + if format[0] < len(springs) && validate(springs, format) { + result += count(springs[format[0]+1:], format[1:]) + } else if format[0] == len(springs) && validate(springs, format) { + result += count(springs[format[0]:], format[1:]) + } + } + + fnTracker.add(identifier, result) + return result +} + +var fnTracker tracker + +// one returns the sum of all arrangement counts +func one(lines []string) int { + parselines(lines) + + fnTracker = newTracker() + sum := 0 + for _, record := range records { + raw := record.raw() + sum += count(raw, record.format) + } + + return sum +} diff --git a/day-12/run.sh b/day-12/run.sh new file mode 100755 index 0000000..fd3a025 --- /dev/null +++ b/day-12/run.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +INPUT="input.txt" + +cat $INPUT | go run . diff --git a/day-12/solution.go b/day-12/solution.go new file mode 100644 index 0000000..6340f80 --- /dev/null +++ b/day-12/solution.go @@ -0,0 +1,22 @@ +package main + +import ( + "fmt" + + log "github.com/sirupsen/logrus" +) + +func init() { + log.SetLevel(log.InfoLevel) +} + +func main() { + lines := readlines() + + ans := one(lines) + fmt.Printf("solution one: %d\n", ans) + + ans = two(lines) + fmt.Printf("solution two: %d\n", ans) + +} diff --git a/day-12/two.go b/day-12/two.go new file mode 100644 index 0000000..281d890 --- /dev/null +++ b/day-12/two.go @@ -0,0 +1,36 @@ +package main + +// multiplyBy unfolds all records by a given factor +func multiplyRawBy(factor int, original []int) []int { + multiplied := []int{} + for i := 0; i < factor; i++ { + multiplied = append(multiplied, original...) + if i < factor-1 { + multiplied = append(multiplied, UNKNOWN) + } + } + + return multiplied +} + +// multiplyBy unfolds all records by a given factor +func multiplyFormatBy(factor int, original []int) []int { + multiplied := []int{} + for i := 0; i < factor; i++ { + multiplied = append(multiplied, original...) + } + + return multiplied +} + +// two returns the sum of all unfolded arrangement counts +func two(lines []string) int { + fnTracker = newTracker() + sum := 0 + for _, record := range records { + raw := record.raw() + sum += count(multiplyRawBy(5, raw), multiplyFormatBy(5, record.format)) + } + + return sum +} diff --git a/day-12/util.go b/day-12/util.go new file mode 100644 index 0000000..d061a9e --- /dev/null +++ b/day-12/util.go @@ -0,0 +1,71 @@ +package main + +import ( + "bufio" + "log" + "os" + "strconv" + "strings" + "unicode" +) + +// readlines reads lines from stdin. +// returns input as an array of strings +func readlines() []string { + lines := []string{} + + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + + if err := scanner.Err(); err != nil { + log.Fatal(err) + } + + return lines +} + +func parselines(lines []string) { + f := func(c rune) bool { + return !unicode.IsDigit(c) + } + + records = make([]record, len(lines)) + for i, line := range lines { + record := newRecord() + for _, r := range line { + switch r { + case '.': + record.springs = append(record.springs, newSpring(OPERATIONAL)) + case '#': + record.springs = append(record.springs, newSpring(DAMAGED)) + case '?': + record.springs = append(record.springs, newSpring(UNKNOWN)) + } + } + record.format = convertToInts(strings.FieldsFunc(line, f)) + + records[i] = record + } +} + +// convertToInts converts a string representing ints to an array of ints +func convertToInts(data []string) []int { + nums := []int{} + for _, elem := range data { + n, _ := strconv.Atoi(elem) + nums = append(nums, n) + } + return nums +} + +// contains returns true if a slice of elements contains a given element +func contains[T comparable](elems []T, v T) bool { + for _, s := range elems { + if v == s { + return true + } + } + return false +}