346 lines
8.1 KiB
Go
346 lines
8.1 KiB
Go
package day19
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
func Run() int {
|
|
fmt.Println("hello day 19. sorting parts")
|
|
filename := "day19/example"
|
|
|
|
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, "qqz", sorters)
|
|
log.Print("got and checked ", 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 = '>'
|
|
)
|
|
|
|
type OperationData struct {
|
|
AttrName string
|
|
Operation Operation
|
|
Num int
|
|
SentToName string
|
|
String string
|
|
}
|
|
|
|
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.String = 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) AttrIntervals {
|
|
result := AttrIntervals{
|
|
"x": [][]int{},
|
|
"m": [][]int{},
|
|
"a": [][]int{},
|
|
"s": [][]int{},
|
|
}
|
|
|
|
if sorterName == "A" {
|
|
return i
|
|
}
|
|
if sorterName == "R" {
|
|
return result
|
|
}
|
|
|
|
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)
|
|
|
|
result = MergeAuthIntervals(result, ofThoseAreAccepted)
|
|
log.Printf(">> %s; results so far are %+v", sorterName, result)
|
|
|
|
intervalsFailingAndPassedToNextCheck := operation.getFailingIntervals(i)
|
|
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)
|
|
result = MergeAuthIntervals(result, intervalsAfterDefault)
|
|
log.Printf(">> %s; results after default %+v", sorterName, result)
|
|
|
|
return result
|
|
}
|
|
|
|
func MergeAuthIntervals(a AttrIntervals, b AttrIntervals) AttrIntervals {
|
|
result := AttrIntervals{
|
|
"x": [][]int{},
|
|
"m": [][]int{},
|
|
"a": [][]int{},
|
|
"s": [][]int{},
|
|
}
|
|
|
|
for key := range result {
|
|
aAttrIntervals := a[key]
|
|
bAttrIntervals := b[key]
|
|
allIntervals := append(aAttrIntervals, bAttrIntervals...)
|
|
result[key] = merge(allIntervals)
|
|
}
|
|
|
|
return result
|
|
}
|