1 Commits

Author SHA1 Message Date
81f244bde9 Implementation day 23 2021-12-31 17:44:31 +01:00
9 changed files with 296 additions and 763 deletions

View File

@@ -5,7 +5,7 @@ edition = "2021"
[dependencies]
clap = { version = "3", features = ["derive"] }
clap = { version = "3.0.0-rc.0", features = ["derive"] }
itertools = "0.10"
nom = "7"

View File

@@ -90,65 +90,7 @@ where
let mut buffer = Vec::new();
input.read_to_end(&mut buffer).unwrap();
match parser(&buffer).finish() {
Ok((_, output)) => output,
Err(err) => {
panic!(
"Failed to parse input with error {:?} at \"{}\"",
err.code,
String::from_utf8_lossy(err.input)
);
}
}
}
#[derive(Default)]
pub struct BitSet {
buffer: Vec<u32>,
}
impl BitSet {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(capacity: usize) -> Self {
let buffer = vec![0; capacity / 32];
Self { buffer }
}
fn convert_value(value: usize) -> (usize, u32) {
let chunk = value / 32;
let bit = 1 << (31 - (value % 32));
(chunk, bit)
}
pub fn insert(&mut self, value: usize) -> bool {
let (chunk, bit) = Self::convert_value(value);
if self.buffer.len() <= chunk + 1 {
self.buffer.resize(chunk + 1, 0);
}
let not_present = self.buffer[chunk] & bit;
self.buffer[chunk] |= bit;
not_present == 0
}
pub fn len(&self) -> usize {
self.buffer.iter().map(|c| c.count_ones() as usize).sum()
}
pub fn contains(&self, value: usize) -> bool {
let (chunk, bit) = Self::convert_value(value);
self.buffer
.get(chunk)
.map(|&c| c & bit != 0)
.unwrap_or(false)
}
let (_, output) = parser(&buffer).finish().unwrap();
output
}

View File

@@ -1,95 +1,69 @@
use std::collections::HashMap;
use std::io::Read;
use std::iter::repeat;
use nom::bytes::complete::tag;
use nom::character::complete::newline;
use nom::combinator::map;
use nom::multi::separated_list1;
use nom::sequence::separated_pair;
use nom::sequence::tuple;
use nom::Finish;
use nom::IResult;
use crate::common::ordered;
use crate::common::read_input;
use crate::common::BitSet;
use crate::common::LineIter;
type Coord = (u16, u16);
fn coordinates(input: &[u8]) -> IResult<&[u8], Coord> {
use nom::character::complete::char;
use nom::character::complete::u16;
fn coordinates(input: &str) -> IResult<&str, Coord> {
use nom::character::complete;
separated_pair(u16, char(','), u16)(input)
let (input, (x, _, y)) = tuple((complete::u16, complete::char(','), complete::u16))(input)?;
Ok((input, (x, y)))
}
fn parse_input(input: &[u8]) -> IResult<&[u8], Vec<(Coord, Coord)>> {
let read_line = map(
separated_pair(coordinates, tag(" -> "), coordinates),
|(begin, end)| ordered(begin, end),
);
fn line_definition(input: &str) -> IResult<&str, (Coord, Coord)> {
let (input, (begin, _, end)) = tuple((coordinates, tag(" -> "), coordinates))(input)?;
separated_list1(newline, read_line)(input)
// Sorting the coordinates saves trouble later
Ok((input, ordered(begin, end)))
}
fn stripe(
once: &mut BitSet,
twice: &mut BitSet,
width: usize,
map: &mut HashMap<Coord, u16>,
xs: impl Iterator<Item = u16>,
ys: impl Iterator<Item = u16>,
) {
for (x, y) in xs.zip(ys) {
let index = x as usize + y as usize * width;
if !once.insert(index) {
twice.insert(index);
}
*map.entry((x, y)).or_default() += 1;
}
}
fn part_common(input: &mut dyn Read, diagonals: bool) -> String {
let lines = read_input(input, parse_input);
let mut reader = LineIter::new(input);
let mut map = HashMap::new();
let width = lines
.iter()
.map(|&(_, (x, _))| x as usize + 1)
.max()
.unwrap();
while let Some(line) = reader.next() {
let (begin, end) = line_definition(line).finish().unwrap().1;
let mut once_map = BitSet::new();
let mut twice_map = BitSet::new();
for (begin, end) in lines {
if begin.0 == end.0 {
let y_range = begin.1..=end.1;
stripe(
&mut once_map,
&mut twice_map,
width,
repeat(begin.0),
y_range,
);
stripe(&mut map, repeat(begin.0), y_range);
} else if begin.1 == end.1 {
let x_range = begin.0..=end.0;
stripe(
&mut once_map,
&mut twice_map,
width,
x_range,
repeat(begin.1),
);
stripe(&mut map, x_range, repeat(begin.1));
} else if diagonals {
let x_range = begin.0..=end.0;
let y_range = (begin.1.min(end.1))..=(begin.1.max(end.1));
if begin.1 > end.1 {
// For a downward slope we need to reverse Y
stripe(&mut once_map, &mut twice_map, width, x_range, y_range.rev());
stripe(&mut map, x_range, y_range.rev());
} else {
stripe(&mut once_map, &mut twice_map, width, x_range, y_range);
stripe(&mut map, x_range, y_range);
}
}
}
twice_map.len().to_string()
map.values().filter(|&&v| v > 1).count().to_string()
}
pub fn part1(input: &mut dyn Read) -> String {
@@ -108,6 +82,11 @@ mod tests {
const SAMPLE: &[u8] = include_bytes!("samples/05.txt");
#[test]
fn test_parser() {
assert_eq!(line_definition("6,4 -> 2,0"), Ok(("", ((2, 0), (6, 4)))));
}
#[test]
fn sample_part1() {
test_implementation(part1, SAMPLE, 5)

View File

@@ -182,7 +182,7 @@ mod tests {
fn sample_part1() {
let answers = [16, 12, 23, 31];
for (&sample, answer) in SAMPLE.iter().zip(answers) {
for (&sample, answer) in SAMPLE.into_iter().zip(answers) {
test_implementation(part1, sample, answer);
}
}

View File

@@ -1,8 +1,6 @@
use std::collections::HashMap;
use std::collections::HashSet;
use std::io::Read;
use std::ops::Add;
use std::ops::Deref;
use std::ops::Sub;
use nom::bytes::complete::tag;
@@ -25,10 +23,6 @@ impl Point3 {
pub fn manhattan(&self) -> i32 {
self.0.into_iter().map(i32::abs).sum()
}
pub fn euler_square(&self) -> i32 {
self.0.into_iter().map(|c| c * c).sum()
}
}
impl Sub for Point3 {
@@ -55,44 +49,6 @@ impl Add for Point3 {
}
}
struct Scanner {
visible: Vec<Point3>,
distances: HashMap<i32, (Point3, Point3)>,
}
impl Scanner {
pub fn new(visible: Vec<Point3>) -> Self {
let distances = visible
.iter()
.enumerate()
.flat_map(|(skip, &a)| {
visible[(skip + 1)..]
.iter()
.map(move |&b| ((a - b).euler_square(), (a, b)))
})
.collect();
Self { visible, distances }
}
pub fn can_overlap(&self, other: &Self) -> bool {
other
.distances
.keys()
.filter(|&k| self.distances.contains_key(k))
.count()
>= 11
}
}
impl Deref for Scanner {
type Target = [Point3];
fn deref(&self) -> &Self::Target {
&self.visible
}
}
struct Rotations<'a> {
points: &'a [Point3],
axes: [usize; 3],
@@ -163,56 +119,32 @@ fn parse_point(input: &[u8]) -> IResult<&[u8], Point3> {
)(input)
}
fn parse_input(input: &[u8]) -> IResult<&[u8], Vec<Scanner>> {
fn parse_input(input: &[u8]) -> IResult<&[u8], Vec<Vec<Point3>>> {
use nom::character::complete::i32;
let parse_header = delimited(tag("--- scanner "), i32, tag(" ---\n"));
let parse_scanner = map(
preceded(parse_header, many1(terminated(parse_point, newline))),
Scanner::new,
);
let parse_scanner = preceded(parse_header, many1(terminated(parse_point, newline)));
separated_list1(newline, parse_scanner)(input)
}
fn find_pivot(group: &Scanner, related: &Scanner) -> Option<Point3> {
let mut counter = HashMap::new();
for (distance, &(a, b)) in &group.distances {
if related.distances.contains_key(distance) {
*counter.entry(a).or_insert(0) += 1;
*counter.entry(b).or_insert(0) += 1;
}
}
counter
.into_iter()
.max_by_key(|(_, count)| *count)
.map(|t| t.0)
}
fn try_overlap(matched: &Scanner, candidate: &Scanner) -> Option<(Point3, Scanner)> {
if !matched.can_overlap(candidate) {
return None;
}
let matched_pivot = find_pivot(matched, candidate)?;
let correct: HashSet<_> = matched.iter().map(|&base| base - matched_pivot).collect();
fn try_overlap(
correct: &[(Point3, HashSet<Point3>)],
candidate: &[Point3],
) -> Option<(Point3, Vec<Point3>)> {
let mut relative = HashSet::new();
for rot in Rotations::new(candidate) {
for &start in &rot {
let translated_iter = rot.iter().map(|&other| other - start);
relative.clear();
if translated_iter
.clone()
.filter(|p| correct.contains(p))
.count()
>= 12
{
relative.extend(rot.iter().map(|&other| other - start));
if let Some((base, _)) = correct.iter().find(|(_, correct_relative)| {
correct_relative.intersection(&relative).count() >= 12
}) {
// Found a solution, build the correct output
let translated = translated_iter.map(|point| point + matched_pivot).collect();
let translated = relative.drain().map(|point| point + *base).collect();
return Some((start - matched_pivot, Scanner::new(translated)));
return Some((start - *base, translated));
}
}
}
@@ -225,30 +157,33 @@ fn parts_common(input: &mut dyn Read) -> (HashSet<Point3>, Vec<Point3>) {
let mut points: HashSet<_> = scanners[0].iter().copied().collect();
let mut todo = vec![scanners.remove(0)];
let mut todo = vec![std::mem::take(&mut scanners[0])];
let mut scanners_found = vec![Point3::default()];
while let Some(matched) = todo.pop() {
if scanners.is_empty() {
if scanners.iter().all(Vec::is_empty) {
break;
}
let mut i = 0;
let relative: Vec<(Point3, HashSet<Point3>)> = matched
.iter()
.map(|&base| (base, matched.iter().map(|&other| (other - base)).collect()))
.collect();
while i < scanners.len() {
if let Some((scanner, result)) = try_overlap(&matched, &scanners[i]) {
scanners.remove(i);
for candidate in &mut scanners {
if candidate.is_empty() {
continue;
}
if let Some((scanner, result)) = try_overlap(&relative, candidate) {
scanners_found.push(scanner);
points.extend(result.iter().copied());
todo.push(result);
} else {
i += 1;
candidate.clear();
}
}
}
assert!(scanners.is_empty());
(points, scanners_found)
}

View File

@@ -1,124 +1,10 @@
use std::fmt::Display;
use std::collections::HashSet;
use std::io::Read;
use std::ops::Index;
use crate::common::BitSet;
use std::mem::swap;
type Translation = [bool; 512];
struct Field {
width: usize,
height: usize,
infinity: bool,
finite: BitSet,
}
impl Field {
pub fn from_input<'a>(input: impl Iterator<Item = &'a [u8]>) -> Self {
let mut input = input.peekable();
let width = input.peek().unwrap().len();
let mut finite = BitSet::new();
let len = input
.flatten()
.enumerate()
.map(|(index, &c)| {
if c == b'#' {
finite.insert(index);
}
})
.count();
debug_assert_eq!(len % width, 0);
let height = len / width;
Self {
width,
height,
finite,
infinity: false,
}
}
pub fn advance(&mut self, translation: &[bool; 512]) {
const INDEX_MASK: usize = (1 << 9) - 1;
let new_width = self.width + 2;
let new_height = self.height + 2;
let mut new_finite = BitSet::with_capacity(new_width * new_height);
for y in 0..new_height {
let mut mask = if self.infinity { INDEX_MASK } else { 0 };
for x in 0..new_width {
const COLUMN_MASK: usize = 0b001001001;
let mut submask = if self.infinity { COLUMN_MASK } else { 0 };
for y in y.saturating_sub(2)..=y {
submask = (submask << 3) | (self[(x, y)] as usize);
}
mask <<= 1;
mask &= !COLUMN_MASK;
mask |= submask;
mask &= INDEX_MASK;
if translation[mask] {
let index = x + y * new_width;
new_finite.insert(index);
}
}
}
self.width += 2;
self.height += 2;
self.finite = new_finite;
self.infinity = translation[if self.infinity { INDEX_MASK } else { 0 }];
}
pub fn len(&self) -> usize {
assert!(!self.infinity);
self.finite.len()
}
}
impl Index<(usize, usize)> for Field {
type Output = bool;
#[inline]
fn index(&self, (x, y): (usize, usize)) -> &Self::Output {
if x >= self.width || y >= self.height {
return &self.infinity;
}
let index = x + y * self.width;
if self.finite.contains(index) {
&true
} else {
&false
}
}
}
impl Display for Field {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for y in 0..self.height {
for x in 0..self.width {
if self[(x, y)] {
write!(f, "#")?
} else {
write!(f, ".")?
}
}
writeln!(f)?;
}
Ok(())
}
}
type Point = (i32, i32);
type Field = HashSet<Point>;
fn read_input(input: &mut dyn Read) -> (Translation, Field) {
let mut buffer = Vec::new();
@@ -133,16 +19,67 @@ fn read_input(input: &mut dyn Read) -> (Translation, Field) {
.zip(it.next().unwrap())
.for_each(|(t, &c)| *t = c == b'#');
let field = Field::from_input(it.skip(1));
let mut field = Field::default();
for (y, line) in it.skip(1).enumerate() {
for (x, _) in line.iter().enumerate().filter(|(_, &c)| c == b'#') {
field.insert((x as i32, y as i32));
}
}
(translation, field)
}
fn find_dimensions(field: &Field) -> ((i32, i32), (i32, i32)) {
field
.iter()
.fold(((0, 0), (0, 0)), |((xmin, xmax), (ymin, ymax)), &(x, y)| {
((xmin.min(x), xmax.max(x)), (ymin.min(y), ymax.max(y)))
})
}
fn advance(translation: &Translation, field: &Field, new_field: &mut Field, infinity: &mut bool) {
const INDEX_MASK: usize = (1 << 9) - 1;
new_field.clear();
let ((xmin, xmax), (ymin, ymax)) = find_dimensions(field);
for x in (xmin - 1)..=(xmax + 1) {
let mut index = if *infinity { INDEX_MASK } else { 0 };
for y in (ymin - 1)..=(ymax + 1) {
for dx in -1..=1 {
index <<= 1;
let nx = x + dx;
let ny = y + 1;
if nx < xmin || nx > xmax || ny < ymin || ny > ymax {
index |= *infinity as usize;
} else if field.contains(&(nx, ny)) {
index |= 1;
}
}
index &= INDEX_MASK;
if translation[index] {
new_field.insert((x, y));
}
}
}
*infinity = translation[if *infinity { 511 } else { 0 }]
}
fn parts_common(input: &mut dyn Read, count: usize) -> String {
let (translation, mut field) = read_input(input);
let mut new_field = Field::new();
let mut infinity = false;
for _ in 0..count {
field.advance(&translation);
advance(&translation, &field, &mut new_field, &mut infinity);
swap(&mut field, &mut new_field);
}
field.len().to_string()

View File

@@ -8,9 +8,8 @@ use std::mem::swap;
use crate::common::LineIter;
type Item<const S: usize> = (u32, State<S>);
type Todo<const S: usize> = BinaryHeap<Reverse<Item<S>>>;
type Visited<const S: usize> = HashMap<State<S>, u32>;
type Item = (u32, u32, State);
type Todo = BinaryHeap<Reverse<Item>>;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash)]
enum Pod {
@@ -29,24 +28,6 @@ impl Pod {
Pod::D => 1000,
}
}
pub fn dest(self) -> usize {
self as usize
}
}
impl TryFrom<usize> for Pod {
type Error = usize;
fn try_from(index: usize) -> Result<Self, Self::Error> {
match index {
0 => Ok(Pod::A),
1 => Ok(Pod::B),
2 => Ok(Pod::C),
3 => Ok(Pod::D),
_ => Err(index),
}
}
}
impl TryFrom<char> for Pod {
@@ -63,40 +44,26 @@ impl TryFrom<char> for Pod {
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
struct State<const S: usize> {
hallway: [Option<Pod>; 11],
rooms: [[Option<Pod>; S]; 4],
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
struct State {
hallway: [Option<Pod>; 7],
rooms: [[Option<Pod>; 2]; 4],
}
fn room_hallway_pos(room: usize) -> usize {
room * 2 + 2
}
fn abs_delta(a: usize, b: usize) -> usize {
if a < b {
b - a
} else {
a - b
}
}
impl<const S: usize> State<S> {
const VALID_HALLWAY_POS: [usize; 7] = [0, 1, 3, 5, 7, 9, 10];
impl State {
pub fn is_done(&self) -> bool {
self == &State {
hallway: Default::default(),
hallway: [None; 7],
rooms: [
[Some(Pod::A); S],
[Some(Pod::B); S],
[Some(Pod::C); S],
[Some(Pod::D); S],
[Some(Pod::A); 2],
[Some(Pod::B); 2],
[Some(Pod::C); 2],
[Some(Pod::D); 2],
],
}
}
fn add_to_queue(self, cost: u32, todo: &mut Todo<S>, visited: &mut Visited<S>) {
fn add_to_queue(self, cost: u32, todo: &mut Todo, visited: &mut HashMap<Self, u32>) {
let entry = visited.entry(self.clone());
if matches!(&entry, Entry::Occupied(entry) if *entry.get() <= cost) {
@@ -104,219 +71,167 @@ impl<const S: usize> State<S> {
return;
}
// print!("Next: \n{}", self);
// nightly only :'(
// entry.insert(cost);
*entry.or_default() = cost;
todo.push(Reverse((cost + self.estimate(), self)))
todo.push(Reverse((cost + self.estimate(), cost, self)))
}
fn estimate(&self) -> u32 {
// A* estimate. For every entry that is not already "at rest", the cost is the cost
// required to get it to the top of its intended room.
let mut estimate = 0;
// Cost to enter the hole for all pods that still need to
let enter_estimate: u32 = self
.rooms
.iter()
.enumerate()
.map(|(index, room)| {
let pod = Pod::try_from(index).unwrap();
for (x, &pod) in self.hallway.iter().enumerate() {
if let Some(pod) = pod {
let cost = if x == 0 {
4 + pod as u32 * 2
} else if x == 6 {
4 + (3 - pod as u32) * 2
} else if x <= (pod as usize) + 1 {
2 + 2 * (pod as u32 + (x as u32 - 1))
} else {
2 + 2 * (x as u32 - pod as u32 - 2)
};
estimate += cost * pod.cost();
}
}
room.iter()
.enumerate()
.rev()
.skip_while(|&(_, &entry)| entry == Some(pod))
.map(|(index, _)| index as u32 + 1)
.sum::<u32>()
* pod.cost()
})
.sum();
// Cost for all of the hallway to move to above their intended rooms
let hallway_estimate: u32 = self
.hallway
.iter()
.enumerate()
.filter_map(|(pos, &pod)| {
let pod = pod?;
let destination_pos = room_hallway_pos(pod.dest());
Some(abs_delta(pos, destination_pos) as u32 * pod.cost())
})
.sum();
// Cost to move out of the room and above the correct rooms
let rooms_estimate: u32 = self
.rooms
.iter()
.enumerate()
.map(|(room_index, room)| {
let hallway_pos = room_hallway_pos(room_index);
room.iter()
.enumerate()
.rev()
.skip_while(|&(_, &entry)| {
entry.map(|pod| pod.dest() == room_index).unwrap_or(false)
})
.filter_map(|(room_pos, &pod)| {
let pod = pod?;
let destination_pos = room_hallway_pos(pod.dest());
let steps = 1 + room_pos + abs_delta(hallway_pos, destination_pos).max(2);
Some(steps as u32 * pod.cost())
})
.sum::<u32>()
})
.sum();
enter_estimate + hallway_estimate + rooms_estimate
}
pub fn generate_next(&self, cost: u32, todo: &mut Todo<S>, visited: &mut Visited<S>) {
self.room_to_hallway(cost, todo, visited);
self.hallway_to_room(cost, todo, visited);
}
fn room_to_hallway(&self, cost: u32, todo: &mut Todo<S>, visited: &mut Visited<S>) {
for (index, room) in self.rooms.iter().enumerate() {
// Check if we even want to move anything out of this room
if room
if let Some(last) = room
.iter()
.all(|entry| entry.map(|pod| pod.dest() == index).unwrap_or(true))
.rposition(|&pod| !matches!(pod, Some(pod) if pod as usize == index))
{
for pos in 0..=last {
if let Some(pod) = room[pos] {
if pod as usize != index {
let abs_diff = index.max(pod as usize) - index.min(pod as usize);
estimate += (pos + 2 + 2 * abs_diff) as u32 * pod.cost();
}
}
}
}
}
estimate
}
pub fn generate_next(&self, cost: u32, todo: &mut Todo, visited: &mut HashMap<Self, u32>) {
self.generate_hallway(cost, todo, visited);
self.generate_rooms(cost, todo, visited);
}
fn generate_rooms(&self, cost: u32, todo: &mut Todo, visited: &mut HashMap<Self, u32>) {
for (index, room) in self.rooms.iter().enumerate() {
// Check what part of the room should still move
if let Some(last) = room
.iter()
.rposition(|&pod| !matches!(pod, Some(pod) if pod as usize == index))
{
for pos in 0..=last {
let pod = match room[pos] {
Some(pod) => pod,
None => continue,
};
// Check if we can move up
if pos > 0 && room[pos - 1].is_none() {
let mut new_state = self.clone();
new_state.rooms[index].swap(pos, pos - 1);
let new_cost = cost + pod.cost();
new_state.add_to_queue(new_cost, todo, visited);
}
// Check if we can move down
if pos + 1 < room.len() && room[pos + 1].is_none() {
let mut new_state = self.clone();
new_state.rooms[index].swap(pos, pos + 1);
let new_cost = cost + pod.cost();
new_state.add_to_queue(new_cost, todo, visited);
}
}
// Check if we can pop out of the room
if let Some(pod) = room[0] {
for pos in [index + 1, index + 2] {
if self.hallway[pos].is_none() {
let mut new_state = self.clone();
swap(&mut new_state.rooms[index][0], &mut new_state.hallway[pos]);
let new_cost = cost + pod.cost();
new_state.add_to_queue(new_cost, todo, visited);
}
}
}
}
}
}
fn generate_hallway(&self, cost: u32, todo: &mut Todo, visited: &mut HashMap<Self, u32>) {
for index in 0..self.hallway.len() {
let pod = if let Some(pod) = self.hallway[index] {
pod
} else {
continue;
};
// Check if we can move right
if index + 1 < self.hallway.len() && self.hallway[index + 1].is_none() {
let mut new_state = self.clone();
new_state.hallway.swap(index, index + 1);
let added_cost = if index == 0 || index == 5 {
pod.cost()
} else {
2 * pod.cost()
};
let new_cost = cost + added_cost;
new_state.add_to_queue(new_cost, todo, visited);
}
let (pos, pod) = room
.iter()
.enumerate()
.find_map(|(pos, entry)| entry.map(|pod| (pos, pod)))
.unwrap(); // Safe unwrap, we know it exists from above.
// Check if we can move left
if index > 1 && self.hallway[index - 1].is_none() {
let mut new_state = self.clone();
new_state.hallway.swap(index, index - 1);
let added_cost = if index == 1 || index == 6 {
pod.cost()
} else {
2 * pod.cost()
};
let base_cost = 1 + pos;
let hallway_pos = room_hallway_pos(index);
let new_cost = cost + added_cost;
new_state.add_to_queue(new_cost, todo, visited);
}
let mut queue_new = |new_pos, new_cost| {
// Check if we can pop into a room to the right
if (1..=4).contains(&index) && self.rooms[index - 1][0].is_none() {
let mut new_state = self.clone();
swap(
&mut new_state.hallway[new_pos],
&mut new_state.rooms[index][pos],
&mut new_state.hallway[index],
&mut new_state.rooms[index - 1][0],
);
new_state.add_to_queue(new_cost + cost, todo, visited)
};
// Check positions to the left
for new_pos in (0..hallway_pos).rev() {
if self.hallway[new_pos].is_some() {
// Hit an occupied room
break;
}
if !Self::VALID_HALLWAY_POS.contains(&new_pos) {
// Not allowed to stop here
continue;
}
let new_cost = (base_cost + hallway_pos - new_pos) as u32 * pod.cost();
queue_new(new_pos, new_cost);
let new_cost = cost + 2 * pod.cost();
new_state.add_to_queue(new_cost, todo, visited);
}
// And to the right
for new_pos in hallway_pos..self.hallway.len() {
if self.hallway[new_pos].is_some() {
// Hit an occupied room
break;
}
if (2..=5).contains(&index) && self.rooms[index - 2][0].is_none() {
let mut new_state = self.clone();
swap(
&mut new_state.hallway[index],
&mut new_state.rooms[index - 2][0],
);
if !Self::VALID_HALLWAY_POS.contains(&new_pos) {
// Not allowed to stop here
continue;
}
let new_cost = (base_cost + new_pos - hallway_pos) as u32 * pod.cost();
queue_new(new_pos, new_cost);
let new_cost = cost + 2 * pod.cost();
new_state.add_to_queue(new_cost, todo, visited);
}
}
}
fn hallway_to_room(&self, cost: u32, todo: &mut Todo<S>, visited: &mut Visited<S>) {
for (pos, pod) in self
.hallway
.iter()
.enumerate()
.filter_map(|(pos, pod)| pod.map(|pod| (pos, pod)))
{
let room = pod.dest();
let new_hallway_pos = room_hallway_pos(room);
// Check if the path is free
let in_between = if new_hallway_pos < pos {
&self.hallway[(new_hallway_pos + 1)..pos]
} else {
&self.hallway[(pos + 1)..new_hallway_pos]
};
if in_between.iter().any(Option::is_some) {
// Something's in the way
continue;
}
// Check if we can move into the room
if self.rooms[room]
.iter()
.copied()
.flatten()
.any(|other| other != pod)
{
// Scared of other pods
continue;
}
let room_pos = if let Some(pos) = self.rooms[room].iter().rposition(Option::is_none) {
pos
} else {
continue;
};
let new_cost = (abs_delta(pos, new_hallway_pos) + room_pos + 1) as u32 * pod.cost();
let mut new_state = self.clone();
swap(
&mut new_state.hallway[pos],
&mut new_state.rooms[room][room_pos],
);
new_state.add_to_queue(cost + new_cost, todo, visited);
}
}
pub fn solve(&self) -> u32 {
let mut todo = Todo::new();
let mut visited = HashMap::new();
visited.insert(self.clone(), 0);
todo.push(Reverse((self.estimate(), self.clone())));
while let Some(Reverse((_, state))) = todo.pop() {
let cost = *visited.get(&state).unwrap_or(&0);
if state.is_done() {
return cost;
}
state.generate_next(cost, &mut todo, &mut visited);
}
panic!("No route found!")
}
}
impl<const S: usize> Display for State<S> {
impl Display for State {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let helper = |opt_pod| match opt_pod {
Some(Pod::A) => 'A',
@@ -326,14 +241,13 @@ impl<const S: usize> Display for State<S> {
None => '.',
};
writeln!(f, "#############")?;
write!(f, "#")?;
for entry in self.hallway {
write!(f, "{}", helper(entry))?;
write!(f, "#{}{}", helper(self.hallway[0]), helper(self.hallway[1]))?;
for i in 2..=5 {
write!(f, ".{}", helper(self.hallway[i]))?;
}
writeln!(f, "#")?;
writeln!(f, "{}#", helper(self.hallway[6]))?;
for i in 0..S {
for i in 0..(self.rooms[0].len()) {
writeln!(
f,
" #{}#{}#{}#{}#",
@@ -348,12 +262,9 @@ impl<const S: usize> Display for State<S> {
}
}
fn read_input(input: &mut dyn Read) -> State<2> {
fn read_input(input: &mut dyn Read) -> State {
let mut reader = LineIter::new(input);
let mut state = State {
hallway: Default::default(),
rooms: Default::default(),
};
let mut state = State::default();
let _ = reader.next();
let _ = reader.next();
@@ -376,44 +287,28 @@ fn read_input(input: &mut dyn Read) -> State<2> {
pub fn part1(input: &mut dyn Read) -> String {
let state = read_input(input);
let mut todo = Todo::new();
state.solve().to_string()
let mut visited = HashMap::new();
visited.insert(state.clone(), 0);
todo.push(Reverse((state.estimate(), 0, state)));
while let Some(Reverse((_, cost, state))) = todo.pop() {
if state.is_done() {
return cost.to_string();
}
// println!("\nExpanding:\n{}", state);
state.generate_next(cost, &mut todo, &mut visited);
}
panic!("No route found!")
}
pub fn part2(input: &mut dyn Read) -> String {
let state2 = read_input(input);
let state4 = State {
hallway: Default::default(),
rooms: [
[
state2.rooms[0][0],
Some(Pod::D),
Some(Pod::D),
state2.rooms[0][1],
],
[
state2.rooms[1][0],
Some(Pod::C),
Some(Pod::B),
state2.rooms[1][1],
],
[
state2.rooms[2][0],
Some(Pod::B),
Some(Pod::A),
state2.rooms[2][1],
],
[
state2.rooms[3][0],
Some(Pod::A),
Some(Pod::C),
state2.rooms[3][1],
],
],
};
state4.solve().to_string()
pub fn part2(_input: &mut dyn Read) -> String {
todo!()
}
#[cfg(test)]
@@ -430,9 +325,9 @@ mod tests {
hallway: Default::default(),
rooms: [
[Some(Pod::A); 2],
[Some(Pod::B); 2],
[Some(Pod::C); 2],
[Some(Pod::D); 2],
[Some(Pod::B), Some(Pod::B)],
[Some(Pod::C), Some(Pod::C)],
[Some(Pod::D), Some(Pod::D)],
],
};
@@ -443,9 +338,4 @@ mod tests {
fn sample_part1() {
test_implementation(part1, SAMPLE, 12521);
}
#[test]
fn sample_part2() {
test_implementation(part2, SAMPLE, 44169);
}
}

View File

@@ -1,159 +1,9 @@
//! Very input-specific reverse-engineered solution
//!
//! # General implementation
//!
//! The code in the examples is a series of 14 times this:
//!
//! ```txt
//! inp w -> read digit
//! mul x 0
//! add x z
//! mod x 26 -> x = z % 26
//! div z $A -> pop Z (see below)
//! add x $B
//! eql x w -> x = ((z + $B) == w)
//! eql x 0 -> x = ((z + $B) != w)
//! mul y 0
//! add y 25
//! mul y x
//! add y 1 -> if x { 26 } else { 1 }
//! mul z y -> if x { z *= 26 } (push z, see below)
//! mul y 0
//! add y w
//! add y $C -> y = w + $C
//! mul y x
//! add z y -> if x { z += w + $C }
//! ```
//!
//! `$A` is either `1` or `26` which we can translate to a bool `$A == 26` for convenience. This
//! simplifies to the following rust.
//!
//! ```
//! fn validate<const A: bool, const B: i32, const C: i32>(mut z: i32, digit: i32) -> i32 {
//! let x = (z % 26 + B) != digit;
//! if A {
//! z /= 26;
//! }
//!
//! if x {
//! z = 26 * z + digit + C;
//! }
//!
//! z
//! }
//! ```
//!
//! In human terms, `z` is used to hold a base 26 number. When `$A` is `true`, we pop off the least
//! significant digit by dividing by 26. Then, depending on whether `(z + $B) % 26` is equal to our
//! digit, we push `digit + $C`. Ideally, we should pop as often as we push in order to arrive at `z
//! == 0` in the end. The input contains 7 pops, so we want each of those to not push.
//!
//! To solve this problem, we use a backtracking memoizing algorithm, where we cancel every sequence
//! that fails to pop at some point. A pop is failed whenever we execute a validation pop where we
//! can pop if `x` happened to be set to `0` at the time of the check. We can memoize this over the
//! running value of `z`.
//!
//! This implementation probably doesn't work on other people's input; to fix it, you'll want to
//! update the `parse_step` function. Good luck with that.
use std::collections::HashMap;
use std::io::Read;
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::newline;
use nom::combinator::map;
use nom::multi::separated_list1;
use nom::sequence::delimited;
use nom::sequence::preceded;
use nom::sequence::tuple;
use nom::IResult;
use crate::common::read_input;
type Cache = HashMap<(usize, i32), Option<i64>>;
#[derive(Debug)]
struct Step(bool, i32, i32);
impl Step {
fn evaluate(&self, digit: i32, z: i32) -> Option<i32> {
if self.0 {
(z % 26 + self.1 == digit).then(|| z / 26)
} else {
Some(z * 26 + digit + self.2)
}
}
pub fn part1(_input: &mut dyn Read) -> String {
todo!()
}
fn parse_step(input: &[u8]) -> IResult<&[u8], Step> {
use nom::character::complete::i32;
let parse_pop = preceded(
tag("inp w\nmul x 0\nadd x z\nmod x 26\ndiv z "),
alt((map(tag("1"), |_| false), map(tag("26"), |_| true))),
);
let parse_a = preceded(tag("\nadd x "), i32);
let parse_b = delimited(
tag("\neql x w\neql x 0\nmul y 0\nadd y 25\nmul y x\nadd y 1\nmul z y\nmul y 0\nadd y w\nadd y "),
i32,
tag("\nmul y x\nadd z y"),
);
map(tuple((parse_pop, parse_a, parse_b)), |(pop, a, b)| {
Step(pop, a, b)
})(input)
}
fn parse_input(input: &[u8]) -> IResult<&[u8], Vec<Step>> {
separated_list1(newline, parse_step)(input)
}
fn optimize(
current: usize,
steps: &[Step],
digits: &[i32],
z: i32,
cache: &mut Cache,
) -> Option<i64> {
if current >= steps.len() {
return (z == 0).then(|| 0);
}
if let Some(&memoized) = cache.get(&(current, z)) {
return memoized;
}
let result = digits.iter().find_map(|&digit| {
let z = steps[current].evaluate(digit, z)?;
let result = optimize(current + 1, steps, digits, z, cache)?;
Some(result + digit as i64 * 10i64.pow(13 - current as u32))
});
cache.insert((current, z), result);
result
}
fn parts_common(input: &mut dyn Read, digits: &[i32]) -> String {
let steps = read_input(input, parse_input);
let mut cache = Cache::new();
optimize(0, &steps, digits, 0, &mut cache)
.unwrap()
.to_string()
}
pub fn part1(input: &mut dyn Read) -> String {
let digits = [9, 8, 7, 6, 5, 4, 3, 2, 1];
parts_common(input, &digits)
}
pub fn part2(input: &mut dyn Read) -> String {
let digits = [1, 2, 3, 4, 5, 6, 7, 8, 9];
parts_common(input, &digits)
pub fn part2(_input: &mut dyn Read) -> String {
todo!()
}

View File

@@ -92,7 +92,7 @@ pub fn get_implementation(day: usize, part2: bool) -> Solution {
#[cfg(test)]
fn test_implementation(solution: Solution, data: &[u8], answer: impl ToString) {
let result = solution(&mut &*data);
let result = solution(&mut &data[..]);
assert_eq!(answer.to_string(), result);
}