diff --git a/.gitignore b/.gitignore index ad48c9b..5888b12 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /.direnv/ /.go +example +/day19/input diff --git a/day19/notes.org b/day19/notes.org new file mode 100644 index 0000000..444e9e4 --- /dev/null +++ b/day19/notes.org @@ -0,0 +1,27 @@ +#+title: Notes +* testing things + + testSorter := day19.ReadSorterLine("qqz{s>2770:qs,m<1801:hdj,R}") + log.Printf("my test sorter is %+v", testSorter) + + testOperation := day19.ReadOperationLine("s>2770:qs") + log.Println(testOperation) +** testing simplification + lnx{m>1548:A,A} + qqz{s>2770:qs,m<1801:hdj,R} + kt{m>2215:R,x>3386:A,x<3107:R,R} + + testSorter := day19.ReadSorterLine("kt{m>2215:R,x>3386:A,x<3107:R,R}") + log.Printf("my test sorter is %+v", testSorter) + + simplified := day19.SimplifyOperation(testSorter) + log.Printf("> simplivied %+v", simplified) +* i probably don't need 'actual actors' +just a generic function that takes 'detail' and 'sorterData' +then applies sorterData to the detail, +and calls itself with new sorter + +with special cases for "R" and "A" + +so. have funciton from OpeartionData & Detail -> true/false +if true take the destination, if false, check next diff --git a/day19/sortingParts.go b/day19/sortingParts.go new file mode 100644 index 0000000..35ecd4f --- /dev/null +++ b/day19/sortingParts.go @@ -0,0 +1,233 @@ +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)) +} diff --git a/main.go b/main.go index 63538d1..d67c550 100644 --- a/main.go +++ b/main.go @@ -3,12 +3,24 @@ package main import ( "log" - "sunshine.industries/aoc2023/day18" + "sunshine.industries/aoc2023/day19" ) func main() { log.Print("> starting run:") - result := day18.Run() - log.Printf("\n\nday18 result: %d\n****\n", result) + // lnx{m>1548:A,A} + // qqz{s>2770:qs,m<1801:hdj,R} + // kt{m>2215:R,x>3386:A,x<3107:R,R} + testSorter := day19.ReadSorterLine("kt{m>2215:R,x>3386:A,x<3107:R,R}") + log.Printf("my test sorter is %+v", testSorter) + + simplified := day19.SimplifyOperation(testSorter) + log.Printf("> simplivied %+v", simplified) + + detail := day19.ReadDetailLine("{x=787,m=2655,a=1222,s=2876}") + log.Printf("> detail %+v", detail) + + result := day19.Run() + log.Printf("\n\nday19 result: %d\n****\n", result) }