From be386182d59d8840ed818b74179d7b42996338bc Mon Sep 17 00:00:00 2001 From: Shautvast Date: Tue, 12 Nov 2024 10:47:25 +0100 Subject: [PATCH] wrapping up --- README.md | 22 ++++ solution2/src/algorithm.rs | 247 +++++++++++++++++++++++++++++++++++++ solution2/src/grid.rs | 25 +++- solution2/src/lib.rs | 180 +-------------------------- solution2/src/main.rs | 36 +----- 5 files changed, 295 insertions(+), 215 deletions(-) create mode 100644 README.md create mode 100644 solution2/src/algorithm.rs diff --git a/README.md b/README.md new file mode 100644 index 0000000..f4f6deb --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +**find optimal optimal path for drones in a grid of assigned values** + +*solution1* +* written in java +* single drone solution +* employs a floodfill algorithm +* where possible paths are always sorted in descending order of value (so far) of the points in the path +* and which uses backtracking to find alternative paths that may be of higher value +* non-recursivity ensures no issues for large grids +* the algorithm is embedded in a rest api +* it also has a html canvas frontend that draws the path on the grid + +installation: +* install jdk22, and apache maven +* run `mvn spring-boot:run` +* go to http://localhost:8080 +* in the console type `fly` [enter] + +*solution2* +*written in rust +*single and multiple drones +*see tests and main for validity of the algorithm diff --git a/solution2/src/algorithm.rs b/solution2/src/algorithm.rs new file mode 100644 index 0000000..981b72c --- /dev/null +++ b/solution2/src/algorithm.rs @@ -0,0 +1,247 @@ +use crate::grid::{Grid, Path, PathsResult, Point}; +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( + grid: Grid, + ndrones: usize, + N: u16, + t: usize, + T: u128, +) -> PathsResult { + let arc = Arc::new(Mutex::new(grid)); + + let mut handles = vec![]; + + for _ in 0..ndrones { + let gridref = Arc::clone(&arc); + + 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); + } + + let mut paths = vec![]; + for handle in handles { + paths.push(handle.join().unwrap()); + } + + let overall_score = paths.iter().map(|p| p.value).sum(); + + PathsResult { + paths, + overall_score, + } +} + +/// finds the optimal path for a drone in a grid of points(x,y) that each has a fixed initial value +/// observing a point (hit) resets the value to 0, after wich it gradually increases with time in a fixed rate +/// N size of square grid (rows and cols) +/// t max length of a flight path +/// 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 { + 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); + + // always current max + let mut max: Path = path.clone(); // sorry + + // add the first path + paths_to_consider.push(path); + + // keep track of time + let t0 = SystemTime::now(); + let mut running = true; + + // this is time spent for the path, grid keeps the global timeframe + // same as path length, so remove? + let mut discrete_elapsed = 0; + + // will keep at most 8 new directions from current location + let mut new_directions = vec![]; + + let mut current_path; + while running && !paths_to_consider.is_empty() { + paths_to_consider.sort(); + current_path = paths_to_consider.last().unwrap().clone(); // assert = Some + + // evict paths that are of max len + while current_path.length() >= t { + _ = paths_to_consider.remove(paths_to_consider.len() - 1); + if current_path.value() > max.value() { + max = current_path.clone(); // sorry + } + current_path = paths_to_consider.last().unwrap().clone(); + } + + let head = current_path.last(); + let x = head.x; + let y = head.y; + + // 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, discrete_elapsed), + )); + if x < N - 1 { + new_directions.push(Point::new( + x + 1, + y - 1, + grid.get_value(x + 1, y - 1, discrete_elapsed), + )); + } + } + + if x > 0 { + new_directions.push(Point::new( + x - 1, + y, + grid.get_value(x - 1, y, discrete_elapsed), + )); + if y > 0 { + new_directions.push(Point::new( + x - 1, + y - 1, + grid.get_value(x - 1, y - 1, discrete_elapsed), + )); + } + } + + if x < N - 1 { + new_directions.push(Point::new( + x + 1, + y, + grid.get_value(x + 1, y, discrete_elapsed), + )); + if y < N - 1 { + new_directions.push(Point::new( + x + 1, + y + 1, + grid.get_value(x + 1, y + 1, discrete_elapsed), + )); + } + } + + if y < N - 1 { + new_directions.push(Point::new( + x, + y + 1, + grid.get_value(x, y + 1, discrete_elapsed), + )); + if x > 0 { + new_directions.push(Point::new( + x - 1, + y + 1, + grid.get_value(x - 1, y + 1, discrete_elapsed), + )); + } + } + + 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, discrete_elapsed); + paths_to_consider.push(new_path); + taken_paths.insert(hash); + } + } + } + if !points_added { + // dead end, evict + let ended = paths_to_consider.remove(paths_to_consider.len() - 1); + if ended.value > max.value { + max = ended; + } + } + } + + // continue? + let t1 = SystemTime::now(); + if let Ok(elapsed) = t1.duration_since(t0) { + if elapsed.as_millis() > T { + running = false; + } + } + discrete_elapsed += 1; + } + max +} + +#[cfg(test)] +mod tests { + use super::*; + + #[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 all_points: HashSet = HashSet::new(); + for point in opt.points.iter() { + if all_points.contains(point) { + if all_points.contains(point) { + let iv = grid.get_initial_value(point.x, point.y); + assert!(point.value < iv); + } + } + all_points.insert(point.clone()); + } + } + + #[test] + pub fn test_multiple_drones() { + let grid = Grid::new(100); + let paths_result = find_optimal_path_for_n_drones(grid.clone(), 4, 100, 10, 1000); + + let mut all_points: HashSet = HashSet::new(); + + for path in paths_result.paths.iter() { + for point in path.points.iter() { + if all_points.contains(point) { + let iv = grid.get_initial_value(point.x, point.y); + assert!(point.value < iv); + } + all_points.insert(point.clone()); + } + } + } +} diff --git a/solution2/src/grid.rs b/solution2/src/grid.rs index d45b7ec..e69e3dc 100644 --- a/solution2/src/grid.rs +++ b/solution2/src/grid.rs @@ -5,8 +5,10 @@ use std::{ sync::{Arc, Mutex}, }; +const RECOVERY_FACTOR: f32 = 0.01; + #[allow(non_snake_case)] -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Grid { data: Vec>, // keep track of every point that has been hit at some time @@ -45,17 +47,24 @@ impl Grid { } } + pub fn get_initial_value(&self, x: u16, y: u16) -> f32 { + *(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)); - let initial_value = *(self.data.get(y as usize).unwrap().get(x as usize).unwrap()) as f32; + 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; - - return f32::min(elapsed_since_hit * initial_value * 0.1, initial_value); + let value = f32::min( + elapsed_since_hit * initial_value * RECOVERY_FACTOR, + initial_value, + ); + return value; } } 0.0 @@ -75,7 +84,7 @@ impl Grid { #[derive(Debug, Clone)] pub struct Path { - points: LinkedList, + pub points: LinkedList, pub value: f32, } @@ -198,3 +207,9 @@ mod test { assert_eq!(grid.get_value(0, 1, 0), 1.0); } } + +#[derive(Debug)] +pub struct PathsResult { + pub paths: Vec, + pub overall_score: f32, +} diff --git a/solution2/src/lib.rs b/solution2/src/lib.rs index a835359..df1eaf4 100644 --- a/solution2/src/lib.rs +++ b/solution2/src/lib.rs @@ -1,180 +1,2 @@ -use grid::{Grid, Path, Point}; -use std::collections::HashSet; -use std::hash::{DefaultHasher, Hash, Hasher}; -use std::sync::{Arc, Mutex}; -use std::time::SystemTime; - +pub mod algorithm; pub mod grid; - -/// finds the optimal path for a drone in a grid of points(x,y) that each has a fixed initial value -/// observing a point (hit) resets the value to 0, after wich it gradually increases with time in a fixed rate -/// N size of square grid (rows and cols) -/// t max length of a flight path -/// 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 { - 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); - - // always current max - let mut max: Path = path.clone(); // sorry - - // add the first path - paths_to_consider.push(path); - - // keep track of time - let t0 = SystemTime::now(); - let mut running = true; - - // this is time spent for the path, grid keeps the global timeframe - // same as path length, so remove? - let mut discrete_elapsed = 0; - - // will keep at most 8 new directions from current location - let mut new_directions = vec![]; - - let mut current_path; - while running && !paths_to_consider.is_empty() { - paths_to_consider.sort(); - current_path = paths_to_consider.last().unwrap().clone(); // assert = Some - - // evict paths that are of max len - while current_path.length() >= t { - _ = paths_to_consider.remove(paths_to_consider.len() - 1); - if current_path.value() > max.value() { - max = current_path.clone(); // sorry - } - current_path = paths_to_consider.last().unwrap().clone(); - } - - let head = current_path.last(); - let x = head.x; - let y = head.y; - - // 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, discrete_elapsed), - )); - if x < N - 1 { - new_directions.push(Point::new( - x + 1, - y - 1, - grid.get_value(x + 1, y - 1, discrete_elapsed), - )); - } - } - - if x > 0 { - new_directions.push(Point::new( - x - 1, - y, - grid.get_value(x - 1, y, discrete_elapsed), - )); - if y > 0 { - new_directions.push(Point::new( - x - 1, - y - 1, - grid.get_value(x - 1, y - 1, discrete_elapsed), - )); - } - } - - if x < N - 1 { - new_directions.push(Point::new( - x + 1, - y, - grid.get_value(x + 1, y, discrete_elapsed), - )); - if y < N - 1 { - new_directions.push(Point::new( - x + 1, - y + 1, - grid.get_value(x + 1, y + 1, discrete_elapsed), - )); - } - } - - if y < N - 1 { - new_directions.push(Point::new( - x, - y + 1, - grid.get_value(x, y + 1, discrete_elapsed), - )); - if x > 0 { - new_directions.push(Point::new( - x - 1, - y + 1, - grid.get_value(x - 1, y + 1, discrete_elapsed), - )); - } - } - - 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, discrete_elapsed); - paths_to_consider.push(new_path); - taken_paths.insert(hash); - } - } - } - if !points_added { - // dead end, evict - let ended = paths_to_consider.remove(paths_to_consider.len() - 1); - if ended.value > max.value { - max = ended; - } - } - } - - // continue? - let t1 = SystemTime::now(); - if let Ok(elapsed) = t1.duration_since(t0) { - if elapsed.as_millis() > T { - running = false; - } - } - discrete_elapsed += 1; - } - max -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - pub fn test() { - let mut grid = Grid::new(20); - let opt = find_optimal_path(Arc::new(Mutex::new(grid)), 100, 10, 1000, 9, 9); - println!("value {:?}", opt); - } -} diff --git a/solution2/src/main.rs b/solution2/src/main.rs index 4680721..0a7bed8 100644 --- a/solution2/src/main.rs +++ b/solution2/src/main.rs @@ -1,36 +1,10 @@ -use rand::Rng; -use solution2::{find_optimal_path, grid::Grid}; -use std::{ - sync::{Arc, Mutex}, - thread, -}; +use solution2::{algorithm::find_optimal_path_for_n_drones, grid::Grid}; /// this app calculates paths for 4 drones, concurrently, with a shared grid fn main() { - find_optimal_path_for_n_drones(4, 100, 10, 1000); -} - -#[allow(non_snake_case)] -pub fn find_optimal_path_for_n_drones(ndrones: usize, N: u16, t: usize, T: u128) { - let grid = Grid::new(100); - let arc = Arc::new(Mutex::new(grid)); - - let mut handles = vec![]; - - for _ in 0..ndrones { - let gridref = Arc::clone(&arc); - - let mut rng = rand::thread_rng(); - // start at random position - let x: u16 = rng.gen_range(0..100); - let y: u16 = rng.gen_range(0..100); - // start new thread for a single drone - let handle = thread::spawn(move || find_optimal_path(gridref, N, t, T, x, y)); - handles.push(handle); - } - - for handle in handles { - let result = handle.join().unwrap(); - println!("{result:?}"); + let result = find_optimal_path_for_n_drones(Grid::new(100), 4, 100, 15, 2000); + for (i, path) in result.paths.iter().enumerate() { + println!("path {}: score {}", i, path.value()) } + println!("overall: {}", result.overall_score); }