#[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 { 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::().unwrap(); let y = caps.at(2).unwrap().parse::().unwrap(); let size = caps.at(3).unwrap().parse::().unwrap(); let used = caps.at(4).unwrap().parse::().unwrap(); Ok(Disk::new(x, y, size, used)) } else { Err(()) } } } type Cell = (usize, usize); #[derive(Debug, PartialEq)] pub struct Grid { grid: Vec>, lock: Option } 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 { 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::>(); } neighbors } pub fn path_between(&mut self, start: Cell, goal: Cell) -> Result, ()> { 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, current: Cell) -> Vec { 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::().ok()) .collect::>(); // 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) { 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!(""); }