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\D+){(?P.+)}`) 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\D)(?P[\>\<])(?P\d+):(?P\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\D)=(?P\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 }