diff --git a/main.go b/main.go index 74672b0..835f526 100644 --- a/main.go +++ b/main.go @@ -69,4 +69,5 @@ func main() { // Day 16 fmt.Println(sixteen.PartOne()) + fmt.Println(sixteen.PartTwo()) } diff --git a/sixteen/day_sixteen.go b/sixteen/day_sixteen.go index 6d88bf0..e001b7e 100644 --- a/sixteen/day_sixteen.go +++ b/sixteen/day_sixteen.go @@ -10,17 +10,21 @@ import ( type ticket struct { values []int - fields []string } type constraint struct { min, max int } +type constraints []constraint + type ticketScanner struct { - rules map[string][]constraint + rules map[string]constraints me *ticket nearby []ticket + valid []ticket + + fields []string } func (ts *ticketScanner) load(filename string) error { @@ -30,7 +34,7 @@ func (ts *ticketScanner) load(filename string) error { } defer file.Close() - ts.rules = make(map[string][]constraint) + ts.rules = make(map[string]constraints) var rules, myticket bool scanner := bufio.NewScanner(file) for scanner.Scan() { @@ -95,19 +99,23 @@ func (ts *ticketScanner) load(filename string) error { return nil } +func (c *constraints) valid(input int) bool { + for _, r := range *c { + if input >= r.min && input <= r.max { + return true + } + } + return false +} + func (ts *ticketScanner) nearbyErrorRate() int { errorRate := 0 for _, t := range ts.nearby { for _, value := range t.values { valid := false for _, rule := range ts.rules { - for _, r := range rule { - if value >= r.min && value <= r.max { - valid = true - break - } - } - if valid { + if rule.valid(value) { + valid = true break } } @@ -120,6 +128,109 @@ func (ts *ticketScanner) nearbyErrorRate() int { return errorRate } +func (ts *ticketScanner) scanValid() { + for _, t := range ts.nearby { + validTicket := true + for _, v := range t.values { + validValue := false + for _, r := range ts.rules { + if r.valid(v) { + validValue = true + break + } + } + if !validValue { + validTicket = false + break + } + } + if validTicket { + ts.valid = append(ts.valid, t) + } + } +} + +func (ts *ticketScanner) determineFields() { + ts.fields = make([]string, len(ts.rules)) + + possibilities := map[string][]int{} + for name, r := range ts.rules { + possibilities[name] = []int{} + + for i := 0; i < len(ts.fields); i++ { + isValid := true + for j := 0; j < len(ts.valid); j++ { + if !r.valid(ts.valid[j].values[i]) { + isValid = false + } + } + if isValid { + possibilities[name] = append(possibilities[name], i) + } + } + } + + for i := 0; i < len(ts.fields); i++ { + if ts.fields[i] != "" { + continue + } + options := []string{} + for name, indexes := range possibilities { + for _, index := range indexes { + if i == index { + options = append(options, name) + } + } + } + + if len(options) == 1 { + ts.fields[i] = options[0] + possibilities = removeIndex(i, options[0], possibilities) + continue + } + + min := len(ts.fields) + minKey := "" + for _, option := range options { + if len(possibilities[option]) < min { + min = len(possibilities[option]) + minKey = option + } + } + ts.fields[i] = minKey + possibilities = removeIndex(i, minKey, possibilities) + } +} + +func removeIndex(index int, used string, possibilities map[string][]int) map[string][]int { + updatedPossibilites := make(map[string][]int) + for key, indexes := range possibilities { + if used == key { + continue + } + updatedIndexes := []int{} + for _, i := range indexes { + if i != index { + updatedIndexes = append(updatedIndexes, i) + } + } + + updatedPossibilites[key] = updatedIndexes + } + return updatedPossibilites +} + +func (ts *ticketScanner) fieldProduct(prefix string) int { + product := 1 + for i, f := range ts.fields { + if strings.Contains(f, prefix) { + product *= ts.me.values[i] + } + } + + return product +} + // PartOne What is my nearby ticket scanning error rate func PartOne() string { ts := ticketScanner{} @@ -129,3 +240,16 @@ func PartOne() string { return fmt.Sprintf("The nearby scanning error rate is %d", ts.nearbyErrorRate()) } + +// PartTwo What is the product of all fields beginning with "departure" +func PartTwo() string { + ts := ticketScanner{} + if err := ts.load("sixteen/input.txt"); err != nil { + return err.Error() + } + + ts.scanValid() + ts.determineFields() + + return fmt.Sprintf("The product of departure is %d", ts.fieldProduct("departure")) +} diff --git a/sixteen/day_sixteen_test.go b/sixteen/day_sixteen_test.go index 10e7fa4..5479f52 100644 --- a/sixteen/day_sixteen_test.go +++ b/sixteen/day_sixteen_test.go @@ -24,7 +24,7 @@ func Test_ticket_loading(t *testing.T) { t.Fail() } } - + func Test_ticket_scanerror(t *testing.T) { ts := ticketScanner{} if err := ts.load("sample.txt"); err != nil { @@ -38,3 +38,45 @@ func Test_ticket_scanerror(t *testing.T) { t.Fail() } } + +func Test_ticket_fieldDetection(t *testing.T) { + ts := ticketScanner{} + if err := ts.load("sample2.txt"); err != nil { + t.Log(err.Error()) + t.FailNow() + } + + ts.scanValid() + if len(ts.valid) != 3 { + t.Logf("Expected 3 valid tickets, got %d", len(ts.valid)) + t.FailNow() + } + + ts.determineFields() + + if len(ts.fields) != 3 || ts.fields[0] != "row" || ts.fields[1] != "class" || ts.fields[2] != "seat" { + t.Logf("Expected row, class, seat but got %v", ts.fields) + t.Fail() + } +} + +func Test_ticket_product(t *testing.T) { + ts := ticketScanner{} + if err := ts.load("sample3.txt"); err != nil { + t.Log(err.Error()) + t.FailNow() + } + + ts.scanValid() + if len(ts.valid) != 3 { + t.Logf("Expected 3 valid tickets, got %d", len(ts.valid)) + t.FailNow() + } + + ts.determineFields() + p := ts.fieldProduct("departure") + if p != 132 { + t.Logf("Expected 132 got %d", p) + t.Fail() + } +} diff --git a/sixteen/sample2.txt b/sixteen/sample2.txt new file mode 100644 index 0000000..c629bd3 --- /dev/null +++ b/sixteen/sample2.txt @@ -0,0 +1,11 @@ +class: 0-1 or 4-19 +row: 0-5 or 8-19 +seat: 0-13 or 16-19 + +your ticket: +11,12,13 + +nearby tickets: +3,9,18 +15,1,5 +5,14,9 \ No newline at end of file diff --git a/sixteen/sample3.txt b/sixteen/sample3.txt new file mode 100644 index 0000000..eda0c0a --- /dev/null +++ b/sixteen/sample3.txt @@ -0,0 +1,11 @@ +departure class: 0-1 or 4-19 +departure row: 0-5 or 8-19 +arrival seat: 0-13 or 16-19 + +your ticket: +11,12,13 + +nearby tickets: +3,9,18 +15,1,5 +5,14,9 \ No newline at end of file