package main import ( "bufio" "fmt" "log" "os" "regexp" "sort" "strconv" "strings" ) func processLines(path string, f func(string) (stop bool, err error)) error { file, err := os.Open(path) if err != nil { return err } defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { stop, err := f(scanner.Text()) if stop || err != nil { return err } } if err := scanner.Err(); err != nil { return err } return nil } func processGroups(path string, f func([]string) (stop bool, err error)) error { group := []string{} if err := processLines(path, func(line string) (stop bool, err error) { if line == "" { stop, err = f(group) if stop || err != nil { return } group = []string{} } else { group = append(group, line) } return }); err != nil { return err } _, err := f(group) return err } // StringSet is a set of strings. type StringSet map[string]bool // Add an item to a StringSet. func (s *StringSet) Add(v string) { (*s)[v] = true } // AddAll adds all items in a list to the set. func (s *StringSet) AddAll(l []string) { for _, v := range l { s.Add(v) } } // Values returns all values in the set as a list. func (s StringSet) Values() []string { values := []string{} for k := range s { values = append(values, k) } return values } // NewStringSet creates an empty string set. func NewStringSet() StringSet { return StringSet{} } // Union returns the union of two SringSets. func Union(s1, s2 StringSet) StringSet { union := NewStringSet() for v := range s1 { union.Add(v) } for v := range s2 { union.Add(v) } return union } // Intersection returns the union of two SringSets. func Intersection(s1, s2 StringSet) StringSet { intersect := NewStringSet() for v := range s1 { if _, ok := s2[v]; ok { intersect.Add(v) } } return intersect } // Difference returns the value of s1 with values of s2 removed. func Difference(s1, s2 StringSet) StringSet { difference := NewStringSet() for v := range s1 { if _, ok := s2[v]; !ok { difference.Add(v) } } return difference } type numRange struct { lower, upper int } func (r numRange) validate(n int) bool { return r.lower <= n && n <= r.upper } type fieldRule struct { name string ranges []numRange } func (f fieldRule) validate(n int) bool { for _, r := range f.ranges { if r.validate(n) { return true } } return false } var ruleMatcher = regexp.MustCompile(`([a-z ]+): ([0-9]+)-([0-9]+) or ([0-9]+)-([0-9]+)`) func parseFieldRule(line string) (fieldRule, error) { res := ruleMatcher.FindStringSubmatch(line) if res == nil { return fieldRule{}, fmt.Errorf("failed parsing rule line: %s", line) } name := res[1] l1, err := strconv.Atoi(res[2]) if err != nil { return fieldRule{}, fmt.Errorf("failed parsing int in rule %w", err) } u1, err := strconv.Atoi(res[3]) if err != nil { return fieldRule{}, fmt.Errorf("failed parsing int in rule %w", err) } l2, err := strconv.Atoi(res[4]) if err != nil { return fieldRule{}, fmt.Errorf("failed parsing int in rule %w", err) } u2, err := strconv.Atoi(res[5]) if err != nil { return fieldRule{}, fmt.Errorf("failed parsing int in rule %w", err) } return fieldRule{ name: name, ranges: []numRange{ numRange{ lower: l1, upper: u1, }, numRange{ lower: l2, upper: u2, }, }, }, nil } type fieldRules []fieldRule func (f fieldRules) validFields(n int) []string { validFields := []string{} for _, r := range f { if r.validate(n) { validFields = append(validFields, r.name) } } return validFields } type ticket []int func parseTicket(line string) (ticket, error) { t := ticket{} for _, n := range strings.Split(line, ",") { i, err := strconv.Atoi(n) if err != nil { return nil, err } t = append(t, i) } return t, nil } func parseInput() (fieldRules, ticket, []ticket) { rules := fieldRules{} nearby := []ticket{} var t ticket if err := processGroups("input.txt", func(group []string) (bool, error) { if len(rules) == 0 { for _, line := range group { rule, err := parseFieldRule(line) if err != nil { log.Fatal(err) } rules = append(rules, rule) } } else if group[0] == "your ticket:" { var err error t, err = parseTicket(group[1]) if err != nil { log.Fatal(err) } } else if group[0] == "nearby tickets:" { for _, line := range group[1:] { nt, err := parseTicket(line) if err != nil { log.Fatal(err) } nearby = append(nearby, nt) } } else { log.Fatal(fmt.Errorf("unknown section %v", group)) } return false, nil }); err != nil { log.Fatal(err) } return rules, t, nearby } func part1() { rules, _, nearby := parseInput() invalid := 0 for _, t := range nearby { for _, v := range t { if len(rules.validFields(v)) == 0 { invalid += v } } } fmt.Printf("Total scanning error rate is %d\n", invalid) } func reduceFields(fieldOptions []StringSet) ([]string, error) { type fieldOption struct { options StringSet position int } workingFields := []fieldOption{} for i, o := range fieldOptions { workingFields = append( workingFields, fieldOption{o, i}, ) } // Sort slice by number of options sort.Slice(workingFields, func(i, j int) bool { return len(workingFields[i].options) < len(workingFields[j].options) }) for i, o1 := range workingFields { if len(o1.options) == 1 { // Remove this option from everywhere else for j, o2 := range workingFields { if i == j { // Skip current outer index continue } workingFields[j].options = Difference(o2.options, o1.options) } } } // fmt.Printf("All options? %+v", workingFields) // Sort slice by number of options sort.Slice(workingFields, func(i, j int) bool { return workingFields[i].position < workingFields[j].position }) results := []string{} for _, o := range workingFields { if len(o.options) != 1 { return []string{}, fmt.Errorf("some options have multiple values") } results = append(results, o.options.Values()[0]) } return results, nil } func part2() { rules, myTicket, nearby := parseInput() fieldOptions := []StringSet{} // Get all possible field names allFields := []string{} for _, rule := range rules { allFields = append(allFields, rule.name) } // Add them to every field in ticket for range myTicket { s := StringSet{} s.AddAll(allFields) fieldOptions = append(fieldOptions, s) } var ticketOptions []StringSet for _, nearbyTicket := range nearby { ticketOptions = []StringSet{} for _, ticketValue := range nearbyTicket { validFields := StringSet{} validFields.AddAll(rules.validFields(ticketValue)) if len(validFields) == 0 { // This ticket is invalid ticketOptions = nil break } ticketOptions = append(ticketOptions, validFields) } // Done checking ticket if ticketOptions != nil { // Ticket is valid for i := range fieldOptions { fieldOptions[i] = Intersection(fieldOptions[i], ticketOptions[i]) } } } // fmt.Printf("Final fieldOptions %+v\n", fieldOptions) fieldLabels, err := reduceFields(fieldOptions) if err != nil { log.Fatal(err) } // fmt.Println("Values: ", fieldLabels) product := 1 for i, label := range fieldLabels { if strings.HasPrefix(label, "departure") { product *= myTicket[i] } } fmt.Printf("Product of all departures %d\n", product) } func main() { part1() part2() }