package main import java.io.File import kotlin.math.abs import kotlin.math.max import kotlin.math.min fun main() { val coll = loadLines(File("./input").readText()) val intersections = coll.intersections(); // Get shorted manhattan path println("Shortest manhattan distance: ") println(intersections.map { abs(it.x) + abs(it.y) }.sorted().first()) println("Shortest wire distance: ") println(intersections.map { it.segmentA.getDistance(it) + it.segmentB.getDistance(it) }.sorted().first()) } /** * Really dirty line reader that makes a LineCollection from input */ fun loadLines(input: String): LineCollection { return LineCollection( input .trim() // Split lines .lines() .map { // This records the current point in the line var current = Point(0, 0) Line( it .trim() // Segments are split with , .split(',') .map { // First character is the direction var d = it[0]; // Rest is distance/length var distance = it.substring(1).toInt() // Down and Left are basically Up and Right but inverted. if (d == 'D' || d == 'L') { d = if (d == 'D') 'U' else 'R' distance = -distance } if (d == 'U') { // if direction is up, line is over the Y axis Segment(Axis.Y, current.y, current.y + distance, current.x).also { // Set the new current point current = Point(current.x, current.y + distance); } } else { // If direction is anything else it's Right, so it runs over the X axis Segment(Axis.X, current.x, current.x + distance, current.y).also { // Set the new current point current = Point(current.x + distance, current.y); } } } ) } ) } // Helper class indicating a point (x, y) coordinates data class Point(val x: Int, val y: Int) // Class that holds all lines known data class LineCollection(val lines: List) { fun intersections(): List { val items = mutableListOf() for (i in 0 until lines.count() - 1) { for (j in (i + 1) until lines.count()) { items.addAll(lines[i].intersections(lines[j])); } } return items; } } // Helper class that holds a segment and it's corresponding line data class LineSegment(val line: Line, val segment: Segment) { // Get distance to point in line. // It is assumed that the point is part of the [segment] fun getDistance(it: Point): Int { // Since point intersects, use the point coordinate instead of [Segment.end] val lastEnd = if (segment.axis == Axis.X) it.y else it.x // Distance traveled var cost = 0 for (sgm in line.segments) { // If we found the same segment as we're holding we can stop if (sgm === segment) { break } // Add length of segment to cost cost += sgm.high - sgm.low } // Get length to point from last segment start return cost + (max(lastEnd, segment.start) - min(lastEnd, segment.start)) } fun getDistance(it: Intersection) = getDistance(Point(it.x, it.y)) } // Holds an intersection, it's point and the 2 LineSegments intersecting data class Intersection(val x: Int, val y: Int, val segmentA: LineSegment, val segmentB: LineSegment) // Line is a collection of Segments data class Line(val segments: List) { // Find all intersections between this and given line fun intersections(rhs: Line): List { // flatMap will allow use to return an array // and will concat all returned arrays return segments.flatMap { seg -> // map but it ignores all null returns rhs.segments.mapNotNull { otherSeg -> // Find intersection ( ?. indicates only access .let if result is not null ) otherSeg.intersection(seg)?.let { // Create intersection object Intersection(it.x, it.y, LineSegment(this, seg), LineSegment(rhs, otherSeg)) } } } } } // Quick helper function to make a range between 2 ints infix fun Int.between(rhs: Int) = (this + 1) until rhs // Enum with known Axis'es enum class Axis { X, Y; } // Class for a segment in a line, a segment is basically a single direction stroke // // if [axis] is X, [start] and [end] are on the X axis, and [other] is on the Y axis // if [axis] is Y, [start] and [end] are on the Y axis, and [other] is on the X axis data class Segment(val axis: Axis, val start: Int, val end: Int, val other: Int) { // Get the lowest value from start or end // helpful for intersection calculation val low by lazy { min(start, end) } // Get the highest value from start or end // helpful for intersection calculation val high by lazy { max(start, end) } // Find intersection between 2 segments fun intersection(rhs: Segment): Point? { // If 2 lines are on a different axis, // the calculations are different from them being on the same axis return if (axis != rhs.axis) { // If on different axis, check if [other] is between each others [low] and [high] if (rhs.other in low between high && other in rhs.low between rhs.high) { if (rhs.axis == Axis.Y) { Point(other, rhs.other) } else { Point(rhs.other, other) } } else { null } } else { // If lines are on the same axis, [which is an edge case that never happens :)] // [other] should be the same for both to intersect if (other != rhs.other) { null } else { // only intersect if our [high] is higher than their [low] and vice versa if (low < rhs.high && rhs.low < high) { val first = min(rhs.high, high) Point(if (axis == Axis.Y) other else first, if (axis == Axis.Y) first else other) } else { null } } } } }