diff --git a/pom.xml b/pom.xml
index f69c17b..f03aaf9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -90,6 +90,14 @@
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 8
+ 8
+
+
diff --git a/src/main/kotlin/com/basdado/adventofcode/PrimeSieve.kt b/src/main/kotlin/com/basdado/adventofcode/PrimeSieve.kt
new file mode 100644
index 0000000..7d0d57a
--- /dev/null
+++ b/src/main/kotlin/com/basdado/adventofcode/PrimeSieve.kt
@@ -0,0 +1,198 @@
+package com.basdado.adventofcode
+
+import java.util.function.Consumer
+import kotlin.math.ln
+import kotlin.math.sqrt
+
+/**
+ * Prime number generator based on the sieve of Eratosthenes.
+ *
+ * @author Bas Dado
+ */
+class PrimeSieve(private var until: Int) : Iterable {
+
+ private var sieve: BoolArray
+
+ override fun iterator(): MutableIterator {
+ return PrimeSieveIterator(sieve)
+ }
+
+ /**
+ * @param n The number to check
+ * @return True iff n is even
+ */
+ private fun isEven(n: Int): Boolean {
+ return n % 2 == 0
+ }
+
+ /**
+ * @param n The number to check
+ * @return Checks if the number is in the sieve, since we skip even numbers and numbers smaller than 3.
+ */
+ fun hasIndex(n: Int): Boolean {
+ return !isEven(n) && n >= 3
+ }
+
+ /**
+ * @return True iff n is prime
+ */
+ fun isPrime(n: Int): Boolean {
+ return if (n == 2) true else if (!hasIndex(n)) false else if (n <= until) sieve[getIndex(n)] else if (n <= until * until) {
+ for (prime in this) if (n % prime == 0) return false
+ true
+ } else {
+ throw IndexOutOfBoundsException("With the current sieve size it's impossible to determine whether n is prime")
+ }
+ }
+
+ /**
+ * @param n The number to check
+ * @return The index of n in the sieve. Gives a lower bound index if n is not in the sieve
+ */
+ private fun getIndex(n: Int): Int {
+ return (n - 3) / 2
+ }
+
+ /**
+ * @param i
+ * @return The number at index i in the Sieve
+ */
+ private fun getNumberAtIndex(i: Int): Int {
+ return i * 2 + 3
+ }
+
+ /**
+ * @param until The number to check against
+ * @return Expected number of primes below "until", based on the "Prime Number Theorem",
+ */
+ fun getExpectedPrimeCount(until: Double): Int {
+ return (until / (ln(until) - 1.0)).toInt()
+ }
+
+ /**
+ * Marks of multiples of n in the sieve, starting at the square of n
+ * @param sieve The sieve in which the numbers should be marked of
+ * @param n
+ * @param i
+ */
+ private fun markMultiples(sieve: BoolArray, n: Int, i: Int) {
+ var cur = i + (i + 1) * n // Start at the square of the number.
+ while (cur < sieve.size()) { // Since we're multiplying two odd numbers, the result is always odd, and thus in our sieve.
+ sieve[cur] = false
+ cur += n
+ }
+ }
+
+ /**
+ * Builds a prime sieve up to the current "until" value.
+ * @return
+ */
+ private fun generateSieve(): BoolArray { // Initialize the sieve assuming all numbers are prime
+ val sieve = BoolArray(getIndex(until) + 1, true)
+ val sqrtUntil = (sqrt(until.toDouble()) + 1).toInt()
+ val iSqrtUntil = getIndex(sqrtUntil)
+ for (i in 0..iSqrtUntil) { // If the current number is prime, mark all multiples of it as being not prime:
+ if (sieve[i]) { // n is the current number that is considered
+ val n = getNumberAtIndex(i)
+ //MarkMultiples(sieve, n, until);
+ markMultiples(sieve, n, i)
+ }
+ }
+ return sieve
+ }
+
+ inner class PrimeSieveIterator(private val sieve: BoolArray) : MutableIterator {
+
+ private var i: Int = -1
+ private val lastPrimeIndex: Int
+
+ private fun getLastPrimeIndex(): Int {
+ for (i in sieve.size() - 1 downTo 1) {
+ if (sieve[i]) return i
+ }
+ return 0
+ }
+
+ override fun hasNext(): Boolean {
+ return i < lastPrimeIndex
+ }
+
+ override fun next(): Int {
+ if (i == -1) {
+ i++
+ return 2
+ }
+ while (!sieve[i]) i++
+ return getNumberAtIndex(i++)
+ }
+
+ override fun remove() {
+ throw UnsupportedOperationException()
+ }
+
+ override fun forEachRemaining(action: Consumer) {
+ while (hasNext()) {
+ action.accept(next())
+ }
+ }
+
+ init {
+ lastPrimeIndex = getLastPrimeIndex()
+ }
+ }
+
+ class BoolArray(size: Int, initial: Boolean) {
+
+ private val data: IntArray
+ private val size: Int
+
+ private fun getWordIndex(i: Int): Int {
+ return i shr 5
+ }
+
+ private fun getBitIndex(i: Int): Int {
+ return i and 0x1F
+ }
+
+ /**
+ * @param i
+ * @return The bit mask that masks the bit corresponding to index i
+ */
+ private fun getWordMask(i: Int): Int {
+ return 1 shl getBitIndex(i)
+ }
+
+ /**
+ * @param i The index of the wanted boolean
+ * @return The value of the boolean stored at index i
+ */
+ operator fun get(i: Int): Boolean {
+ return data[getWordIndex(i)] and getWordMask(i) != 0
+ }
+
+ /**
+ * Sets the boolean at index i to the value given in value.
+ * @param i
+ * @param value
+ */
+ operator fun set(i: Int, value: Boolean) {
+ if (value) data[getWordIndex(i)] = data[getWordIndex(i)] or getWordMask(i) else data[getWordIndex(i)] =
+ data[getWordIndex(i)] and getWordMask(i).inv()
+ }
+
+ fun size(): Int {
+ return size
+ }
+
+ init {
+ data = IntArray(getWordIndex(size - 1) + 1)
+ // Default int value is 0, so we only set it if the initial value is true
+ if (initial) for (i in data.indices) data[i] = 0.inv()
+ this.size = size
+ }
+ }
+
+ init {
+ sieve = generateSieve()
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/com/basdado/adventofcode/day12/Day12.kt b/src/main/kotlin/com/basdado/adventofcode/day12/Day12.kt
index 5776c57..194edc1 100644
--- a/src/main/kotlin/com/basdado/adventofcode/day12/Day12.kt
+++ b/src/main/kotlin/com/basdado/adventofcode/day12/Day12.kt
@@ -1,10 +1,11 @@
package com.basdado.adventofcode.day12
+import com.basdado.adventofcode.PrimeSieve
import com.basdado.adventofcode.lines
-import java.lang.Long.compare
import java.util.stream.Collectors
import java.util.stream.LongStream
import kotlin.math.abs
+import kotlin.math.sqrt
const val DAY12_INPUT_PATH = "/day/12/input.txt"
@@ -15,12 +16,14 @@ fun main() {
object Day12 {
+ val primes = PrimeSieve(1000000)
+
fun puzzle1() {
val positions = parseInput()
val velocities = Array(positions.size) { Vector3l.ZERO }
repeat(1000) {
- println("$it: ${positions.toList()}")
+// println("$it: pos: ${positions.toList()}, vel: ${velocities.toList()}")
// println("$it: energy: ${energy(positions, velocities)}")
applyGravity(positions, velocities)
applyVelocity(positions, velocities)
@@ -37,24 +40,68 @@ object Day12 {
val initialPositions = positions.clone()
val initialVelocities = velocities.clone()
- val initialRelativePositions = relative(positions)
- println(LongStream.iterate(0) { it + 1 }
- .peek {
- applyGravity(positions, velocities)
- applyVelocity(positions, velocities)
- if (it % 100000L == 0L) println("$it: pos: ${positions.toList()}, vel: ${velocities.toList()}")
- }
- .dropWhile { !(positions.contentEquals(initialRelativePositions) && velocities.contentEquals(initialVelocities)) }
- .findFirst()!!
- )
+ var periodPerAxis = Vector3l(0L, 0L, 0L)
// Oh wait, X, Y and Z are completely independent, so we could just simulate the universe per component, and
// then find the lowest number that's divisible by all three numbers
+ LongStream.iterate(1) { it + 1 }
+ .peek { t ->
+ applyGravity(positions, velocities)
+ applyVelocity(positions, velocities)
+
+ var xEqual = true
+ var yEqual = true
+ var zEqual = true
+ for (i in positions.indices) {
+ xEqual = xEqual && positions[i].x == initialPositions[i].x && velocities[i].x == initialVelocities[i].x
+ yEqual = yEqual && positions[i].y == initialPositions[i].y && velocities[i].y == initialVelocities[i].y
+ zEqual = zEqual && positions[i].z == initialPositions[i].z && velocities[i].z == initialVelocities[i].z
+ }
+ if (xEqual && periodPerAxis.x == 0L) periodPerAxis = periodPerAxis.copy(x = t)
+ if (yEqual && periodPerAxis.y == 0L) periodPerAxis = periodPerAxis.copy(y = t)
+ if (zEqual && periodPerAxis.z == 0L) periodPerAxis = periodPerAxis.copy(z = t)
+// if (t % 100000L == 0L) println("$t: $periodPerAxis, pos: ${positions.toList()}, vel: ${velocities.toList()}")
+ }
+ .dropWhile { periodPerAxis.x == 0L || periodPerAxis.y == 0L || periodPerAxis.z == 0L }
+ .findFirst()
+ println(periodPerAxis)
+
+ // Now all we need to do is find the LCM (Lowest Common Multiple) of the periods per axis:
+ println(lcm(longArrayOf(periodPerAxis.x, periodPerAxis.y, periodPerAxis.z)))
}
+ private fun lcm(numbers: LongArray, tryUntilMultiplier: Long = 1024): Long {
+
+ val factorizations = numbers.map { factors(it) }
+ return factorizations
+ .flatMap { it.keys }.distinct()
+ .map { p -> Pair(p, factorizations.map { it[p] ?: 0 }.max()!!) }
+ .map { p -> Math.pow(p.first.toDouble(), p.second.toDouble()).toLong() }
+ .reduce { x, y -> x * y }
+ }
+
+ private fun factors(n: Long): Map {
+
+ var rem = n
+ val res = mutableMapOf()
+
+ for (prime in primes) {
+ var primeCount = 0L
+ while (rem % prime.toLong() == 0L) {
+ rem /= prime
+ primeCount++
+ }
+ if (primeCount > 0L) {
+ res[prime.toLong()] = primeCount
+ }
+ }
+
+ return res
+ }
+
private fun energy(positions: Array, velocities: Array): Long {
return positions.indices.map { i -> positions[i].manhattan() * velocities[i].manhattan() }.sum()
}
diff --git a/src/main/resources/day/12/ex2.txt b/src/main/resources/day/12/ex2.txt
new file mode 100644
index 0000000..c0edb40
--- /dev/null
+++ b/src/main/resources/day/12/ex2.txt
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file