Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
FlorianCassayre committed Nov 12, 2024
0 parents commit 377068b
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 0 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/scala.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Scala CI

on: [push]

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v2
with:
distribution: zulu
java-version: 8
- name: Run tests
run: sbt test
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.iml
.idea
.bsp
target/
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 Florian Cassayre

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
89 changes: 89 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
_My solutions to the 2024 edition of [Advent of Code](https://adventofcode.com/2024)._

## Previous participations

* [2023](https://github.com/FlorianCassayre/AdventOfCode-2023)
* [2022](https://github.com/FlorianCassayre/AdventOfCode-2022)
* [2021](https://github.com/FlorianCassayre/AdventOfCode-2021)
* [2020](https://github.com/FlorianCassayre/AdventOfCode-2020)
* [2019](https://github.com/FlorianCassayre/AdventOfCode-2019)
* [2018](https://github.com/FlorianCassayre/AdventOfCode-2018)
* [2017](https://github.com/FlorianCassayre/AdventOfCode-2017)

## Problem statements & solutions

<div align="center">

| Day | Code |
|:---:|:---:|
| **[01](https://adventofcode.com/2024/day/1)** | [](src/main/scala/adventofcode/solutions/Day01.scala) |
| **[02](https://adventofcode.com/2024/day/2)** | [](src/main/scala/adventofcode/solutions/Day02.scala) |
| **[03](https://adventofcode.com/2024/day/3)** | [](src/main/scala/adventofcode/solutions/Day03.scala) |
| **[04](https://adventofcode.com/2024/day/4)** | [](src/main/scala/adventofcode/solutions/Day04.scala) |
| **[05](https://adventofcode.com/2024/day/5)** | [](src/main/scala/adventofcode/solutions/Day05.scala) |
| **[06](https://adventofcode.com/2024/day/6)** | [](src/main/scala/adventofcode/solutions/Day06.scala) |
| **[07](https://adventofcode.com/2024/day/7)** | [](src/main/scala/adventofcode/solutions/Day07.scala) |
| **[08](https://adventofcode.com/2024/day/8)** | [](src/main/scala/adventofcode/solutions/Day08.scala) |
| **[09](https://adventofcode.com/2024/day/9)** | [](src/main/scala/adventofcode/solutions/Day09.scala) |
| **[10](https://adventofcode.com/2024/day/10)** | [](src/main/scala/adventofcode/solutions/Day10.scala) |
| **[11](https://adventofcode.com/2024/day/11)** | [](src/main/scala/adventofcode/solutions/Day11.scala) |
| **[12](https://adventofcode.com/2024/day/12)** | [](src/main/scala/adventofcode/solutions/Day12.scala) |
| **[13](https://adventofcode.com/2024/day/13)** | [](src/main/scala/adventofcode/solutions/Day13.scala) |
| **[14](https://adventofcode.com/2024/day/14)** | [](src/main/scala/adventofcode/solutions/Day14.scala) |
| **[15](https://adventofcode.com/2024/day/15)** | [](src/main/scala/adventofcode/solutions/Day15.scala) |
| **[16](https://adventofcode.com/2024/day/16)** | [](src/main/scala/adventofcode/solutions/Day16.scala) |
| **[17](https://adventofcode.com/2024/day/17)** | [](src/main/scala/adventofcode/solutions/Day17.scala) |
| **[18](https://adventofcode.com/2024/day/18)** | [](src/main/scala/adventofcode/solutions/Day18.scala) |
| **[19](https://adventofcode.com/2024/day/19)** | [](src/main/scala/adventofcode/solutions/Day19.scala) |
| **[20](https://adventofcode.com/2024/day/20)** | [](src/main/scala/adventofcode/solutions/Day20.scala) |
| **[21](https://adventofcode.com/2024/day/21)** | [](src/main/scala/adventofcode/solutions/Day21.scala) |
| **[22](https://adventofcode.com/2024/day/22)** | [](src/main/scala/adventofcode/solutions/Day22.scala) |
| **[23](https://adventofcode.com/2024/day/23)** | [](src/main/scala/adventofcode/solutions/Day23.scala) |
| **[24](https://adventofcode.com/2024/day/24)** | [](src/main/scala/adventofcode/solutions/Day24.scala) |
| **[25](https://adventofcode.com/2024/day/25)** | [](src/main/scala/adventofcode/solutions/Day25.scala) |

</div>

In order to make the challenge more interesting, I set myself the following rules:

* **Pure**: no usage of `var` or mutable datastructures
* **Self-contained**: no third-party libraries, one file per day (*)
* **Efficient**: optimal asymptotic complexity, as far as reasonable
* **Concise**: readability is key

Note that these rules do not necessarily apply while _solving_ a problem, but rather when _committing_ the code to this repository.

(*): this rule could be subject to modification, for instance if the puzzles implicitly require it ([Intcode](https://adventofcode.com/2019/day/9) in 2019).

## Usage

This project runs on [Scala](https://scala-lang.org) `3.5.2` and sbt `1.10.5`.

Use the following template to write a solution for a given day:

```Scala
package adventofcode.solutions

import adventofcode.Definitions.*

@main def Day01 = Day(1) { (input, part) =>

part(1) = ???

part(2) = ???

}
```
(change `1` to the current day number and fill in the `???`)

Paste your input as a file named `01.txt` in `input/`.

To run the code, enter `sbt run Day01`.

The output(s) will be printed to the console and stored in `output/` as `01-1.txt` and `01-2.txt`.

Additionally, the command `sbt test` will run all the implemented solutions and compare their result against the currently stored output, to detect any potential regression.

## License

MIT
9 changes: 9 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name := "AdventOfCode-2024"

version := "0.1"

scalaVersion := "3.5.2"

libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.17" % "test"

scalacOptions ++= Seq("-deprecation")
1 change: 1 addition & 0 deletions project/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sbt.version = 1.10.5
62 changes: 62 additions & 0 deletions src/main/scala/adventofcode/Definitions.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package adventofcode

import java.io.{File, PrintWriter}
import scala.compiletime.ops.int.*
import scala.compiletime.constValue
import scala.io.Source
import scala.util.{Failure, Success, Try}

object Definitions:
private[Definitions] var testMode = false

type PartNumber = 1 | 2
type DayNumber = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25

opaque type Input <: String = String
type Output = Any

private inline def dayValue[N <: DayNumber]: N = constValue[N]

trait Solution[N <: DayNumber]:
def apply(input: Input, part: Part[N]): Unit

val lineSeparator = "\n"
extension (input: Input) def toLines: IndexedSeq[String] = input.split("\r?\n").toIndexedSeq

final class Part[N <: DayNumber](day: N):
import scala.collection.mutable
private[Definitions] var last: Int = 0
def update(part: PartNumber, v: => Output): Unit =
require(part == last + 1)
last = part
Try(v).map(String.valueOf) match
case Success(output) =>
println(output)
if !testMode then
writeOutput(output, day, part)
else
val expected = Source.fromFile(pathForOutput(day, part)).mkString
assert(output == expected, s"Day $day part $part: output '$output' didn't match '$expected'")
case Failure(e: NotImplementedError) =>
println(s"(ignored part $part)")
case Failure(e) => throw new Exception(e)

inline def Day[N <: DayNumber](day: N)(implementation: Solution[N]): Unit = implementation(readInput(day), new Part(constValue[N]))

private def formatDay(day: DayNumber): String = f"$day%02d"

private val (inputDirectory, outputDirectory) = ("input", "output")
private def pathForInput(day: DayNumber): String =
s"$inputDirectory/${formatDay(day)}.txt"
private def pathForOutput(day: DayNumber, part: PartNumber): String =
s"$outputDirectory/${formatDay(day)}-$part.txt"

private def readInput(day: DayNumber): Input =
Source.fromFile(pathForInput(day)).mkString

private def writeOutput(output: String, day: DayNumber, part: PartNumber): Unit =
val path = pathForOutput(day, part)
new File(path).getParentFile.mkdirs()
new PrintWriter(path):
write(output)
close()
36 changes: 36 additions & 0 deletions src/test/scala/adventofcode/Reflect.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package adventofcode

object Reflect:
import scala.quoted.*
import scala.compiletime._

inline def generateAOCTests: Any = ${ generateAOCTestsImpl }

private def generateAOCTestsImpl(using Quotes): Expr[Any] =
import quotes.reflect.*
val ignored = Set[Int]()
val values: Seq[(String, (String, Term))] = (1 to 25).map { day =>
val dayPadded = f"$day%02d"
val method = Symbol.requiredMethod(s"adventofcode.solutions.Day$dayPadded")
val testData = method.annotations.nonEmpty match { // Cheap trick to determine if the method is real or mocked
case true if !ignored.contains(day) => ("test", Ref(method))
case _ => ("ignore", '{ () }.asTerm)
}
(s"day $day", testData)
}
val testCasesTerm = values.foldLeft('{ () }.asTerm) { case (acc, (testName, (methodName, bodyTerm))) =>
val argumentsAsTerms = List()
val repeatedAnyTypeTree = Applied(TypeIdent(defn.RepeatedParamClass), List(TypeTree.of[org.scalatest.Tag]))
val varargsTerm = Typed(Inlined(None, Nil, Repeated(argumentsAsTerms, TypeTree.of[Any])), repeatedAnyTypeTree)
val test = Select.unique(resolveThis, methodName)
val testTerm = Apply(Apply(test, List(Literal(StringConstant(testName)), varargsTerm)), List(bodyTerm))
Block(List(acc), testTerm)
}
testCasesTerm.asExpr

private def resolveThis(using Quotes): quotes.reflect.Term =
import quotes.reflect.*
var sym = Symbol.spliceOwner
while sym != null && !sym.isClassDef do
sym = sym.owner
This(sym)
20 changes: 20 additions & 0 deletions src/test/scala/adventofcode/SolutionsTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package adventofcode

import adventofcode.Definitions.*
import adventofcode.Reflect.generateAOCTests
import org.scalatest.funsuite.AnyFunSuite

import scala.jdk.CollectionConverters.*
import java.io.File
import java.net.URL
import scala.io.Source

class SolutionsTest extends AnyFunSuite {

val testModeField = Definitions.getClass.getDeclaredField("testMode")
testModeField.setAccessible(true)
testModeField.set(Definitions, true)

generateAOCTests

}

0 comments on commit 377068b

Please sign in to comment.