diff --git a/2020/day16/main.go b/2020/day16/main.go index bde80ba..3e6a747 100644 --- a/2020/day16/main.go +++ b/2020/day16/main.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/alexchao26/advent-of-code-go/mathutil" + "github.com/alexchao26/advent-of-code-go/util" ) @@ -15,32 +16,135 @@ func main() { flag.Parse() fmt.Println("Running part", part) + ans := ticketTranslation(util.ReadFile("./input.txt"), part) + fmt.Println("Output:", ans) +} + +func ticketTranslation(input string, part int) int { + rules, myTicket, nearbyTickets := parseInput(input) + + var validTickets [][]int // for part 2 + var errorRate int + for _, ticket := range nearbyTickets { + isValidTicket := true + for _, ticketValue := range ticket { + valuePassesRules := false + for _, ruleBounds := range rules { + if valuePassesRule(ticketValue, ruleBounds) { + valuePassesRules = true + break + } + } + if !valuePassesRules { + errorRate += ticketValue + isValidTicket = false // for part 2 + break + } + } + // filter out valid tickets for part 2 + if isValidTicket { + validTickets = append(validTickets, ticket) + } + } + if part == 1 { - ans := part1(util.ReadFile("./input.txt")) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) - } else { - ans := part2(util.ReadFile("./input.txt")) - util.CopyToClipboard(fmt.Sprintf("%v", ans)) - fmt.Println("Output:", ans) + return errorRate } -} -func part1(input string) int { - parsed := parseInput(input) - _ = parsed + // part 2, figure out which field belongs to which + fieldNameToIndex := map[string]int{} + skipTicketIndices := map[int]bool{} + // run until all the rules are accounted for + for len(rules) > 0 { + // iterate over "columns" of the valid tickets matrix + for ticketValIndex := range validTickets[0] { + if skipTicketIndices[ticketValIndex] { + continue + } + // run all the rules against each ticket, store which ones pass for + // all values at this ticket index. if only one rule applies, it + // must be for this index within a ticket + var passingNames []string + for ruleName, ruleBounds := range rules { + allValuesPassed := true + // iterate over all tickets and if any fail for this rule, break out + for _, ticket := range validTickets { + ticketValue := ticket[ticketValIndex] + if !valuePassesRule(ticketValue, ruleBounds) { + allValuesPassed = false + break + } + } - return 0 -} + // append this rule name as one that passed for these values + if allValuesPassed { + passingNames = append(passingNames, ruleName) + } + } -func part2(input string) int { - return 0 -} - -func parseInput(input string) (ans []int) { - lines := strings.Split(input, "\n") - for _, l := range lines { - ans = append(ans, mathutil.StrToInt(l)) + // if only one rule passes, assign it to this ticket value index + // remove it from the rules list + if len(passingNames) == 1 { + fieldNameToIndex[passingNames[0]] = ticketValIndex + // remove the rule from the map b/c we've determined its index + delete(rules, passingNames[0]) + // remember which indices have already been taken by a rule + skipTicketIndices[ticketValIndex] = true + } + } } - return ans + + // get final answer by multiplying all ticket details/rules prefixed "departure" + departureProduct := 1 + for rule, valueIndex := range fieldNameToIndex { + if strings.HasPrefix(rule, "departure") { + departureProduct *= myTicket[valueIndex] + } + } + + return departureProduct +} + +func valuePassesRule(value int, ruleBounds [2][2]int) bool { + firstBounds := ruleBounds[0] + secondBounds := ruleBounds[1] + return ((value >= firstBounds[0] && value <= firstBounds[1]) || + (value >= secondBounds[0] && value <= secondBounds[1])) +} + +func parseInput(input string) (map[string][2][2]int, []int, [][]int) { + blocks := strings.Split(input, "\n\n") + + // parse rules from first block + rules := map[string][2][2]int{} + for _, rule := range strings.Split(blocks[0], "\n") { + parts := strings.Split(rule, ": ") + name := parts[0] + + var r1L, r1H, r2L, r2H int + fmt.Sscanf(parts[1], "%d-%d or %d-%d", &r1L, &r1H, &r2L, &r2H) + rules[name] = [2][2]int{ + [2]int{r1L, r1H}, + [2]int{r2L, r2H}, + } + } + + // my ticket values in second block + splitTicket := strings.Split(blocks[1], "\n") + var myTicket []int + for _, v := range strings.Split(splitTicket[1], ",") { + myTicket = append(myTicket, mathutil.StrToInt(v)) + } + + // all values for nearby tickets + var nearbyTickets [][]int + for _, nearby := range strings.Split(blocks[2], "\n")[1:] { + var near []int + for _, v := range strings.Split(nearby, ",") { + near = append(near, mathutil.StrToInt(v)) + } + nearbyTickets = append(nearbyTickets, near) + } + + return rules, myTicket, nearbyTickets } diff --git a/2020/day16/main_test.go b/2020/day16/main_test.go index 22a4487..95826d3 100644 --- a/2020/day16/main_test.go +++ b/2020/day16/main_test.go @@ -2,37 +2,38 @@ package main import ( "testing" + + "github.com/alexchao26/advent-of-code-go/util" ) -func Test_part1(t *testing.T) { - tests := []struct { - name string - input string - want int - }{ - // {"actual", util.ReadFile("input.txt"), ACTUAL_ANSWER}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := part1(tt.input); got != tt.want { - t.Errorf("part1() = %v, want %v", got, tt.want) - } - }) - } -} +var example1 = `class: 1-3 or 5-7 +row: 6-11 or 33-44 +seat: 13-40 or 45-50 -func Test_part2(t *testing.T) { +your ticket: +7,1,14 + +nearby tickets: +7,3,47 +40,4,50 +55,2,20 +38,6,12` + +func Test_ticketTranslation(t *testing.T) { tests := []struct { name string input string + part int want int }{ - // {"actual", util.ReadFile("input.txt"), ACTUAL_ANSWER}, + {"example_part1", example1, 1, 71}, + {"actual", util.ReadFile("input.txt"), 1, 32835}, + {"actual", util.ReadFile("input.txt"), 2, 514662805187}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := part2(tt.input); got != tt.want { - t.Errorf("part2() = %v, want %v", got, tt.want) + if got := ticketTranslation(tt.input, tt.part); got != tt.want { + t.Errorf("ticketTranslation() = %v, want %v", got, tt.want) } }) }