Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions bench-sources/adventOfCode/day01.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Adapted from https://github.com/scalacenter/scala-advent-of-code
// 2023 Day 01: Trebuchet?!
package aoc.day01

/** The textual representation of digits. */
val stringDigitReprs = Map(
"one" -> 1,
"two" -> 2,
"three" -> 3,
"four" -> 4,
"five" -> 5,
"six" -> 6,
"seven" -> 7,
"eight" -> 8,
"nine" -> 9,
)

/** All the string representation of digits, including the digits themselves. */
val digitReprs = stringDigitReprs ++ (1 to 9).map(i => i.toString() -> i)

def part2(input: String): String =
// A regex that matches any of the keys of `digitReprs`
val digitReprRegex = digitReprs.keysIterator.mkString("|").r

def lineToCoordinates(line: String): Int =
val matchesIter =
for
lineTail <- line.tails
oneMatch <- digitReprRegex.findPrefixOf(lineTail)
yield
oneMatch
val matches = matchesIter.toList

// Convert the string representations into actual digits and form the result
val firstDigit = digitReprs(matches.head)
val lastDigit = digitReprs(matches.last)
s"$firstDigit$lastDigit".toInt
end lineToCoordinates

// Process lines as in part1
val result = input
.linesIterator
.map(lineToCoordinates(_))
.sum
result.toString()
end part2
47 changes: 47 additions & 0 deletions bench-sources/adventOfCode/day08.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Adapted from https://github.com/scalacenter/scala-advent-of-code
// 2025 Day 08: Playground
package aoc.day08

import scala.util.boundary
import scala.util.boundary.break

/** A junction box in 3D space with an associated circuit ID. */
case class Box(val x: Long, val y: Long, val z: Long, var circuit: Int):
def distanceSquare(other: Box): Long =
(x - other.x) * (x - other.x) + (y - other.y) * (y - other.y) + (z - other.z) * (z - other.z)

/** Parses comma-separated coordinates from the given `line` into a `Box` with
* the given `circuit` ID.
*/
def parseBox(line: String, circuit: Int): Box =
val parts = line.split(",")
Box(parts(0).toLong, parts(1).toLong, parts(2).toLong, circuit)

/** Parses the input, returning a sequence of `Box`es and all unique pairs
* of boxes sorted by distance.
*/
def load(input: String): (Seq[Box], Seq[(Box, Box)]) =
val lines = input.linesIterator.filter(_.nonEmpty)
val boxes = lines.zipWithIndex.map(parseBox).toSeq
val pairsByDistance = boxes.pairs.toSeq.sortBy((b1, b2) => b1.distanceSquare(b2))
(boxes, pairsByDistance)

extension [T](self: Seq[T])
/** Generates all unique pairs (combinations of 2) from the sequence. */
def pairs: Iterator[(T, T)] =
self.combinations(2).map(pair => (pair(0), pair(1)))

/** Sets all boxes with circuit `c2` to circuit `c1`. */
def merge(c1: Int, c2: Int, boxes: Seq[Box]): Unit =
for b <- boxes if b.circuit == c2 do b.circuit = c1

def part2(input: String): Long =
val (boxes, pairsByDistance) = load(input)
var n = boxes.length
boundary:
for (b1, b2) <- pairsByDistance if b1.circuit != b2.circuit do
merge(b1.circuit, b2.circuit, boxes)
n -= 1
if n <= 1 then
break(b1.x * b2.x)
throw Exception("Should not reach here")
52 changes: 52 additions & 0 deletions bench-sources/adventOfCode/day10.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Adapted from https://github.com/scalacenter/scala-advent-of-code
// 2023 Day 10: Pipe Maze
package aoc.day10

def parse(input: String) = input.linesIterator.toSeq

/** The tiles connected to point `p` in the `grid` */
def connected(grid: Seq[String])(p: (Int, Int)): Set[(Int, Int)] =
val (i, j) = p
grid(i)(j) match
case '|' => Set((i - 1, j), (i + 1, j))
case '-' => Set((i, j - 1), (i, j + 1))
case 'L' => Set((i - 1, j), (i, j + 1))
case 'J' => Set((i - 1, j), (i, j - 1))
case '7' => Set((i + 1, j), (i, j - 1))
case 'F' => Set((i + 1, j), (i, j + 1))
case '.' => Set()
case 'S' => Set((i + 1, j), (i - 1, j), (i, j + 1), (i, j - 1))
.filter((i, j) => grid.isDefinedAt(i) && grid(i).isDefinedAt(j))
.filter(connected(grid)(_).contains(i, j))
end connected

/** The loop starting from 'S' in the grid */
def findLoop(grid: Seq[String]): Seq[(Int, Int)] =
val start =
val startI = grid.indexWhere(_.contains('S'))
(startI, grid(startI).indexOf('S'))

/** List of connected points starting from 'S' (p0, p1) :: (p1, p2) :: (p2, p3) :: ... */
val loop = LazyList.iterate((start, connected(grid)(start).head)): (prev, curr) =>
val next = connected(grid)(curr) - prev
(curr, next.head)

start +: loop.map(_._2).takeWhile(_ != start)
end findLoop

def part2(input: String): String =
val grid = parse(input)
val inLoop = findLoop(grid).toSet

/** True iff `grid(i)(j)` is a pipe connecting to the north */
def connectesNorth(i: Int, j: Int): Boolean = connected(grid)(i, j).contains(i - 1, j)

/** Number of tiles enclosed by the loop in `grid(i)` */
def enclosedInLine(i: Int): Int = (grid(i).indices.foldLeft(false, 0):
case ((enclosed, count), j) if inLoop(i, j) => (enclosed ^ connectesNorth(i, j), count)
case ((true, count), j) => (true, count + 1)
case ((false, count), j) => (false, count)
)._2

grid.indices.map(enclosedInLine).sum.toString
end part2
51 changes: 51 additions & 0 deletions bench-sources/adventOfCode/day11.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Adapted from https://github.com/scalacenter/scala-advent-of-code
// 2023 Day 11: Cosmic Expansion
package aoc.day11

def part2(input: Seq[String]) = SweepLine(1_000_000)(input)

/** Sweep line algorithm.
*
* It follows from the following observations:
* - The distance between two points is simply a distance between the x coordinates, plus
* the difference between the y coordinates. Therefore, we can calculate them independently.
* - When we are calculating one coordinate, the other one does not matter: therefore we can
* simply treat all the other ones as the same; for example, when calculating the x coordinate
* distances, we treat all points having the same y coordinate the same.
* We just need to keep a count of points for each row and column.
* - For each point, we calculate its distance from it to all points coming before (from left->right
* or top->down). Note that for a set of points from before, the total distance from them to a certain
* point on the right increases by "no. of points" x "delta coordinate" as we move that latter point
* by the delta. So we can just keep track of "no. of points" and the total distance.
*/
class SweepLine(expandEmptyBy: Int):
def apply(board: Seq[String]) =
val b = board.map(_.toSeq)
val byRow = countByRow(b).toList
val byCol = countByColumn(b).toList
loop(0, 0, 0)(byRow) + loop(0, 0, 0)(byCol)

/** Count the number of # for each row in the input board */
def countByRow(board: Seq[Seq[Char]]) = board.map(_.count(_ == '#'))
/** Same thing, but by columns */
def countByColumn(board: Seq[Seq[Char]]) = countByRow(board.transpose)

@scala.annotation.tailrec
private def loop(
/** the number of points we saw */
points: Int,
/** The total distance from all points we saw */
totalDistance: Long,
/** The accumulated sum of distance so far */
accum: Long
)(
/** The list of count */
counts: List[Int]
): Long = counts match
case Nil => accum /* no more rows */
case 0 :: next => /* empty row, we expand it by [[expandEmptyBy]] */
loop(points, totalDistance + points.toLong * expandEmptyBy, accum)(next)
case now :: next => /* non-empty row */
val addedDistance = now * totalDistance /* from each point `totalDistance` is the sum of distance to all previous points */
val addedPoints = points + now
loop(addedPoints, totalDistance + addedPoints, accum + addedDistance)(next)
80 changes: 80 additions & 0 deletions bench-sources/adventOfCode/day12.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Adapted from https://github.com/scalacenter/scala-advent-of-code
// 2023 Day 12: Hot Springs
package aoc.day12

/** Sums `countRow` over all rows in `input`. */
def countAll(input: String): Long = input.split("\n").map(countRow).sum

/** Counts all of the different valid arrangements of operational and broken
* springs in the given row.
*/
def countRow(input: String): Long =
val Array(conditions, damagedCounts) = input.split(" ")
count2(
conditions.toList,
damagedCounts.split(",").map(_.toInt).toList
)

extension (b: Boolean) private inline def toLong: Long = if b then 1L else 0L

def count2(input: List[Char], ds: List[Int]): Long =
val dim1 = input.length + 1
val dim2 = ds.length + 1
val cache = Array.fill(dim1 * dim2)(-1L)

inline def count2Cached(input: List[Char], ds: List[Int]): Long =
val key = input.length * dim2 + ds.length
val result = cache(key)
if result == -1L then
val result = count2Uncached(input, ds)
cache(key) = result
result
else result

def count2Uncached(input: List[Char], ds: List[Int]): Long =
// We've seen all expected damaged sequences. The arrangement is therefore
// valid only if the input does not contain damaged springs.
if ds.isEmpty then input.forall(_ != '#').toLong
// The input is empty but we expected some damaged springs, so this is not a
// valid arrangement.
else if input.isEmpty then 0L
else
inline def operationalCase(): Long =
// Operational case: we can consume all operational springs to get to
// the next choice.
count2Cached(input.tail.dropWhile(_ == '.'), ds)
inline def damagedCase(): Long =
// If the length of the input is less than the expected length of the
// damaged sequence, then this is not a valid arrangement.
if input.length < ds.head then 0L
else
// Split the input into a group of length ds.head and the rest.
val (group, rest) = input.splitAt(ds.head)
// If the group contains any operational springs, then this is not a a
// group of damaged springs, so this is not a valid arrangement.
if !group.forall(_ != '.') then 0L
// If the rest of the input is empty, then this is a valid arrangement
// only if the damaged sequence is the last one expected.
else if rest.isEmpty then ds.tail.isEmpty.toLong
// If we now have a damaged spring, then this is not the end of a
// damaged sequence as expected, and therefore not a valid
// arrangement.
else if rest.head == '#' then 0L
// Otherwise, we can continue with the rest of the input and the next
// expected damaged sequence.
else count2Cached(rest.tail, ds.tail)
input.head match
case '?' => operationalCase() + damagedCase()
case '.' => operationalCase()
case '#' => damagedCase()

count2Cached(input, ds)

def countAllUnfolded(input: String): Long =
input.split("\n").map(unfoldRow).map(countRow).sum

def unfoldRow(input: String): String =
val Array(conditions, damagedCounts) = input.split(" ")
val conditionsUnfolded = (0 until 5).map(_ => conditions).mkString("?")
val damagedCountUnfolded = (0 until 5).map(_ => damagedCounts).mkString(",")
f"$conditionsUnfolded $damagedCountUnfolded"
39 changes: 39 additions & 0 deletions bench-sources/adventOfCode/day13.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Adapted from https://github.com/scalacenter/scala-advent-of-code
// 2023 Day 13: Point of Incidence
package aoc.day13

import scala.collection.mutable.Buffer

type Tile = '.' | '#'
type Line = Seq[Tile]
type Pattern = Seq[Line]

def part2(input: Seq[String]) =
parseInput(input)
.flatMap: pattern =>
findReflectionWithSmudge(pattern).map(100 * _)
.orElse(findReflectionWithSmudge(pattern.transpose))
.sum

def parseInput(input: Seq[String]): Seq[Pattern] =
val currentPattern = Buffer.empty[Line]
val patterns = Buffer.empty[Pattern]
def addPattern() =
patterns += currentPattern.toSeq
currentPattern.clear()
for lineStr <- input do
if lineStr.isEmpty then addPattern()
else
val line = lineStr.collect[Tile] { case tile: Tile => tile }
currentPattern += line
addPattern()
patterns.toSeq

def findReflectionWithSmudge(pattern: Pattern): Option[Int] =
1.until(pattern.size).find: i =>
val (leftPart, rightPart) = pattern.splitAt(i)
val smudges = leftPart.reverse
.zip(rightPart)
.map((l1, l2) => l1.zip(l2).count(_ != _))
.sum
smudges == 1
67 changes: 67 additions & 0 deletions bench-sources/adventOfCode/day15.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Adapted from https://github.com/scalacenter/scala-advent-of-code
// 2023 Day 15: Lens Library
package aoc.day15

/** The `HASH` function. */
def hash(sequence: String): Int =
sequence.foldLeft(0) { (prev, c) =>
((prev + c.toInt) * 17) % 256
}
end hash

/** Parses the input into a list of sequences. */
def inputToSequences(input: String): List[String] =
input.filter(_ != '\n').split(',').toList

/** A labeled lens, as found in the boxes. */
final case class LabeledLens(label: String, focalLength: Int)

def part2(input: String): String =
val steps = inputToSequences(input)

val boxes = Array.fill[List[LabeledLens]](256)(Nil)

// --- Processing all the steps --------------------

// Remove the lens with the given label from the box it belongs to
def removeLens(label: String): Unit =
val boxIndex = hash(label)
boxes(boxIndex) = boxes(boxIndex).filter(_.label != label)

// Add a lens in the contents of a box; replace an existing label or add to the end
def addLensToList(lens: LabeledLens, list: List[LabeledLens]): List[LabeledLens] =
list match
case Nil => lens :: Nil // add to the end
case LabeledLens(lens.label, _) :: tail => lens :: tail // replace
case head :: tail => head :: addLensToList(lens, tail) // keep looking

// Add a lens with the given label and focal length into the box it belongs to, in the right place
def addLens(label: String, focalLength: Int): Unit =
val lens = LabeledLens(label, focalLength)
val boxIndex = hash(label)
boxes(boxIndex) = addLensToList(lens, boxes(boxIndex))

// Parse and execute the steps
for step <- steps do
step match
case s"$label-" => removeLens(label)
case s"$label=$focalLength" => addLens(label, focalLength.toInt)

// --- Computing the focusing power --------------------

// Focusing power of a lens in a given box and at a certain position within that box
def focusingPower(boxIndex: Int, lensIndex: Int, lens: LabeledLens): Int =
(boxIndex + 1) * (lensIndex + 1) * lens.focalLength

// Focusing power of all the lenses
val focusingPowers =
for
(box, boxIndex) <- boxes.zipWithIndex
(lens, lensIndex) <- box.zipWithIndex
yield
focusingPower(boxIndex, lensIndex, lens)

// Sum it up
val result = focusingPowers.sum
result.toString()
end part2
Loading