313 lines
8.0 KiB
Rust
313 lines
8.0 KiB
Rust
|
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);
|
||
|
}
|
||
|
}
|