aoc2023/18/src/main.rs

313 lines
8.0 KiB
Rust
Raw Normal View History

2023-12-17 23:04:56 -08:00
use itertools::Itertools;
use std::collections::{HashMap, LinkedList};
use std::fmt::{Display, Write};
use std::fs::File;
use std::io::{BufRead, BufReader, Lines};
use std::ops::Range;
use std::time::Instant;
// BOILERPLATE
type InputIter = Lines<BufReader<File>>;
fn get_input() -> InputIter {
let f = File::open("input").unwrap();
let br = BufReader::new(f);
br.lines()
}
fn main() {
let start = Instant::now();
let ans1 = problem1(get_input());
let duration = start.elapsed();
println!("Problem 1 solution: {} [{}s]", ans1, duration.as_secs_f64());
let start = Instant::now();
let ans2 = problem2(get_input());
let duration = start.elapsed();
println!("Problem 2 solution: {} [{}s]", ans2, duration.as_secs_f64());
}
// DATA
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
enum Direction {
Left,
Right,
Up,
Down,
}
impl Direction {
const fn all() -> &'static [Self; 4] {
&[
Direction::Left,
Direction::Right,
Direction::Up,
Direction::Down,
]
}
const fn opposite(&self) -> Self {
match self {
Direction::Left => Direction::Right,
Direction::Right => Direction::Left,
Direction::Up => Direction::Down,
Direction::Down => Direction::Up,
}
}
const fn offset(&self) -> (isize, isize) {
match self {
Direction::Left => (-1, 0),
Direction::Right => (1, 0),
Direction::Up => (0, -1),
Direction::Down => (0, 1),
}
}
}
impl From<&str> for Direction {
fn from(s: &str) -> Self {
match s {
"L" => Direction::Left,
"R" => Direction::Right,
"U" => Direction::Up,
"D" => Direction::Down,
s => panic!("{} is not a valid direction", s),
}
}
}
impl From<&Direction> for char {
fn from(dir: &Direction) -> Self {
match dir {
Direction::Left => '←',
Direction::Right => '→',
Direction::Up => '↑',
Direction::Down => '↓',
}
}
}
#[derive(Debug, Clone)]
struct DigInstruction {
dir: Direction,
count: usize,
color: String,
}
impl From<&str> for DigInstruction {
fn from(s: &str) -> Self {
let mut parts = s.split_ascii_whitespace();
let (dir, count, color) = (
parts.next().unwrap(),
parts.next().unwrap(),
parts.next().unwrap(),
);
Self {
dir: dir.into(),
count: count.parse().unwrap(),
color: color.into(),
}
}
}
#[derive(Debug)]
struct DigPlan {
instructions: Vec<DigInstruction>,
}
#[derive(Debug, Clone)]
struct DigTile {
position: Position,
depth: usize,
color: String,
}
impl Default for DigTile {
fn default() -> Self {
Self {
position: (0, 0),
depth: 0,
color: "000000".into(),
}
}
}
type Position = (isize, isize);
#[derive(Debug)]
struct DigHole {
tiles_loop: LinkedList<DigTile>,
tiles_map: HashMap<Position, DigTile>,
x_range: Range<isize>,
y_range: Range<isize>,
}
impl DigHole {
fn new() -> Self {
DigHole {
tiles_loop: LinkedList::new(),
tiles_map: HashMap::new(),
x_range: 0..0,
y_range: 0..0,
}
}
fn pos_offset(&self, pos: Position, offset: (isize, isize)) -> Position {
(pos.0 + offset.0, pos.1 + offset.1)
}
fn run_plan(&mut self, plan: &DigPlan) {
// start position is 1m deep
let mut cur_pos = (0, 0);
let (mut max_x, mut max_y) = (0, 0);
self.tiles_loop.push_back(DigTile {
position: cur_pos,
depth: 1,
color: "000000".into(),
});
for i in &plan.instructions {
println!("instruction: {:?}", i);
for _ in 0..i.count {
println!(" cur_pos: {:?}", cur_pos);
cur_pos = (cur_pos.0 + i.dir.offset().0, cur_pos.1 + i.dir.offset().1);
(max_x, max_y) = (
std::cmp::max(cur_pos.0, max_x),
std::cmp::max(cur_pos.1, max_y),
);
self.tiles_loop.push_back(DigTile {
position: cur_pos,
depth: 1,
color: i.color.to_owned(),
});
}
}
// 2d vec dimension is (max_x+1, max_y+1)
for tile in &self.tiles_loop {
self.x_range.start = std::cmp::min(self.x_range.start, tile.position.0);
self.x_range.end = std::cmp::max(self.x_range.end, tile.position.0 + 1);
self.y_range.start = std::cmp::min(self.y_range.start, tile.position.1);
self.y_range.end = std::cmp::max(self.y_range.end, tile.position.1 + 1);
self.tiles_map.insert(tile.position, tile.clone());
}
println!("{}", self);
self.fill_at(self.find_first_inside().unwrap());
}
fn fill_at(&mut self, pos: Position) {
println!("filling at {:?}", pos);
let mut to_fill = vec![pos];
while let Some(fill_pos) = to_fill.pop() {
if self.tiles_map.contains_key(&fill_pos) {
continue;
} else {
self.tiles_map.insert(
fill_pos,
DigTile {
position: fill_pos,
depth: 1,
color: "000000".to_owned(),
},
);
}
for new_pos in Direction::all()
.iter()
.map(|dir| self.pos_offset(fill_pos, dir.offset()))
.filter(|pos| !self.tiles_map.contains_key(&pos))
{
to_fill.push(new_pos);
}
}
}
fn find_first_inside(&self) -> Option<Position> {
for y in self.y_range.clone() {
let mut inside = false;
for x in self.x_range.clone() {
if self.tiles_map.contains_key(&(x, y)) && (!self.tiles_map.contains_key(&(x - 1, y)) || !self.tiles_map.contains_key(&(x+1, y)))
{
inside = !inside;
}
if inside && !self.tiles_map.contains_key(&(x, y)) {
return Some((x as isize, y as isize));
};
}
}
None
}
}
impl Display for DigHole {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for y in self.y_range.clone() {
for x in self.x_range.clone() {
f.write_char(if (x, y) == (133, -267) {
'*'
} else if self.tiles_map.contains_key(&(x, y)) {
'#'
} else {
'.'
})?;
}
writeln!(f)?;
}
Ok(())
}
}
impl<T: BufRead> From<Lines<T>> for DigPlan {
fn from(lines: Lines<T>) -> Self {
Self {
instructions: lines
.map(|line| DigInstruction::from(line.unwrap().as_str()))
.collect(),
}
}
}
// PROBLEM 1 solution
fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
let plan = DigPlan::from(input);
println!("{:?}", plan);
let mut dig = DigHole::new();
dig.run_plan(&plan);
println!("{}", dig);
dig.tiles_map.iter().count() as u64
}
// PROBLEM 2 solution
fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
0
}
#[cfg(test)]
mod tests {
use crate::*;
use std::io::Cursor;
const EXAMPLE: &str = &"R 6 (#70c710)
D 5 (#0dc571)
L 2 (#5713f0)
D 2 (#d2c081)
R 2 (#59c680)
D 2 (#411b91)
L 5 (#8ceee2)
U 2 (#caa173)
L 1 (#1b58a2)
U 2 (#caa171)
R 2 (#7807d2)
U 3 (#a77fa3)
L 2 (#015232)
U 2 (#7a21e3)";
#[test]
fn problem1_example() {
let c = Cursor::new(EXAMPLE);
assert_eq!(problem1(c.lines()), 62);
}
#[test]
fn problem2_example() {
let c = Cursor::new(EXAMPLE);
assert_eq!(problem2(c.lines()), 0);
}
}