DAy 12 is now finished, was actually pretty hard
This commit is contained in:
8
pom.xml
8
pom.xml
@@ -90,6 +90,14 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<target>8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
198
src/main/kotlin/com/basdado/adventofcode/PrimeSieve.kt
Normal file
198
src/main/kotlin/com/basdado/adventofcode/PrimeSieve.kt
Normal file
@@ -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<Int?> {
|
||||
|
||||
private var sieve: BoolArray
|
||||
|
||||
override fun iterator(): MutableIterator<Int> {
|
||||
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<Int> {
|
||||
|
||||
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<in Int>) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -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<Long, Long> {
|
||||
|
||||
var rem = n
|
||||
val res = mutableMapOf<Long, Long>()
|
||||
|
||||
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<Vector3l>, velocities: Array<Vector3l>): Long {
|
||||
return positions.indices.map { i -> positions[i].manhattan() * velocities[i].manhattan() }.sum()
|
||||
}
|
||||
|
||||
4
src/main/resources/day/12/ex2.txt
Normal file
4
src/main/resources/day/12/ex2.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
<x=-8, y=-10, z=0>
|
||||
<x=5, y=5, z=10>
|
||||
<x=2, y=-7, z=3>
|
||||
<x=9, y=-8, z=-3>
|
||||
Reference in New Issue
Block a user