317 lines
7.6 KiB
Rust
317 lines
7.6 KiB
Rust
#[macro_use] extern crate log;
|
|
extern crate env_logger;
|
|
#[macro_use] extern crate lazy_static;
|
|
extern crate regex;
|
|
|
|
use std::io::{self, BufRead};
|
|
use std::str::FromStr;
|
|
use std::collections::{BTreeSet, BTreeMap};
|
|
use std::usize;
|
|
|
|
use regex::Regex;
|
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub struct Disk {
|
|
x: usize,
|
|
y: usize,
|
|
size: u16,
|
|
used: u16,
|
|
}
|
|
|
|
impl Disk {
|
|
pub fn new(x: usize, y: usize, size: u16, used: u16) -> Self {
|
|
Disk {
|
|
x: x,
|
|
y: y,
|
|
size: size,
|
|
used: used
|
|
}
|
|
}
|
|
}
|
|
|
|
impl FromStr for Disk {
|
|
type Err = ();
|
|
|
|
fn from_str(s: &str) -> Result<Disk, Self::Err> {
|
|
lazy_static! {
|
|
static ref RE: Regex = Regex::new(r"-x(\d+)-y(\d+)\s+(\d+)T\s+(\d+)T").unwrap();
|
|
}
|
|
|
|
if let Some(caps) = RE.captures(s) {
|
|
let x = caps.at(1).unwrap().parse::<usize>().unwrap();
|
|
let y = caps.at(2).unwrap().parse::<usize>().unwrap();
|
|
|
|
let size = caps.at(3).unwrap().parse::<u16>().unwrap();
|
|
let used = caps.at(4).unwrap().parse::<u16>().unwrap();
|
|
|
|
Ok(Disk::new(x, y, size, used))
|
|
} else {
|
|
Err(())
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
type Cell = (usize, usize);
|
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub struct Grid {
|
|
grid: Vec<Vec<Disk>>,
|
|
lock: Option<Cell>
|
|
}
|
|
|
|
impl Grid {
|
|
pub fn new() -> Self {
|
|
Grid {
|
|
grid: Vec::new(),
|
|
lock: None
|
|
}
|
|
}
|
|
|
|
fn lock(&mut self, cell: Cell) {
|
|
self.lock = Some(cell);
|
|
}
|
|
|
|
fn cell_neighbors(&mut self, cell: Cell) -> Vec<Cell> {
|
|
let mut neighbors = Vec::new();
|
|
|
|
let ref disk = self.grid[cell.1][cell.0];
|
|
|
|
debug!("neighbor[@]={:?}", disk);
|
|
|
|
// Check disk above.
|
|
if cell.1 > 0 && self.grid[cell.1 - 1][cell.0].used <= disk.size {
|
|
debug!("neighbor[^]={:?}", self.grid[cell.1 - 1][cell.0]);
|
|
neighbors.push((cell.0, cell.1 - 1));
|
|
}
|
|
|
|
// Check disk below.
|
|
if cell.1 < self.grid.len() - 1 && self.grid[cell.1 + 1][cell.0].used <= disk.size {
|
|
debug!("neighbor[v]={:?}", self.grid[cell.1 + 1][cell.0]);
|
|
neighbors.push((cell.0, cell.1 + 1));
|
|
}
|
|
|
|
// Check left disk.
|
|
if cell.0 > 0 && self.grid[cell.1][cell.0 - 1].used <= disk.size {
|
|
debug!("neighbor[<]={:?}", self.grid[cell.1][cell.0 - 1]);
|
|
neighbors.push((cell.0 - 1, cell.1));
|
|
}
|
|
|
|
// Check right disk.
|
|
if cell.0 < self.grid[0].len() - 1 && self.grid[cell.1][cell.0 + 1].used <= disk.size {
|
|
debug!("neighbor[>]={:?}", self.grid[cell.1][cell.0 + 1]);
|
|
neighbors.push((cell.0 + 1, cell.1));
|
|
}
|
|
|
|
if let Some(lock) = self.lock {
|
|
neighbors = neighbors.iter()
|
|
.filter(|cell| **cell != lock)
|
|
.map(|cell| *cell)
|
|
.collect::<Vec<_>>();
|
|
}
|
|
|
|
neighbors
|
|
}
|
|
|
|
pub fn path_between(&mut self, start: Cell, goal: Cell) -> Result<Vec<Cell>, ()> {
|
|
let mut closed_set = BTreeSet::new();
|
|
let mut open_set = BTreeSet::new();
|
|
|
|
open_set.insert(start);
|
|
|
|
let mut came_from = BTreeMap::new();;
|
|
|
|
let mut g_score = BTreeMap::new();
|
|
|
|
g_score.insert(start, 0);
|
|
|
|
let mut f_score = BTreeMap::new();
|
|
|
|
f_score.insert(start, heuristic_cost_estimate(start, goal));
|
|
|
|
while !open_set.is_empty() {
|
|
let (current, _) = f_score.iter()
|
|
.filter(|&(&(x, y), _)| open_set.contains(&(x, y)))
|
|
.fold(((0, 0), usize::MAX), |((min_x, min_y), min_score), (&(x, y), &score)| {
|
|
if score < min_score {
|
|
((x, y), score)
|
|
} else {
|
|
((min_x, min_y), min_score)
|
|
}
|
|
});
|
|
|
|
if current == goal {
|
|
return Ok(reconstruct_path(&came_from, current));
|
|
}
|
|
|
|
open_set.remove(¤t);
|
|
closed_set.insert(current);
|
|
|
|
for neighbor in self.cell_neighbors(current) {
|
|
if closed_set.contains(&neighbor) {
|
|
continue;
|
|
}
|
|
|
|
let tentative_g_score = *g_score.entry(current).or_insert(usize::MAX) + dist_between(current, neighbor);
|
|
|
|
if !open_set.contains(&neighbor) {
|
|
open_set.insert(neighbor);
|
|
} else if tentative_g_score >= *g_score.entry(neighbor).or_insert(usize::MAX) {
|
|
continue
|
|
}
|
|
|
|
came_from.insert(neighbor, current);
|
|
|
|
g_score.insert(neighbor, tentative_g_score);
|
|
f_score.insert(neighbor, tentative_g_score + heuristic_cost_estimate(neighbor, goal));
|
|
}
|
|
}
|
|
|
|
Err(())
|
|
}
|
|
}
|
|
|
|
fn reconstruct_path(came_from: &BTreeMap<Cell, Cell>, current: Cell) -> Vec<Cell> {
|
|
let mut current = current;
|
|
let mut path = Vec::new();
|
|
|
|
path.push(current);
|
|
|
|
while came_from.contains_key(¤t) {
|
|
if let Some(next) = came_from.get(¤t) {
|
|
current = *next;
|
|
|
|
path.push(*next);
|
|
}
|
|
}
|
|
|
|
path
|
|
}
|
|
|
|
fn dist_between(start: Cell, goal: Cell) -> usize {
|
|
let delta_x = goal.0 as i64 - start.0 as i64;
|
|
let delta_y = goal.1 as i64 - start.1 as i64;
|
|
|
|
(delta_x.abs() + delta_y.abs()) as usize
|
|
}
|
|
|
|
fn heuristic_cost_estimate(start: Cell, goal: Cell) -> usize {
|
|
dist_between(start, goal)
|
|
}
|
|
|
|
|
|
fn main() {
|
|
env_logger::init().unwrap();
|
|
|
|
let stdin = io::stdin();
|
|
|
|
let mut disks = stdin.lock().lines()
|
|
.filter_map(|line| line.ok())
|
|
.filter_map(|line| line.parse::<Disk>().ok())
|
|
.collect::<Vec<_>>();
|
|
|
|
// Part 1.
|
|
let pairs: usize = disks.iter()
|
|
.filter(|&a| a.used > 0)
|
|
.map(|a| {
|
|
disks.iter()
|
|
.filter(|&b| *a != *b)
|
|
.filter(|&b| a.used <= (b.size - b.used))
|
|
.count()
|
|
})
|
|
.sum();
|
|
|
|
println!("number_of_pairs={}", pairs);
|
|
|
|
|
|
// Part 2.
|
|
let empty = {
|
|
if let Some(disk) = disks.iter().find(|&disk| disk.used == 0) {
|
|
(disk.x, disk.y)
|
|
} else {
|
|
(0, 0)
|
|
}
|
|
};
|
|
|
|
println!("empty={:?}", empty);
|
|
|
|
let mut grid = Grid::new();
|
|
|
|
disks.sort_by_key(|disk| (disk.y, disk.x));
|
|
|
|
for disk in disks {
|
|
while grid.grid.len() < disk.y + 1 {
|
|
grid.grid.push(Vec::new());
|
|
}
|
|
|
|
grid.grid[disk.y].push(disk);
|
|
}
|
|
|
|
let mut steps = 0;
|
|
|
|
let goal = {
|
|
(grid.grid[0].len() - 1, 0)
|
|
};
|
|
|
|
let path = grid.path_between(empty, goal).unwrap();
|
|
|
|
steps += path.len();
|
|
|
|
render_grid(&grid, &path);
|
|
debug!("path={:?}", path);
|
|
|
|
grid.lock(path[1]);
|
|
|
|
let mut start = path[0];
|
|
let mut end = (path[0].0 - 2, 0);
|
|
|
|
loop {
|
|
let path = grid.path_between(start, end).unwrap();
|
|
|
|
render_grid(&grid, &path);
|
|
|
|
steps += path.len();
|
|
|
|
if end == (0, 0) {
|
|
break;
|
|
}
|
|
|
|
grid.lock(path[0]);
|
|
|
|
start.0 -= 1;
|
|
end.0 -= 1;
|
|
}
|
|
|
|
println!("steps={}", steps - 1);
|
|
}
|
|
|
|
fn render_grid(grid: &Grid, path: &Vec<Cell>) {
|
|
for (y, row) in grid.grid.iter().enumerate() {
|
|
for (x, disk) in row.iter().enumerate() {
|
|
let symbol = if disk.used == 0 {
|
|
'_'
|
|
} else if disk.size > 97 {
|
|
'#'
|
|
} else {
|
|
'.'
|
|
};
|
|
|
|
if path.contains(&(x, y)) {
|
|
print!("\x1b[32m");
|
|
}
|
|
|
|
print!("{}", symbol);
|
|
|
|
if path.contains(&(x, y)) {
|
|
print!("\x1b[0m");
|
|
}
|
|
}
|
|
|
|
println!("");
|
|
}
|
|
|
|
println!("");
|
|
}
|