commit 7d47a10acfcedcf9de6902c4e6bf1b96926a6c44 Author: hal8174 Date: Wed Dec 6 21:25:03 2023 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..dee727d --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,316 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "anstream" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "base64" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "factorio_blueprint" +version = "0.1.0" +dependencies = [ + "base64", + "clap", + "flate2", + "serde", + "serde_json", +] + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "serde" +version = "1.0.192" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.192" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "2.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6e00d4a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "factorio_blueprint" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +base64 = "0.21.5" +clap = { version = "4.4.8", features = ["derive"] } +flate2 = "1.0.28" +serde = { version = "1.0.192", features = ["derive"] } +serde_json = "1.0.108" diff --git a/examples/decode_blueprint.rs b/examples/decode_blueprint.rs new file mode 100644 index 0000000..d73426d --- /dev/null +++ b/examples/decode_blueprint.rs @@ -0,0 +1,24 @@ +use clap::Parser; +use factorio_blueprint::blueprint; +use std::path::PathBuf; + +#[derive(Debug, Parser)] +struct Args { + blueprint: PathBuf, +} + +fn main() { + let args = Args::parse(); + + let s = std::fs::read_to_string(args.blueprint).unwrap(); + + let raw = blueprint::decode(s.trim_end()); + // println!("{}", &raw); + + let bp = serde_json::from_str::(&raw).unwrap(); + + dbg!(&bp); + + // let reencode = encode(&raw); + // println!("{}", &reencode); +} diff --git a/examples/path.rs b/examples/path.rs new file mode 100644 index 0000000..cad4b17 --- /dev/null +++ b/examples/path.rs @@ -0,0 +1,14 @@ +use factorio_blueprint::{ + belt_finding::{find_path, Field, Position, QueueObject}, + misc::Map, + priority_queue::BinaryHeap, +}; + +fn main() { + let mut map: Map = Map::new(5, 5); + map.get_mut(2, 0).blocked = true; + map.get_mut(2, 1).blocked = true; + map.get_mut(2, 2).blocked = true; + + find_path::>(map, Position { x: 0, y: 0 }, Position { x: 4, y: 0 }); +} diff --git a/src/belt_finding/mod.rs b/src/belt_finding/mod.rs new file mode 100644 index 0000000..8e6d44c --- /dev/null +++ b/src/belt_finding/mod.rs @@ -0,0 +1,170 @@ +use crate::{misc::Map, priority_queue::PriorityQueue}; + +#[derive(Clone, Copy)] +pub enum Direction { + Up, + Right, + Down, + Left, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Position { + pub x: usize, + pub y: usize, +} + +#[derive(Default, Clone, Copy)] +pub struct Field { + pub blocked: bool, +} + +#[derive(Debug, Clone)] +pub struct QueueObject { + pos: Position, + score: usize, +} + +impl QueueObject { + fn new(pos: Position, score: usize) -> Self { + Self { pos, score } + } +} + +impl Eq for QueueObject {} + +impl Ord for QueueObject { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.score.cmp(&other.score) + } +} + +impl PartialOrd for QueueObject { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.score.cmp(&other.score)) + } +} + +impl PartialEq for QueueObject { + fn eq(&self, other: &Self) -> bool { + self.score == other.score + } +} + +struct FieldInternal { + score: usize, + marked: bool, + key: Option, + parent: Option, +} + +impl Default for FieldInternal { + fn default() -> Self { + Self { + score: usize::MAX, + marked: false, + key: None, + parent: None, + } + } +} + +fn relax

( + internal_map: &mut Map>, + q: &mut P, + x: usize, + y: usize, + score: usize, + parent: Direction, +) where + P: PriorityQueue, +{ + let f = internal_map.get_mut(x, y); + if !f.marked { + if let Some(h) = &mut f.key { + if score < f.score { + f.score = score; + f.parent = Some(parent); + q.decrease_key(h, |o| o.score = score); + } + } else { + let key = q.insert(QueueObject::new(Position { x, y }, score)); + internal_map.set( + x, + y, + FieldInternal { + score, + marked: false, + key: Some(key), + parent: Some(parent), + }, + ) + } + } +} + +pub fn find_path

(map: Map, start: Position, end: Position) -> Option> +where + P: PriorityQueue + std::fmt::Debug, +{ + let mut q = P::new(); + + let mut internal_map: Map> = Map::new(map.width, map.height); + + q.insert(QueueObject::new(start, 0)); + internal_map.get_mut(start.x, start.y).score = 0; + + while let Some(o) = q.pop_min() { + dbg!(&o); + if o.pos.x > 0 && !map.get(o.pos.x - 1, o.pos.y).blocked { + relax::

( + &mut internal_map, + &mut q, + o.pos.x - 1, + o.pos.y, + o.score + 1, + Direction::Right, + ); + } + + if o.pos.x < map.width - 1 && !map.get(o.pos.x + 1, o.pos.y).blocked { + relax::

( + &mut internal_map, + &mut q, + o.pos.x + 1, + o.pos.y, + o.score + 1, + Direction::Left, + ); + } + + if o.pos.y > 0 && !map.get(o.pos.x, o.pos.y - 1).blocked { + relax::

( + &mut internal_map, + &mut q, + o.pos.x, + o.pos.y - 1, + o.score + 1, + Direction::Down, + ); + } + + if o.pos.y < map.height - 1 && !map.get(o.pos.x, o.pos.y + 1).blocked { + relax::

( + &mut internal_map, + &mut q, + o.pos.x, + o.pos.y + 1, + o.score + 1, + Direction::Up, + ); + } + // dbg!(&q); + if o.pos == end { + break; + } + internal_map.get_mut(o.pos.x, o.pos.y).marked = true; + } + + None +} diff --git a/src/blueprint/mod.rs b/src/blueprint/mod.rs new file mode 100644 index 0000000..06a7d57 --- /dev/null +++ b/src/blueprint/mod.rs @@ -0,0 +1,36 @@ +use base64::engine::general_purpose::STANDARD; +use base64::Engine; +use std::io::Cursor; +use std::io::Read; + +pub mod structs; + +pub use structs::*; + +pub fn decode(s: &str) -> String { + let raw = s.as_bytes(); + assert!(raw[0] == b'0'); + + let u = STANDARD.decode(&raw[1..]).unwrap(); + + let mut o = String::new(); + + flate2::bufread::ZlibDecoder::new(Cursor::new(u)) + .read_to_string(&mut o) + .unwrap(); + + o +} + +pub fn encode(s: &str) -> String { + let mut u = Vec::new(); + flate2::bufread::ZlibEncoder::new(Cursor::new(s), flate2::Compression::new(9)) + .read_to_end(&mut u) + .unwrap(); + + let mut o = String::from("0"); + + STANDARD.encode_string(u, &mut o); + + o +} diff --git a/src/blueprint/structs.rs b/src/blueprint/structs.rs new file mode 100644 index 0000000..66947f4 --- /dev/null +++ b/src/blueprint/structs.rs @@ -0,0 +1,196 @@ +use std::collections::HashMap; + +use serde::Deserialize; +use serde::Serialize; + +#[derive(Serialize, Deserialize, Debug)] +pub enum BlueprintString { + #[serde(rename = "blueprint_book")] + BlueprintBook(BlueprintBook), + #[serde(rename = "blueprint")] + Blueprint(Blueprint), +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BlueprintBook { + item: String, + label: Option, + label_color: Option, + blueprints: Vec, + active_index: i32, + version: u64, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BlueprintBookEntry { + #[serde(flatten)] + entry: BlueprintString, + index: u32, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Blueprint { + item: String, + label: Option, + label_color: Option, + #[serde(default)] + entities: Vec, + #[serde(default)] + tiles: Vec, + #[serde(default)] + icons: Vec, + #[serde(default)] + schedules: Vec, + description: Option, + snap_to_grid: Option, + absolute_snapping: Option, + position_relative_to_grid: Option, + version: u64, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BlueprintEntity { + entity_number: u64, + name: String, + position: BlueprintPosition, + direciton: Option, + connections: Option, + neighbours: Option>, + control_behaviour: Option<()>, + items: Option>, + recipe: Option, + bar: Option, + inventory: Option, + infinity_settings: Option<()>, + #[serde(rename = "type")] + underground_type: Option, + input_priority: Option, + output_priority: Option, + filter: Option, + filters: Option>, + filter_mode: Option, + override_stack_size: Option, + drop_position: Option, + pickup_position: Option, + request_filters: Option>, + request_from_buffers: Option, + parameters: Option, + alert_parameters: Option, + auto_launch: Option, + variation: Option, + color: Option, + station: Option, + manuel_trains_limit: Option, + switch_state: Option, + tags: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BlueprintInventory { + filters: Vec, + bar: u16, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BlueprintConnection { + #[serde(rename = "1")] + first: Option, + #[serde(rename = "2")] + second: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BlueprintConnectionPoint { + #[serde(default)] + red: Vec, + #[serde(default)] + green: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BlueprintConnectionData { + entity_id: u64, + circuit_id: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BlueprintItemFilter { + name: String, + index: u32, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BlueprintLogisticFilter { + name: String, + index: u32, + count: u32, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BlueprintSpeakerParameter { + playback_volume: f64, + playback_globally: bool, + allow_polyphony: bool, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BlueprintSpeakerAlertParameter { + show_alert: bool, + show_on_map: bool, + icon_signal_id: BlueprintSignalID, + alert_message: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BlueprintSignalID { + name: String, + #[serde(rename = "type")] + signal_type: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BlueprintPosition { + x: f64, + y: f64, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BlueprintIcon { + index: u32, + signal: BlueprintSignalID, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BlueprintSchedule { + schedule: Vec, + locomotives: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BlueprintScheduleRecord { + station: String, + wait_conditions: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BlueprintWaitCondition { + #[serde(rename = "type")] + condition_type: String, + compare_type: String, + ticks: Option, + condition: Option<()>, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BlueprintTile { + name: String, + position: BlueprintPosition, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BlueprintColor { + r: f32, + g: f32, + b: f32, + a: f32, +} diff --git a/src/graph/mod.rs b/src/graph/mod.rs new file mode 100644 index 0000000..48cb554 --- /dev/null +++ b/src/graph/mod.rs @@ -0,0 +1 @@ +pub mod wheighted_graph; diff --git a/src/graph/wheighted_graph/adjacency_list.rs b/src/graph/wheighted_graph/adjacency_list.rs new file mode 100644 index 0000000..9f9fcde --- /dev/null +++ b/src/graph/wheighted_graph/adjacency_list.rs @@ -0,0 +1,27 @@ +use super::WheightedGraph; + +pub struct WheightedAdjacencyList { + pub(crate) nodes: Vec, + pub(crate) edges: Vec<(usize, f64)>, +} + +impl WheightedGraph for WheightedAdjacencyList { + type Node = usize; + + fn num_edges(&self, node: &Self::Node) -> usize { + assert!(*node < self.nodes.len()); + if *node == self.nodes.len() - 1 { + self.nodes.len() - self.nodes[0] + } else { + self.nodes[node + 1] - self.nodes[*node] + } + } + + fn edge(&self, node: &Self::Node, num: usize) -> Option<(Self::Node, f64)> { + if num < self.num_edges(node) { + Some(self.edges[self.nodes[*node] + num]) + } else { + None + } + } +} diff --git a/src/graph/wheighted_graph/mod.rs b/src/graph/wheighted_graph/mod.rs new file mode 100644 index 0000000..20063ee --- /dev/null +++ b/src/graph/wheighted_graph/mod.rs @@ -0,0 +1,48 @@ +pub mod adjacency_list; +pub mod shortest_path; + +use clap::builder::NonEmptyStringValueParser; + +pub trait WheightedGraph: Sized { + type Node; + fn num_edges(&self, node: &Self::Node) -> usize; + fn edge(&self, node: &Self::Node, num: usize) -> Option<(Self::Node, f64)>; + + fn edge_iter<'a, 'b>(&'a self, node: &'b Self::Node) -> WheightedGraphEdgeIter<'a, 'b, Self> { + WheightedGraphEdgeIter::new(self, node) + } +} + +pub struct WheightedGraphEdgeIter<'a, 'b, G> +where + G: WheightedGraph, +{ + graph: &'a G, + node: &'b G::Node, + count: usize, +} + +impl<'a, 'b, G> WheightedGraphEdgeIter<'a, 'b, G> +where + G: WheightedGraph, +{ + pub fn new(graph: &'a G, node: &'b G::Node) -> Self { + Self { + graph, + node, + count: 0, + } + } +} + +impl<'a, 'b, G> Iterator for WheightedGraphEdgeIter<'a, 'b, G> +where + G: WheightedGraph, +{ + type Item = (G::Node, f64); + + fn next(&mut self) -> Option { + self.count += 1; + self.graph.edge(self.node, self.count - 1) + } +} diff --git a/src/graph/wheighted_graph/shortest_path.rs b/src/graph/wheighted_graph/shortest_path.rs new file mode 100644 index 0000000..bde1aa6 --- /dev/null +++ b/src/graph/wheighted_graph/shortest_path.rs @@ -0,0 +1,230 @@ +use std::{ + collections::{BinaryHeap, HashMap}, + fmt::Debug, + hash::Hash, + hash::Hasher, +}; + +use crate::priority_queue::PriorityQueue; + +use super::WheightedGraph; + +#[derive(Debug, Clone, Copy)] +pub struct QueueObject { + node: N, + score: f64, +} + +impl QueueObject { + fn new(node: N, score: f64) -> Self { + Self { node, score } + } +} + +impl PartialOrd for QueueObject { + fn partial_cmp(&self, other: &Self) -> Option { + self.score.partial_cmp(&other.score) + } +} + +impl PartialEq for QueueObject { + fn eq(&self, other: &Self) -> bool { + self.score == other.score + } +} + +impl Hash for QueueObject +where + N: Hash, +{ + fn hash(&self, state: &mut H) { + self.node.hash(state); + } +} + +#[derive(Debug)] +struct MapObject { + parent: N, + score: f64, + key: Option, +} + +impl MapObject { + fn new(parent: N, score: f64, key: H) -> Self { + Self { + parent, + score, + key: Some(key), + } + } +} + +pub fn dijkstra(graph: &G, start: G::Node, end: G::Node) -> Option> +where + P: PriorityQueue> + Debug, + P::Handle: Debug, + G::Node: Eq + Hash + Clone + Debug, + G: WheightedGraph, +{ + let mut map: HashMap> = HashMap::new(); + + let mut q = P::new(); + q.insert(QueueObject::new(start.clone(), 0.0)); + + while let Some(o) = q.pop_min() { + if let Some(m) = map.get_mut(&o.node) { + m.key = None; + } + + if o.node == end { + break; + } + + for (node, wheight) in graph.edge_iter(&o.node) { + let score = o.score + wheight; + if let Some(n) = map.get_mut(&node) { + if let Some(h) = &n.key { + if score < n.score { + n.parent = o.node.clone(); + n.score = score; + q.decrease_key(&h, |i| i.score = score); + } + } + } else { + let h = q.insert(QueueObject::new(node.clone(), o.score + wheight)); + map.insert(node, MapObject::new(o.node.clone(), o.score + wheight, h)); + } + } + dbg!(&q); + } + + if map.get(&end).is_none() { + return None; + } + + let mut result = vec![end]; + + dbg!(&map); + + loop { + let parent = map.get(result.last().expect("last")).expect("get"); + result.push(parent.parent.clone()); + if parent.parent == start { + break; + } + } + + result.reverse(); + + Some(result) +} + +#[cfg(test)] +mod test { + use crate::{ + belt_finding::QueueObject, + graph::wheighted_graph::{ + adjacency_list::WheightedAdjacencyList, shortest_path::dijkstra, WheightedGraph, + }, + priority_queue::{BinaryHeap, PriorityQueue}, + }; + + #[test] + fn trivial() { + let a = WheightedAdjacencyList { + nodes: vec![0, 1], + edges: vec![(1, 1.0)], + }; + + assert_eq!( + dijkstra::>(&a, 0, 1), + Some(vec![0, 1]) + ); + } + + #[test] + fn simple() { + let a = WheightedAdjacencyList { + nodes: vec![0, 2, 3, 5, 5, 7, 10], + edges: vec![ + (1, 2.0), + (4, 10.0), + (2, 3.0), + (3, 2.0), + (5, 1.0), + (0, 4.0), + (2, 5.0), + (2, 9.0), + (3, 8.0), + (4, 0.0), + (5, 7.0), + ], + }; + + assert_eq!( + dijkstra::>(&a, 0, 4), + Some(vec![0, 1, 2, 5, 4]) + ); + assert_eq!( + dijkstra::>(&a, 0, 6), + None + ); + } + + struct Grid { + width: usize, + height: usize, + } + + impl WheightedGraph for Grid { + type Node = (usize, usize); + + fn num_edges(&self, node: &Self::Node) -> usize { + let mut c = 0; + + if node.0 > 0 { + c += 1 + } + + if node.0 < self.width - 1 { + c += 1 + } + + if node.1 > 0 { + c += 1 + } + + if node.1 < self.height - 1 { + c += 1 + } + + c + } + + fn edge(&self, node: &Self::Node, num: usize) -> Option<(Self::Node, f64)> { + dbg!(&node); + let edges = [ + (node.0 > 0).then_some((node.0.saturating_sub(1), node.1)), + (node.0 < self.width - 1).then_some((node.0 + 1, node.1)), + (node.1 > 0).then_some((node.0, node.1.saturating_sub(1))), + (node.1 < self.height - 1).then_some((node.0, node.1 + 1)), + ]; + + edges.iter().flatten().nth(num).map(|&p| (p, 1.0)) + } + } + + #[test] + fn grid() { + let g = Grid { + width: 600, + height: 600, + }; + + dbg!(dijkstra::>( + &g, + (0, 0), + (g.width - 1, g.height - 1) + )); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..da96512 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,5 @@ +pub mod belt_finding; +pub mod blueprint; +pub mod graph; +pub mod misc; +pub mod priority_queue; diff --git a/src/misc/arena.rs b/src/misc/arena.rs new file mode 100644 index 0000000..22a68d5 --- /dev/null +++ b/src/misc/arena.rs @@ -0,0 +1,52 @@ +#[derive(Debug)] +pub struct Arena { + data: Vec>, + free: Vec, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ArenaKey(usize); + +impl Default for ArenaKey { + fn default() -> Self { + ArenaKey(usize::MAX) + } +} + +impl Arena { + pub fn new() -> Self { + Self { + data: Vec::new(), + free: Vec::new(), + } + } + + pub fn insert(&mut self, item: T) -> ArenaKey { + if let Some(i) = self.free.pop() { + self.data[i] = Some(item); + ArenaKey(i) + } else { + self.data.push(Some(item)); + ArenaKey(self.data.len() - 1) + } + } + + pub fn get(&self, key: &ArenaKey) -> &T { + self.data.get(key.0).unwrap().as_ref().unwrap() + } + + pub fn get_mut(&mut self, key: &ArenaKey) -> &mut T { + self.data.get_mut(key.0).unwrap().as_mut().unwrap() + } + + pub fn remove(&mut self, key: ArenaKey) -> T { + self.free.push(key.0); + self.data[key.0].take().unwrap() + } +} + +impl Default for Arena { + fn default() -> Self { + Self::new() + } +} diff --git a/src/misc/map.rs b/src/misc/map.rs new file mode 100644 index 0000000..d39c6c7 --- /dev/null +++ b/src/misc/map.rs @@ -0,0 +1,45 @@ +pub struct Map { + pub width: usize, + pub height: usize, + data: Vec, +} + +impl Map +where + T: Default, +{ + pub fn new(width: usize, height: usize) -> Self { + let mut data = Vec::new(); + for _ in 0..(width * height) { + data.push(T::default()); + } + + Self { + width, + height, + data, + } + } + + fn index(&self, x: usize, y: usize) -> usize { + x + y * self.width + } + + pub fn get(&self, x: usize, y: usize) -> &T { + assert!(x < self.width); + assert!(y < self.height); + let i = self.index(x, y); + self.data.get(i).unwrap() + } + + pub fn get_mut(&mut self, x: usize, y: usize) -> &mut T { + assert!(x < self.width); + assert!(y < self.height); + let i = self.index(x, y); + self.data.get_mut(i).unwrap() + } + + pub fn set(&mut self, x: usize, y: usize, item: T) { + *self.get_mut(x, y) = item; + } +} diff --git a/src/misc/mod.rs b/src/misc/mod.rs new file mode 100644 index 0000000..4ae6e20 --- /dev/null +++ b/src/misc/mod.rs @@ -0,0 +1,5 @@ +pub mod arena; +pub mod map; + +pub use arena::*; +pub use map::*; diff --git a/src/priority_queue/fibonacci_heap.rs b/src/priority_queue/fibonacci_heap.rs new file mode 100644 index 0000000..8dcaa98 --- /dev/null +++ b/src/priority_queue/fibonacci_heap.rs @@ -0,0 +1,186 @@ +use crate::misc::{Arena, ArenaKey}; + +use super::PriorityQueue; + +#[derive(Debug)] +struct FibonacciHeapObject { + left: ArenaKey, + right: ArenaKey, + parent: Option, + child: Option, + rank: usize, + marked: bool, + data: T, +} + +#[derive(Debug)] +pub struct FibonacciHeap { + arena: Arena>, + min: Option, + update_rank: Vec>, +} + +pub struct FibonacciHeapHandle(ArenaKey); + +impl PriorityQueue for FibonacciHeap +where + T: std::cmp::Ord, +{ + type Handle = FibonacciHeapHandle; + + fn new() -> Self { + todo!() + } + + fn insert(&mut self, item: T) -> Self::Handle { + let h = self.arena.insert(FibonacciHeapObject { + left: ArenaKey::default(), + right: ArenaKey::default(), + parent: None, + child: None, + rank: 0, + marked: false, + data: item, + }); + + self.insert_tree(h); + + FibonacciHeapHandle(h) + } + + fn pop_min(&mut self) -> Option { + if let Some(k) = self.min.take() { + let o = self.arena.remove(k); + + if let Some(child) = o.child { + let mut p = child; + loop { + self.arena.get_mut(&p).parent = None; + self.arena.get_mut(&p).marked = false; + let t = self.arena.get(&p).right; + + self.insert_tree(p); + + p = t; + + if p == child { + break; + } + } + } + + if o.left != o.right { + self.arena.get_mut(&o.left).right = o.right; + self.arena.get_mut(&o.right).left = o.left; + self.update_min(o.left); + } + + Some(o.data) + } else { + None + } + } + + fn decrease_key(&mut self, handle: &Self::Handle, f: impl Fn(&mut T)) { + todo!() + } +} + +impl FibonacciHeap +where + T: std::cmp::Ord, +{ + fn insert_tree(&mut self, h: ArenaKey) { + if let Some(h_min) = self.min { + let h_last = self.arena.get(&h_min).left; + + self.arena.get_mut(&h_min).left = h; + + self.arena.get_mut(&h).left = h_last; + self.arena.get_mut(&h).right = h_min; + + self.arena.get_mut(&h_last).right = h; + + if self.arena.get(&h).data < self.arena.get(&h_min).data { + self.min = Some(h) + } + } else { + self.arena.get_mut(&h).left = h; + self.arena.get_mut(&h).right = h; + + self.min = Some(h); + } + } + + fn update_min(&mut self, mut h: ArenaKey) { + let h_start = h; + + loop { + let next_h = self.arena.get(&h).right; + + self.add_to_update_rank(h); + + h = next_h; + if h == h_start { + break; + } + } + + let mut prev = None; + let mut start = None; + + for h in &mut self.update_rank { + if let Some(h) = h.take() { + if let Some(p) = prev { + self.arena.get_mut(&h).left = p; + self.arena.get_mut(&p).right = h; + } else { + prev = Some(h); + start = Some(h); + } + if let Some(m) = self.min { + if self.arena.get(&h).data < self.arena.get(&m).data { + self.min = Some(h); + } + } else { + self.min = Some(h); + } + } + } + } + + fn add_to_update_rank(&mut self, h: ArenaKey) { + let rank = self.arena.get(&h).rank; + + while self.update_rank.len() + 1 < rank { + self.update_rank.push(None) + } + + if let Some(o) = self.update_rank.get_mut(rank).unwrap().take() { + if let Some(child) = self.arena.get(&h).child { + let last = self.arena.get(&child).left; + + self.arena.get_mut(&child).left = h; + self.arena.get_mut(&last).right = h; + + self.arena.get_mut(&o).parent = Some(h); + self.arena.get_mut(&o).marked = false; + self.arena.get_mut(&o).left = last; + self.arena.get_mut(&o).right = child; + } else { + self.arena.get_mut(&h).child = Some(o); + + self.arena.get_mut(&o).parent = Some(h); + self.arena.get_mut(&o).marked = false; + self.arena.get_mut(&o).left = o; + self.arena.get_mut(&o).right = o; + } + + self.arena.get_mut(&h).rank += 1; + + self.add_to_update_rank(h); + } else { + *self.update_rank.get_mut(rank).unwrap() = Some(h); + } + } +} diff --git a/src/priority_queue/mod.rs b/src/priority_queue/mod.rs new file mode 100644 index 0000000..7a960a7 --- /dev/null +++ b/src/priority_queue/mod.rs @@ -0,0 +1,176 @@ +pub mod fibonacci_heap; + +pub trait PriorityQueue +where + Item: PartialOrd, +{ + type Handle; + fn new() -> Self; + // fn with_capacity() -> Self { + // Self::new() + // } + + fn insert(&mut self, item: Item) -> Self::Handle; + fn pop_min(&mut self) -> Option; + fn decrease_key(&mut self, handle: &Self::Handle, f: impl Fn(&mut Item)); +} + +#[derive(Debug)] +pub struct BinaryHeap { + data: Vec, +} + +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] < self.data[right] { + left + } else { + right + }; + if self.data[index] > self.data[smaller] { + self.data.swap(index, smaller); + self.downheap(smaller); + } + } else if left < self.data.len() && self.data[index] > self.data[left] { + 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] > self.data[index] { + self.data.swap(parent, index); + self.upheap(parent); + } + } + } + + fn search(&self, item: &Item) -> Option { + for (i, d) in self.data.iter().enumerate() { + if d == item { + return Some(i); + } + } + None + } +} + +impl PriorityQueue for BinaryHeap +where + Item: PartialOrd + Clone, +{ + type Handle = Item; + + fn insert(&mut self, item: Item) -> Self::Handle { + self.data.push(item.clone()); + self.upheap(self.data.len() - 1); + item + } + + 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) + } + } + + 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]); + self.upheap(index); + } + } + + fn new() -> Self { + Self { data: Vec::new() } + } +} + +#[cfg(test)] +mod tests { + use super::PriorityQueue; + + macro_rules! test_generics { + ($($fun_mul:ident )+ ; $gen:ident $($gen_mul:ident)+) => { + test_generics!(@internal_mod $($fun_mul )*; $gen); + test_generics!($($fun_mul )*; $($gen_mul )*); + }; + ($($fun_mul:ident )+ ;$gen:ident) => { + test_generics!(@internal_mod $($fun_mul )*; $gen); + }; + (@internal_mod $($fun_mul:ident )+; $gen:ident) => { + #[allow(non_snake_case)] + mod $gen { + test_generics!(@internal $($fun_mul )*; $gen); + } + }; + (@internal $fun:ident $($fun_mul:ident )+; $gen:ident) => { + test_generics!(@internal $fun; $gen); + test_generics!(@internal $($fun_mul )*; $gen); + }; + (@internal $fun:ident; $gen:ident) => { + + #[test] + fn $fun() { + super::$fun::>(); + } + } + } + + test_generics!(basic_generic decrease_key_generic; BinaryHeap); + + fn basic_generic>() { + let mut q = T::new(); + + q.insert(2); + q.insert(3); + q.insert(10); + q.insert(12); + q.insert(9); + q.insert(6); + q.insert(4); + q.insert(5); + q.insert(8); + q.insert(7); + q.insert(11); + q.insert(1); + + assert_eq!(q.pop_min(), Some(1)); + assert_eq!(q.pop_min(), Some(2)); + assert_eq!(q.pop_min(), Some(3)); + assert_eq!(q.pop_min(), Some(4)); + assert_eq!(q.pop_min(), Some(5)); + assert_eq!(q.pop_min(), Some(6)); + assert_eq!(q.pop_min(), Some(7)); + assert_eq!(q.pop_min(), Some(8)); + assert_eq!(q.pop_min(), Some(9)); + assert_eq!(q.pop_min(), Some(10)); + assert_eq!(q.pop_min(), Some(11)); + assert_eq!(q.pop_min(), Some(12)); + assert_eq!(q.pop_min(), None); + } + + fn decrease_key_generic>() { + let mut q = T::new(); + + q.insert(4); + let h = q.insert(5); + + q.decrease_key(&h, |i| *i -= 3); + + assert_eq!(q.pop_min(), Some(2)); + assert_eq!(q.pop_min(), Some(4)); + } +}