• 1 Post
  • 1.17K Comments
Joined 2 years ago
cake
Cake day: June 29th, 2023

help-circle

  • Kotlin

    Looking at the puzzle, I knew that I had no clue how to solve it. So I came here to see if I was missing something or if there were any hints.

    And the hint I saw was to do the simplest check possible, so I gave it a shot.

    And that got the test input wrong, but I ran it against the real input anyway just to see if it was right. And it was.

    I think if I had gone on my instincts and just tried to solve this, I could have gone around in circles for hours or days trying to get it right.

    fun main() {
        val input = getInput(12)
        val (gifts, regions) = parseInput1(input)
        var total = 0
        for (i in regions.indices) {
            val totalAreaOfGifts = regions[i].gifts.mapIndexed { index, count -> count * gifts[index].area }.sum()
            if (totalAreaOfGifts <= regions[i].area) {
                total++
            }
        }
        println(total)
    }
    
    data class Gift(val shape: List<List<Char>>, val area: Int)
    
    data class Region(val width: Int, val height: Int, val area: Int, val gifts: List<Int>)
    
    fun parseInput1(input: String): Pair<List<Gift>, List<Region>> {
        val gifts: MutableList<Gift> = mutableListOf()
        val regions: MutableList<Region> = mutableListOf()
        val lines = input.lines()
        lines.forEachIndexed { index, line ->
            if (line.contains(":")) {
                if (line.contains("x")) {
                    val split = line.split(" ")
                    val shape = split.first().replace(":", "").split("x")
                    val width = shape.first().toInt()
                    val height = shape.last().toInt()
                    regions.add(
                        Region(
                            width,
                            height,
                            width * height,
                            split.slice(1..<split.size).map { str -> str.toInt() })
                    )
                } else {
                    var nextBlankLineIndex = 0
                    for (i in index + 1..<lines.size) {
                        if (lines[i].isBlank()) {
                            nextBlankLineIndex = i
                            break
                        }
                    }
                    val shape = lines.slice(index + 1..<nextBlankLineIndex).map { it.toCharArray().toList() }
                    val area = shape.flatten().filter { it == '#' }.size
                    gifts.add(Gift(shape, area))
                }
            }
        }
        return gifts to regions
    }
    



  • Kotlin

    Part 1 had me assuming, like a lot of other folks, that Part 2 would be a simple weighted graph. So I coded Part 1 as a non-weighted graph and it was great.

    Part 2 looked simple enough, but different. Part 1 was breadth first, I assumed depth first would work for Part 2. When it worked great on the test input, but took 12 seconds, I knew I was in trouble. So I added caching. And it was super quick on the test input.

    Real input was a whole 'nother story. I watched my system resources balloon. At last count it was using 8GB of RAM before I killed it. And that was before solving even the first line.

    So I went online to find what I’m missing to see people saying it’s a linear algebra problem, and that it’s best to use some kind of library for it.

    I will admit that I leaned pretty heavily on asking Gemini questions to figure out how to use the Google OR-Tools library.

    So here’s my Part 2 code:

    import com.google.ortools.Loader
    import com.google.ortools.linearsolver.MPSolver
    import com.google.ortools.linearsolver.MPVariable
    import utils.*
    
    fun main() {
        val input = getInput(10)
        val machines = parseInput1(input)
        Loader.loadNativeLibraries()
        var total = 0
        for (machine in machines) {
            val buttons = machine.buttons
            val joltages = machine.joltages
            val solver = MPSolver.createSolver("SCIP") ?: throw Exception("Could not create solver")
            val x = arrayOfNulls<MPVariable>(machine.buttons.size)
            for (i in buttons.indices) {
                x[i] = solver.makeIntVar(0.0, Double.POSITIVE_INFINITY, "x$i")
            }
            val target = joltages.map { it.toDouble() }
            val aMatrix = joltages.indices.map { joltageToArray(it, buttons) }.toTypedArray()
            for (j in joltages.indices) {
                val ct = solver.makeConstraint(target[j], target[j], "joltage_constraint_$j")
                for (i in buttons.indices) {
                    ct.setCoefficient(x[i], aMatrix[j][i])
                }
            }
            val objective = solver.objective()
            for (i in buttons.indices) {
                objective.setCoefficient(x[i], 1.0)
            }
            objective.setMinimization()
            val resultStatus = solver.solve()
            if (resultStatus == MPSolver.ResultStatus.OPTIMAL) {
                val result = objective.value().toInt()
                total += result
            } else {
                println("Problem could not be solved.")
            }
        }
        println(total)
    }
    
    data class Machine(val configuration: List<Boolean>, val buttons: List<List<Int>>, val joltages: List<Int>)
    
    fun parseInput1(input: String): List<Machine> {
        return input.lines()
            .filter { it.isNotBlank() }
            .map {
                val split = it.split(" ")
                val configuration = split.first().toCharArray()
                    .slice(1..<split.first().length - 1)
                    .map { char ->
                        when (char) {
                            '#' -> true
                            else -> false
                        }
                    }
                val buttons = split.slice(1..<split.size - 1)
                    .map { str ->
                        str.slice(1..<str.length - 1)
                            .split(",")
                            .map { number -> number.toInt() }
                    }
                val joltages = split.last()
                    .slice(1..<split.last().length - 1)
                    .split(",")
                    .map { number -> number.toInt() }
                Machine(configuration, buttons, joltages)
            }
    }
    
    fun joltageToArray(joltageIndex: Int, buttons: List<List<Int>>): Array<Double> {
        val array = DoubleArray(buttons.size) { 0.0 }.toTypedArray()
        for (i in buttons.indices) {
            if (joltageIndex in buttons[i]) {
                array[i] = 1.0
            }
        }
        return array
    }
    

  • Kotlin

    This was substantially easier than yesterday’s problem. Especially considering I still haven’t done Part 2 from yesterday.

    Anyway, here is my Part 2 solution for today. Given how quickly Part 1 ran without a cache, I didn’t think Part 2 would be much different, until my computer fans spun up and stayed there for over a minute. So I added a cache and re-ran it and it ran in 31ms.

    const val end = "out"
    var map: Map<String, List<String>>? = null
    val problemVertices = "dac" to "fft"
    val cache: MutableMap<Pair<String, Pair<Boolean, Boolean>>, Long> = mutableMapOf()
    
    fun main() {
        val input = getInput(11)
        map = parseInput1(input)
        val total = countPaths2("svr", false to false).first
        println(total)
    }
    
    fun countPaths2(vertex: String, problemVerticesEncountered: Pair<Boolean, Boolean>): Pair<Long, Pair<Boolean, Boolean>> {
        val otherVertices = map!![vertex]!!
        var total = 0L
        val nextProblemVerticesEncountered =
            (problemVerticesEncountered.first || vertex == problemVertices.first) to (problemVerticesEncountered.second || vertex == problemVertices.second)
        for (otherVertex in otherVertices) {
            val key = otherVertex to nextProblemVerticesEncountered
            if (cache.contains(key)) {
                total += cache[key]!!
            } else if (otherVertex == end) {
                if (nextProblemVerticesEncountered.first && nextProblemVerticesEncountered.second) {
                    total++
                }
            } else {
                total += countPaths2(otherVertex, nextProblemVerticesEncountered).first
            }
        }
        cache[vertex to nextProblemVerticesEncountered] = total
        return total to nextProblemVerticesEncountered
    }
    
    
    fun parseInput1(input: String): Map<String, List<String>> = input.lines()
        .filter { it.isNotBlank() }
        .associate {
            val split = it.split(": ")
            split[0] to split[1].split(" ").toList()
        }
    



  • 95%+ of you would be leaving massive, massive performance on the table by using Linux

    Pulling numbers out of thin air.

    Also, I’m not seeing the “massive” here. But I’m not someone who sweats about framerates as long as they’re stable and above 60fps. I have always been a mid-spec player.

    Those Nvidia numbers aren’t great, but in general, fuck Nvidia. They are getting better about their drivers, and the performance on Linux will continue to get better.

    But me, I’m happy gaming on Linux, and so are lots of folks.


  • Kotlin

    My solution can’t be very good because it took over a minute to run part 2. I used a ray casting algorithm to determine if a point was in the main polygon. I did that for the 2 corners of each rectangle that weren’t given as input. If both of those corners were in the polygon, then I checked each point of each edge of that rectangle.

    My first stab at this didn’t consider the edge points of the rectangle, and after looking at some visualizations of the polygon, I could see where I was going wrong. I wouldn’t have considered a rectangle with all 4 corners in the polygon could have an edge that goes outside the polygon without looking at a visualization.

    Part 2 code:

    fun main() {
        val input = getInput(9)
        val polygon = parseInput1(input)
        val rectangles: MutableList<Rectangle> = mutableListOf()
        for (i in polygon.indices) {
            for (j in i + 1..<polygon.size) {
                rectangles.add(Rectangle(polygon[i], polygon[j]))
            }
        }
        rectangles.sortByDescending { it.area }
        var answer: Rectangle? = null
        for (rectangle in rectangles) {
            if (isInsidePolygon(rectangle.corner3, polygon)
                && isInsidePolygon(rectangle.corner4, polygon)
            ) {
                val edgePoints: MutableList<Pair<Long, Long>> = mutableListOf()
                edgePoints.addAll(getAllPointsBetween(rectangle.corner1, rectangle.corner3))
                edgePoints.addAll(getAllPointsBetween(rectangle.corner1, rectangle.corner4))
                edgePoints.addAll(getAllPointsBetween(rectangle.corner2, rectangle.corner3))
                edgePoints.addAll(getAllPointsBetween(rectangle.corner2, rectangle.corner4))
                var isInside = true
                for (edgePoint in edgePoints) {
                    if (!isInsidePolygon(edgePoint, polygon)) {
                        isInside = false
                        break
                    }
                }
                if (isInside) {
                    answer = rectangle
                    break
                }
            }
        }
        println(answer?.area ?: "Whoops")
    }
    
    fun parseInput1(input: String): List<Pair<Long, Long>> = input.lines()
        .filter { it.isNotBlank() }
        .map {
            val split = it.split(",")
            split[0].toLong() to split[1].toLong()
        }
    
    data class Rectangle(val corner1: Pair<Long, Long>, val corner2: Pair<Long, Long>) {
        val corner3: Pair<Long, Long> = corner1.first to corner2.second
        val corner4: Pair<Long, Long> = corner2.first to corner1.second
        val area: Long = (1 + abs(corner1.first - corner2.first)) * (1 + abs(corner1.second - corner2.second))
    }
    
    fun isInsidePolygon(point: Pair<Long, Long>, polygon: List<Pair<Long, Long>>): Boolean {
        val x = point.first
        val y = point.second
        var intersectionCount = 0L
        for (i in polygon.indices) {
            val p1 = polygon[i]
            val p2 = polygon[(i + 1) % polygon.size]
            if (point == p1 || point == p2 || isOnEdge(point, p1, p2)) {
                return true
            }
            val y1 = p1.second
            val y2 = p2.second
            val crossesRay = (y in y1..<y2) || (y in y2..<y1)
            if (crossesRay) {
                val x1 = p1.first
                val x2 = p2.first
                val xIntersect = (x2 - x1) * (y - y1).toDouble() / (y2 - y1) + x1
                if (x < xIntersect) {
                    intersectionCount++
                }
            }
        }
        return intersectionCount % 2L != 0L
    }
    
    fun isOnEdge(p: Pair<Long, Long>, p1: Pair<Long, Long>, p2: Pair<Long, Long>): Boolean {
        val crossProduct = (p.second - p1.second) * (p2.first - p1.first) -
                (p.first - p1.first) * (p2.second - p1.second)
        if (crossProduct != 0L) {
            return false
        }
        val isBetweenX = p.first in minOf(p1.first, p2.first)..maxOf(p1.first, p2.first)
        val isBetweenY = p.second in minOf(p1.second, p2.second)..maxOf(p1.second, p2.second)
        return isBetweenX && isBetweenY
    }
    
    fun getAllPointsBetween(left: Pair<Long, Long>, right: Pair<Long, Long>): List<Pair<Long, Long>> {
        if (right.first == left.first) {
            val max = maxOf(left.second, right.second)
            val min = minOf(left.second, right.second)
            return (min + 1..<max).toList().map { right.first to it }
        } else if (right.second == left.second) {
            val max = maxOf(left.first, right.first)
            val min = minOf(left.first, right.first)
            return (min + 1..<max).toList().map { it to right.second }
        } else {
            throw Exception("Whoops")
        }
    }
    



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

  • I didn’t do recursion on part 1, so my part 1 and 2 were fairly different.

    const val start = 'S'
    const val empty = '.'
    const val splitter = '^'
    const val beam = '|'
    
    var width: IntRange = IntRange(0, 0)
    var height: IntRange = IntRange(0, 0)
    val cache: MutableMap<Pair<Int, Int>, Long> = mutableMapOf()
    var map: List<List<Char>> = listOf()
    
    fun main() {
        val input = getInput(7)
        map = parseInput1(input)
        height = map.indices
        width = map[0].indices
        val startLocation = map[0].indexOf(start) to 0
        val splits = moveBeam(startLocation) + 1
        println(splits)
    }
    
    fun parseInput1(input: String): List<List<Char>> = input.lines()
        .filter { it.isNotBlank() }
        .map { it.toCharArray().toList() }
    
    fun moveBeam(beamLocation: Pair<Int, Int>): Long {
        if (cache.containsKey(beamLocation)) {
            return cache[beamLocation]!!
        }
        val belowLocation = beamLocation.first to beamLocation.second + 1
        if (belowLocation.second !in height) {
            return 0L
        }
        if (cache.containsKey(belowLocation)) {
            return cache[belowLocation]!!
        }
        val below = map[belowLocation.second][belowLocation.first]
        var splits = 0L
        if (below == empty) {
            splits = moveBeam(belowLocation)
        } else if (below == splitter) {
            splits++
            val leftLocation = belowLocation.first - 1 to belowLocation.second
            val left = if (leftLocation.first in width) map[leftLocation.second][leftLocation.first] else '!'
            if (left == empty || left == splitter) {
                splits += moveBeam(leftLocation)
            }
            val rightLocation = belowLocation.first + 1 to belowLocation.second
            val right = if (rightLocation.first in width) map[rightLocation.second][rightLocation.first] else '!'
            if (right == empty || right == splitter) {
                splits += moveBeam(rightLocation)
            }
        }
        cache[beamLocation] = splits
        return splits
    }
    

  • If you get down voted frequently and it really bothers you, it might be time to do some introspection on what you think or how you present yourself.

    Or do some reflection on where you’re sharing your thoughts. An anti-AI community won’t respond positively to someone hyping Google’s new image generation model, for instance.

    If I’m getting downtown in a community, it’s a sign that maybe it’s not a good fit for me.



  • I also thought about trying to rotate, but not for very long. Mine would be a bit simpler if I’d done what you did and build the number string and then check if it’s blank.

    fun main() {
        val input = getInput(6)
        val output = parseInput2(input)
        var total = 0L
        for ((numbers, operator) in output) {
            when (operator) {
                '+' -> { total += numbers.sum() }
                '*' -> { total += numbers.reduce { acc, number -> acc * number }}
            }
        }
        println(getElapsedTime())
        println(total)
    }
    
    fun parseInput2(input: String): List<Pair<List<Long>, Char>> {
        val rows = input.lines()
            .filter { it.isNotBlank() }
            .map { it.toCharArray() }
        val output: MutableList<Pair<List<Long>, Char>> = mutableListOf()
        val numberRowCount = rows.size - 1
        var isNewProblem = true
        var currentNumbers: MutableList<Long> = mutableListOf()
        var operator = ' '
        for (column in rows[0].indices) {
            if (!isNewProblem && isColumnEmpty(rows, column)) {
                isNewProblem = true
                output.add(currentNumbers to operator)
                continue
            }
            if (isNewProblem) {
                isNewProblem = false
                currentNumbers = mutableListOf()
                operator = rows.last()[column]
            }
            var number = ""
            for (row in 0..<numberRowCount) {
                if (rows[row][column] != ' ') {
                    number += rows[row][column]
                }
            }
            currentNumbers.add(number.toLong())
        }
        if (!isNewProblem) {
            output.add(currentNumbers to operator)
        }
        return output
    }
    
    fun isColumnEmpty(rows: List<CharArray>, column: Int): Boolean {
        for (i in rows.indices) {
            if (rows[i][column] != ' ') {
                return false
            }
        }
        return true
    }
    

  • Edit: looking at your code, I had forgotten about .indices. That would have made this a little easier to write.

    I completely forgot to do the puzzle yesterday somehow. I struggled a bit on this one for a while because I’d used a <= 4 where I should have used a < 4. Just a complete brainfart of thinking, “It needs to be 4 or less”. I wasted more time on that than I’d like to admit.

    My first stab at this set all of the adjacency counts to 0, and that lead to a few rolls that had no rolls adjacent to them staying on the map by accident.

    const val toiletPaperRoll = '@'
    const val adjacentLimit = 4
    var height = 0
    var width = 0
    
    fun main() {
        val input = getInput(4)
        val map = parseInput1(input)
        height = map.size
        width = map[0].size
        val adjacencyMap = createAdjacencyMap(map)
        var anyRemoved = true
        var count = 0
        while (anyRemoved) {
            anyRemoved = false
            for (y in 0..<height) {
                for (x in 0..<width) {
                    if (adjacencyMap[y][x] in 0..<adjacentLimit) {
                        count++
                        anyRemoved = true
                        removeToiletPaperRoll(adjacencyMap, x, y)
                    }
                }
            }
        }
        println(count)
    }
    
    fun parseInput1(input: String): List<List<Char>> = input
        .lines()
        .filter { it.isNotBlank() }
        .map { it.toCharArray().toList() }
    
    fun createAdjacencyMap(map: List<List<Char>>): List<MutableList<Int>> {
        val adjacencyMap = List(height) { MutableList(width) { -1 } }
        for (y in 0..<height) {
            for (x in 0..<width) {
                if (map[y][x] != toiletPaperRoll) {
                    continue
                }
                adjacencyMap[y][x]++
                for (y2 in y - 1..y + 1) {
                    for (x2 in x - 1..x + 1) {
                        if (y == y2 && x == x2 || (y2 < 0 || x2 < 0 || y2 >= height || x2 >= width)) {
                            continue
                        }
                        if (map[y2][x2] == toiletPaperRoll) {
                            adjacencyMap[y2][x2]++
                        }
                    }
                }
            }
        }
        return adjacencyMap
    }
    
    fun removeToiletPaperRoll(adjacencyMap: List<MutableList<Int>>, x: Int, y: Int) {
        adjacencyMap[y][x] = -1
        for (y2 in y - 1..y + 1) {
            for (x2 in x - 1..x + 1) {
                if (y == y2 && x == x2 || (y2 < 0 || x2 < 0 || y2 >= height || x2 >= width)) {
                    continue
                }
                if (adjacencyMap[y2][x2] >= adjacentLimit) {
                    adjacencyMap[y2][x2]--
                }
            }
        }
    }