Skip to content

Commit 3599efe

Browse files
committed
Day 15
1 parent 11ec71d commit 3599efe

File tree

5 files changed

+690
-82
lines changed

5 files changed

+690
-82
lines changed

src/day02.rs

Lines changed: 2 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use std::collections::{HashMap, HashSet};
1+
use std::collections::HashMap;
22
use std::hash::Hash;
3+
use crate::util::IteratorExt;
34

45
pub fn solve(input: &str) -> (usize, usize) {
56
let input = parse_input(input);
@@ -132,84 +133,3 @@ where
132133
.into_iter()
133134
.collect()
134135
}
135-
136-
137-
// upside: can be chained nicely, is lazy
138-
// downside: need clonable, up to 2x memory requirements
139-
struct Uniques<I, T>
140-
where
141-
T: Eq + Hash + Clone,
142-
I: Iterator<Item = T>,
143-
{
144-
input: I,
145-
seen: HashSet<T>,
146-
}
147-
148-
impl<I, T> Uniques<I, T>
149-
where
150-
T: Eq + Hash + Clone,
151-
I: Iterator<Item = T>,
152-
{
153-
fn new(input: I) -> Self {
154-
Self {
155-
input,
156-
seen: HashSet::new(),
157-
}
158-
}
159-
}
160-
161-
impl<I, T> Iterator for Uniques<I, T>
162-
where
163-
T: Eq + Hash + Clone,
164-
I: Iterator<Item = T>,
165-
{
166-
type Item = T;
167-
168-
fn next(&mut self) -> Option<Self::Item> {
169-
while let Some(item) = self.input.next() {
170-
if self.seen.insert(item.clone()) {
171-
return Some(item);
172-
}
173-
}
174-
None
175-
}
176-
}
177-
178-
// Extension trait to make it convenient to use
179-
trait IteratorExt: Iterator {
180-
fn uniques(self) -> Uniques<Self, Self::Item>
181-
where
182-
Self: Sized,
183-
Self::Item: Eq + Hash + Clone,
184-
{
185-
Uniques::new(self)
186-
}
187-
}
188-
189-
impl<I: Iterator> IteratorExt for I {}
190-
191-
#[cfg(test)]
192-
mod tests {
193-
use super::*;
194-
195-
#[test]
196-
fn test_uniques() {
197-
let input = vec![1, 2, 3, 2, 4, 1, 5];
198-
let result: Vec<i32> = input.into_iter().uniques().collect();
199-
assert_eq!(result, vec![1, 2, 3, 4, 5]);
200-
}
201-
202-
#[test]
203-
fn test_uniques_strings() {
204-
let input = vec!["a", "b", "c", "b", "a", "d"];
205-
let result: Vec<&str> = input.into_iter().uniques().collect();
206-
assert_eq!(result, vec!["a", "b", "c", "d"]);
207-
}
208-
209-
#[test]
210-
fn test_empty() {
211-
let input: Vec<i32> = vec![];
212-
let result: Vec<i32> = input.into_iter().uniques().collect();
213-
assert_eq!(result, vec![]);
214-
}
215-
}

src/day15.rs

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
use core::fmt;
2+
3+
use crate::{
4+
grid::{dyadic, Direction, Grid, GridParser, Index, Point, Vector},
5+
util::bfs,
6+
};
7+
8+
pub fn solve(input: &str) -> (usize, usize) {
9+
let (mut warehouse, movements) = parse(input);
10+
let mut warehouse2 = warehouse.clone().into_part2();
11+
(
12+
part1(&mut warehouse, &movements),
13+
part2(&mut warehouse2, &movements),
14+
)
15+
}
16+
17+
fn part1(warehouse: &mut Warehouse, movements: &[Direction]) -> usize {
18+
for &m in movements.into_iter() {
19+
warehouse.do_move(m)
20+
}
21+
warehouse.score()
22+
}
23+
24+
fn part2(warehouse: &mut Warehouse2, movements: &[Direction]) -> usize {
25+
for &m in movements.into_iter() {
26+
warehouse.do_move(m)
27+
}
28+
warehouse.score()
29+
}
30+
31+
fn score(i: Index) -> usize {
32+
let (x, y) = i.into();
33+
x + y * 100
34+
}
35+
36+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37+
enum Thing {
38+
Wall,
39+
Box,
40+
Floor,
41+
}
42+
43+
#[derive(Debug, Clone)]
44+
struct Warehouse {
45+
robot: Point,
46+
map: Grid<Thing>,
47+
}
48+
49+
impl Warehouse {
50+
fn new(plan: &str) -> Warehouse {
51+
// Here we iterate twice over plan - this not cost a meaningful amount of time
52+
let map = GridParser::from([
53+
('#', Thing::Wall),
54+
('.', Thing::Floor),
55+
('O', Thing::Box),
56+
('@', Thing::Floor),
57+
])
58+
.parse(plan)
59+
.unwrap();
60+
Warehouse {
61+
robot: plan
62+
.lines()
63+
.flat_map(|l| l.chars())
64+
.enumerate()
65+
.filter_map(|(i, c)| match c {
66+
'@' => Some(dyadic(map.size, i).try_into().unwrap()),
67+
_ => None,
68+
})
69+
.next()
70+
.unwrap(),
71+
map,
72+
}
73+
}
74+
75+
fn do_move(&mut self, movement: Direction) {
76+
let step = movement.into();
77+
let start = self.robot + step;
78+
let mut end = start;
79+
80+
while self.map.at_point(end) == Some(&Thing::Box) {
81+
end += step;
82+
}
83+
84+
if self.map.at_point(end) == Some(&Thing::Wall) {
85+
// blocked
86+
return;
87+
}
88+
89+
self.robot = start;
90+
self.map
91+
.swap(start.try_into().unwrap(), end.try_into().unwrap());
92+
}
93+
94+
fn score(&self) -> usize {
95+
self.map
96+
.iter_indices()
97+
.filter(|&i| self.map[i] == Thing::Box)
98+
.map(score)
99+
.sum()
100+
}
101+
102+
fn into_part2(self) -> Warehouse2 {
103+
Warehouse2 {
104+
robot: self.robot.scaled_x(2),
105+
map: Grid::new(
106+
(self.map.size.0 * 2, self.map.size.1),
107+
self.map
108+
.elements
109+
.into_iter()
110+
.flat_map(|t| match t {
111+
Thing::Wall => [Thing2::Wall, Thing2::Wall],
112+
Thing::Box => [Thing2::BoxLeft, Thing2::BoxRight],
113+
Thing::Floor => [Thing2::Floor, Thing2::Floor],
114+
})
115+
.collect(),
116+
),
117+
}
118+
}
119+
}
120+
121+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122+
enum Thing2 {
123+
Wall,
124+
BoxLeft,
125+
BoxRight,
126+
Floor,
127+
}
128+
129+
fn is_box(t: &Thing2) -> bool {
130+
t == &Thing2::BoxLeft || t == &Thing2::BoxRight
131+
}
132+
133+
#[derive(Debug)]
134+
struct Warehouse2 {
135+
robot: Point,
136+
map: Grid<Thing2>,
137+
}
138+
139+
impl Warehouse2 {
140+
fn do_move(&mut self, movement: Direction) {
141+
let step = movement.into();
142+
self.push(self.robot, step);
143+
if self.map.at_point(self.robot + step) == Some(&Thing2::Floor) {
144+
self.robot += step;
145+
}
146+
}
147+
148+
fn push(&mut self, pos: Point, step: Vector) {
149+
let pushes = |p| {
150+
let next = p + step;
151+
if self.map.at_point(next).is_some_and(is_box) {
152+
vec![next, Warehouse2::other_box(&self.map, next).unwrap()]
153+
} else {
154+
Vec::new()
155+
}
156+
};
157+
158+
let potentially_pushed: Vec<_> = bfs(vec![pos], |&p| pushes(p)).skip(1).collect();
159+
160+
if potentially_pushed
161+
.iter()
162+
.any(|&p| self.map.at_point(p + step) == Some(&Thing2::Wall))
163+
{
164+
return;
165+
}
166+
167+
// note: rev works here because we did a bfs
168+
for p in potentially_pushed.into_iter().rev() {
169+
self.map
170+
.swap(p.try_into().unwrap(), (p + step).try_into().unwrap());
171+
}
172+
}
173+
174+
fn other_box(map: &Grid<Thing2>, p: Point) -> Option<Point> {
175+
match map.at_point(p) {
176+
Some(&Thing2::BoxLeft) => Some(p + Direction::Right.into()),
177+
Some(&Thing2::BoxRight) => Some(p + Direction::Left.into()),
178+
_ => None,
179+
}
180+
}
181+
182+
fn score(&self) -> usize {
183+
self.map
184+
.iter_indices()
185+
.filter(|&i| self.map[i] == Thing2::BoxLeft)
186+
.map(score)
187+
.sum()
188+
}
189+
}
190+
191+
// no error handling ¯\_(ツ)_/¯
192+
fn parse(input: &str) -> (Warehouse, Vec<Direction>) {
193+
fn from_arrow(a: char) -> Direction {
194+
match a {
195+
'^' => Direction::Up,
196+
'>' => Direction::Right,
197+
'v' => Direction::Down,
198+
'<' => Direction::Left,
199+
_ => panic!("Not an arrow"),
200+
}
201+
}
202+
let mut blocks = input.split("\n\n");
203+
if let (Some(warehouse_plan), Some(movements)) = (blocks.next(), blocks.next()) {
204+
return (
205+
Warehouse::new(warehouse_plan),
206+
movements
207+
.lines()
208+
.flat_map(|l| l.chars().map(from_arrow))
209+
.collect(),
210+
);
211+
}
212+
panic!("Input looks unexpected");
213+
}
214+
215+
impl fmt::Display for Warehouse2 {
216+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217+
fn to_char(t: Thing2) -> char {
218+
match t {
219+
Thing2::Wall => '#',
220+
Thing2::BoxLeft => '[',
221+
Thing2::BoxRight => ']',
222+
Thing2::Floor => '.',
223+
}
224+
}
225+
let robot = self.robot.try_into().unwrap();
226+
for row in self.map.iter_indices_by_rows().into_iter() {
227+
for idx in row {
228+
if idx == robot {
229+
write!(f, "@")?;
230+
} else {
231+
write!(f, "{}", to_char(self.map[idx]))?;
232+
}
233+
}
234+
write!(f, "\n")?;
235+
}
236+
Ok(())
237+
}
238+
}

0 commit comments

Comments
 (0)