package main import ( "flag" "fmt" "strings" "github.com/alexchao26/advent-of-code-go/cast" "github.com/alexchao26/advent-of-code-go/util" ) func main() { var part int flag.IntVar(&part, "part", 1, "part 1 or 2") 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 { return errorRate } // 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 } } // append this rule name as one that passed for these values if allValuesPassed { passingNames = append(passingNames, ruleName) } } // 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 } } } // 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, cast.ToInt(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, cast.ToInt(v)) } nearbyTickets = append(nearbyTickets, near) } return rules, myTicket, nearbyTickets }