diff --git a/src/maps.rs b/src/maps.rs index f79d4a8..161fca5 100644 --- a/src/maps.rs +++ b/src/maps.rs @@ -3,25 +3,18 @@ use std::ops::{Index, IndexMut}; use crate::traits::GraphTopology; pub struct VertexMap { - data: Vec, - default: T, - to_index: fn(V) -> usize, + inner: EntityMap, } impl VertexMap { pub fn new(default: T, to_index: fn(V) -> usize, capacity: usize) -> Self { Self { - data: vec![default.clone(); capacity], - default, - to_index, + inner: EntityMap::new(default, to_index, capacity), } } pub fn sync>(&mut self, graph: &G) { - let capacity = graph.vertex_capacity(); - if capacity > self.data.len() { - self.data.resize(capacity, self.default.clone()); - } + self.inner.resize(graph.vertex_capacity()); } } @@ -29,7 +22,73 @@ impl Index for VertexMap { type Output = T; fn index(&self, v: V) -> &T { - let i = (self.to_index)(v); + &self.inner[v] + } +} + +impl IndexMut for VertexMap { + fn index_mut(&mut self, v: V) -> &mut T { + &mut self.inner[v] + } +} + +pub struct EdgeMap { + inner: EntityMap, +} + +impl EdgeMap { + pub fn new(default: T, to_index: fn(E) -> usize, capacity: usize) -> Self { + Self { + inner: EntityMap::new(default, to_index, capacity), + } + } + + pub fn sync>(&mut self, graph: &G) { + self.inner.resize(graph.edge_capacity()); + } +} + +impl Index for EdgeMap { + type Output = T; + + fn index(&self, e: E) -> &T { + &self.inner[e] + } +} + +impl IndexMut for EdgeMap { + fn index_mut(&mut self, e: E) -> &mut T { + &mut self.inner[e] + } +} + +struct EntityMap { + data: Vec, + default: T, + to_index: fn(E) -> usize, +} + +impl EntityMap { + pub fn new(default: T, to_index: fn(E) -> usize, capacity: usize) -> Self { + Self { + data: vec![default.clone(); capacity], + default, + to_index, + } + } + + pub fn resize(&mut self, capacity: usize) { + if capacity > self.data.len() { + self.data.resize(capacity, self.default.clone()); + } + } +} + +impl Index for EntityMap { + type Output = T; + + fn index(&self, e: E) -> &T { + let i = (self.to_index)(e); if i < self.data.len() { &self.data[i] } else { @@ -38,9 +97,9 @@ impl Index for VertexMap { } } -impl IndexMut for VertexMap { - fn index_mut(&mut self, v: V) -> &mut T { - let i = (self.to_index)(v); +impl IndexMut for EntityMap { + fn index_mut(&mut self, e: E) -> &mut T { + let i = (self.to_index)(e); if i >= self.data.len() { self.data.resize(i + 1, self.default.clone()); } @@ -49,7 +108,7 @@ impl IndexMut for VertexMap { } #[cfg(test)] -mod append_graph_tests { +mod append_graph_vertex_map_tests { use crate::models::append_graph::AppendGraph; use crate::traits::GraphTopology; @@ -57,10 +116,27 @@ mod append_graph_tests { } #[cfg(test)] -mod graph_tests { +mod append_graph_edge_map_tests { + use crate::models::append_graph::AppendGraph; + use crate::traits::GraphTopology; + + crate::edge_map_tests!(AppendGraph); +} + +#[cfg(test)] +mod graph_vertex_map_tests { use crate::models::graph::Graph; use crate::traits::{GraphTopology, GraphTopologyDeletion}; crate::vertex_map_tests!(Graph); crate::vertex_map_deletion_tests!(Graph); } + +#[cfg(test)] +mod graph_edge_map_tests { + use crate::models::graph::Graph; + use crate::traits::{GraphTopology, GraphTopologyDeletion}; + + crate::edge_map_tests!(Graph); + crate::edge_map_deletion_tests!(Graph); +} diff --git a/src/models/append_graph.rs b/src/models/append_graph.rs index 816e6af..cd4d92a 100644 --- a/src/models/append_graph.rs +++ b/src/models/append_graph.rs @@ -1,4 +1,4 @@ -use crate::maps::VertexMap; +use crate::maps::{EdgeMap, VertexMap}; use crate::traits::GraphTopology; #[derive(Copy, Clone, PartialEq, Eq, Debug)] @@ -92,6 +92,14 @@ impl GraphTopology for AppendGraph { self.incidences.len() / 2 } + fn edge_capacity(&self) -> usize { + self.incidences.len() / 2 + } + + fn edge_map(&self, default: T) -> EdgeMap { + EdgeMap::new(default, |e| e.0 / 2, self.edge_capacity()) + } + fn degree(&self, v: Self::Vertex) -> usize { self.vertices[v.0].incidence_count } diff --git a/src/models/graph.rs b/src/models/graph.rs index 8970e24..41c7f95 100644 --- a/src/models/graph.rs +++ b/src/models/graph.rs @@ -1,6 +1,6 @@ use typed_generational_arena::{Arena, Index}; -use crate::maps::VertexMap; +use crate::maps::{EdgeMap, VertexMap}; use crate::traits::{GraphTopology, GraphTopologyDeletion}; type Vertex = Index; @@ -171,6 +171,14 @@ impl GraphTopology for Graph { self.incidences.len() / 2 } + fn edge_capacity(&self) -> usize { + self.incidences.capacity() / 2 + } + + fn edge_map(&self, default: T) -> EdgeMap { + EdgeMap::new(default, |e| e.arr_idx() / 2, self.edge_capacity()) + } + fn degree(&self, v: Self::Vertex) -> usize { self.vertices[v].incidence_count } diff --git a/src/testing.rs b/src/testing.rs index 7975b1f..6dccf6c 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -1,2 +1,2 @@ pub(crate) mod graph_topology_testing; -pub(crate) mod vertex_map_testing; +pub(crate) mod maps_testing; diff --git a/src/testing/maps_testing.rs b/src/testing/maps_testing.rs new file mode 100644 index 0000000..bc73c6f --- /dev/null +++ b/src/testing/maps_testing.rs @@ -0,0 +1,261 @@ +#[cfg(test)] +#[macro_export] +macro_rules! vertex_map_tests { + ($T:ty) => { + #[test] + fn initial_values_are_default() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let map = graph.vertex_map(42); + assert_eq!(map[v1], 42); + assert_eq!(map[v2], 42); + } + + #[test] + fn write_and_read() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let mut map = graph.vertex_map(0); + map[v1] = 7; + assert_eq!(map[v1], 7); + assert_eq!(map[v2], 0); + } + + #[test] + fn lazy_growth_on_read() { + let mut graph = <$T>::new(); + graph.add_vertex(); + let map = graph.vertex_map(99); + let v = graph.add_vertex(); + assert_eq!(map[v], 99); + } + + #[test] + fn lazy_growth_on_write() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let mut map = graph.vertex_map(0); + let v2 = graph.add_vertex(); + map[v2] = 7; + assert_eq!(map[v1], 0); + assert_eq!(map[v2], 7); + } + + #[test] + fn sync_expands_to_new_vertices() { + let mut graph = <$T>::new(); + graph.add_vertex(); + let mut map = graph.vertex_map(42); + let initial_len = map.inner.data.len(); + while graph.vertex_capacity() <= initial_len { + graph.add_vertex(); + } + assert!( + map.inner.data.len() < graph.vertex_capacity(), + "precondition: map is stale before sync" + ); + map.sync(&graph); + assert_eq!(map.inner.data.len(), graph.vertex_capacity()); + } + + #[test] + fn sync_does_not_overwrite_existing_values() { + let mut graph = <$T>::new(); + let v = graph.add_vertex(); + let mut map = graph.vertex_map(0); + map[v] = 5; + graph.add_vertex(); + map.sync(&graph); + assert_eq!(map[v], 5); + } + }; +} + +#[cfg(test)] +#[macro_export] +macro_rules! vertex_map_deletion_tests { + ($T:ty) => { + #[test] + fn surviving_vertex_readable_after_delete() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let mut map = graph.vertex_map(0); + map[v1] = 1; + map[v2] = 2; + graph.delete_vertex(v2); + assert_eq!(map[v1], 1); + } + + #[test] + fn capacity_does_not_shrink_after_delete() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let mut map = graph.vertex_map(0); + map[v1] = 5; + let capacity_before = graph.vertex_capacity(); + graph.delete_vertex(v2); + map.sync(&graph); + assert_eq!(map.inner.data.len(), capacity_before); + assert_eq!(map[v1], 5); + } + + #[test] + fn reused_slot_returns_old_value() { + let mut graph = <$T>::new(); + graph.add_vertex(); + let v1 = graph.add_vertex(); + let mut map = graph.vertex_map(0); + map[v1] = 99; + graph.delete_vertex(v1); + let v2 = graph.add_vertex(); + // VertexMap uses raw indices, not vertex identity. A new vertex v2 reusing the slot + // of previously deleted v1 sees the old value. Callers must reinitialize stale slots + // after deletion. + assert_eq!(map[v2], 99); + } + }; +} + +#[cfg(test)] +#[macro_export] +macro_rules! edge_map_tests { + ($T:ty) => { + #[test] + fn initial_values_are_default() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let e1 = graph.add_edge(v1, v2); + let e2 = graph.add_edge(v1, v2); + let map = graph.edge_map(42); + assert_eq!(map[e1], 42); + assert_eq!(map[e2], 42); + } + + #[test] + fn write_and_read() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let e1 = graph.add_edge(v1, v2); + let e2 = graph.add_edge(v1, v2); + let mut map = graph.edge_map(0); + map[e1] = 7; + assert_eq!(map[e1], 7); + assert_eq!(map[e2], 0); + } + + #[test] + fn lazy_growth_on_read() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + graph.add_edge(v1, v2); + let map = graph.edge_map(99); + let e = graph.add_edge(v1, v2); + assert_eq!(map[e], 99); + } + + #[test] + fn lazy_growth_on_write() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let e1 = graph.add_edge(v1, v2); + let mut map = graph.edge_map(0); + let e2 = graph.add_edge(v1, v2); + map[e2] = 7; + assert_eq!(map[e1], 0); + assert_eq!(map[e2], 7); + } + + #[test] + fn sync_expands_to_new_edges() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + graph.add_edge(v1, v2); + let mut map = graph.edge_map(42); + let initial_len = map.inner.data.len(); + while graph.edge_capacity() <= initial_len { + graph.add_edge(v1, v2); + } + assert!( + map.inner.data.len() < graph.edge_capacity(), + "precondition: map is stale before sync" + ); + map.sync(&graph); + assert_eq!(map.inner.data.len(), graph.edge_capacity()); + } + + #[test] + fn sync_does_not_overwrite_existing_values() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let e = graph.add_edge(v1, v2); + let mut map = graph.edge_map(0); + map[e] = 5; + graph.add_edge(v1, v2); + map.sync(&graph); + assert_eq!(map[e], 5); + } + }; +} + +#[cfg(test)] +#[macro_export] +macro_rules! edge_map_deletion_tests { + ($T:ty) => { + #[test] + fn surviving_edge_readable_after_delete() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let e1 = graph.add_edge(v1, v2); + let e2 = graph.add_edge(v1, v2); + let mut map = graph.edge_map(0); + map[e1] = 1; + map[e2] = 2; + graph.delete_edge(e2); + assert_eq!(map[e1], 1); + } + + #[test] + fn capacity_does_not_shrink_after_delete() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + let e1 = graph.add_edge(v1, v2); + let e2 = graph.add_edge(v1, v2); + let mut map = graph.edge_map(0); + map[e1] = 5; + let capacity_before = graph.edge_capacity(); + graph.delete_edge(e2); + map.sync(&graph); + assert_eq!(map.inner.data.len(), capacity_before); + assert_eq!(map[e1], 5); + } + + #[test] + fn reused_slot_returns_old_value() { + let mut graph = <$T>::new(); + let v1 = graph.add_vertex(); + let v2 = graph.add_vertex(); + graph.add_edge(v1, v2); + let e1 = graph.add_edge(v1, v2); + let mut map = graph.edge_map(0); + map[e1] = 99; + graph.delete_edge(e1); + let e2 = graph.add_edge(v1, v2); + // EdgeMap uses raw indices, not edge identity. Because to_index uses arr_idx/2, + // both halves of a deleted edge pair map to the same index, so a new edge reusing + // either slot sees the old value. Callers must reinitialize stale slots after deletion. + assert_eq!(map[e2], 99); + } + }; +} diff --git a/src/testing/vertex_map_testing.rs b/src/testing/vertex_map_testing.rs deleted file mode 100644 index e97df88..0000000 --- a/src/testing/vertex_map_testing.rs +++ /dev/null @@ -1,120 +0,0 @@ -#[cfg(test)] -#[macro_export] -macro_rules! vertex_map_tests { - ($T:ty) => { - #[test] - fn initial_values_are_default() { - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let map = graph.vertex_map(42); - assert_eq!(map[v1], 42); - assert_eq!(map[v2], 42); - } - - #[test] - fn write_and_read() { - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let mut map = graph.vertex_map(0); - map[v1] = 7; - assert_eq!(map[v1], 7); - assert_eq!(map[v2], 0); - } - - #[test] - fn lazy_growth_on_read() { - let mut graph = <$T>::new(); - graph.add_vertex(); - let map = graph.vertex_map(99); - let v = graph.add_vertex(); - assert_eq!(map[v], 99); - } - - #[test] - fn lazy_growth_on_write() { - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let mut map = graph.vertex_map(0); - let v2 = graph.add_vertex(); - map[v2] = 7; - assert_eq!(map[v1], 0); - assert_eq!(map[v2], 7); - } - - #[test] - fn sync_expands_to_new_vertices() { - let mut graph = <$T>::new(); - graph.add_vertex(); - let mut map = graph.vertex_map(42); - let initial_len = map.data.len(); - while graph.vertex_capacity() <= initial_len { - graph.add_vertex(); - } - assert!( - map.data.len() < graph.vertex_capacity(), - "precondition: map is stale before sync" - ); - map.sync(&graph); - assert_eq!(map.data.len(), graph.vertex_capacity()); - } - - #[test] - fn sync_does_not_overwrite_existing_values() { - let mut graph = <$T>::new(); - let v = graph.add_vertex(); - let mut map = graph.vertex_map(0); - map[v] = 5; - graph.add_vertex(); - map.sync(&graph); - assert_eq!(map[v], 5); - } - }; -} - -#[cfg(test)] -#[macro_export] -macro_rules! vertex_map_deletion_tests { - ($T:ty) => { - #[test] - fn surviving_vertex_readable_after_delete() { - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let mut map = graph.vertex_map(0); - map[v1] = 1; - map[v2] = 2; - graph.delete_vertex(v2); - assert_eq!(map[v1], 1); - } - - #[test] - fn capacity_does_not_shrink_after_delete() { - let mut graph = <$T>::new(); - let v1 = graph.add_vertex(); - let v2 = graph.add_vertex(); - let mut map = graph.vertex_map(0); - let capacity_before = graph.vertex_capacity(); - graph.delete_vertex(v2); - map.sync(&graph); - assert_eq!(map.data.len(), capacity_before); - assert_eq!(map[v1], 0); - } - - #[test] - fn reused_slot_returns_old_value() { - let mut graph = <$T>::new(); - graph.add_vertex(); - let v1 = graph.add_vertex(); - let mut map = graph.vertex_map(0); - map[v1] = 99; - graph.delete_vertex(v1); - let v2 = graph.add_vertex(); - // VertexMap uses raw indices, not vertex identity. A new vertex v2 reusing the slot - // of previously deleted v1 sees the old value. Callers must reinitialize stale slots - // after deletion. - assert_eq!(map[v2], 99); - } - }; -} diff --git a/src/traits.rs b/src/traits.rs index 16c7129..9964ad0 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,4 +1,4 @@ -use crate::maps::VertexMap; +use crate::maps::{EdgeMap, VertexMap}; // TODO: Add functions to reserve memory for vertices and edges. // TODO: Add iterator of incident edges for a vertex. @@ -12,6 +12,8 @@ pub trait GraphTopology { fn vertex_capacity(&self) -> usize; fn vertex_map(&self, default: T) -> VertexMap; fn edge_count(&self) -> usize; + fn edge_capacity(&self) -> usize; + fn edge_map(&self, default: T) -> EdgeMap; fn degree(&self, v: Self::Vertex) -> usize; fn are_adjacent(&self, v1: Self::Vertex, v2: Self::Vertex) -> bool; fn vertices(&self) -> impl Iterator;