Compare commits
10 commits
906818d864
...
aca97e1c55
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aca97e1c55 | ||
|
|
226dee33cc | ||
|
|
af2a2fc8fc | ||
|
|
8de66358c0 | ||
|
|
9e9c0a76f8 | ||
|
|
2d6775d1a0 | ||
|
|
3db72e68b5 | ||
|
|
b170817020 | ||
|
|
8074d42482 | ||
|
|
be386182d5 |
5 changed files with 321 additions and 238 deletions
25
README.md
Normal file
25
README.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
**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
|
||||
|
||||
running:
|
||||
* install jdk22, and apache maven
|
||||
* run `mvn spring-boot:run`
|
||||
* go to http://localhost:8080
|
||||
* in the console in the ui type `fly` [enter]
|
||||
* input values for the algorithm can be updated, eg `T=2000` or `x=10` etc.
|
||||
* use `clear` to refresh
|
||||
|
||||
*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
|
||||
223
solution2/src/algorithm.rs
Normal file
223
solution2/src/algorithm.rs
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
use crate::grid::{Grid, Path, PathsResult, Point};
|
||||
use rand::Rng;
|
||||
use std::collections::HashSet;
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
use std::time::SystemTime;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn find_optimal_path_for_n_drones(
|
||||
grid: Grid,
|
||||
ndrones: usize,
|
||||
N: u16,
|
||||
t: usize,
|
||||
T: u128,
|
||||
) -> PathsResult {
|
||||
// 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 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);
|
||||
// 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);
|
||||
|
||||
// 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();
|
||||
|
||||
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: &mut Grid, N: u16, t: usize, T: u128, x: u16, y: u16) -> Path {
|
||||
let mut paths_to_consider: Vec<Path> = Vec::new();
|
||||
let mut taken_paths: HashSet<u64> = HashSet::new();
|
||||
|
||||
// starting point
|
||||
let path = Path::new(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;
|
||||
|
||||
// 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() {
|
||||
// would have liked a list like datastructure that is guaranteed to be sorted
|
||||
// BTreeSet would be nice, but equals/hash/cmp calls would be unneeded overhead
|
||||
paths_to_consider.sort(); // but this is also overhead compared to BTreeSet
|
||||
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.pop(); // discards element that = current_path
|
||||
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();
|
||||
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 - 1,
|
||||
grid.get_value(x + 1, y - 1, Some(¤t_path)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
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 + 1,
|
||||
y + 1,
|
||||
grid.get_value(x + 1, y + 1, Some(¤t_path)),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
if !points_added {
|
||||
// dead end, evict
|
||||
let ended = paths_to_consider.pop().unwrap();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
max
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
pub fn test_single_drone() {
|
||||
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<Point> = HashSet::new();
|
||||
let mut loop_in_path = false;
|
||||
for point in opt.points.iter() {
|
||||
if all_points.contains(point) {
|
||||
// we have a path that crosses itself
|
||||
// path value should be less than sum of individual initial values of point
|
||||
loop_in_path = true
|
||||
}
|
||||
all_points.insert(point.clone());
|
||||
}
|
||||
if loop_in_path {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,19 @@
|
|||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::{BTreeSet, HashMap, LinkedList},
|
||||
collections::{HashMap, HashSet, LinkedList},
|
||||
hash::{Hash, Hasher},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
const RECOVERY_FACTOR: f32 = 0.1;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Grid {
|
||||
data: Vec<Vec<u16>>,
|
||||
// keep track of every point that has been hit at some time
|
||||
hits: HashMap<(u16, u16), BTreeSet<usize>>, // TreeSet<usize> 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<usize> 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 {
|
||||
|
|
@ -45,27 +46,51 @@ impl Grid {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_value(&self, x: u16, y: u16, time: usize) -> f32 {
|
||||
let hit = self.hits.get(&(x, y));
|
||||
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
|
||||
}
|
||||
|
||||
let initial_value = *(self.data.get(y as usize).unwrap().get(x as usize).unwrap()) as f32;
|
||||
/// 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;
|
||||
// 1.
|
||||
let mut value = initial_value;
|
||||
|
||||
return f32::min(elapsed_since_hit * initial_value * 0.1, 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 {
|
||||
|
|
@ -75,16 +100,14 @@ impl Grid {
|
|||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Path {
|
||||
points: LinkedList<Point>,
|
||||
pub points: LinkedList<Point>,
|
||||
pub points_lookup: HashMap<(u16, u16), usize>,
|
||||
pub value: f32,
|
||||
}
|
||||
|
||||
impl Path {
|
||||
pub fn new(grid: Arc<Mutex<Grid>>, 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,
|
||||
|
|
@ -92,10 +115,19 @@ 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
|
||||
pub fn length(&self) -> usize {
|
||||
self.points.len()
|
||||
}
|
||||
|
|
@ -194,7 +226,13 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PathsResult {
|
||||
pub paths: Vec<Path>,
|
||||
pub overall_score: f32,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Mutex<Grid>>,
|
||||
N: u16,
|
||||
t: usize,
|
||||
T: u128,
|
||||
x: u16,
|
||||
y: u16,
|
||||
) -> Path {
|
||||
let mut paths_to_consider: Vec<Path> = Vec::new();
|
||||
let mut taken_paths: HashSet<u64> = 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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +1,11 @@
|
|||
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
|
||||
/// this app calculates paths for 4 drones
|
||||
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, 4, 100, 15, 2000);
|
||||
for (i, path) in result.paths.iter().enumerate() {
|
||||
println!("path {}: score {}", i, path.value())
|
||||
}
|
||||
println!("overall: {}", result.overall_score);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue