Day 8: Playground

Megathread guidelines

  • Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
  • You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL

FAQ

you are viewing a single comment's thread
view the rest of the comments
[–] 4 points 9 months ago* (last edited 9 months ago) (5 children)

Kotlin

First tricky puzzle today, instantly leading me to more or less brute force for part2. I took a lot of time to understand which data structures I needed today, and how to compute my matrix.

Runtimes:
part1: 141ms
part2: 45.9 seconds .-.

Solution

class Day08 : Puzzle {

    lateinit var boxes: List<Point3D>
    lateinit var edges: List<Pair<List<Point3D>, Double>>

    override fun readFile() {
        val input = readInputFromFile(2025, 8, false)
        boxes = input.lines().filter { it.isNotBlank() }
            .map { it.split(",") }
            .map { Point3D(it[0].toInt(), it[1].toInt(), it[2].toInt()) }
        edges = calculateEdges(boxes)
    }

    private fun calculateEdges(boxes: List<Point3D>): List<Pair<List<Point3D>, Double>> {
        val edges = mutableListOf<Pair<MutableList<Point3D>, Double>>()
        for (i in boxes.indices) {
            for (j in i + 1 until boxes.size) {
                edges.add(Pair(mutableListOf(boxes[i], boxes[j]), boxes[i].dist(boxes[j])))
            }
        }
        return edges.sortedBy { it.second }
    }

    override fun solvePartOne(): String {
        val connections = buildEmptyConnectionMatrix(boxes.size)
        val mutableEdges = edges.toMutableList()
        for (i in 0..<1000) {
            connectNextEdge(mutableEdges, connections)
        }

        val connectionSet = buildConnectionSet(boxes, connections)

        return connectionSet.map { it.size }
            .sortedByDescending { it }
            .take(3)
            .reduce(Int::times)
            .toString()
    }

    override fun solvePartTwo(): String {
        val connections = buildEmptyConnectionMatrix(boxes.size)
        val mutableEdges = edges.toMutableList()

        var result: Long? = null
        while (result == null) {
            result = connectNextEdge(mutableEdges, connections, true)
        }

        return result.toString()
    }

    // size: width & height of (square) matrix
    private fun buildEmptyConnectionMatrix(size: Int): Array<Array<Boolean>> {
        val connections = Array(size) { Array(size) { false } }
        for (i in connections.indices) {
            connections[i][i] = true
        }
        return connections
    }

    private fun connectNextEdge(mutableEdges: MutableList<Pair<List<Point3D>, Double>>, connections: Array<Array<Boolean>>, part2: Boolean = false): Long? {
        if (mutableEdges.isEmpty()) return null
        val next = mutableEdges[0]

        val point = next.first[0]
        val other = next.first[1]
        connectAll(boxes.indexOf(point), boxes.indexOf(other), connections)
        mutableEdges.remove(next)

        // all nodes are connected, assume that this is happening for the first time
        return if (part2 && connections[0].all { it }) {
            next.first[0].x.toLong() * next.first[1].x.toLong()
        } else {
            null
        }
    }

    private fun connectAll(index: Int, other: Int, connections: Array<Array<Boolean>>) {
        fun connectHelper(hIndex: Int) {
            val newConnections = mutableSetOf<Int>()
            for (i in connections[hIndex].indices) {
                if (connections[hIndex][i]) newConnections.add(i)
            }
            for (boxIndex in newConnections.filter { it != hIndex }) {
                for (conn in newConnections.filter { it != boxIndex }) {
                    connections[boxIndex][conn] = true
                }
            }
        }
        connections[index][other] = true

        connectHelper(index) // update matrix with all values from node at [index]
        connectHelper(other) // update matrix with all values from node at [other]
    }

    // returns 2D-list of all indices of currently active connections
    private fun buildConnectionSet(boxes: List<Point3D>, connections: Array<Array<Boolean>>): Set<List<Int>> {
        val connectionSet = mutableSetOf<List<Int>>()
        val indices = (0 until boxes.size).toMutableList()
        while (indices.isNotEmpty()) {
            val list = mutableListOf<Int>()
            val curr = indices.removeFirst()
            val array = connections[curr]
            for (j in array.indices) {
                if (array[j]) list.add(j)
            }
            connectionSet.add(list)
        }
        return connectionSet
    }

    data class Point3D(val x: Int, val y: Int, val z: Int) {
        fun dist(other: Point3D): Double {
            return sqrt(
                (x - other.x).toDouble().pow(2) +
                        (y - other.y).toDouble().pow(2) +
                        (z - other.z).toDouble().pow(2)
            )
        }
    }
}

full code on Codeberg

  • source
  • hideshow 5 child comments
  • [–] [S] 4 points 9 months ago (1 child)

    That is a very unoptimal pt2 time! Have you tried profiling to see what's causing the slowdown? Mine is 300ms, or worst case, 4s to process all links.

  • source
  • parent
  • hideshow 1 child comment
  • [–] 3 points 9 months ago (2 children)

    That's interesting. I found part 1 to be the hard part and part 2 was just simplifying part 1. They ran in about the same time for me, too.

    Part 1 run time:

    Input IO: 0m 0s 10ms
    Input Parse: 0m 0s 21ms
    Algorithm: 0m 0s 250ms
    Total: 0m 0s 281ms
    

    Part 2 run time:

    Input IO: 0m 0s 11ms
    Input Parse: 0m 0s 22ms
    Algorithm: 0m 0s 255ms
    Total: 0m 0s 288ms
    

    Code:

    const val connectionsLimit = 1000
    const val circuitsLimit = 3
    
    fun part1() {
        val input = getInput(8)
        val circuits: MutableList<MutableSet<Triple<Int, Int, Int>>> = parseInput1(input)
        val distances: MutableList<Pair<Pair<Triple<Int, Int, Int>, Triple<Int, Int, Int>>, Double>> = mutableListOf()
        val tempList = circuits.toList()
        for (i in tempList.indices) {
            val left = tempList[i].first()
            for (j in i + 1..<tempList.size) {
                val right = tempList[j].first()
                val distance = calculateDistance(left, right)
                val coordinates = left to right
                distances.add(coordinates to distance)
            }
        }
        distances.sortBy { it.second }
        // Parts 1 and 2 are the same until here
        for (i in 0..<connectionsLimit) {
            val distance = distances[i]
            val leftCircuit = circuits.first { it.contains(distance.first.first) }
            val rightCircuit = circuits.first { it.contains(distance.first.second) }
            if (leftCircuit != rightCircuit) {
                leftCircuit.addAll(rightCircuit)
                circuits.remove(rightCircuit)
            }
        }
        val sizes = circuits.map { it.size.toLong() }.sortedDescending().slice(0..<circuitsLimit)
        val total = sizes.reduce { acc, i -> acc * i }
        println(total)
    }
    
    fun part2() {
        val input = getInput(8)
        val circuits: MutableList<MutableSet<Triple<Int, Int, Int>>> = parseInput1(input)
        val distances: MutableList<Pair<Pair<Triple<Int, Int, Int>, Triple<Int, Int, Int>>, Double>> = mutableListOf()
        val tempList = circuits.toList()
        for (i in tempList.indices) {
            val left = tempList[i].first()
            for (j in i + 1..<tempList.size) {
                val right = tempList[j].first()
                val distance = calculateDistance(left, right)
                val coordinates = left to right
                distances.add(coordinates to distance)
            }
        }
        distances.sortBy { it.second }
        // Part 2 differs starting here
        var answer = 0
        for (distance in distances) {
            val leftCircuit = circuits.first { it.contains(distance.first.first) }
            val rightCircuit = circuits.first { it.contains(distance.first.second) }
            if (leftCircuit != rightCircuit) {
                leftCircuit.addAll(rightCircuit)
                circuits.remove(rightCircuit)
                if (circuits.size == 1) {
                    answer = distance.first.first.first * distance.first.second.first
                    break
                }
            }
        }
        println(answer)
    }
    
    fun parseInput1(input: String): MutableList<MutableSet<Triple<Int, Int, Int>>> {
        return input.lines()
            .filter { it.isNotBlank() }
            .map {
                val split = it.split(",")
                mutableSetOf(Triple(split[0].toInt(), split[1].toInt(), split[2].toInt()))
            }
            .toMutableList()
    }
    
    fun calculateDistance(left: Triple<Int, Int, Int>, right: Triple<Int, Int, Int>): Double {
        val dx = (left.first - right.first).toDouble()
        val dy = (left.second - right.second).toDouble()
        val dz = (left.third - right.third).toDouble()
        val distanceSquared = dx.pow(2) + dy.pow(2) + dz.pow(2)
        return sqrt(distanceSquared)
    }
    
  • source
  • parent
  • hideshow 2 child comments
  • [–] 2 points 9 months ago (1 child)

    Oh wow, I see you went the Kotlin way of hacking together an object with Pairs and Triples. I used to do that too but then it got too confusing lol

  • source
  • parent
  • hideshow 1 child comment