From a5dfc7cbd23d919c7acaadc56cf04607f053f5a5 Mon Sep 17 00:00:00 2001 From: Bas Dado Date: Sun, 15 Dec 2019 00:38:09 +0100 Subject: [PATCH] DAy 12 is now finished, was actually pretty hard --- pom.xml | 8 + .../com/basdado/adventofcode/PrimeSieve.kt | 198 ++++++++++++++++++ .../com/basdado/adventofcode/day12/Day12.kt | 71 +++++-- src/main/resources/day/12/ex2.txt | 4 + 4 files changed, 269 insertions(+), 12 deletions(-) create mode 100644 src/main/kotlin/com/basdado/adventofcode/PrimeSieve.kt create mode 100644 src/main/resources/day/12/ex2.txt 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