advent-of-code-2019/day3/go/main.go

236 lines
5.2 KiB
Go

package main
import (
"errors"
"fmt"
"io/ioutil"
"strconv"
"strings"
)
// Direction represents a direction in space
type Direction byte
const (
// DirectionUp represents the up direction
DirectionUp Direction = 'U'
// DirectionDown represents the down direction
DirectionDown = 'D'
// DirectionLeft represents the left direction
DirectionLeft = 'L'
// DirectionRight represents the right direction
DirectionRight = 'R'
)
// Point represents a point in space
type Point struct {
x int
y int
}
// Path represents a path taken
type Path []Point
// DistanceTo finds the manhattan distance between p and p2
func (p Point) DistanceTo(p2 Point) int {
return abs(p.x-p2.x) + abs(p.y-p2.y)
}
// Add adds the components of two points
func (p Point) Add(p2 Point) Point {
return Point{
x: p.x + p2.x,
y: p.y + p2.y,
}
}
// Intersection finds the intersection between two paths
func (path Path) Intersection(path2 Path) []Point {
res := []Point{}
pathPointSet := make(map[Point]struct{})
// Add all elements from the first path into the path set
for _, point := range path {
pathPointSet[point] = struct{}{}
}
for _, point := range path2 {
if _, ok := pathPointSet[point]; ok {
res = append(res, point)
}
}
return res
}
// Index returns the index at which the point occurs in the given path
func (path Path) Index(point Point) (int, error) {
for i, pathPoint := range path {
if point == pathPoint {
return i, nil
}
}
return -1, errors.New("point not in path")
}
func abs(n int) int {
if n < 0 {
return n * -1
}
return n
}
// makeDeltaPoint makes a point that represents the delta produced by the path component
func makeDeltaPoint(pathComponent string) (Point, error) {
direction := Direction(pathComponent[0])
distance, err := strconv.Atoi(pathComponent[1:])
if err != nil {
return Point{}, errors.New("Invalid number in path component")
}
switch direction {
case DirectionUp:
return Point{x: 0, y: distance}, nil
case DirectionDown:
return Point{x: 0, y: -distance}, nil
case DirectionLeft:
return Point{x: -distance, y: 0}, nil
case DirectionRight:
return Point{x: distance, y: 0}, nil
default:
return Point{}, errors.New("Invalid direction in path component")
}
}
// NewPathFromPathString makes a Path from a string representing it
func NewPathFromPathString(rawPath string) (Path, error) {
cursor := Point{0, 0}
pathComponents := strings.Split(rawPath, ",")
path := Path{}
for _, component := range pathComponents {
deltaPoint, err := makeDeltaPoint(component)
if err != nil {
return nil, fmt.Errorf("Could not make delta point: %s", err)
}
newCursor := cursor.Add(deltaPoint)
// Trace out the paths
// This is a bit gross, but I don't know of a better way to do it
if cursor.x < newCursor.x {
for i := cursor.x; i < newCursor.x; i++ {
path = append(path, Point{x: i, y: cursor.y})
}
} else {
for i := cursor.x; i > newCursor.x; i-- {
path = append(path, Point{x: i, y: cursor.y})
}
}
if cursor.y < newCursor.y {
for i := cursor.y; i < newCursor.y; i++ {
path = append(path, Point{x: cursor.x, y: i})
}
} else {
for i := cursor.y; i > newCursor.y; i-- {
path = append(path, Point{x: cursor.x, y: i})
}
}
cursor = newCursor
}
return path, nil
}
func part1(paths []Path) (int, error) {
intersections := paths[0]
// Get all points that intersect between the paths
for _, path := range paths[1:] {
intersections = intersections.Intersection(path)
}
// this bitshift represents the max int on the system
// https://stackoverflow.com/a/6878625
minDistance := int(^uint(0) >> 1)
for _, point := range intersections {
// We don't care about 0,0 as an intersection, as every path starts there.
if point == (Point{0, 0}) {
continue
}
distance := point.DistanceTo(Point{0, 0})
if distance < minDistance {
minDistance = distance
}
}
return minDistance, nil
}
func part2(paths []Path) (int, error) {
intersections := paths[0]
// Get all points that intersect between the paths
for _, path := range paths[1:] {
intersections = intersections.Intersection(path)
}
// this bitshift represents the max int on the system
// https://stackoverflow.com/a/6878625
minLength := int(^uint(0) >> 2)
for _, point := range intersections {
// We don't care about 0,0 as an intersection, as every path starts there.
if point == (Point{0, 0}) {
continue
}
length := 0
for _, path := range paths {
// Index will equal the path length, as 0,0 is the first item in all paths
index, err := path.Index(point)
if err != nil {
return -1, fmt.Errorf("could not get index of point in path: %s", err)
}
length += index
}
if length < minLength {
minLength = length
}
}
return minLength, nil
}
func main() {
inputContents, err := ioutil.ReadFile("../input.txt")
if err != nil {
panic(err)
}
trimmedInput := strings.TrimSpace(string(inputContents))
rawPaths := strings.Split(trimmedInput, "\n")
paths := make([]Path, len(rawPaths))
for i, rawPath := range rawPaths {
paths[i], err = NewPathFromPathString(rawPath)
if err != nil {
panic(err)
}
}
part1Res, err := part1(paths)
if err != nil {
panic(err)
}
fmt.Println(part1Res)
part2Res, err := part2(paths)
if err != nil {
panic(err)
}
fmt.Println(part2Res)
}