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
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |