language: go prompt: https://adventofcode.com/2020/day/16 learn: https://tour.golang.org
#!/usr/bin/env gorun
package main
import (
"fmt"
"io/ioutil"
"strconv"
"strings"
)
const NFields = 20
type Interval struct {
Start, End int
}
func (i Interval) Contains(n int) bool {
return i.Start <= n && n <= i.End
}
type Header struct {
Name string
Intervals []Interval
}
func main() {
input, _ := ioutil.ReadFile("./input")
lines := strings.Split(string(input), "\n")
headers := parseHeaders(lines[0:NFields])
myTicket := parseTicket(lines[NFields+2])
tickets := parseTickets(lines[NFields+5:])
invalidFields := findInvalidFields(headers, tickets)
// Part 1 solution
fmt.Println("Sum of invalid fields:", sum(invalidFields))
validTickets := removeInvalids(headers, tickets)
orderedHeaders := orderHeaders(headers, validTickets)
departureFields := getDepartureFields(orderedHeaders)
solution2 = product(selectByIndices(myTicket, departureFields))
// Part 2 solution
fmt.Println("Product of departure fields in my ticket:", solution2)
}
func getDepartureFields(headers []Header) []int {
fields := make([]int, 0)
for i, header := range headers {
if strings.HasPrefix(header.Name, "departure") {
fields = append(fields, i)
}
}
return fields
}
func orderHeaders(unorderedHeaders []Header, tickets [][]int) []Header {
possibleHeaders := make([][]Header, NFields)
// Find all possible headers for a given field
for i := 0; i < NFields; i++ {
for _, header := range unorderedHeaders {
valid := true
for _, ticket := range tickets {
contained := false
for _, interval := range header.Intervals {
if interval.Contains(ticket[i]) {
contained = true
}
}
valid = valid && contained
}
if valid {
possibleHeaders[i] = append(possibleHeaders[i], header)
}
}
}
// Filter down so there is only one header per field
filtered := false
for !filtered {
filtered = true
settleds := make([]bool, NFields)
for i, headers := range possibleHeaders {
settled := 1 == len(headers)
settleds[i] = settled
filtered = filtered && settled
if settled {
settledName := headers[0].Name
for j, unfilteredHeaders := range possibleHeaders {
if j != i {
filteredHeaders := make([]Header, 0)
for _, unfilteredHeader := range unfilteredHeaders {
if unfilteredHeader.Name != settledName {
filteredHeaders = append(filteredHeaders, unfilteredHeader)
}
}
possibleHeaders[j] = filteredHeaders
}
}
}
}
}
headers := make([]Header, NFields)
for i, actualHeaders := range possibleHeaders {
headers[i] = actualHeaders[0]
}
return headers
}
func findInvalidFields(headers []Header, tickets [][]int) []int {
invalids := make([]int, 0)
for _, ticket := range tickets {
invalids = append(invalids, findInvalidField(headers, ticket)...)
}
return invalids
}
// Wow, I finally get the hubbub about why people want generics in Go.
// No wonder the std library feels lower level rather than higher level.
// (They can't implement a generic filter, map, reduce etc. without ugly reflection hijinks)
// https://blog.golang.org/generics-next-step
// Whoa, Phil Wadler is on the case! (Haskell monads and Java 5 Generics and more)
func removeInvalids(headers []Header, tickets [][]int) [][]int {
valids := make([][]int, 0)
for _, ticket := range tickets {
if isValidTicket(headers, ticket) {
valids = append(valids, ticket)
}
}
return valids
}
func isValidTicket(headers []Header, ticket []int) bool {
return 0 == len(findInvalidField(headers, ticket)) && len(ticket) == NFields
}
func findInvalidField(headers []Header, ticket []int) []int {
invalids := make([]int, 0)
for _, num := range ticket {
valid := false
for _, header := range headers {
for _, interval := range header.Intervals {
valid = valid || interval.Contains(num)
}
}
if !valid {
invalids = append(invalids, num)
}
}
return invalids
}
func selectByIndices(ns []int, is []int) []int {
selected := make([]int, 0)
for _, i := range is {
selected = append(selected, ns[i])
}
return selected
}
func product(ns []int) int {
if len(ns) == 0 {
return 0
}
product := 1
for _, n := range ns {
product *= n
}
return product
}
func sum(ns []int) int {
sum := 0
for _, n := range ns {
sum += n
}
return sum
}
func parseHeaders(rawHeaders []string) []Header {
headers := make([]Header, len(rawHeaders))
for i, line := range rawHeaders {
parts := strings.Split(line, ": ")
name := parts[0]
rawIntervals := strings.Split(parts[1], " or ")
intervals := make([]Interval, len(rawIntervals))
for j, rawInterval := range rawIntervals {
parsedInterval := strings.Split(rawInterval, "-")
start, _ := strconv.Atoi(parsedInterval[0])
end, _ := strconv.Atoi(parsedInterval[1])
intervals[j] = Interval{start, end}
}
headers[i] = Header{name, intervals}
}
return headers
}
func parseTickets(rawTickets []string) [][]int {
tickets := make([][]int, len(rawTickets))
for i, rawTicket := range rawTickets {
tickets[i] = parseTicket(rawTicket)
}
return tickets
}
func parseTicket(rawTicket string) []int {
if len(rawTicket) == 0 {
return []int{}
}
rawNums := strings.Split(rawTicket, ",")
nums := make([]int, len(rawNums))
for i, rawNum := range rawNums {
nums[i], _ = strconv.Atoi(rawNum)
}
return nums
}