From 906818d86412abaa430d46cab6a29bca5a635172 Mon Sep 17 00:00:00 2001 From: Shautvast Date: Mon, 11 Nov 2024 16:03:51 +0100 Subject: [PATCH] multiple drones --- .../main/java/assessment/algorithm/Grid.java | 7 +- .../algorithm/OptimalPathFinder.java | 38 ++-- .../assessment/algorithm/GridReadTest.java | 11 + .../assessment/algorithm/PathFinderTest.java | 4 +- solution2/Cargo.lock | 133 ++++++++++++ solution2/Cargo.toml | 1 + solution2/src/grid.rs | 96 ++++++--- solution2/src/lib.rs | 200 ++++++++++++------ solution2/src/main.rs | 35 ++- 9 files changed, 407 insertions(+), 118 deletions(-) diff --git a/solution1/src/main/java/assessment/algorithm/Grid.java b/solution1/src/main/java/assessment/algorithm/Grid.java index e336b73..11bc3e0 100644 --- a/solution1/src/main/java/assessment/algorithm/Grid.java +++ b/solution1/src/main/java/assessment/algorithm/Grid.java @@ -14,14 +14,15 @@ public record Grid(List> grid) { public static Grid fromFile(String resource) { try { - BufferedReader reader = new BufferedReader(new InputStreamReader(Grid.class.getClassLoader().getResourceAsStream(resource))); + BufferedReader reader = new BufferedReader(new InputStreamReader( + Grid.class.getClassLoader().getResourceAsStream(resource))); String line; List> rows = new ArrayList<>(); while ((line = reader.readLine()) != null) { String[] values = line.split(" "); List row = new ArrayList<>(values.length); - for (int i = 0; i < values.length; i++) { - row.add(Integer.parseInt(values[i])); + for (String value : values) { + row.add(Integer.parseInt(value)); } rows.add(row); } diff --git a/solution1/src/main/java/assessment/algorithm/OptimalPathFinder.java b/solution1/src/main/java/assessment/algorithm/OptimalPathFinder.java index 285dc9f..855fbf1 100644 --- a/solution1/src/main/java/assessment/algorithm/OptimalPathFinder.java +++ b/solution1/src/main/java/assessment/algorithm/OptimalPathFinder.java @@ -87,29 +87,27 @@ public class OptimalPathFinder { newDirections.add(Point.create(g, path, x - 1, y + 1)); } - if (!newDirections.isEmpty()) { - boolean pointsAdded = false; - for (Point p : newDirections) { - // is it worthwile going there? - if (p.value > 0) { // use (higher) cutoff point? - // create a new Path based on the current - Path newPath = path.copy(); + boolean pointsAdded = false; + for (Point p : newDirections) { + // is it worthwile going there? + if (p.value > 0) { // use (higher) cutoff point? + // create a new Path based on the current + Path newPath = path.copy(); - // add the new point - newPath.add(g, p); - if (!takenPaths.contains(newPath)) { - paths.add(newPath); - takenPaths.add(newPath); - pointsAdded = true; - } + // add the new point + newPath.add(g, p); + if (!takenPaths.contains(newPath)) { + paths.add(newPath); + takenPaths.add(newPath); + pointsAdded = true; } } - if (!pointsAdded) { - // dead end, evict - Path ended = paths.poll(); - if (ended != null && ended.value() > max.value()) { - max = ended; - } + } + if (!pointsAdded) { + // dead end, evict + Path ended = paths.poll(); + if (ended != null && ended.value() > max.value()) { + max = ended; } } } diff --git a/solution1/src/test/java/assessment/algorithm/GridReadTest.java b/solution1/src/test/java/assessment/algorithm/GridReadTest.java index 27ddb6f..64e18df 100644 --- a/solution1/src/test/java/assessment/algorithm/GridReadTest.java +++ b/solution1/src/test/java/assessment/algorithm/GridReadTest.java @@ -17,4 +17,15 @@ public class GridReadTest { String row10line = row10.stream().map(i -> "" + i).collect(Collectors.joining(" ")); assertEquals("1 1 1 2 0 0 1 1 2 2 0 2 2 2 2 1 1 2 0 2", row10line); } + + @Test + void testGridReader1() { + Grid grid = Grid.fromFile("grids/100.txt"); + System.out.println(grid.getInitialValue(9, 9)); + System.out.println(grid.getInitialValue(10, 8)); + System.out.println(grid.getInitialValue(8, 10)); + + Path optimalPath = new OptimalPathFinder().findOptimalPath(grid, 100, 2, 1000, 9, 9); + System.out.println(optimalPath); + } } diff --git a/solution1/src/test/java/assessment/algorithm/PathFinderTest.java b/solution1/src/test/java/assessment/algorithm/PathFinderTest.java index a6c9b35..9823eae 100644 --- a/solution1/src/test/java/assessment/algorithm/PathFinderTest.java +++ b/solution1/src/test/java/assessment/algorithm/PathFinderTest.java @@ -7,14 +7,14 @@ public class PathFinderTest { @Test public void testBestPath20() { Grid grid = Grid.fromFile("grids/20.txt"); - Path path = new OptimalPathFinder().findOptimalPath(grid, 20, 8, 1000, 9, 9); + Path path = new OptimalPathFinder().findOptimalPath(grid, 20, 10, 1000, 9, 9); System.out.println(path); } @Test public void testBestPath100() { Grid grid = Grid.fromFile("grids/100.txt"); - Path path = new OptimalPathFinder().findOptimalPath(grid, 100, 10, 100, 50, 50); + Path path = new OptimalPathFinder().findOptimalPath(grid, 100, 10, 100, 9, 9); System.out.println(path); } diff --git a/solution2/Cargo.lock b/solution2/Cargo.lock index 71369af..94d7f87 100644 --- a/solution2/Cargo.lock +++ b/solution2/Cargo.lock @@ -2,6 +2,139 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.162" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "solution2" version = "0.1.0" +dependencies = [ + "rand", +] + +[[package]] +name = "syn" +version = "2.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/solution2/Cargo.toml b/solution2/Cargo.toml index 4606f20..8a312e3 100644 --- a/solution2/Cargo.toml +++ b/solution2/Cargo.toml @@ -4,3 +4,4 @@ version = "0.1.0" edition = "2021" [dependencies] +rand = "0.8" diff --git a/solution2/src/grid.rs b/solution2/src/grid.rs index ee1b78b..d45b7ec 100644 --- a/solution2/src/grid.rs +++ b/solution2/src/grid.rs @@ -1,9 +1,11 @@ use std::{ cmp::Ordering, - collections::{BTreeSet, HashMap}, + collections::{BTreeSet, HashMap, LinkedList}, hash::{Hash, Hasher}, + sync::{Arc, Mutex}, }; +#[allow(non_snake_case)] #[derive(Debug)] pub struct Grid { data: Vec>, @@ -14,6 +16,7 @@ pub struct Grid { } impl Grid { + #[allow(non_snake_case)] pub fn new(N: usize) -> Self { let grid20 = include_str!("grids/20.txt"); let grid100 = include_str!("grids/100.txt"); @@ -27,10 +30,12 @@ impl Grid { }; let mut data = vec![]; - for row in datafile.split("\n") { + for row in datafile.split('\n') { let mut datarow = vec![]; - for col in row.split(" ") { - datarow.push(u16::from_str_radix(col, 10).unwrap()); + for col in row.split(' ') { + if let Ok(v) = col.parse::() { + datarow.push(v); + } } data.push(datarow); } @@ -50,10 +55,7 @@ impl Grid { if time > *t { let elapsed_since_hit = (time - *t) as f32; - return f32::min( - elapsed_since_hit * initial_value as f32 * 0.1, - initial_value, - ); + return f32::min(elapsed_since_hit * initial_value * 0.1, initial_value); } } 0.0 @@ -62,6 +64,10 @@ impl Grid { } } + pub fn hit(&mut self, x: u16, y: u16, time: usize) { + self.hits.entry((x, y)).or_default().insert(time); + } + pub fn size(&self) -> u16 { self.data.len() as u16 } @@ -69,13 +75,15 @@ impl Grid { #[derive(Debug, Clone)] pub struct Path { - points: Vec, + points: LinkedList, pub value: f32, } impl Path { - pub fn new(grid: &Grid, initial_x: u16, initial_y: u16) -> Self { - let mut points = vec![]; + 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); let p = Point { @@ -84,7 +92,7 @@ impl Path { value, }; - points.push(p); + points.push_front(p); Self { points, value } } @@ -92,27 +100,23 @@ impl Path { self.points.len() } - pub fn head(&self) -> &Point { - self.points.get(self.points.len() - 1).unwrap() // assert Some + pub fn last(&self) -> &Point { + self.points.front().unwrap() // assert Some } pub fn value(&self) -> f32 { self.points.iter().map(|p| p.value).sum() } + + pub fn add(&mut self, p: Point) { + self.points.push_front(p); + self.value = self.value(); + } } impl PartialOrd for Path { fn partial_cmp(&self, other: &Path) -> Option { - match self.value > other.value { - true => Some(Ordering::Greater), - false => { - if self.value == other.value { - Some(Ordering::Equal) - } else { - Some(Ordering::Less) - } - } - } + Some(self.cmp(other)) } } @@ -120,13 +124,39 @@ impl Eq for Path {} impl Ord for Path { fn cmp(&self, other: &Self) -> Ordering { - self.partial_cmp(other).unwrap() + match self.value > other.value { + true => Ordering::Greater, + false => { + if self.value() == other.value { + Ordering::Equal + } else { + Ordering::Less + } + } + } } } impl PartialEq for Path { fn eq(&self, other: &Path) -> bool { - self.value == other.value + if self.points.len() != other.points.len() { + return false; + } + for p in self.points.iter() { + if !other.points.contains(p) { + return false; + } + } + true + } +} + +impl Hash for Path { + fn hash(&self, state: &mut H) { + for p in self.points.iter() { + p.x.hash(state); + p.y.hash(state); + } } } @@ -139,7 +169,7 @@ pub struct Point { impl Point { pub fn new(x: u16, y: u16, value: f32) -> Self { - Self { x, y, value: 0.0 } + Self { x, y, value } } } @@ -156,3 +186,15 @@ impl Hash for Point { self.y.hash(state); } } + +#[cfg(test)] +mod test { + use super::Grid; + + #[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); + } +} diff --git a/solution2/src/lib.rs b/solution2/src/lib.rs index f5f7021..a835359 100644 --- a/solution2/src/lib.rs +++ b/solution2/src/lib.rs @@ -1,98 +1,157 @@ use grid::{Grid, Path, Point}; -use std::collections::BTreeSet; -use std::time::{Duration, SystemTime}; +use std::collections::HashSet; +use std::hash::{DefaultHasher, Hash, Hasher}; +use std::sync::{Arc, Mutex}; +use std::time::SystemTime; pub mod grid; -pub fn find_optimal_path(grid: &Grid, t: usize, T: u128, x: u16, y: u16) { - let mut paths_to_consider: BTreeSet = BTreeSet::new(); - let taken_paths: Vec = vec![]; +/// 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(); - let path = Path::new(grid, x, y); + // starting point + let path = Path::new(Arc::clone(&grid), x, y); + // always current max let mut max: Path = path.clone(); // sorry - paths_to_consider.insert(path); + // 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; - while running { - let N = grid.size(); - let mut current_path = paths_to_consider.pop_last().unwrap(); // assert Some + // will keep at most 8 new directions from current location + let mut new_directions = vec![]; - if current_path.value > max.value { - max = current_path.clone(); // sorry - } + 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 { - current_path = paths_to_consider.pop_last().unwrap(); // highest - if current_path.value > max.value { + _ = 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.head(); + let head = current_path.last(); let x = head.x; let y = head.y; - let mut new_directions = vec![]; - 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), - )); + // 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 - 1, + x, y - 1, - grid.get_value(x - 1, y - 1, discrete_elapsed), + 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 < 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), + 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; + } } } @@ -105,4 +164,17 @@ pub fn find_optimal_path(grid: &Grid, t: usize, T: u128, x: u16, y: u16) { } 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 6a3bac2..4680721 100644 --- a/solution2/src/main.rs +++ b/solution2/src/main.rs @@ -1,5 +1,36 @@ -use solution2::grid::Grid; +use rand::Rng; +use solution2::{find_optimal_path, grid::Grid}; +use std::{ + sync::{Arc, Mutex}, + thread, +}; +/// this app calculates paths for 4 drones, concurrently, with a shared grid fn main() { - Grid::new(20); + 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:?}"); + } }