DAy 12 is now finished, was actually pretty hard

This commit is contained in:
2019-12-15 00:38:09 +01:00
parent dc1149971f
commit a5dfc7cbd2
4 changed files with 269 additions and 12 deletions

View File

@@ -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>

View 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()
}
}

View File

@@ -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()
}

View 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>