Skip to content

Commit 4703326

Browse files
committed
2024-11 Rust & Scala 2
1 parent bca501b commit 4703326

File tree

14 files changed

+288
-101
lines changed

14 files changed

+288
-101
lines changed

rust/Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust/y2024/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ advent-of-code-common = { path = "../common" }
1414
chumsky.workspace = true
1515
itertools.workspace = true
1616
regex.workspace = true
17+
memoize.workspace = true
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use memoize::memoize;
2+
3+
type N = u64;
4+
5+
const DATA: [N; 8] = [6_563_348, 67, 395, 0, 6, 4425, 89567, 739_318];
6+
7+
type R = usize;
8+
9+
#[expect(
10+
clippy::cast_possible_truncation,
11+
clippy::cast_sign_loss,
12+
clippy::cast_precision_loss
13+
)]
14+
fn halves(n: N) -> Option<(N, N)> {
15+
let digits = (n as f64).log10() as usize + 1;
16+
(digits % 2 == 0).then_some({
17+
let half_length = digits / 2;
18+
let divisor = 10u64.pow(half_length as u32);
19+
let left = n / divisor;
20+
let right = n % divisor;
21+
(left, right)
22+
})
23+
}
24+
25+
#[memoize]
26+
#[allow(clippy::collapsible_else_if)]
27+
fn solve_one(n: N, blinks: usize) -> R {
28+
if blinks == 0 {
29+
1
30+
} else {
31+
if n == 0 {
32+
solve_one(1, blinks - 1)
33+
} else {
34+
match halves(n) {
35+
Some((left, right)) => solve_one(left, blinks - 1) + solve_one(right, blinks - 1),
36+
None => solve_one(n * 2024, blinks - 1),
37+
}
38+
}
39+
}
40+
}
41+
42+
fn solve(data: &[N], blinks: usize) -> R {
43+
data.iter().map(|n| solve_one(*n, blinks)).sum()
44+
}
45+
46+
fn main() {
47+
let result_1 = solve(&DATA, 25);
48+
println!("Part 1: {result_1}");
49+
50+
let result_2 = solve(&DATA, 75);
51+
println!("Part 2: {result_2}");
52+
}
53+
54+
#[cfg(test)]
55+
mod tests {
56+
use super::*;
57+
58+
#[test]
59+
fn test_solve_1_real() {
60+
assert_eq!(solve(&DATA, 25), 184_927);
61+
}
62+
63+
#[test]
64+
fn test_solve_2_real() {
65+
assert_eq!(solve(&DATA, 75), 220_357_186_726_677);
66+
}
67+
}

scala2/src/main/resources/2024/11-test-00.txt

Whitespace-only changes.

scala2/src/main/resources/2024/11.txt

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
noop

scala2/src/main/resources/2024/12.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
noop

scala2/src/main/scala/jurisk/adventofcode/y2024/Advent11.scala

+53-30
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,70 @@
11
package jurisk.adventofcode.y2024
22

3-
import jurisk.utils.FileInput._
4-
import jurisk.utils.Parsing.StringOps
3+
import jurisk.math.pow
4+
import jurisk.utils.Memoize
5+
import mouse.all.booleanSyntaxMouse
6+
7+
import scala.math.log10
58

69
object Advent11 {
7-
type Input = List[Command]
10+
type Input = Vector[Long]
811
type N = Long
912

10-
sealed trait Command extends Product with Serializable
11-
object Command {
12-
case object Noop extends Command
13-
final case class Something(
14-
values: List[N]
15-
) extends Command
16-
final case class Other(value: String) extends Command
17-
18-
def parse(s: String): Command =
19-
s match {
20-
case "noop" => Noop
21-
case s"something $rem" => Something(rem.extractLongList)
22-
case s if s.nonEmpty => Other(s)
23-
case _ => s.failedToParse
24-
}
13+
private def halves(n: Long): Option[(Long, Long)] = {
14+
val digits = log10(n.doubleValue).toInt + 1
15+
16+
(digits % 2 == 0).option {
17+
val halfLength = digits / 2
18+
val divisor = pow(10, halfLength)
19+
val left = n / divisor
20+
val right = n % divisor
21+
(left, right)
22+
}
2523
}
2624

27-
def parse(input: String): Input =
28-
input.parseLines(Command.parse)
25+
def blink(data: Input): Input =
26+
data.flatMap { n =>
27+
if (n == 0) {
28+
Vector(1)
29+
} else {
30+
halves(n) match {
31+
case Some((left, right)) =>
32+
Vector(left, right)
33+
case None =>
34+
Vector(n * 2024)
35+
}
36+
}
37+
}
2938

30-
def part1(data: Input): N =
31-
0
39+
private val memoizedSolve: (Long, Int) => N = Memoize.memoize2(solve)
40+
private def solve(n: Long, blinks: Int): Long =
41+
if (blinks <= 0) {
42+
1
43+
} else {
44+
if (n == 0) {
45+
memoizedSolve(1, blinks - 1)
46+
} else {
47+
halves(n) match {
48+
case Some((left, right)) =>
49+
memoizedSolve(left, blinks - 1) + memoizedSolve(right, blinks - 1)
50+
case None =>
51+
memoizedSolve(n * 2024, blinks - 1)
52+
}
53+
}
54+
}
3255

33-
def part2(data: Input): N =
34-
0
56+
def blinkNTimes(data: Input, blinks: Int): Input =
57+
(0 until blinks).foldLeft(data)((acc, _) => blink(acc))
3558

36-
def parseFile(fileName: String): Input =
37-
parse(readFileText(fileName))
59+
def part1(data: Input, blinks: Int = 25): N =
60+
blinkNTimes(data, blinks).size
3861

39-
def fileName(suffix: String): String =
40-
s"2024/11$suffix.txt"
62+
def part2(data: Input, blinks: Int = 75): N =
63+
data.map(n => memoizedSolve(n, blinks)).sum
4164

42-
def main(args: Array[String]): Unit = {
43-
val realData: Input = parseFile(fileName(""))
65+
val realData: Input = Vector(6563348, 67, 395, 0, 6, 4425, 89567, 739318)
4466

67+
def main(args: Array[String]): Unit = {
4568
println(s"Part 1: ${part1(realData)}")
4669
println(s"Part 2: ${part2(realData)}")
4770
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package jurisk.adventofcode.y2024
2+
3+
import jurisk.utils.FileInput._
4+
import jurisk.utils.Parsing.StringOps
5+
6+
object Advent12 {
7+
type Input = List[Command]
8+
type N = Long
9+
10+
sealed trait Command extends Product with Serializable
11+
object Command {
12+
case object Noop extends Command
13+
final case class Something(
14+
values: List[N]
15+
) extends Command
16+
final case class Other(value: String) extends Command
17+
18+
def parse(s: String): Command =
19+
s match {
20+
case "noop" => Noop
21+
case s"something $rem" => Something(rem.extractLongList)
22+
case s if s.nonEmpty => Other(s)
23+
case _ => s.failedToParse
24+
}
25+
}
26+
27+
def parse(input: String): Input =
28+
input.parseLines(Command.parse)
29+
30+
def part1(data: Input): N =
31+
0
32+
33+
def part2(data: Input): N =
34+
0
35+
36+
def parseFile(fileName: String): Input =
37+
parse(readFileText(fileName))
38+
39+
def fileName(suffix: String): String =
40+
s"2024/12$suffix.txt"
41+
42+
def main(args: Array[String]): Unit = {
43+
val realData: Input = parseFile(fileName(""))
44+
45+
println(s"Part 1: ${part1(realData)}")
46+
println(s"Part 2: ${part2(realData)}")
47+
}
48+
}

scala2/src/test/scala/jurisk/adventofcode/y2024/Advent11Spec.scala

+11-10
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,27 @@ import org.scalatest.freespec.AnyFreeSpec
55
import org.scalatest.matchers.should.Matchers._
66

77
class Advent11Spec extends AnyFreeSpec {
8-
private def testData = parseFile(fileName("-test-00"))
9-
private def realData = parseFile(fileName(""))
10-
118
"part 1" - {
129
"test" in {
13-
part1(testData) shouldEqual 0
10+
blink(Vector(0, 1, 10, 99, 999)) shouldEqual Vector(1, 2024, 1, 0, 9, 9,
11+
2021976)
12+
blink(Vector(125, 17)) shouldEqual Vector(253000, 1, 7)
13+
blinkNTimes(Vector(125, 17), 2) shouldEqual Vector(253, 0, 2024, 14168)
14+
part1(Vector(125, 17), 6) shouldEqual 22
15+
part1(Vector(125, 17)) shouldEqual 55312
1416
}
1517

1618
"real" in {
17-
part1(realData) shouldEqual 0
19+
part1(realData) shouldEqual 184927
1820
}
1921
}
2022

2123
"part 2" - {
22-
"test" in {
23-
part2(testData) shouldEqual 0
24+
"real 25" in {
25+
part2(realData, 25) shouldEqual 184927
2426
}
25-
26-
"real" in {
27-
part2(realData) shouldEqual 0
27+
"real 75" in {
28+
part2(realData) shouldEqual 220357186726677L
2829
}
2930
}
3031
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package jurisk.adventofcode.y2024
2+
3+
import Advent12._
4+
import org.scalatest.freespec.AnyFreeSpec
5+
import org.scalatest.matchers.should.Matchers._
6+
7+
class Advent12Spec extends AnyFreeSpec {
8+
private def testData = parseFile(fileName("-test-00"))
9+
private def realData = parseFile(fileName(""))
10+
11+
"part 1" - {
12+
"test" in {
13+
part1(testData) shouldEqual 0
14+
}
15+
16+
"real" in {
17+
part1(realData) shouldEqual 0
18+
}
19+
}
20+
21+
"part 2" - {
22+
"test" in {
23+
part2(testData) shouldEqual 0
24+
}
25+
26+
"real" in {
27+
part2(realData) shouldEqual 0
28+
}
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,36 @@
11
package jurisk.adventofcode.y2020
22

3-
import scala.io.Source
4-
5-
object Advent02 extends App:
6-
final case class Item(
7-
a: Int,
8-
b: Int,
9-
letter: Char,
10-
password: String,
11-
):
12-
def isValid1: Boolean =
13-
val count = password.count(_ == letter)
14-
(count >= a) && (count <= b)
15-
16-
def isValid2: Boolean =
17-
List(password(a - 1), password(b - 1)).count(_ == letter) == 1
18-
19-
def parse(x: String): Item =
3+
import jurisk.adventofcode.AdventApp.ErrorMessage
4+
import jurisk.adventofcode.SingleLineAdventApp
5+
import cats.implicits._
6+
7+
final case class Item(
8+
a: Int,
9+
b: Int,
10+
letter: Char,
11+
password: String,
12+
):
13+
def isValid1: Boolean =
14+
val count = password.count(_ == letter)
15+
(count >= a) && (count <= b)
16+
17+
def isValid2: Boolean =
18+
List(password(a - 1), password(b - 1)).count(_ == letter) == 1
19+
20+
object Advent02 extends SingleLineAdventApp[Item, Int]:
21+
val year: Int = 2020
22+
val exercise: Int = 2
23+
24+
override def parseLine(line: String): Either[ErrorMessage, Item] = {
2025
val Pattern = """(\d+)-(\d+) (\w): (\w+)""".r
21-
x match
26+
27+
line match
2228
case Pattern(a, b, letter, password) if letter.length == 1 =>
23-
Item(a.toInt, b.toInt, letter.head, password)
29+
Item(a.toInt, b.toInt, letter.head, password).asRight
2430
case _ =>
25-
sys.error(s"Couldn't parse $x")
26-
27-
val list = Source.fromResource("2020/02.txt").getLines().map(parse).toList
28-
29-
def solve(f: Item => Boolean): Unit =
30-
println(list.count(f))
31-
32-
List[Item => Boolean](_.isValid1, _.isValid2) foreach solve
31+
ErrorMessage(s"Couldn't parse $line").asLeft
32+
}
33+
34+
override def solution1(input: List[Item]): Int = input.count(_.isValid1)
35+
36+
override def solution2(input: List[Item]): Int = input.count(_.isValid2)

0 commit comments

Comments
 (0)