You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

185 lines
6.6 KiB
Kotlin

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<Line>) {
fun intersections(): List<Intersection> {
val items = mutableListOf<Intersection>()
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<Segment>) {
// Find all intersections between this and given line
fun intersections(rhs: Line): List<Intersection> {
// 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
}
}
}
}
}