Compare commits

..

5 Commits

Author SHA1 Message Date
d935de1fb0 Day 20 part 2 solution. 2020-12-26 12:34:44 -08:00
5535aaf810 Day 25 part 2 text. 2020-12-26 08:28:24 -08:00
1c5032a5e5 Day 25 part 1 solution. 2020-12-26 08:25:30 -08:00
e3d9c13731 Day 23 hint for improving runtime. 2020-12-26 08:25:06 -08:00
e04081ac4a Day 22 lint cleanup. 2020-12-26 08:24:49 -08:00
5 changed files with 326 additions and 110 deletions

View File

@ -262,7 +262,9 @@ use std::str::FromStr;
use aoc_runner_derive::{aoc, aoc_generator}; use aoc_runner_derive::{aoc, aoc_generator};
#[derive(Default, Hash, Eq, PartialEq)] use crate::debug_println;
#[derive(Clone, Default, Hash, Eq, PartialEq)]
struct Tile { struct Tile {
id: usize, id: usize,
pixels: Vec<u8>, pixels: Vec<u8>,
@ -272,7 +274,7 @@ struct Tile {
impl fmt::Debug for Tile { impl fmt::Debug for Tile {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Tile {}:\n", self.id)?; write!(f, "Tile {} ({}x{}):\n", self.id, self.width, self.height)?;
for y in 0..self.height { for y in 0..self.height {
for x in 0..self.width { for x in 0..self.width {
write!(f, "{}", self[(x, y)] as char)?; write!(f, "{}", self[(x, y)] as char)?;
@ -330,6 +332,7 @@ impl Index<(usize, usize)> for Tile {
} }
} }
#[cfg(debug_assertions)]
fn border_to_str(border: &[u8]) -> String { fn border_to_str(border: &[u8]) -> String {
std::str::from_utf8(border).unwrap().to_string() std::str::from_utf8(border).unwrap().to_string()
} }
@ -337,6 +340,14 @@ fn border_to_str(border: &[u8]) -> String {
impl Tile { impl Tile {
/// Copy `t` into self @ x_off,y_off. /// Copy `t` into self @ x_off,y_off.
fn blit(&mut self, t: &Tile, x_off: usize, y_off: usize) { fn blit(&mut self, t: &Tile, x_off: usize, y_off: usize) {
debug_println!(
"blitting tile {} {}x{} @ {},{}",
t.id,
t.width,
t.height,
x_off,
y_off
);
(0..t.height) (0..t.height)
.for_each(|y| (0..t.width).for_each(|x| self[(x_off + x, y_off + y)] = t[(x, y)])); .for_each(|y| (0..t.width).for_each(|x| self[(x_off + x, y_off + y)] = t[(x, y)]));
} }
@ -456,56 +467,62 @@ impl Tile {
} }
/// Tries various orientations, until predicate matches. /// Tries various orientations, until predicate matches.
fn reorient<F>(img: &Tile, predicate: F) -> Tile fn reorient<F>(img: &Tile, predicate: F) -> Option<Tile>
where where
F: Fn(&Tile) -> bool, F: Fn(&Tile) -> bool,
{ {
if predicate(&img) {
return Some(img.clone());
}
let rotated = img.rotate90(); let rotated = img.rotate90();
if predicate(&rotated) { if predicate(&rotated) {
return rotated; return Some(rotated);
} }
let rotated = img.rotate180(); let rotated = img.rotate180();
if predicate(&rotated) { if predicate(&rotated) {
return rotated; return Some(rotated);
} }
let rotated = img.rotate270(); let rotated = img.rotate270();
if predicate(&rotated) { if predicate(&rotated) {
return rotated; return Some(rotated);
} }
let horiz = img.flip_horizontal(); let horiz = img.flip_horizontal();
if predicate(&horiz) {
return Some(horiz);
}
let rotated = horiz.rotate90(); let rotated = horiz.rotate90();
if predicate(&rotated) { if predicate(&rotated) {
return rotated; return Some(rotated);
} }
let rotated = horiz.rotate180(); let rotated = horiz.rotate180();
if predicate(&rotated) { if predicate(&rotated) {
return rotated; return Some(rotated);
} }
let rotated = horiz.rotate270(); let rotated = horiz.rotate270();
if predicate(&rotated) { if predicate(&rotated) {
return rotated; return Some(rotated);
} }
panic!("couldn't find an orientation matching predicate"); None
} }
fn stitch(tiles: &[Tile]) -> Tile { fn stitch(tiles: &[Tile]) -> Tile {
// Make sure there's a sqare number of tiles. // Make sure there's a square number of tiles.
let sqrt = (tiles.len() as f32).sqrt() as usize; let sqrt = (tiles.len() as f32).sqrt() as usize;
assert_eq!(sqrt * sqrt, tiles.len()); assert_eq!(sqrt * sqrt, tiles.len());
let width = sqrt * (tiles[0].width - 2); let width = sqrt * (tiles[0].width - 2);
let height = sqrt * (tiles[0].width - 2); let height = sqrt * (tiles[0].height - 2);
let mut image = Tile { let mut image = Tile {
id: 0, id: 0,
width, width,
height, height,
pixels: vec![b' '; width * height], pixels: vec![b'X'; width * height],
}; };
let mut border_counts = HashMap::new(); let mut border_counts = HashMap::new();
@ -518,91 +535,135 @@ fn stitch(tiles: &[Tile]) -> Tile {
}) })
}); });
// Find a corner, and then stitch from there. #[cfg(debug_assertions)]
let corner = tiles border_counts.iter().for_each(|(b, c)| {
let _ = b;
let _ = c;
debug_println!("{}: {}", border_to_str(b), c);
});
let edge_borders: HashSet<_> = border_counts
.iter() .iter()
.filter(|t| { .filter(|(_b, c)| **c == 1)
let matches: usize = t.border_set().iter().map(|b| border_counts[b]).sum(); .map(|(b, _c)| b)
matches == 12 .collect();
}) // Count the number of borders that are in edge_borders. The answer should be 0, 1 or 2
// Grab the min for repeatable results. // if the tile is a middle, edge or corner, respectively.
.min_by(|l, r| l.id.cmp(&r.id)) let (corner_tiles, _edge_tiles, _center_tiles) = tiles.iter().fold(
.expect("couldn't find corner"); (vec![], vec![], vec![]),
/* |(mut corner, mut edge, mut center), t| {
let corner = reorient(&corner.flip_horizontal(), |im| { let edge_count = vec![
// Reorient until the top and left borders are edges. This has a 50/50 chance of being t.top_border(),
// right, and we can't verify it until the first neighbor is found. t.right_border(),
border_counts[&im.top_border()] == 2 && border_counts[&im.left_border()] == 2 t.bottom_border(),
}); t.left_border(),
*/ ]
let mut map_ids = vec![vec![0; sqrt]; sqrt]; .into_iter()
.filter(|b| edge_borders.contains(b))
map_ids[0][0] = corner.id; .count();
let id_to_tile: HashMap<_, _> = tiles.iter().map(|t| (t.id, t)).collect(); match edge_count {
let mut remaining_tiles: HashSet<_> = tiles.iter().collect(); 0 => center.push(t),
remaining_tiles.remove(&corner); 1 => edge.push(t),
let mut prev_tile = corner; 2 => corner.push(t),
c => panic!(format!("unexpected edge_count for {:?}: {}", t, c)),
(0..sqrt)
.map(|y| (0..sqrt).map(move |x| (x, y)))
.flatten()
.skip(1)
.for_each(|(x, y)| {
if x == 0 {
// Beginning of a new row, use the tile above as the previous tile.
prev_tile = id_to_tile[&map_ids[x][y - 1]];
}
dbg!((x, y), prev_tile.id);
let neighbor = remaining_tiles
.iter()
.filter(|t| t.border_set().intersection(&prev_tile.border_set()).count() > 0)
.nth(0)
.expect("couldn't find neighbor");
map_ids[x][y] = neighbor.id;
dbg!(&map_ids);
prev_tile = neighbor;
remaining_tiles.remove(prev_tile);
});
let corner = reorient(corner, |im| {
let right_set = id_to_tile[&map_ids[1][0]].border_set();
let bottom_set = id_to_tile[&map_ids[0][1]].border_set();
right_set.contains(&im.right_border()) && bottom_set.contains(&im.bottom_border())
});
// Map tile id to correctly oriented Tile.
let mut oriented = HashMap::new();
oriented.insert(corner.id, corner);
(0..sqrt)
.map(|y| (0..sqrt).map(move |x| (x, y)))
.flatten()
.skip(1)
.for_each(|(x, y)| {
let t = id_to_tile[&map_ids[x][y]];
let t = if x == 0 {
// Beginning of a new row, use the tile above as the previous tile.
let above_tile = id_to_tile[&map_ids[x][y - 1]];
reorient(t, |im| {
dbg!(
border_to_str(&above_tile.right_border()),
border_to_str(&im.left_border())
);
above_tile.bottom_border() == im.top_border()
})
// TODO(wathiede): reorient and blit.
} else {
// Use the tile to the left as previous tile.
let left_tile = id_to_tile[&map_ids[x - 1][y]];
reorient(t, |im| {
dbg!(
border_to_str(&left_tile.right_border()),
border_to_str(&im.left_border())
);
left_tile.right_border() == im.left_border()
})
}; };
(corner, edge, center)
},
);
let mut tile_map = vec![vec![None; sqrt]; sqrt];
let corner = corner_tiles[0];
// Make this the upper-left corner at 0,0.
let corner = reorient(corner, |im| {
edge_borders.contains(&im.left_border()) && edge_borders.contains(&im.top_border())
})
.expect("couldn't find proper orientation");
let mut remaining_tiles: HashSet<_> = tiles.iter().filter(|t| t.id != corner.id).collect();
let mut last = corner.clone();
tile_map[0][0] = Some(corner);
(0..sqrt)
.map(|y| (0..sqrt).map(move |x| (x, y)))
.flatten()
.skip(1)
.for_each(|(x, y)| {
debug_println!("Solving for tile {},{}", x, y);
let mut local_last = last.clone();
let orientation_check: Box<dyn Fn(&Tile) -> bool> = if y == 0 {
debug_println!("search for top row tiles");
// Top row, tiles should be match the tile to the left and have their top_border in the
// edge set.
// Find a tile that matches last and reorient so it's edge is on top.
Box::new(|im: &Tile| {
edge_borders.contains(&im.top_border())
&& im.left_border() == local_last.right_border()
})
} else if x == 0 {
debug_println!("search for left column tiles");
// When we're in the first column, we need to match against the tile above, instead of
// the last tile on the previous row.
local_last = tile_map[0][y - 1]
.as_ref()
.expect(&format!("couldn't file tile above {},{}", x, y))
.clone();
Box::new(|im: &Tile| {
edge_borders.contains(&im.left_border())
&& im.top_border() == local_last.bottom_border()
})
} else {
debug_println!("search for regular tiles");
// Default, last is to the left match shared edge.
Box::new(|im: &Tile| im.left_border() == local_last.right_border())
};
debug_println!("last tile {}", last.id);
let mut found: Option<Tile> = None;
for candidate in &remaining_tiles {
match reorient(candidate, &orientation_check) {
Some(good) => {
debug_println!("found3 {}", good.id);
found = Some(good);
break;
}
None => continue,
};
}
match found {
Some(rm) => {
debug_println!(
"rm3 {} {:?}",
rm.id,
remaining_tiles.iter().map(|t| t.id).collect::<Vec<_>>()
);
last = rm.clone();
tile_map[x][y] = Some(last.clone());
let rm = remaining_tiles
.iter()
.filter(|t| t.id == rm.id)
.nth(0)
.expect(&format!("Couldn't find {} in remaining_tiles", rm.id))
.clone();
remaining_tiles.remove(rm);
}
None => panic!("couldn't find match for {},{}", x, y),
};
});
debug_println!("Stitched titles");
#[cfg(debug_assertions)]
(0..sqrt).for_each(|y| {
let row_ids: Vec<_> = (0..sqrt)
.map(|x| tile_map[x][y].as_ref().unwrap().id)
.collect();
debug_println!("{:?}", row_ids);
});
(0..sqrt)
.map(|y| (0..sqrt).map(move |x| (x, y)))
.flatten()
.for_each(|(x, y)| {
let t = tile_map[x][y]
.as_ref()
.expect(&format!("missing tile {},{} in completed tile_map", x, y));
let out = t.strip_border(); let out = t.strip_border();
image.blit(&out.strip_border(), x * out.width, y * out.height); image.blit(&out, x * out.width, y * out.height);
oriented.insert(t.id, t);
}); });
// TODO(wathiede) paste oriented into image. // TODO(wathiede) paste oriented into image.
@ -659,7 +720,9 @@ fn contains_seamonster(t: &Tile) -> bool {
#[aoc(day20, part2)] #[aoc(day20, part2)]
fn solution2(tiles: &[Tile]) -> usize { fn solution2(tiles: &[Tile]) -> usize {
habitat(&reorient(&stitch(tiles), contains_seamonster)) let full_map = stitch(tiles);
debug_println!("Full map\n{:?}", full_map);
habitat(&reorient(&full_map, contains_seamonster).expect("couldn't find proper orientation"))
} }
#[cfg(test)] #[cfg(test)]
@ -845,6 +908,7 @@ mod tests {
let monster = seamonster(); let monster = seamonster();
assert_eq!( assert_eq!(
reorient(&img, contains_seamonster) reorient(&img, contains_seamonster)
.expect("couldn't find proper orientation")
.find_hashes(&monster) .find_hashes(&monster)
.len(), .len(),
2 2
@ -887,10 +951,42 @@ mod tests {
#[test] #[test]
fn test_habitat() { fn test_habitat() {
let img: Tile = OUTPUT_IMAGE.parse().expect("failed to part want image"); let img: Tile = OUTPUT_IMAGE.parse().expect("failed to part want image");
// TODO(wathiede) Reorient img until you find a seamonster.
dbg!(img.count_hashes()); dbg!(img.count_hashes());
dbg!(seamonster().count_hashes()); dbg!(seamonster().count_hashes());
assert_eq!(habitat(&reorient(&img, contains_seamonster)), 273); assert_eq!(
habitat(
&reorient(&img, contains_seamonster).expect("couldn't find proper orientation")
),
273
);
}
#[test]
fn test_stitch() {
let want: Tile = OUTPUT_IMAGE.parse().expect("can't parse stitched input");
let output = stitch(&generator(INPUT));
/*
let output = reorient(&output, |im| {
dbg!(&want, &im);
im == &want
})
.unwrap();
*/
let output = reorient(&output, contains_seamonster);
match output {
None => assert!(false, "Failed to reorient stitched image to reference"),
Some(im) => {
dbg!(&im);
assert_eq!(
habitat(&im),
273,
"\n im {}\nwant {}",
border_to_str(&im.pixels),
border_to_str(&want.pixels)
);
}
}
} }
#[test] #[test]
fn test_solution2() { fn test_solution2() {

View File

@ -410,6 +410,9 @@ impl Players {
fn play_recursive(&mut self, game: usize, parent_game: usize) -> bool { fn play_recursive(&mut self, game: usize, parent_game: usize) -> bool {
debug_println!("=== Game {} ===\n", game); debug_println!("=== Game {} ===\n", game);
let mut round = 0; let mut round = 0;
// For debug builds only.
let _ = round;
let _ = parent_game;
let mut previous_rounds = HashSet::new(); let mut previous_rounds = HashSet::new();
while !self.p1.is_empty() && !self.p2.is_empty() { while !self.p1.is_empty() && !self.p2.is_empty() {
let p1s = deck_to_str(&self.p1); let p1s = deck_to_str(&self.p1);

View File

@ -128,6 +128,8 @@ struct TargetCup {
idx: usize, idx: usize,
} }
/// TODO(wathiede): redo based on this sentence from glenng:
/// `So a circular linked list containing 2,1,3 would be [3,1,2]`
#[derive(Debug)] #[derive(Debug)]
struct FastHand { struct FastHand {
idx_to_val: Vec<usize>, idx_to_val: Vec<usize>,

114
2020/src/day25.rs Normal file
View File

@ -0,0 +1,114 @@
//! --- Day 25: Combo Breaker ---
//! You finally reach the check-in desk. Unfortunately, their registration systems are currently offline, and they cannot check you in. Noticing the look on your face, they quickly add that tech support is already on the way! They even created all the room keys this morning; you can take yours now and give them your room deposit once the registration system comes back online.
//!
//! The room key is a small RFID card. Your room is on the 25th floor and the elevators are also temporarily out of service, so it takes what little energy you have left to even climb the stairs and navigate the halls. You finally reach the door to your room, swipe your card, and - beep - the light turns red.
//!
//! Examining the card more closely, you discover a phone number for tech support.
//!
//! "Hello! How can we help you today?" You explain the situation.
//!
//! "Well, it sounds like the card isn't sending the right command to unlock the door. If you go back to the check-in desk, surely someone there can reset it for you." Still catching your breath, you describe the status of the elevator and the exact number of stairs you just had to climb.
//!
//! "I see! Well, your only other option would be to reverse-engineer the cryptographic handshake the card does with the door and then inject your own commands into the data stream, but that's definitely impossible." You thank them for their time.
//!
//! Unfortunately for the door, you know a thing or two about cryptographic handshakes.
//!
//! The handshake used by the card and the door involves an operation that transforms a subject number. To transform a subject number, start with the value 1. Then, a number of times called the loop size, perform the following steps:
//!
//! Set the value to itself multiplied by the subject number.
//! Set the value to the remainder after dividing the value by 20201227.
//! The card always uses a specific, secret loop size when it transforms a subject number. The door always uses a different, secret loop size.
//!
//! The cryptographic handshake works like this:
//!
//! The card transforms the subject number of 7 according to the card's secret loop size. The result is called the card's public key.
//! The door transforms the subject number of 7 according to the door's secret loop size. The result is called the door's public key.
//! The card and door use the wireless RFID signal to transmit the two public keys (your puzzle input) to the other device. Now, the card has the door's public key, and the door has the card's public key. Because you can eavesdrop on the signal, you have both public keys, but neither device's loop size.
//! The card transforms the subject number of the door's public key according to the card's loop size. The result is the encryption key.
//! The door transforms the subject number of the card's public key according to the door's loop size. The result is the same encryption key as the card calculated.
//! If you can use the two public keys to determine each device's loop size, you will have enough information to calculate the secret encryption key that the card and door use to communicate; this would let you send the unlock command directly to the door!
//!
//! For example, suppose you know that the card's public key is 5764801. With a little trial and error, you can work out that the card's loop size must be 8, because transforming the initial subject number of 7 with a loop size of 8 produces 5764801.
//!
//! Then, suppose you know that the door's public key is 17807724. By the same process, you can determine that the door's loop size is 11, because transforming the initial subject number of 7 with a loop size of 11 produces 17807724.
//!
//! At this point, you can use either device's loop size with the other device's public key to calculate the encryption key. Transforming the subject number of 17807724 (the door's public key) with a loop size of 8 (the card's loop size) produces the encryption key, 14897079. (Transforming the subject number of 5764801 (the card's public key) with a loop size of 11 (the door's loop size) produces the same encryption key: 14897079.)
//!
//! What encryption key is the handshake trying to establish?
//! --- Part Two ---
//! The light turns green and the door unlocks. As you collapse onto the bed in your room, your pager goes off!
//!
//! "It's an emergency!" the Elf calling you explains. "The soft serve machine in the cafeteria on sub-basement 7 just failed and you're the only one that knows how to fix it! We've already dispatched a reindeer to your location to pick you up."
//!
//! You hear the sound of hooves landing on your balcony.
//!
//! The reindeer carefully explores the contents of your room while you figure out how you're going to pay the 50 stars you owe the resort before you leave. Noticing that you look concerned, the reindeer wanders over to you; you see that it's carrying a small pouch.
//!
//! "Sorry for the trouble," a note in the pouch reads. Sitting at the bottom of the pouch is a gold coin with a little picture of a starfish on it.
//!
//! Looks like you only needed 49 stars after all.
//!
//! You don't have enough stars to pay the deposit, though. You need 2 more.
use aoc_runner_derive::aoc;
const MOD: usize = 20201227;
const SUBJECT_NUM: usize = 7;
// Returns loop size for given initial state.
fn solve(subject: usize, pk: usize) -> usize {
let mut acc = subject;
(0..)
.position(|_| {
acc = (acc * subject) % MOD;
acc == pk
})
.unwrap()
+ 1
}
fn find_encryption_key(pk0: usize, pk1: usize, subject: usize) -> usize {
//let l0 = solve(subject, pk0);
let l1 = solve(subject, pk1);
//(0..l0).fold(pk1, |acc, _| (acc * pk1) % MOD);
(0..l1).fold(pk0, |acc, _| (acc * pk0) % MOD)
}
#[aoc(day25, part1)]
fn solution1(input: &str) -> usize {
let pks: Vec<usize> = input
.split('\n')
.map(|l| l.parse::<usize>().expect("couldn't parse public key"))
.collect();
find_encryption_key(pks[0], pks[1], SUBJECT_NUM)
}
#[cfg(test)]
mod tests {
use super::*;
const CARD_PUBKEY: usize = 5764801;
const DOOR_PUBKEY: usize = 17807724;
#[test]
fn loop_solver() {
// Puzzle gives input in 1's based numbering.
assert_eq!(solve(SUBJECT_NUM, CARD_PUBKEY), 8 - 1);
assert_eq!(solve(SUBJECT_NUM, DOOR_PUBKEY), 11 - 1);
}
#[test]
fn enc_solver() {
assert_eq!(
find_encryption_key(CARD_PUBKEY, DOOR_PUBKEY, SUBJECT_NUM),
14897079
);
}
#[test]
fn part1() {
assert_eq!(
solution1(&format!("{}\n{}", CARD_PUBKEY, DOOR_PUBKEY)),
14897079
)
}
}

View File

@ -10,11 +10,12 @@ pub mod day17;
//pub mod day18; //pub mod day18;
pub mod day19; pub mod day19;
pub mod day2; pub mod day2;
//pub mod day20; pub mod day20;
pub mod day21; pub mod day21;
pub mod day22; pub mod day22;
//pub mod day23; //pub mod day23;
pub mod day24; pub mod day24;
pub mod day25;
pub mod day3; pub mod day3;
pub mod day4; pub mod day4;
pub mod day5; pub mod day5;