1
main.go
1
main.go
@@ -69,4 +69,5 @@ func main() {
|
||||
|
||||
// Day 16
|
||||
fmt.Println(sixteen.PartOne())
|
||||
fmt.Println(sixteen.PartTwo())
|
||||
}
|
||||
|
@@ -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"))
|
||||
}
|
||||
|
@@ -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()
|
||||
}
|
||||
}
|
||||
|
11
sixteen/sample2.txt
Normal file
11
sixteen/sample2.txt
Normal file
@@ -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
|
11
sixteen/sample3.txt
Normal file
11
sixteen/sample3.txt
Normal file
@@ -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
|
Reference in New Issue
Block a user