diff --git a/brood/src/commands/path.rs b/brood/src/commands/path.rs index eaac33a..724339a 100644 --- a/brood/src/commands/path.rs +++ b/brood/src/commands/path.rs @@ -1,8 +1,9 @@ +use std::collections::BinaryHeap; use std::fs::File; use std::io::{self, BufReader}; use std::path::Path; -use crate::data::{AdjacencyList, Page, PageInfo}; +use crate::data::{AdjacencyList, LinkInfo, Page, PageInfo}; use crate::util; fn find_index_of_title(pages: &[Page], title: &str) -> u32 { @@ -17,25 +18,121 @@ fn find_index_of_title(pages: &[Page], title: &str) -> u32 { } struct DijkstraPageInfo { - distance: u32, + cost: u32, prev_page_idx: u32, } impl Default for DijkstraPageInfo { fn default() -> Self { Self { - distance: u32::MAX, + cost: u32::MAX, prev_page_idx: u32::MAX, } } } +struct DijkstraLinkInfo { + cost: u32, +} + +impl DijkstraLinkInfo { + const BASE_COST: u32 = 1000; + + fn from_link_info(info: LinkInfo) -> Self { + DijkstraLinkInfo { + cost: Self::BASE_COST + info.start, + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +struct Entry { + cost: u32, + page_idx: u32, +} + +impl Entry { + pub fn new(cost: u32, page_idx: u32) -> Self { + Self { cost, page_idx } + } +} + +// Manual implementation so the queue is a min-heap instead of a max-heap. +impl Ord for Entry { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + other + .cost + .cmp(&self.cost) + .then_with(|| self.page_idx.cmp(&other.page_idx)) + } +} + +impl PartialOrd for Entry { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +/// Closely matches the dijkstra example in [std::collections::binary_heap]. fn dijkstra( - mut data: AdjacencyList, + data: AdjacencyList, from_idx: u32, to_idx: u32, ) -> Option> { - todo!() + println!("> Prepare state"); + let mut data = data + .change_page_data(&|_| DijkstraPageInfo::default()) + .change_link_data(&DijkstraLinkInfo::from_link_info); + let mut queue = BinaryHeap::new(); + data.page_mut(from_idx).data.cost = 0; + queue.push(Entry::new(0, from_idx)); + + println!("> Run dijkstra"); + while let Some(Entry { cost, page_idx }) = queue.pop() { + if page_idx == to_idx { + // We've found the shortest path to our target + break; + } + + if cost > data.page(page_idx).data.cost { + // This queue entry is outdated + continue; + } + + for link_idx in data.link_range(page_idx) { + let link = data.link(link_idx); + + let next = Entry { + cost: cost + link.data.cost, + page_idx: link.to, + }; + + let mut target_page = data.page_mut(link.to); + if next.cost < target_page.data.cost { + target_page.data.cost = next.cost; + target_page.data.prev_page_idx = page_idx; + queue.push(next); + } + } + } + + println!("> Collect results"); + let mut steps = vec![]; + let mut at_idx = to_idx; + loop { + steps.push(at_idx); + at_idx = data.page(at_idx).data.prev_page_idx; + if at_idx == u32::MAX { + break; + }; + } + steps.reverse(); + Some(steps) + // if steps.first() == Some(&from_idx) { + // Some(steps) + // } else { + // None + // } } pub fn path(datafile: &Path, from: &str, to: &str) -> io::Result<()> { @@ -43,9 +140,6 @@ pub fn path(datafile: &Path, from: &str, to: &str) -> io::Result<()> { let mut databuf = BufReader::new(File::open(datafile)?); let data = AdjacencyList::read(&mut databuf)?; let pages = data.pages.clone(); - let data = data - .change_page_data(&|_| DijkstraPageInfo::default()) - .change_link_data(&|_| ()); println!(">> Locate from and to"); let from_idx = find_index_of_title(&pages, from); diff --git a/brood/src/data.rs b/brood/src/data.rs index 91d8db5..05ded17 100644 --- a/brood/src/data.rs +++ b/brood/src/data.rs @@ -1,4 +1,5 @@ use std::io::{self, Read, Write}; +use std::ops::Range; mod ioutil { use std::io::{self, Read, Write}; @@ -217,6 +218,28 @@ impl AdjacencyList { } impl AdjacencyList { + pub fn page(&self, idx: u32) -> &Page

{ + &self.pages[idx as usize] + } + + pub fn page_mut(&mut self, idx: u32) -> &mut Page

{ + &mut self.pages[idx as usize] + } + + pub fn link_range(&self, page_idx: u32) -> Range { + let start_idx = self.page(page_idx).link_idx; + let end_idx = self.page(page_idx + 1).link_idx; + start_idx..end_idx + } + + pub fn link(&self, idx: u32) -> &Link { + &self.links[idx as usize] + } + + pub fn link_mut(&mut self, idx: u32) -> &mut Link { + &mut self.links[idx as usize] + } + pub fn change_page_data(self, page_f: &impl Fn(P) -> P2) -> AdjacencyList { let pages = self .pages