Advent-of-Code-2023/day19/sortingParts.go

342 lines
8.3 KiB
Go

package day19
import (
"fmt"
"log"
"os"
"regexp"
"strconv"
"strings"
"sync"
)
func Run() int {
fmt.Println("hello day 19. sorting parts")
filename := "day19/input"
bytes, err := os.ReadFile(filename)
if err != nil {
panic(fmt.Sprint("cannot read file ", filename))
}
text := string(bytes)
split := strings.Split(text, "\n\n")
sorters := ReadSorters(split[0])
details := ReadDetailsPart(split[1])
log.Printf("yay, got sorters\n%+v\nand details\n%+v", sorters, details)
// countApproved := CountApprovedDetails(details, sorters)
result := 0
fullIntervals := AttrIntervals{
"x": [][]int{[]int{1, 4000}},
"m": [][]int{[]int{1, 4000}},
"a": [][]int{[]int{1, 4000}},
"s": [][]int{[]int{1, 4000}},
}
andChecked := processInterval(fullIntervals, "in", sorters)
log.Print("got and checked ", andChecked)
result = andChecked
return result
}
func CountApprovedDetails(details []DetailData, sorters map[string]SorterData) int {
var wg sync.WaitGroup
wg.Add(len(details))
approvedDetails := make(chan DetailData)
go func() {
wg.Wait()
close(approvedDetails)
}()
count := 0
acceptedScore := 0
done := make(chan any)
go func() {
for detail := range approvedDetails {
log.Println("got approved ", detail)
count += 1
for _, attrValue := range detail.Attrs {
acceptedScore += attrValue
}
}
close(done)
}()
for _, d := range details {
go func(d DetailData) {
log.Print("> starting for ", d)
isAccepted := ProcessDetail(d, sorters)
if isAccepted {
log.Println("> accepting ", d)
approvedDetails <- d
} else {
log.Println("> rejecting ", d)
}
wg.Done()
}(d)
}
<-done
return acceptedScore
}
type Operation rune
const (
LessThan Operation = '<'
MoreThan Operation = '>'
)
func (o Operation)String() string {
return string(o)
}
type OperationData struct {
AttrName string
Operation Operation
Num int
SentToName string
InitialString string
}
func (od OperationData)String() string {
return od.InitialString
}
type SorterData struct {
Name string
DefaultState string
Operations []OperationData
}
func ReadSorters(sortersText string) map[string]SorterData {
result := make(map[string]SorterData)
sortersText = strings.TrimSpace(sortersText)
lines := strings.Split(sortersText, "\n")
for _, line := range lines {
sorter := SimplifyOperation( ReadSorterLine(line) )
result[sorter.Name] = sorter
}
return result
}
// qqz{s>2770:qs,m<1801:hdj,R}
func ReadSorterLine(line string) (result SorterData) {
re1 := regexp.MustCompile(`(?P<NAME>\D+){(?P<OPERATIONS>.+)}`)
firstSplit := re1.FindStringSubmatch(line)
result.Name = firstSplit[1]
operationLines := strings.Split(firstSplit[2], ",")
operations := make([]OperationData, len(operationLines)-1)
result.Operations = operations
result.DefaultState = operationLines[len(operationLines)-1]
for i, line := range operationLines[:len(operationLines)-1] {
operations[i] = ReadOperationLine(line)
}
log.Printf("mathed %s got %+v; operations : %+v\n", line, firstSplit, operations)
return
}
// s>2770:qs
func ReadOperationLine(line string) (result OperationData) {
result.InitialString = line
re := regexp.MustCompile(`(?P<ATTRNAME>\D)(?P<OPERATION>[\>\<])(?P<NUMBER>\d+):(?P<TARGET>\D+)`)
split := re.FindStringSubmatch(line)
log.Printf("matching operation %s into %+v\n", line, split)
result.AttrName = split[1]
result.Operation = Operation([]rune(split[2])[0])
result.SentToName = split[4]
num, err := strconv.Atoi(split[3])
if err != nil {
panic(fmt.Sprintf("error getting number %s in line %s. %s", split[3], line, err))
}
result.Num = num
return
}
// drop last operations which target same 'next' as default. these check are not necessary
func SimplifyOperation(sorter SorterData) SorterData {
actualLast := len(sorter.Operations) - 1
for i := actualLast; i >= 0; i-- {
if sorter.Operations[i].SentToName != sorter.DefaultState {
break
}
actualLast -= 1
}
sorter.Operations = sorter.Operations[:actualLast+1]
return sorter
}
type DetailData struct {
Attrs map[string]int
}
func ReadDetailsPart(text string) (result []DetailData) {
text = strings.TrimSpace(text)
for _, line := range strings.Split(text, "\n") {
result = append(result, ReadDetailLine(line))
}
return
}
// {x=787,m=2655,a=1222,s=2876}
func ReadDetailLine(line string) (result DetailData) {
attrs := make(map[string]int)
result.Attrs = attrs
line = line[1 : len(line)-1]
attrsLine := strings.Split(line, ",")
re := regexp.MustCompile(`(?P<ATTR>\D)=(?P<NUM>\d+)`)
for _, attrLine := range attrsLine {
split := re.FindStringSubmatch(attrLine)
attrName := split[1]
num, err := strconv.Atoi(split[2])
if err != nil {
panic(fmt.Sprint("error parsing detail ", line))
}
attrs[attrName] = num
}
return
}
func ProcessDetail(d DetailData, sorters map[string]SorterData) (isAccepted bool) {
curSorterName := "in"
for curSorterName != "A" && curSorterName != "R" {
sorter, found := sorters[curSorterName]
if !found {
panic(fmt.Sprint("error finding soter ", curSorterName))
}
curSorterName = sorter.NextSorterNameFor(d)
}
return curSorterName == "A"
}
func (s SorterData) NextSorterNameFor(d DetailData) string {
for _, operation := range s.Operations {
if operation.IsDetailPassing(d) {
return operation.SentToName
}
}
return s.DefaultState
}
func (o OperationData) IsDetailPassing(d DetailData) bool {
detailValue := d.Attrs[o.AttrName]
switch o.Operation {
case LessThan:
return detailValue < o.Num
case MoreThan:
return detailValue > o.Num
}
panic(fmt.Sprint("unknown operation. ", o, d))
}
type AttrIntervals map[string][][]int
func (o OperationData) getPassingIntervals(i AttrIntervals) AttrIntervals {
result := make(AttrIntervals, 0)
for key, value := range i {
result[key] = value
}
operationKey := o.AttrName
operatedIntervals := result[operationKey]
switch o.Operation {
case LessThan:
result[operationKey] = applyLessThan(operatedIntervals, o.Num)
case MoreThan:
result[operationKey] = applyMoreThan(operatedIntervals, o.Num)
}
return result
}
func (o OperationData) getFailingIntervals(i AttrIntervals) AttrIntervals {
result := make(AttrIntervals, 0)
for key, value := range i {
result[key] = value
}
operationKey := o.AttrName
operatedIntervals := result[operationKey]
switch o.Operation {
case LessThan:
result[operationKey] = applyMoreThan(operatedIntervals, o.Num-1)
case MoreThan:
result[operationKey] = applyLessThan(operatedIntervals, o.Num+1)
}
return result
}
func processInterval(i AttrIntervals, sorterName string, sorters map[string]SorterData) (combinationsAccepted int) {
if sorterName == "A" {
mul := 1
for key, attrIntervals := range i {
allowedValuesOfAttr := 0
for _, interval := range attrIntervals {
from := interval[0]
to := interval[1]
len := to - from + 1
allowedValuesOfAttr += len
log.Printf("for %s allowed attrs are %d", key, allowedValuesOfAttr)
}
mul *= allowedValuesOfAttr
}
log.Printf("exit recursion for %s. Accept interval %+v . result %d. max is %d", sorterName, i, mul, 40000 * 4000 * 4000 * 4000)
return mul
}
if sorterName == "R" {
return 0
}
s := sorters[sorterName]
log.Printf("> starting interval check for %s (%+v) on %+v", sorterName, s, i)
intervalsPassingOnThisStep := i
for _, operation := range s.Operations {
intervalsPassing := operation.getPassingIntervals(intervalsPassingOnThisStep)
log.Printf(">> %s; in operation %+v. passing are %+v", sorterName, operation, intervalsPassing)
ofThoseAreAccepted := processInterval(intervalsPassing, operation.SentToName, sorters)
combinationsAccepted += ofThoseAreAccepted
log.Printf(">> %s; results so far are %d", sorterName, combinationsAccepted)
intervalsFailingAndPassedToNextCheck := operation.getFailingIntervals(intervalsPassingOnThisStep)
log.Printf(">> %s; failing for the next step %+v", sorterName, intervalsFailingAndPassedToNextCheck)
intervalsPassingOnThisStep = intervalsFailingAndPassedToNextCheck
}
log.Printf(">> %s; about to go into DEFAULT", sorterName)
intervalsAfterDefault := processInterval(intervalsPassingOnThisStep, s.DefaultState, sorters)
log.Printf(">> %s; after defaul. passing are %+v", sorterName, intervalsAfterDefault)
combinationsAccepted += intervalsAfterDefault
log.Printf(">> %s; results after default %d", sorterName, combinationsAccepted)
return
}