diff --git a/README.md b/README.md index 93df22f..53239f7 100644 --- a/README.md +++ b/README.md @@ -21,4 +21,5 @@ running: *solution2* * written in rust * single and multiple drones +* uses basically the same method, but after a definitive flight path is calculated, the grid is updated for the next drone * see tests and main for validity of the algorithm diff --git a/solution2/src/algorithm.rs b/solution2/src/algorithm.rs index 50e5a3c..1435654 100644 --- a/solution2/src/algorithm.rs +++ b/solution2/src/algorithm.rs @@ -3,10 +3,6 @@ use rand::Rng; use std::collections::HashSet; use std::hash::{DefaultHasher, Hash, Hasher}; use std::time::SystemTime; -use std::{ - sync::{Arc, Mutex}, - thread, -}; #[allow(non_snake_case)] pub fn find_optimal_path_for_n_drones( @@ -16,25 +12,26 @@ pub fn find_optimal_path_for_n_drones( t: usize, T: u128, ) -> PathsResult { - let arc = Arc::new(Mutex::new(grid)); - - let mut handles = vec![]; + // multiple drones are calculated one after another + // grid hits must be updated for the next drone after the optimal for the previous has been calculated + let mut paths = vec![]; + let mut grid = grid.clone(); for _ in 0..ndrones { - let gridref = Arc::clone(&arc); - + let mut current_grid = grid.clone(); let mut rng = rand::thread_rng(); // start at random position let x: u16 = rng.gen_range(0..N); let y: u16 = rng.gen_range(0..N); - // start new thread for a single drone - let handle = thread::spawn(move || find_optimal_path(gridref, N, t, T, x, y)); - handles.push(handle); - } + // divide max algorithm time by number of drones + let time_per_drone = T / ndrones as u128; + let optimal = find_optimal_path(&mut current_grid, N, t, time_per_drone, x, y); - let mut paths = vec![]; - for handle in handles { - paths.push(handle.join().unwrap()); + // update the grid to the lastes state + for (index, p) in optimal.points.iter().enumerate() { + grid.hit(p.x, p.y, index); + } + paths.push(optimal); } let overall_score = paths.iter().map(|p| p.value).sum(); @@ -52,19 +49,12 @@ pub fn find_optimal_path_for_n_drones( /// T max duration of the algorithm /// x,y drone start position in the grid #[allow(non_snake_case)] -pub fn find_optimal_path( - grid: Arc>, - N: u16, - t: usize, - T: u128, - x: u16, - y: u16, -) -> Path { +pub fn find_optimal_path(grid: &mut Grid, N: u16, t: usize, T: u128, x: u16, y: u16) -> Path { let mut paths_to_consider: Vec = Vec::new(); let mut taken_paths: HashSet = HashSet::new(); // starting point - let path = Path::new(Arc::clone(&grid), x, y); + let path = Path::new(grid, x, y); // always current max let mut max: Path = path.clone(); // sorry @@ -101,97 +91,90 @@ pub fn find_optimal_path( // create a list of directions to take new_directions.clear(); - { - let arc = Arc::clone(&grid); - let mut lock = arc.lock(); - let grid = lock.as_mut().unwrap(); - if y > 0 { - new_directions.push(Point::new( - x, - y - 1, - grid.get_value(x, y - 1, current_path.length() + 1), - )); - if x < N - 1 { - new_directions.push(Point::new( - x + 1, - y - 1, - grid.get_value(x + 1, y - 1, current_path.length() + 1), - )); - } - } - - if x > 0 { - new_directions.push(Point::new( - x - 1, - y, - grid.get_value(x - 1, y, current_path.length() + 1), - )); - if y > 0 { - new_directions.push(Point::new( - x - 1, - y - 1, - grid.get_value(x - 1, y - 1, current_path.length() + 1), - )); - } - } - + if y > 0 { + new_directions.push(Point::new( + x, + y - 1, + grid.get_value(x, y - 1, Some(¤t_path)), + )); if x < N - 1 { new_directions.push(Point::new( x + 1, - y, - grid.get_value(x + 1, y, current_path.length() + 1), + y - 1, + grid.get_value(x + 1, y - 1, Some(¤t_path)), )); - if y < N - 1 { - new_directions.push(Point::new( - x + 1, - y + 1, - grid.get_value(x + 1, y + 1, current_path.length() + 1), - )); - } } + } + if x > 0 { + new_directions.push(Point::new( + x - 1, + y, + grid.get_value(x - 1, y, Some(¤t_path)), + )); + if y > 0 { + new_directions.push(Point::new( + x - 1, + y - 1, + grid.get_value(x - 1, y - 1, Some(¤t_path)), + )); + } + } + + if x < N - 1 { + new_directions.push(Point::new( + x + 1, + y, + grid.get_value(x + 1, y, Some(¤t_path)), + )); if y < N - 1 { new_directions.push(Point::new( - x, + x + 1, y + 1, - grid.get_value(x, y + 1, current_path.length() + 1), + grid.get_value(x + 1, y + 1, Some(¤t_path)), )); - if x > 0 { - new_directions.push(Point::new( - x - 1, - y + 1, - grid.get_value(x - 1, y + 1, current_path.length() + 1), - )); + } + } + + if y < N - 1 { + new_directions.push(Point::new( + x, + y + 1, + grid.get_value(x, y + 1, Some(¤t_path)), + )); + if x > 0 { + new_directions.push(Point::new( + x - 1, + y + 1, + grid.get_value(x - 1, y + 1, Some(¤t_path)), + )); + } + } + + let mut points_added = false; + for point in new_directions.iter() { + if point.value > 0.0 { + let mut new_path = current_path.clone(); + new_path.add(point.clone()); + + let mut s = DefaultHasher::new(); + new_path.hash(&mut s); + let hash = s.finish(); + + if !taken_paths.contains(&hash) { + points_added = true; + grid.hit(point.x, point.y, new_path.length()); + paths_to_consider.push(new_path); + taken_paths.insert(hash); } } - - let mut points_added = false; - for point in new_directions.iter() { - if point.value > 0.0 { - let mut new_path = current_path.clone(); - new_path.add(point.clone()); - - let mut s = DefaultHasher::new(); - new_path.hash(&mut s); - let hash = s.finish(); - - if !taken_paths.contains(&hash) { - points_added = true; - grid.hit(point.x, point.y, new_path.length()); - paths_to_consider.push(new_path); - taken_paths.insert(hash); - } - } + } + if !points_added { + // dead end, evict + let ended = paths_to_consider.pop().unwrap(); + if ended.value > max.value { + max = ended; } - if !points_added { - // dead end, evict - let ended = paths_to_consider.pop().unwrap(); - if ended.value > max.value { - max = ended; - } - } - - //drop lock } // continue? @@ -211,8 +194,9 @@ mod tests { #[test] pub fn test_single_drone() { - let grid = Grid::new(20); - let opt = find_optimal_path(Arc::new(Mutex::new(grid.clone())), 100, 10, 1000, 9, 9); + let mut grid = Grid::new(100); + // values for x and y are chosen so that a loop must occur + let opt = find_optimal_path(&mut grid, 20, 20, 1000, 9, 9); let mut all_points: HashSet = HashSet::new(); let mut loop_in_path = false; @@ -225,12 +209,14 @@ mod tests { all_points.insert(point.clone()); } if loop_in_path { - //println!("check"); //verify that this occurs + println!("check"); //verify that this occurs let max_sum: f32 = opt .points .iter() .map(|p| grid.get_initial_value(p.x, p.y)) .sum(); + println!("max sum {:?}, opt path {:?}", max_sum, opt); + // verify that the grid value was updated because of the hit assert!(max_sum > opt.value()); } } diff --git a/solution2/src/grid.rs b/solution2/src/grid.rs index c32df46..f47dfc0 100644 --- a/solution2/src/grid.rs +++ b/solution2/src/grid.rs @@ -1,8 +1,7 @@ use std::{ cmp::Ordering, - collections::{BTreeSet, HashMap, LinkedList}, + collections::{HashMap, HashSet, LinkedList}, hash::{Hash, Hasher}, - sync::{Arc, Mutex}, }; const RECOVERY_FACTOR: f32 = 0.1; @@ -12,9 +11,9 @@ const RECOVERY_FACTOR: f32 = 0.1; pub struct Grid { data: Vec>, // keep track of every point that has been hit at some time - hits: HashMap<(u16, u16), BTreeSet>, // TreeSet is integer times that point has been visited. - // Must always be sorted and could probably be a single int - // (keep track of last time hit) + hits: HashMap<(u16, u16), usize>, // TreeSet is integer times that point has been visited. + // Must always be sorted and could probably be a single int + // (keep track of last time hit) } impl Grid { @@ -51,30 +50,47 @@ impl Grid { *(self.data.get(y as usize).unwrap().get(x as usize).unwrap()) as f32 } - pub fn get_value(&self, x: u16, y: u16, time: usize) -> f32 { - let hit = self.hits.get(&(x, y)); - + /// get the value of a point on the grid + /// 1. initial value, given by the datafile + /// 2. value possibly updated by a previous drone in the multiple drone scenario + /// 3. value possibly updated by the drone itself, when it gets to a point that it already occupied + pub fn get_value(&self, x: u16, y: u16, path: Option<&Path>) -> f32 { + let pathlen = path.map_or(0, |p| p.length()); let initial_value = self.get_initial_value(x, y); - if let Some(hit_times) = hit { - for t in hit_times.iter().rev() { - if time > *t { - let elapsed_since_hit = (time - *t) as f32; - let value = f32::min( - elapsed_since_hit * initial_value * RECOVERY_FACTOR, - initial_value, - ); - return value; - } + // 1. + let mut value = initial_value; + + // 2. + let hit_time = self.hits.get(&(x, y)); + if let Some(hit_time) = hit_time { + if pathlen > *hit_time { + // +1 because we are in the process of adding a new point to the path + let elapsed_since_hit = (pathlen + 1 - hit_time) as f32; + value = f32::min( + elapsed_since_hit * initial_value * RECOVERY_FACTOR, + initial_value, + ); } - 0.0 - } else { - initial_value } + + // 3. + if let Some(path) = path { + let maybe_hit = path.points_lookup.get(&(x, y)); + if let Some(hit_time) = maybe_hit { + let elapsed_since_hit = (path.points.len() - hit_time) as f32; + value = f32::min( + elapsed_since_hit * initial_value * RECOVERY_FACTOR, + initial_value, + ); + } + } + + value } pub fn hit(&mut self, x: u16, y: u16, time: usize) { - self.hits.entry((x, y)).or_default().insert(time); + self.hits.insert((x, y), time); } pub fn size(&self) -> u16 { @@ -85,15 +101,13 @@ impl Grid { #[derive(Debug, Clone)] pub struct Path { pub points: LinkedList, + pub points_lookup: HashMap<(u16, u16), usize>, pub value: f32, } impl Path { - pub fn new(grid: Arc>, initial_x: u16, initial_y: u16) -> Self { - let mut points = LinkedList::new(); - let mut lock = grid.lock(); - let grid = lock.as_mut().unwrap(); - let value = grid.get_value(initial_x, initial_y, 0); + pub fn new(grid: &Grid, initial_x: u16, initial_y: u16) -> Self { + let value = grid.get_value(initial_x, initial_y, None); let p = Point { x: initial_x, @@ -101,8 +115,16 @@ impl Path { value, }; + let mut points = LinkedList::new(); points.push_front(p); - Self { points, value } + let mut points_lookup = HashMap::new(); + points_lookup.insert((initial_x, initial_y), 0); + + Self { + points, + value, + points_lookup, + } } // length = age of the path @@ -204,8 +226,8 @@ mod test { #[test] pub fn test() { let grid = Grid::new(20); - assert_eq!(grid.get_value(0, 0, 0), 0.0); - assert_eq!(grid.get_value(0, 1, 0), 1.0); + assert_eq!(grid.get_value(0, 0, None), 0.0); + assert_eq!(grid.get_value(0, 1, None), 1.0); } } diff --git a/solution2/src/main.rs b/solution2/src/main.rs index 0a7bed8..54c7176 100644 --- a/solution2/src/main.rs +++ b/solution2/src/main.rs @@ -1,8 +1,9 @@ use solution2::{algorithm::find_optimal_path_for_n_drones, grid::Grid}; -/// this app calculates paths for 4 drones, concurrently, with a shared grid +/// this app calculates paths for 4 drones fn main() { - let result = find_optimal_path_for_n_drones(Grid::new(100), 4, 100, 15, 2000); + let grid = Grid::new(100); + let result = find_optimal_path_for_n_drones(grid, 4, 100, 15, 2000); for (i, path) in result.paths.iter().enumerate() { println!("path {}: score {}", i, path.value()) }