DEV Community

Discussion on: Advent of Code 2019 Solution Megathread - Day 22: Slam Shuffle

Collapse
 
jbristow profile image
Jon Bristow • Edited

Kotlin solution for part 1.

Fairly straightforward, but completely useless for part 2.

import arrow.core.getOrElse
import arrow.core.toOption
import java.nio.file.Files
import java.nio.file.Paths

sealed class ShuffleMethods : (List<Int>) -> List<Int> {
    object DealIntoStack : ShuffleMethods() {
        override fun invoke(p1: List<Int>) = p1.reversed()
    }

    class Cut(val n: Int) : ShuffleMethods() {
        override fun invoke(p1: List<Int>): List<Int> {
            return if (n >= 0) {
                p1.drop(n) + p1.take(n)
            } else {
                p1.takeLast(-1 * n) + p1.dropLast(-1 * n)
            }
        }
    }

    class DealIncrement(val n: Int) : ShuffleMethods() {
        override fun invoke(p1: List<Int>): List<Int> {
            return p1.withIndex().sortedBy { (it.index * n) % p1.size }.map { it.value }
        }
    }

}


val regexIncrement = """deal with increment (\d+)""".toRegex()
const val dealIntoStack = "deal into new stack"
val regexCut = """cut (-?\d+)""".toRegex()


fun String.toShuffleMethod(): ShuffleMethods {
    if (this == dealIntoStack) {
        return ShuffleMethods.DealIntoStack
    }
    return when (val incrementMatch = regexIncrement.matchEntire(this)) {
        null -> {
            val cutMatch = regexCut.matchEntire(this).toOption()
            ShuffleMethods.Cut(cutMatch.getOrElse { throw error("unknown style: $this") }.groupValues[1].toInt())
        }
        else -> ShuffleMethods.DealIncrement(incrementMatch.groupValues[1].toInt())
    }
}


object Day22 {
    private const val FILENAME = "src/main/resources/day22.txt"
    val fileData get() = Files.readAllLines(Paths.get(FILENAME))

    fun part1(input: List<String>, deck: List<Int> = (0 until 10007).toList()): List<Int> {
        val methods = input.map { it.toShuffleMethod() }
        return shuffle(methods, deck)
    }



    private fun shuffle(
        methods: List<ShuffleMethods>,
        deck: List<Int>
    ): List<Int> {
        return methods.fold(deck) { acc, curr -> curr(acc) }
    }

}

fun main() {
    println(Day22.part1(Day22.fileData).withIndex().first { (i, v) -> v == 2019 })
}
Collapse
 
jbristow profile image
Jon Bristow

Ok, this is the first one I thought was unfair.

The answer to part 2 requires you to know (or to be able to find) a lot of set theory.

Spoilers after my kotlin code:


    object SingleShuffleMethods {
        fun dealIntoStack() = (BigInteger.ONE.negate() to BigInteger.ONE.negate())
        fun cut(n: BigInteger) = BigInteger.ONE to n.negate()
        fun dealIncrement(n: BigInteger) = n to BigInteger.ZERO
    }

    private fun power(
        applyShuffle: (Pair<BigInteger, BigInteger>, Pair<BigInteger, BigInteger>) -> Pair<BigInteger, BigInteger>,
        x: Pair<BigInteger, BigInteger>,
        n: BigInteger
    ): Pair<BigInteger, BigInteger> {
        return when {
            n == BigInteger.ONE -> x
            n % BigInteger.TWO == BigInteger.ZERO -> power(applyShuffle, applyShuffle(x, x), (n / BigInteger.TWO))
            else -> applyShuffle(x, power(applyShuffle, applyShuffle(x, x), (n / BigInteger.TWO)))
        }
    }

    fun part2(input: List<String>): BigInteger {
        val numberOfCards = BigInteger.valueOf(119315717514047)
        val shuffleCount = BigInteger.valueOf(101741582076661)
        val positionToCheck = BigInteger.valueOf(2020)

        val comp = { f: Pair<BigInteger, BigInteger>, g: Pair<BigInteger, BigInteger> ->
            (g.first * f.first) % numberOfCards to (g.first * f.second + g.second) % numberOfCards
        }
        val oneShuffle = input.map { it.toModOffsetPair() }.reduce(comp)
        val finalModOffset = power(comp, oneShuffle, shuffleCount).second
        val inverseModValue =
            oneShuffle.first.modPow(shuffleCount, numberOfCards).modInverse(numberOfCards)

        return (inverseModValue * positionToCheck + (inverseModValue.negate() * finalModOffset) % numberOfCards) % numberOfCards
    }

Ok, so... There's a thing called an "inverse mod" that only works on relatively prime numbers.

Additionally, all of the three shuffling techniques can be expressed as an index offset and the number of mod actions performed.

A cut adds an offset and a single mod (think of applying the size of the cut to the indexes and then just pulling the new list from x = 0... offset of n, mod of 1)

A deal into n stacks shuffle just multiplies the index by n and then you scoop up the values in order. This is a mod shift of n. Trust me.

A deal into new stack goes backwards one, and flips the mod direction of our stack. (-1,-1)

so, any given index can be extracted.

Sample 1: (7,0)->(-1,-1)->(-1,-1) becomes (-7,-7)->(-1,-1) which becomes (7,0) => Every time through, the value of a given starting index changes by i*7%cards.
Sample 2: (1,-6),(7,0),(-1,-1) -> (7,-6),(-1,-1) -> (-7,-3) -> (-7i-3) % cards

Now, the problem here is going backwards, unrolling this. That's where inverse mod comes in, and makes this problem not actually work with non-relatively prime numbers. After n shuffles of c cards, that multiplier will be (-7^n % c) ... unfortunately, the addition part is (mod_a * offset_b + offset_a) % c, which is hard to write here as it's kind of a series of functions.

Anyway, I'm not 100% sure I really understand it, but maybe someone mathier can correct what I'm saying.