aoc-2020/d16/main.go

400 lines
7.3 KiB
Go

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()
}