This Scala solution of <adventofcode.com/2024/day/...
# language-evolution
e
This Scala solution of adventofcode.com/2024/day/4 makes me want to have
for comprehension
in Kotlin
Copy code
import scala.io.Source

object Main

val XMAS = "XMAS"

val directions: List[(Int, Int)] = List(
  (0, 1), // Right (horizontal)
  (1, 0), // Down (vertical)
  (1, 1), // Diagonal down-right
  (1, -1), // Diagonal down-left
  (0, -1), // Left (reverse horizontal)
  (-1, 0), // Up (reverse vertical)
  (-1, -1), // Diagonal up-left
  (-1, 1) // Diagonal up-right
)

@main def run(): Unit = {
  val grid =
    Option(Main.getClass.getClassLoader.getResourceAsStream("input4.txt"))
      .map(Source.fromInputStream)
      .map(_.getLines().toList)
      .getOrElse(List.empty)
  val count = for {
    i <- grid.indices
    j <- grid(i).indices
    (dx, dy) <- directions
    if isMatch(grid, i, j, dx, dy)
  } yield 1
  println(s"Total occurrences of XMAS: ${count.sum}")
}

def isMatch(grid: List[String], i: Int, j: Int, dx: Int, dy: Int): Boolean = {
  val endX = i + (XMAS.length - 1) * dx
  val endY = j + (XMAS.length - 1) * dy
  (grid.isDefinedAt(endX) && grid.isDefinedAt(endY)) &&
    XMAS.indices.forall { k =>
      grid(i + k * dx)(j + k * dy) == XMAS(k)
    }
}
At the moment, I can't see a denested Kotlin implementation comparable to this Scala solution. In Kotlin, it would require 4 nested loops. The only alternative seems to involve denesting with functions, but that’s a different approach altogether. If I’m not mistaken
e
not this example but Scala definitely abuses their for-notation for some very non-for-like things 😛
it wouldn't take that much nesting in Kotlin though:
Copy code
val count = grid.indices
    .flatMap { i -> grid[i].indices.map(i::to) }
    .flatMap { ij -> directions.map(ij::to) }
    .count { (ij, dxdy) -> isMatch(grid, ij.first, ij.second, dxdy.first, dxdy.second) }
🔥 1
Scala's
for
comprehensions desugar into a chain of
flatMap
calls somewhat like that
👍 1
e
Thanks! Now the Kotlin code looks like this. It's fine. It's just my programming incompetence—I didn't even think of desugaring it like this. Perfect
Copy code
const val XMAS = "XMAS"

val directions: List<Pair<Int, Int>> = listOf(
    Pair(0, 1),    // Right (horizontal)
    Pair(1, 0),    // Down (vertical)
    Pair(1, 1),    // Diagonal down-right
    Pair(1, -1),   // Diagonal down-left
    Pair(0, -1),   // Left (reverse horizontal)
    Pair(-1, 0),   // Up (reverse vertical)
    Pair(-1, -1),  // Diagonal up-left
    Pair(-1, 1)    // Diagonal up-right
)

fun main() {
    val grid =
        ({}.javaClass.classLoader.getResourceAsStream("input4.txt") ?: return)
            .reader()
            .use { it.readLines() }
    
    val count = grid.indices
        .flatMap { i -> grid[i].indices.map(i::to) }
        .flatMap { ij -> directions.map(ij::to) }
        .count { (ij, dxdy) -> isMatch(grid, ij.first, ij.second, dxdy.first, dxdy.second) }

    println("Total occurrences of XMAS: $count")
}

fun isMatch(grid: List<String>, i: Int, j: Int, dx: Int, dy: Int): Boolean {
    val endX = i + (XMAS.length - 1) * dx
    val endY = j + (XMAS.length - 1) * dy
    return (endX in grid.indices && endY in grid[0].indices)
            &&
            XMAS.indices.all { k ->
                grid[i + k * dx][j + k * dy] == XMAS[k]
            }
}
c
Hey! Look at what you can do with Parameterize (#C06083PAKEK):
Copy code
val count = sequence {
    parameterize {
        val i by parameterOf(grid.indices)
        val ij by parameterOf(grid[i].indices.map(i::to))
        
        yield(directions.map(ij::to))
    }
}.count [ (ij, dxdy) -> isMatch(grid, ij.first, ij.second, dxdy.first, dxdy.second) }
That looks fairly close to the Scala example 🤔
👍 1
e
Would be cool if they put it into Kotlin Arrow lib
e
yeah these things aren't honestly that hard to write, I made one for our internal test use before I heard of Parameterize which seems to have a similar origin https://kotlinlang.slack.com/archives/C0BJ0GTE2/p1709272607955079?thread_ts=1709271304.876229&amp;cid=C0BJ0GTE2
c
yeah, I had one as well. I found out about Parameterize when I was wondering whether I should make it a public lib
e
I suppose one thing mine has going for it is that it works in
suspend
, so we have tests that do
Copy code
@get:Rule
val errorCollector = ErrorCollector()

@Test
fun test() = runTest {
    chooseAll(errorCollector::addError) {
        coroutineScope {
            val foo = choose(...)
            val bar = choose(...)
but I have been thinking that longer-term it's probably better to build this on top of the Compose machinery instead, so that we get to have the benefits of skipping (which is more likely what a user would expect)
c
oh wow yours is very different internally