From e6e50013342e3a77f2f1afb1eef7b4977bdbbe8e Mon Sep 17 00:00:00 2001 From: hal8174 Date: Fri, 7 Feb 2025 23:41:51 +0100 Subject: [PATCH] Improve binary heap performance --- .../src/bin/generate_factory.rs | 6 +- factorio-pathfinding/src/belt_finding/mod.rs | 5 +- .../graph/wheighted_graph/shortest_path.rs | 2 +- .../src/priority_queue/binary_heap.rs | 231 ++++++++++++++++++ .../src/priority_queue/mod.rs | 139 +---------- 5 files changed, 241 insertions(+), 142 deletions(-) create mode 100644 factorio-pathfinding/src/priority_queue/binary_heap.rs diff --git a/factorio-blueprint-generator/src/bin/generate_factory.rs b/factorio-blueprint-generator/src/bin/generate_factory.rs index 4a41ce0..60b173d 100644 --- a/factorio-blueprint-generator/src/bin/generate_factory.rs +++ b/factorio-blueprint-generator/src/bin/generate_factory.rs @@ -27,8 +27,8 @@ fn main() { dbg!(&factory_graph); let l = ValidLayout { - max_tries: 10, - retries: 20, + max_tries: 4, + retries: 4, start_size: Position::new(64, 64), growth: Position::new(4, 4), }; @@ -38,7 +38,7 @@ fn main() { population_size: 40, population_keep: 5, population_new: 5, - generations: 40, + generations: 80, valid_layout: l, }; let p = ConflictAvoidance { diff --git a/factorio-pathfinding/src/belt_finding/mod.rs b/factorio-pathfinding/src/belt_finding/mod.rs index 47a2032..102f3b3 100644 --- a/factorio-pathfinding/src/belt_finding/mod.rs +++ b/factorio-pathfinding/src/belt_finding/mod.rs @@ -1,7 +1,8 @@ use crate::examples::HashMapMap; use crate::graph::wheighted_graph::WheightedGraph; use crate::misc::Map; -use crate::priority_queue::BinaryHeap; +use crate::priority_queue::binary_heap::BinaryHeap; +use crate::priority_queue::binary_heap::FastBinaryHeap; use crate::Connection; use crate::Map as _; use crate::SinglePathInput; @@ -268,7 +269,7 @@ impl Problem { map: &self.map, end: self.end[i], }; - let p = dijkstra::>(&m, self.start[i], self.end[i]); + let p = dijkstra::>(&m, self.start[i], self.end[i]); if let Some(p) = p { self.path[i] = p; } else { diff --git a/factorio-pathfinding/src/graph/wheighted_graph/shortest_path.rs b/factorio-pathfinding/src/graph/wheighted_graph/shortest_path.rs index b5917cc..56d4d9d 100644 --- a/factorio-pathfinding/src/graph/wheighted_graph/shortest_path.rs +++ b/factorio-pathfinding/src/graph/wheighted_graph/shortest_path.rs @@ -123,7 +123,7 @@ mod test { graph::wheighted_graph::{ adjacency_list::WheightedAdjacencyList, shortest_path::dijkstra, WheightedGraph, }, - priority_queue::{fibonacci_heap::FibonacciHeap, BinaryHeap}, + priority_queue::{binary_heap::BinaryHeap, fibonacci_heap::FibonacciHeap}, }; #[test] diff --git a/factorio-pathfinding/src/priority_queue/binary_heap.rs b/factorio-pathfinding/src/priority_queue/binary_heap.rs new file mode 100644 index 0000000..6754d36 --- /dev/null +++ b/factorio-pathfinding/src/priority_queue/binary_heap.rs @@ -0,0 +1,231 @@ +use std::{collections::HashMap, fmt::Debug}; + +use super::PriorityQueue; + +#[derive(Debug)] +pub struct BinaryHeap { + nextfree: usize, + data: Vec>, +} + +#[derive(Debug)] +struct BinaryHeapEntry { + id: usize, + item: Item, +} + +impl BinaryHeap +where + Item: PartialOrd, +{ + fn downheap(&mut self, index: usize) { + let left = 2 * index + 1; + let right = 2 * index + 2; + + if right < self.data.len() { + let smaller = if self.data[left].item < self.data[right].item { + left + } else { + right + }; + if self.data[index].item > self.data[smaller].item { + self.data.swap(index, smaller); + self.downheap(smaller); + } + } else if left < self.data.len() && self.data[index].item > self.data[left].item { + self.data.swap(index, left); + self.downheap(left); + } + } + + fn upheap(&mut self, index: usize) { + if index > 0 { + let parent = (index - 1) / 2; + if self.data[parent].item > self.data[index].item { + self.data.swap(parent, index); + self.upheap(parent); + } + } + } + + fn search(&self, id: usize) -> Option { + for (i, d) in self.data.iter().enumerate() { + if d.id == id { + return Some(i); + } + } + None + } +} + +impl PriorityQueue for BinaryHeap +where + Item: PartialOrd + Clone, +{ + type Handle = usize; + + fn insert(&mut self, item: Item) -> Self::Handle { + self.data.push(BinaryHeapEntry { + id: self.nextfree, + item: item.clone(), + }); + self.upheap(self.data.len() - 1); + self.nextfree += 1; + self.nextfree - 1 + } + + fn pop_min(&mut self) -> Option { + if self.data.is_empty() { + None + } else { + let d = self.data.swap_remove(0); + self.downheap(0); + Some(d.item) + } + } + + fn decrease_key(&mut self, handle: &Self::Handle, f: impl Fn(&mut Item)) { + if let Some(index) = self.search(*handle) { + f(&mut self.data[index].item); + self.upheap(index); + } + } + + fn new() -> Self { + Self { + data: Vec::new(), + nextfree: 0, + } + } +} + +#[derive(Debug)] +pub struct Trace

{ + inner: P, +} + +impl PriorityQueue for Trace

+where + I: PartialOrd + Clone + Debug, + P: PriorityQueue, +{ + type Handle = P::Handle; + + fn new() -> Self { + println!("New priority queue."); + Self { inner: P::new() } + } + + fn insert(&mut self, item: I) -> Self::Handle { + println!("Insert: {item:?}"); + self.inner.insert(item) + } + + fn pop_min(&mut self) -> Option { + let min = self.inner.pop_min(); + println!("Pop min: {min:?}"); + min + } + + fn decrease_key(&mut self, handle: &Self::Handle, f: impl Fn(&mut I)) { + self.inner.decrease_key(handle, |i| { + let old_i = i.clone(); + f(i); + println!("Decrease key: {old_i:?} -> {i:?}"); + }) + } +} + +#[derive(Debug)] +pub struct FastBinaryHeap { + nextfree: usize, + data: Vec>, + map: HashMap, +} + +impl FastBinaryHeap +where + Item: PartialOrd, +{ + fn downheap(&mut self, index: usize) { + let left = 2 * index + 1; + let right = 2 * index + 2; + + if right < self.data.len() { + let smaller = if self.data[left].item < self.data[right].item { + left + } else { + right + }; + if self.data[index].item > self.data[smaller].item { + self.data.swap(index, smaller); + self.downheap(smaller); + } + } else if left < self.data.len() && self.data[index].item > self.data[left].item { + self.data.swap(index, left); + self.downheap(left); + } + self.map.insert(self.data[index].id, index); + } + + fn upheap(&mut self, index: usize) { + if index > 0 { + let parent = (index - 1) / 2; + if self.data[parent].item > self.data[index].item { + self.data.swap(parent, index); + self.upheap(parent); + } + } + self.map.insert(self.data[index].id, index); + } + + fn search(&self, id: usize) -> Option { + self.map.get(&id).copied() + } +} + +impl PriorityQueue for FastBinaryHeap +where + Item: PartialOrd + Clone, +{ + type Handle = usize; + + fn insert(&mut self, item: Item) -> Self::Handle { + self.map.insert(self.nextfree, self.data.len()); + self.data.push(BinaryHeapEntry { + id: self.nextfree, + item: item.clone(), + }); + self.upheap(self.data.len() - 1); + self.nextfree += 1; + self.nextfree - 1 + } + + fn pop_min(&mut self) -> Option { + if self.data.is_empty() { + None + } else { + let d = self.data.swap_remove(0); + self.map.remove(&d.id); + if !self.data.is_empty() { + self.downheap(0); + } + Some(d.item) + } + } + + fn decrease_key(&mut self, handle: &Self::Handle, f: impl Fn(&mut Item)) { + if let Some(index) = self.search(*handle) { + f(&mut self.data[index].item); + self.upheap(index); + } + } + + fn new() -> Self { + Self { + data: Vec::new(), + map: HashMap::new(), + nextfree: 0, + } + } +} diff --git a/factorio-pathfinding/src/priority_queue/mod.rs b/factorio-pathfinding/src/priority_queue/mod.rs index 9fcc655..f881750 100644 --- a/factorio-pathfinding/src/priority_queue/mod.rs +++ b/factorio-pathfinding/src/priority_queue/mod.rs @@ -1,4 +1,5 @@ use std::fmt::Debug; +pub mod binary_heap; pub mod fibonacci_heap; pub trait PriorityQueue @@ -16,144 +17,10 @@ where fn decrease_key(&mut self, handle: &Self::Handle, f: impl Fn(&mut Item)); } -#[derive(Debug)] -pub struct BinaryHeap { - nextfree: usize, - data: Vec>, -} - -#[derive(Debug)] -struct BinaryHeapEntry { - id: usize, - item: Item, -} - -impl BinaryHeap -where - Item: PartialOrd, -{ - fn downheap(&mut self, index: usize) { - let left = 2 * index + 1; - let right = 2 * index + 2; - - if right < self.data.len() { - let smaller = if self.data[left].item < self.data[right].item { - left - } else { - right - }; - if self.data[index].item > self.data[smaller].item { - self.data.swap(index, smaller); - self.downheap(smaller); - } - } else if left < self.data.len() && self.data[index].item > self.data[left].item { - self.data.swap(index, left); - self.downheap(left); - } - } - - fn upheap(&mut self, index: usize) { - if index > 0 { - let parent = (index - 1) / 2; - if self.data[parent].item > self.data[index].item { - self.data.swap(parent, index); - self.upheap(parent); - } - } - } - - fn search(&self, id: usize) -> Option { - for (i, d) in self.data.iter().enumerate() { - if d.id == id { - return Some(i); - } - } - None - } -} - -impl PriorityQueue for BinaryHeap -where - Item: PartialOrd + Clone, -{ - type Handle = usize; - - fn insert(&mut self, item: Item) -> Self::Handle { - self.data.push(BinaryHeapEntry { - id: self.nextfree, - item: item.clone(), - }); - self.upheap(self.data.len() - 1); - self.nextfree += 1; - self.nextfree - 1 - } - - fn pop_min(&mut self) -> Option { - if self.data.is_empty() { - None - } else { - let d = self.data.swap_remove(0); - self.downheap(0); - Some(d.item) - } - } - - fn decrease_key(&mut self, handle: &Self::Handle, f: impl Fn(&mut Item)) { - if let Some(index) = self.search(*handle) { - f(&mut self.data[index].item); - self.upheap(index); - } - } - - fn new() -> Self { - Self { - data: Vec::new(), - nextfree: 0, - } - } -} - -#[derive(Debug)] -pub struct Trace

{ - inner: P, -} - -impl PriorityQueue for Trace

-where - I: PartialOrd + Clone + Debug, - P: PriorityQueue, -{ - type Handle = P::Handle; - - fn new() -> Self { - println!("New priority queue."); - Self { inner: P::new() } - } - - fn insert(&mut self, item: I) -> Self::Handle { - println!("Insert: {item:?}"); - self.inner.insert(item) - } - - fn pop_min(&mut self) -> Option { - let min = self.inner.pop_min(); - println!("Pop min: {min:?}"); - min - } - - fn decrease_key(&mut self, handle: &Self::Handle, f: impl Fn(&mut I)) { - self.inner.decrease_key(handle, |i| { - let old_i = i.clone(); - f(i); - println!("Decrease key: {old_i:?} -> {i:?}"); - }) - } -} - #[cfg(test)] mod tests { + use super::binary_heap::{BinaryHeap, FastBinaryHeap}; use super::fibonacci_heap::FibonacciHeap; - use super::BinaryHeap; use super::PriorityQueue; macro_rules! test_generics { @@ -184,7 +51,7 @@ mod tests { } mod test_macro { - test_generics!(basic_generic decrease_key_generic; BinaryHeap FibonacciHeap); + test_generics!(basic_generic decrease_key_generic; BinaryHeap FastBinaryHeap FibonacciHeap); } fn basic_generic>() {