2016/22
This commit is contained in:
316
2016/22/src/main.rs
Normal file
316
2016/22/src/main.rs
Normal file
@@ -0,0 +1,316 @@
|
||||
#[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!("");
|
||||
}
|
||||
Reference in New Issue
Block a user