1
main.go
1
main.go
@@ -69,4 +69,5 @@ func main() {
|
|||||||
|
|
||||||
// Day 16
|
// Day 16
|
||||||
fmt.Println(sixteen.PartOne())
|
fmt.Println(sixteen.PartOne())
|
||||||
|
fmt.Println(sixteen.PartTwo())
|
||||||
}
|
}
|
||||||
|
@@ -10,17 +10,21 @@ import (
|
|||||||
|
|
||||||
type ticket struct {
|
type ticket struct {
|
||||||
values []int
|
values []int
|
||||||
fields []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type constraint struct {
|
type constraint struct {
|
||||||
min, max int
|
min, max int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type constraints []constraint
|
||||||
|
|
||||||
type ticketScanner struct {
|
type ticketScanner struct {
|
||||||
rules map[string][]constraint
|
rules map[string]constraints
|
||||||
me *ticket
|
me *ticket
|
||||||
nearby []ticket
|
nearby []ticket
|
||||||
|
valid []ticket
|
||||||
|
|
||||||
|
fields []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ts *ticketScanner) load(filename string) error {
|
func (ts *ticketScanner) load(filename string) error {
|
||||||
@@ -30,7 +34,7 @@ func (ts *ticketScanner) load(filename string) error {
|
|||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
ts.rules = make(map[string][]constraint)
|
ts.rules = make(map[string]constraints)
|
||||||
var rules, myticket bool
|
var rules, myticket bool
|
||||||
scanner := bufio.NewScanner(file)
|
scanner := bufio.NewScanner(file)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
@@ -95,19 +99,23 @@ func (ts *ticketScanner) load(filename string) error {
|
|||||||
return nil
|
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 {
|
func (ts *ticketScanner) nearbyErrorRate() int {
|
||||||
errorRate := 0
|
errorRate := 0
|
||||||
for _, t := range ts.nearby {
|
for _, t := range ts.nearby {
|
||||||
for _, value := range t.values {
|
for _, value := range t.values {
|
||||||
valid := false
|
valid := false
|
||||||
for _, rule := range ts.rules {
|
for _, rule := range ts.rules {
|
||||||
for _, r := range rule {
|
if rule.valid(value) {
|
||||||
if value >= r.min && value <= r.max {
|
valid = true
|
||||||
valid = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if valid {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,6 +128,109 @@ func (ts *ticketScanner) nearbyErrorRate() int {
|
|||||||
return errorRate
|
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
|
// PartOne What is my nearby ticket scanning error rate
|
||||||
func PartOne() string {
|
func PartOne() string {
|
||||||
ts := ticketScanner{}
|
ts := ticketScanner{}
|
||||||
@@ -129,3 +240,16 @@ func PartOne() string {
|
|||||||
|
|
||||||
return fmt.Sprintf("The nearby scanning error rate is %d", ts.nearbyErrorRate())
|
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()
|
t.Fail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_ticket_scanerror(t *testing.T) {
|
func Test_ticket_scanerror(t *testing.T) {
|
||||||
ts := ticketScanner{}
|
ts := ticketScanner{}
|
||||||
if err := ts.load("sample.txt"); err != nil {
|
if err := ts.load("sample.txt"); err != nil {
|
||||||
@@ -38,3 +38,45 @@ func Test_ticket_scanerror(t *testing.T) {
|
|||||||
t.Fail()
|
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