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) return countApproved } 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 := 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)) }