Day 20 part 2 solution.
This commit is contained in:
parent
5535aaf810
commit
d935de1fb0
@ -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() {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ 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;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user