Added Solution for 2019 day 22
This commit is contained in:
parent
ff7e66b259
commit
44759cc979
6 changed files with 454 additions and 0 deletions
8
2019/day22_slam_shuffle/Cargo.toml
Normal file
8
2019/day22_slam_shuffle/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
[package]
|
||||
name = "day22_slam_shuffle"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
170
2019/day22_slam_shuffle/challenge.txt
Normal file
170
2019/day22_slam_shuffle/challenge.txt
Normal file
|
@ -0,0 +1,170 @@
|
|||
There isn't much to do while you wait for the droids to repair your ship. At least you're drifting in the right direction. You decide to practice a new [card shuffle](https://en.wikipedia.org/wiki/Shuffling) you've been working on.
|
||||
|
||||
Digging through the ship's storage, you find a deck of *space cards*! Just like any deck of space cards, there are 10007 cards in the deck numbered `0` through `10006`. The deck must be new - they're still in *factory order*, with `0` on the top, then `1`, then `2`, and so on, all the way through to `10006` on the bottom.
|
||||
|
||||
You've been practicing three different *techniques* that you use while shuffling. Suppose you have a deck of only 10 cards (numbered `0` through `9`):
|
||||
|
||||
*To `deal into new stack`*, create a new stack of cards by dealing the top card of the deck onto the top of the new stack repeatedly until you run out of cards:
|
||||
|
||||
```
|
||||
Top Bottom
|
||||
0 1 2 3 4 5 6 7 8 9 Your deck
|
||||
New stack
|
||||
|
||||
1 2 3 4 5 6 7 8 9 Your deck
|
||||
0 New stack
|
||||
|
||||
2 3 4 5 6 7 8 9 Your deck
|
||||
1 0 New stack
|
||||
|
||||
3 4 5 6 7 8 9 Your deck
|
||||
2 1 0 New stack
|
||||
|
||||
Several steps later...
|
||||
|
||||
9 Your deck
|
||||
8 7 6 5 4 3 2 1 0 New stack
|
||||
|
||||
Your deck
|
||||
9 8 7 6 5 4 3 2 1 0 New stack
|
||||
|
||||
```
|
||||
|
||||
Finally, pick up the new stack you've just created and use it as the deck for the next technique.
|
||||
|
||||
*To `cut N` cards*, take the top `N` cards off the top of the deck and move them as a single unit to the bottom of the deck, retaining their order. For example, to `cut 3`:
|
||||
|
||||
```
|
||||
Top Bottom
|
||||
0 1 2 3 4 5 6 7 8 9 Your deck
|
||||
|
||||
3 4 5 6 7 8 9 Your deck
|
||||
0 1 2 Cut cards
|
||||
|
||||
3 4 5 6 7 8 9 Your deck
|
||||
0 1 2 Cut cards
|
||||
|
||||
3 4 5 6 7 8 9 0 1 2 Your deck
|
||||
|
||||
```
|
||||
|
||||
You've also been getting pretty good at a version of this technique where `N` is negative! In that case, cut (the absolute value of) `N` cards from the bottom of the deck onto the top. For example, to `cut -4`:
|
||||
|
||||
```
|
||||
Top Bottom
|
||||
0 1 2 3 4 5 6 7 8 9 Your deck
|
||||
|
||||
0 1 2 3 4 5 Your deck
|
||||
6 7 8 9 Cut cards
|
||||
|
||||
0 1 2 3 4 5 Your deck
|
||||
6 7 8 9 Cut cards
|
||||
|
||||
6 7 8 9 0 1 2 3 4 5 Your deck
|
||||
|
||||
```
|
||||
|
||||
*To `deal with increment N`*, start by clearing enough space on your table to lay out all of the cards individually in a long line. Deal the top card into the leftmost position. Then, move `N` positions to the right and deal the next card there. If you would move into a position past the end of the space on your table, wrap around and keep counting from the leftmost card again. Continue this process until you run out of cards.
|
||||
|
||||
For example, to `deal with increment 3`:
|
||||
|
||||
```
|
||||
|
||||
0 1 2 3 4 5 6 7 8 9 Your deck
|
||||
. . . . . . . . . . Space on table
|
||||
^ Current position
|
||||
|
||||
Deal the top card to the current position:
|
||||
|
||||
1 2 3 4 5 6 7 8 9 Your deck
|
||||
0 . . . . . . . . . Space on table
|
||||
^ Current position
|
||||
|
||||
Move the current position right 3:
|
||||
|
||||
1 2 3 4 5 6 7 8 9 Your deck
|
||||
0 . . . . . . . . . Space on table
|
||||
^ Current position
|
||||
|
||||
Deal the top card:
|
||||
|
||||
2 3 4 5 6 7 8 9 Your deck
|
||||
0 . . 1 . . . . . . Space on table
|
||||
^ Current position
|
||||
|
||||
Move right 3 and deal:
|
||||
|
||||
3 4 5 6 7 8 9 Your deck
|
||||
0 . . 1 . . 2 . . . Space on table
|
||||
^ Current position
|
||||
|
||||
Move right 3 and deal:
|
||||
|
||||
4 5 6 7 8 9 Your deck
|
||||
0 . . 1 . . 2 . . 3 Space on table
|
||||
^ Current position
|
||||
|
||||
Move right 3, wrapping around, and deal:
|
||||
|
||||
5 6 7 8 9 Your deck
|
||||
0 . 4 1 . . 2 . . 3 Space on table
|
||||
^ Current position
|
||||
|
||||
And so on:
|
||||
|
||||
0 7 4 1 8 5 2 9 6 3 Space on table
|
||||
|
||||
```
|
||||
|
||||
Positions on the table which already contain cards are still counted; they're not skipped. Of course, this technique is carefully designed so it will never put two cards in the same position or leave a position empty.
|
||||
|
||||
Finally, collect the cards on the table so that the leftmost card ends up at the top of your deck, the card to its right ends up just below the top card, and so on, until the rightmost card ends up at the bottom of the deck.
|
||||
|
||||
The complete shuffle process (your puzzle input) consists of applying many of these techniques. Here are some examples that combine techniques; they all start with a *factory order* deck of 10 cards:
|
||||
|
||||
```
|
||||
deal with increment 7
|
||||
deal into new stack
|
||||
deal into new stack
|
||||
Result: 0 3 6 9 2 5 8 1 4 7
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
cut 6
|
||||
deal with increment 7
|
||||
deal into new stack
|
||||
Result: 3 0 7 4 1 8 5 2 9 6
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
deal with increment 7
|
||||
deal with increment 9
|
||||
cut -2
|
||||
Result: 6 3 0 7 4 1 8 5 2 9
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
deal into new stack
|
||||
cut -2
|
||||
deal with increment 7
|
||||
cut 8
|
||||
cut -4
|
||||
deal with increment 7
|
||||
cut 3
|
||||
deal with increment 9
|
||||
deal with increment 3
|
||||
cut -1
|
||||
Result: 9 2 5 8 1 4 7 0 3 6
|
||||
|
||||
```
|
||||
|
||||
Positions within the deck count from `0` at the top, then `1` for the card immediately below the top card, and so on to the bottom. (That is, cards start in the position matching their number.)
|
||||
|
||||
After shuffling your *factory order* deck of 10007 cards, *what is the position of card `2019`?*
|
||||
|
||||
To begin, [get your puzzle input](22/input).
|
||||
|
||||
Answer:
|
BIN
2019/day22_slam_shuffle/day22_slam_shuffle-.core
Normal file
BIN
2019/day22_slam_shuffle/day22_slam_shuffle-.core
Normal file
Binary file not shown.
166
2019/day22_slam_shuffle/src/lib.rs
Normal file
166
2019/day22_slam_shuffle/src/lib.rs
Normal file
|
@ -0,0 +1,166 @@
|
|||
use core::fmt::Display;
|
||||
use std::{num::ParseIntError, collections::VecDeque};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum ParseError {
|
||||
ParseIntError(std::num::ParseIntError),
|
||||
LineMalformed(String),
|
||||
}
|
||||
|
||||
impl From<ParseIntError> for ParseError {
|
||||
fn from(value: ParseIntError) -> Self {
|
||||
Self::ParseIntError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ParseError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::ParseIntError(e) => write!(f, "Unable to parse into integer: {e}"),
|
||||
Self::LineMalformed(v) => write!(f, "Line is malformed: {v}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Technique {
|
||||
Rev,
|
||||
Shift(isize),
|
||||
Zip(usize),
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for Technique {
|
||||
type Error = ParseError;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
let components: Vec<_> = value.split_whitespace().collect();
|
||||
match components[1] {
|
||||
"into" => Ok(Self::Rev),
|
||||
"with" => Ok(Self::Zip(components[3].parse()?)),
|
||||
n => Ok(Self::Shift(n.parse()?)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Technique {
|
||||
fn perform(&self, cards: &mut VecDeque<usize>) {
|
||||
let len = cards.len();
|
||||
match self {
|
||||
Self::Rev => (0..len/2).for_each(|a| cards.swap(a, len-a-1)),
|
||||
Self::Shift(i) if i >= &0 => cards.rotate_left(i.unsigned_abs()),
|
||||
Self::Shift(n) => cards.rotate_right(n.unsigned_abs()),
|
||||
Self::Zip(n) => {
|
||||
let mut new: VecDeque<usize> = cards.clone();
|
||||
(0..len).for_each(|idx| new[(n*idx)%len] = cards[idx]);
|
||||
std::mem::swap(cards, &mut new);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn get_parameters(&self, offset_diff: u128, increment_mul: u128, modulo: u128) -> (u128, u128) {
|
||||
match self {
|
||||
Self::Rev => ((offset_diff+modulo-increment_mul)%modulo, modulo-increment_mul),
|
||||
Self::Shift(p) if p >= &0 => ((offset_diff+(p.unsigned_abs() as u128)*increment_mul)%modulo, increment_mul),
|
||||
Self::Shift(n) => ((offset_diff+(modulo-(n.unsigned_abs()) as u128)*increment_mul)%modulo, increment_mul),
|
||||
Self::Zip(i) => (offset_diff, (increment_mul*pow_mod(*i as u128, modulo-2, modulo))%modulo),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_1(input: &str, cards: usize, target: usize) -> Result<usize, ParseError> {
|
||||
let mut cards: VecDeque<usize> = (0..cards).collect();
|
||||
let instructions: Vec<_> = input.lines().map(Technique::try_from).collect::<Result<Vec<_>, _>>()?;
|
||||
instructions.iter().for_each(|instr| instr.perform(&mut cards));
|
||||
Ok(cards.iter().position(|card| *card == target).unwrap())
|
||||
}
|
||||
|
||||
pub fn run_2(input: &str, cards: u128, target: u128, iterations: u128) -> Result<u128, ParseError> {
|
||||
let mut offset_diff = 0;
|
||||
let mut increment_mul = 1;
|
||||
let instructions: Vec<_> = input.lines().map(Technique::try_from).collect::<Result<Vec<_>, _>>()?;
|
||||
for instruction in instructions {
|
||||
(offset_diff, increment_mul) = instruction.get_parameters(offset_diff, increment_mul, cards);
|
||||
}
|
||||
|
||||
let increment = pow_mod(increment_mul, iterations, cards);
|
||||
let offset = (((cards*cards - offset_diff * (increment-1)) % cards) * pow_mod(cards+1-increment_mul, cards-2, cards))%cards;
|
||||
|
||||
Ok((offset+target*increment)%cards)
|
||||
}
|
||||
|
||||
fn pow_mod(mantisse: u128, exponent: u128, modulo: u128) -> u128 {
|
||||
match exponent {
|
||||
0 => 1,
|
||||
e if e % 2 == 0 => pow_mod((mantisse*mantisse) % modulo, exponent/2, modulo),
|
||||
_ => (mantisse * pow_mod((mantisse * mantisse) % modulo, exponent/2, modulo)) % modulo,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
fn read_file(name: &str) -> String {
|
||||
read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pow_test() {
|
||||
for mantisse in 10..23 {
|
||||
for exponent in 5..14 {
|
||||
for modulo in (3..1_000_003).step_by(1_000) {
|
||||
eprintln!("Testing {mantisse}^{exponent}%{modulo}");
|
||||
assert_eq!(pow_mod(mantisse, exponent as u128, modulo), mantisse.pow(exponent)%modulo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn perform_test() {
|
||||
let master: VecDeque<usize> = (0..10).collect();
|
||||
let reversed = VecDeque::from([9, 8, 7, 6, 5, 4, 3, 2, 1, 0]);
|
||||
let shifted = VecDeque::from([3, 4, 5, 6, 7, 8, 9, 0, 1, 2]);
|
||||
let zipped = VecDeque::from([0, 7, 4, 1, 8, 5, 2, 9, 6, 3]);
|
||||
|
||||
let mut cards = master.clone();
|
||||
Technique::Rev.perform(&mut cards);
|
||||
assert_eq!(cards, reversed);
|
||||
|
||||
let mut cards = master.clone();
|
||||
Technique::Shift(3).perform(&mut cards);
|
||||
assert_eq!(cards, shifted);
|
||||
|
||||
let mut cards = master;
|
||||
Technique::Zip(3).perform(&mut cards);
|
||||
assert_eq!(cards, zipped);
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_parameters_samples() {
|
||||
assert_eq!(Technique::Rev.get_parameters(0, 1, 10), (9, 9));
|
||||
assert_eq!(Technique::Shift(3).get_parameters(0, 1, 10), (3, 1));
|
||||
assert_eq!(Technique::Shift(-4).get_parameters(0, 1, 10), (6, 1));
|
||||
assert_eq!(Technique::Zip(7).get_parameters(0, 1, 11), (0, 8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sample() {
|
||||
let sample_input = read_file("tests/sample_input");
|
||||
assert_eq!(run_1(&sample_input, 10, 5), Ok(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_challenge_part_1() {
|
||||
let challenge_input = read_file("tests/challenge_input");
|
||||
assert_eq!(run_1(&challenge_input, 10007, 2019), Ok(8191));
|
||||
assert_eq!(run_2(&challenge_input, 10007, 2019, 1), Ok(1545));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_challenge_part_2() {
|
||||
let challenge_input = read_file("tests/challenge_input");
|
||||
assert_eq!(run_2(&challenge_input, 119315717514047, 2020, 101741582076661), Ok(1644352419829));
|
||||
}
|
||||
}
|
100
2019/day22_slam_shuffle/tests/challenge_input
Normal file
100
2019/day22_slam_shuffle/tests/challenge_input
Normal file
|
@ -0,0 +1,100 @@
|
|||
deal with increment 41
|
||||
cut 6859
|
||||
deal with increment 23
|
||||
cut -4435
|
||||
deal into new stack
|
||||
deal with increment 27
|
||||
cut -337
|
||||
deal with increment 50
|
||||
cut 5290
|
||||
deal into new stack
|
||||
deal with increment 59
|
||||
cut -9939
|
||||
deal with increment 32
|
||||
cut 4074
|
||||
deal with increment 25
|
||||
cut -2391
|
||||
deal into new stack
|
||||
deal with increment 40
|
||||
cut -8095
|
||||
deal with increment 44
|
||||
cut 9150
|
||||
deal with increment 5
|
||||
cut -5330
|
||||
deal with increment 61
|
||||
cut -1038
|
||||
deal with increment 3
|
||||
cut 2873
|
||||
deal with increment 56
|
||||
cut 6080
|
||||
deal with increment 59
|
||||
cut -6859
|
||||
deal with increment 21
|
||||
cut -2316
|
||||
deal with increment 42
|
||||
cut -8349
|
||||
deal with increment 60
|
||||
cut 5774
|
||||
deal with increment 63
|
||||
cut -1754
|
||||
deal with increment 48
|
||||
cut 4009
|
||||
deal with increment 10
|
||||
cut -7026
|
||||
deal with increment 73
|
||||
cut 3867
|
||||
deal into new stack
|
||||
cut 3754
|
||||
deal with increment 23
|
||||
cut 4222
|
||||
deal with increment 23
|
||||
deal into new stack
|
||||
cut 7294
|
||||
deal into new stack
|
||||
deal with increment 13
|
||||
cut -9537
|
||||
deal with increment 20
|
||||
cut 2910
|
||||
deal with increment 30
|
||||
deal into new stack
|
||||
cut 9409
|
||||
deal with increment 23
|
||||
deal into new stack
|
||||
deal with increment 32
|
||||
cut 6945
|
||||
deal with increment 21
|
||||
deal into new stack
|
||||
cut -3297
|
||||
deal with increment 75
|
||||
cut -5300
|
||||
deal into new stack
|
||||
deal with increment 29
|
||||
cut 8131
|
||||
deal with increment 50
|
||||
cut -8998
|
||||
deal with increment 19
|
||||
cut -1983
|
||||
deal with increment 13
|
||||
deal into new stack
|
||||
cut -7555
|
||||
deal with increment 62
|
||||
cut 5612
|
||||
deal with increment 14
|
||||
cut -412
|
||||
deal with increment 46
|
||||
cut -7349
|
||||
deal with increment 57
|
||||
cut -8783
|
||||
deal with increment 33
|
||||
deal into new stack
|
||||
deal with increment 56
|
||||
cut 4283
|
||||
deal into new stack
|
||||
cut 8053
|
||||
deal with increment 7
|
||||
cut -2776
|
||||
deal with increment 66
|
||||
cut -9633
|
||||
deal with increment 62
|
||||
deal into new stack
|
||||
deal with increment 12
|
10
2019/day22_slam_shuffle/tests/sample_input
Normal file
10
2019/day22_slam_shuffle/tests/sample_input
Normal file
|
@ -0,0 +1,10 @@
|
|||
deal into new stack
|
||||
cut -2
|
||||
deal with increment 7
|
||||
cut 8
|
||||
cut -4
|
||||
deal with increment 7
|
||||
cut 3
|
||||
deal with increment 9
|
||||
deal with increment 3
|
||||
cut -1
|
Loading…
Add table
Add a link
Reference in a new issue