Skip to main content

linkrs_core/
npath.rs

1//! NPath - path representation of a linked list structure
2//!
3//! Refer to nebula-graph's NPath design for prefix sharing using shared ownership.
4//! For scenarios where frequent path expansion is required in graph traversal.
5//!
6//! # Core strengths
7//!
8//! 1. **Shared prefix**: multiple paths share the same prefix portion to save memory
9//! 2. **O(1) Extension **: New path simply creates a new node pointing to parent path
10//! 3. **Fast splicing**: bi-directional BFS path splicing by simply finding the point of intersection
11//!
12
13use std::collections::HashSet;
14use std::sync::Arc;
15
16use crate::types::VertexId;
17use crate::vertex_edge_path::Step;
18use crate::{Edge, Path, Vertex};
19
20/// NPath - path representation of a linked list structure
21///
22/// Shared ownership via Arc using immutable data structures.
23/// Each node contains a vertex and an edge (other than the starting point) that reaches that vertex.
24#[derive(Debug, Clone)]
25pub struct NPath {
26    /// Parent path node (None indicates the starting point)
27    parent: Option<Arc<NPath>>,
28    /// current vertex
29    vertex: Arc<Vertex>,
30    /// Edge to current vertex (starting point None)
31    edge: Option<Arc<Edge>>,
32    /// Path length (cached to avoid recursive calculations)
33    length: usize,
34    /// Path hashing (caching, for fast comparisons)
35    hash: u64,
36}
37
38impl NPath {
39    /// Creating a Starting Path
40    ///
41    /// # Parameters
42    ///
43    /// * `vertex` - starting vertex
44    pub fn new(vertex: Arc<Vertex>) -> Self {
45        let hash = Self::compute_hash(&vertex, None, None);
46        Self {
47            parent: None,
48            vertex,
49            edge: None,
50            length: 0,
51            hash,
52        }
53    }
54
55    /// Extended Path - O(1) Operation
56    ///
57    /// Creates a new path node that points to the parent path for prefix sharing.
58    ///
59    /// # Parameters
60    ///
61    /// * `parent` - parent path
62    /// * `edge` - the edge that reaches the new vertex
63    /// * `vertex` - new vertex
64    pub fn extend(parent: Arc<NPath>, edge: Arc<Edge>, vertex: Arc<Vertex>) -> Self {
65        let length = parent.length + 1;
66        let hash = Self::compute_hash(&vertex, Some(&edge), Some(parent.hash));
67        Self {
68            parent: Some(parent),
69            vertex,
70            edge: Some(edge),
71            length,
72            hash,
73        }
74    }
75
76    /// Extend path and check for loops - O(1) averaging time
77    ///
78    /// Fast detection of loop formation using HashSet, suitable for early pruning in DFS exploration.
79    ///
80    /// # Parameters
81    ///
82    /// * `parent` - parent path
83    /// * `edge` - the edge that reaches the new vertex
84    /// * `vertex` - new vertex
85    /// * :: `seen_vertices` - set of visited vertices
86    ///
87    /// # Back
88    ///
89    /// * `Some(NPath)` - extension successful
90    /// * `None` - loop detected
91    ///
92    /// # Example
93    ///
94    /// ```rust,ignore
95    /// let mut seen = HashSet::new();
96    /// seen.insert(parent.vertex.vid.as_ref().clone());
97    /// if let Some(extended) = NPath::extend_with_set(parent, edge, vertex, &mut seen) {
98    // Continue to explore
99    /// }
100    /// ```
101    pub fn extend_with_set(
102        parent: Arc<NPath>,
103        edge: Arc<Edge>,
104        vertex: Arc<Vertex>,
105        seen_vertices: &mut HashSet<VertexId>,
106    ) -> Option<Self> {
107        if seen_vertices.contains(&vertex.vid) {
108            return None;
109        }
110
111        let new_path = Self::extend(parent, edge, vertex);
112        seen_vertices.insert(new_path.vertex.vid);
113        Some(new_path)
114    }
115
116    /// Create NPath from Path
117    ///
118    /// Facilitates compatibility with existing interfaces, converting traditional Path to NPath.
119    ///
120    /// # Parameters
121    ///
122    /// * :: `path` -- traditional Path structure
123    ///
124    /// # Example
125    ///
126    /// ```rust,ignore
127    /// let npath = NPath::from_path(&path);
128    /// ```
129    pub fn from_path(path: &Path) -> Arc<Self> {
130        let start_vertex = Arc::new((*path.src).clone());
131        let mut current = Arc::new(Self::new(start_vertex));
132
133        for step in &path.steps {
134            let edge = Arc::new((*step.edge).clone());
135            let vertex = Arc::new((*step.dst).clone());
136            current = Arc::new(Self::extend(current, edge, vertex));
137        }
138
139        current
140    }
141
142    /// Get path length (number of edges)
143    pub fn len(&self) -> usize {
144        self.length
145    }
146
147    /// Check if the path is empty (contains only the starting point)
148    pub fn is_empty(&self) -> bool {
149        self.length == 0
150    }
151
152    /// Get current vertex
153    pub fn vertex(&self) -> &Arc<Vertex> {
154        &self.vertex
155    }
156
157    /// Get the edge that reaches the current vertex
158    pub fn edge(&self) -> Option<&Arc<Edge>> {
159        self.edge.as_ref()
160    }
161
162    /// Get Parent Path
163    pub fn parent(&self) -> Option<&Arc<NPath>> {
164        self.parent.as_ref()
165    }
166
167    /// Get start vertex
168    pub fn start_vertex(&self) -> &Arc<Vertex> {
169        let mut current = self;
170        while let Some(ref parent) = current.parent {
171            current = parent;
172        }
173        &current.vertex
174    }
175
176    /// Get end vertex (current vertex)
177    pub fn end_vertex(&self) -> &Arc<Vertex> {
178        &self.vertex
179    }
180
181    /// Translate the text into "Path" (and convert it further if necessary):
182    ///
183    /// Time complexity: O(n), n is the path length
184    pub fn to_path(&self) -> Path {
185        let mut steps = Vec::with_capacity(self.length);
186        let mut current = self;
187
188        // Collect all the steps (from the end point to the starting point).
189        while let Some(ref parent) = current.parent {
190            if let Some(ref edge) = current.edge {
191                steps.push(Step {
192                    dst: Box::new((*current.vertex).clone()),
193                    edge: Box::new((**edge).clone()),
194                });
195            }
196            current = parent;
197        }
198
199        // Reverse the steps (from the starting point to the ending point)
200        steps.reverse();
201
202        Path {
203            src: Box::new((*current.vertex).clone()),
204            steps,
205        }
206    }
207
208    pub fn contains_vertex(&self, vid: &VertexId) -> bool {
209        if &self.vertex.vid == vid {
210            return true;
211        }
212        if let Some(ref parent) = self.parent {
213            return parent.contains_vertex(vid);
214        }
215        false
216    }
217
218    pub fn contains_edge(&self, edge_key: &(VertexId, VertexId, String)) -> bool {
219        if let Some(ref edge) = self.edge {
220            let key = (edge.src, edge.dst, edge.edge_type.clone());
221            if &key == edge_key {
222                return true;
223            }
224        }
225        if let Some(ref parent) = self.parent {
226            return parent.contains_edge(edge_key);
227        }
228        false
229    }
230
231    pub fn has_common_vertices(&self, other: &NPath) -> bool {
232        let self_vertices: HashSet<_> = self.iter_vertices().map(|v| &v.vid).collect();
233        other
234            .iter_vertices()
235            .any(|v| self_vertices.contains(&v.vid))
236    }
237
238    pub fn collect_vertex_ids(&self) -> Vec<VertexId> {
239        self.iter_vertices().map(|v| v.vid).collect()
240    }
241
242    /// Collect all the edges.
243    pub fn collect_edges(&self) -> Vec<Arc<Edge>> {
244        self.iter_edges().cloned().collect()
245    }
246
247    /// Iterator: All vertices from the starting point to the current node.
248    pub fn iter_vertices(&self) -> NPathVertexIter<'_> {
249        NPathVertexIter::new(self)
250    }
251
252    /// Iterator: All edges from the starting point to the current node
253    pub fn iter_edges(&self) -> NPathEdgeIter<'_> {
254        NPathEdgeIter::new(self)
255    }
256
257    /// Iterator: All nodes from the starting point to the current node.
258    pub fn iter(&self) -> NPathIter<'_> {
259        NPathIter::new(self)
260    }
261
262    /// Calculating the path hash
263    fn compute_hash(
264        vertex: &Arc<Vertex>,
265        edge: Option<&Arc<Edge>>,
266        parent_hash: Option<u64>,
267    ) -> u64 {
268        use std::collections::hash_map::DefaultHasher;
269        use std::hash::{Hash, Hasher};
270
271        let mut hasher = DefaultHasher::new();
272
273        if let Some(ph) = parent_hash {
274            ph.hash(&mut hasher);
275        }
276
277        vertex.vid.hash(&mut hasher);
278
279        if let Some(e) = edge {
280            e.edge_type.hash(&mut hasher);
281            e.src.hash(&mut hasher);
282            e.dst.hash(&mut hasher);
283        }
284
285        hasher.finish()
286    }
287
288    /// Obtain the path hash
289    pub fn hash(&self) -> u64 {
290        self.hash
291    }
292}
293
294impl PartialEq for NPath {
295    fn eq(&self, other: &Self) -> bool {
296        if self.hash != other.hash || self.length != other.length {
297            return false;
298        }
299
300        // The hashes are the same; a further comparison of the content is necessary.
301        self.vertex.vid == other.vertex.vid
302            && self.edge == other.edge
303            && self.parent == other.parent
304    }
305}
306
307impl Eq for NPath {}
308
309/// NPath iterator – Traverses all nodes (from the end point to the start point)
310///
311/// Use inert summation, each time .next() jumps up one step to avoid preallocating Vecs
312pub struct NPathIter<'a> {
313    current: Option<&'a NPath>,
314}
315
316impl<'a> NPathIter<'a> {
317    fn new(path: &'a NPath) -> Self {
318        Self {
319            current: Some(path),
320        }
321    }
322}
323
324impl<'a> Iterator for NPathIter<'a> {
325    type Item = &'a NPath;
326
327    fn next(&mut self) -> Option<Self::Item> {
328        let curr = self.current?;
329        self.current = curr.parent.as_deref();
330        Some(curr)
331    }
332}
333
334/// NPath vertex iterator – Lazy traversal of all vertices
335///
336/// Optimization: no pre-allocated Vec, each time .next() jumps up one step
337pub struct NPathVertexIter<'a> {
338    current: Option<&'a NPath>,
339}
340
341impl<'a> NPathVertexIter<'a> {
342    fn new(path: &'a NPath) -> Self {
343        Self {
344            current: Some(path),
345        }
346    }
347}
348
349impl<'a> Iterator for NPathVertexIter<'a> {
350    type Item = &'a Arc<Vertex>;
351
352    fn next(&mut self) -> Option<Self::Item> {
353        let curr = self.current?;
354        let vertex = &curr.vertex;
355        self.current = curr.parent.as_deref();
356        Some(vertex)
357    }
358}
359
360/// NPath edge iterator – Lazy traversal of all edges
361///
362/// Optimization: no pre-allocated Vec, each time .next() jumps up one step
363pub struct NPathEdgeIter<'a> {
364    current: Option<&'a NPath>,
365}
366
367impl<'a> NPathEdgeIter<'a> {
368    fn new(path: &'a NPath) -> Self {
369        Self {
370            current: Some(path),
371        }
372    }
373}
374
375impl<'a> Iterator for NPathEdgeIter<'a> {
376    type Item = &'a Arc<Edge>;
377
378    fn next(&mut self) -> Option<Self::Item> {
379        let curr = self.current?;
380        let edge = curr.edge.as_ref()?;
381        self.current = curr.parent.as_deref();
382        Some(edge)
383    }
384}
385
386/// NPath utility functions
387pub mod utils {
388    use super::*;
389
390    /// Concatenating two paths (for bidirectional BFS)
391    ///
392    /// The left path goes from the starting point to the middle point, while the right path goes from the ending point to the middle point.
393    /// The result path goes from the starting point to the ending point.
394    pub fn combine_paths(left: &Arc<NPath>, right: &Arc<NPath>) -> Option<Path> {
395        // Check whether the two paths intersect at the same vertex.
396        if left.vertex.vid != right.vertex.vid {
397            return None;
398        }
399
400        // Construct a path from the starting point on the left to the intersection point.
401        let left_path = left.to_path();
402
403        // Construct a path from the starting point on the right to the intersection point, and then reverse it.
404        let mut right_path = right.to_path();
405        right_path.reverse();
406
407        // Merge the two paths
408        let mut combined = left_path;
409        combined.steps.extend(right_path.steps);
410
411        Some(combined)
412    }
413
414    /// Convert NPaths to Paths in batches
415    pub fn batch_to_paths(npaths: &[Arc<NPath>]) -> Vec<Path> {
416        npaths.iter().map(|np| np.to_path()).collect()
417    }
418
419    /// Check whether there are any duplicates in the set of paths.
420    pub fn has_duplicates(npaths: &[Arc<NPath>]) -> bool {
421        let mut seen = HashSet::new();
422        for np in npaths {
423            if !seen.insert(np.hash()) {
424                return true;
425            }
426        }
427        false
428    }
429}
430
431#[cfg(test)]
432mod tests {
433    use super::*;
434    use crate::types::VertexId;
435    use crate::vertex_edge_path::Tag;
436    use std::collections::HashMap;
437
438    fn create_test_vertex(id: i64) -> Arc<Vertex> {
439        Arc::new(Vertex::new(
440            VertexId::try_from_int64(id).expect("test vertex id"),
441            Tag::new(String::new(), HashMap::new()),
442        ))
443    }
444
445    fn create_test_edge(src_id: i64, dst_id: i64, edge_type: &str) -> Arc<Edge> {
446        use std::collections::HashMap;
447        Arc::new(Edge::new(
448            VertexId::try_from_int64(src_id).expect("test vertex id"),
449            VertexId::try_from_int64(dst_id).expect("test vertex id"),
450            edge_type.to_string(),
451            0,
452            HashMap::new(),
453        ))
454    }
455
456    #[test]
457    fn test_npath_new() {
458        let v = create_test_vertex(1);
459        let path = NPath::new(v.clone());
460
461        assert_eq!(path.len(), 0);
462        assert!(path.is_empty());
463        assert_eq!(
464            path.vertex().vid,
465            VertexId::try_from_int64(1).expect("test vertex id")
466        );
467        assert!(path.parent().is_none());
468        assert!(path.edge().is_none());
469    }
470
471    #[test]
472    fn test_npath_extend() {
473        let v1 = create_test_vertex(1);
474        let v2 = create_test_vertex(2);
475        let e = create_test_edge(1, 2, "friend");
476
477        let start = Arc::new(NPath::new(v1));
478        let extended = NPath::extend(start, e, v2);
479
480        assert_eq!(extended.len(), 1);
481        assert!(!extended.is_empty());
482        assert_eq!(
483            extended.vertex().vid,
484            VertexId::try_from_int64(2).expect("test vertex id")
485        );
486        assert!(extended.parent().is_some());
487        assert!(extended.edge().is_some());
488    }
489
490    #[test]
491    fn test_npath_to_path() {
492        let v1 = create_test_vertex(1);
493        let v2 = create_test_vertex(2);
494        let v3 = create_test_vertex(3);
495        let e1 = create_test_edge(1, 2, "friend");
496        let e2 = create_test_edge(2, 3, "friend");
497
498        let start = Arc::new(NPath::new(v1));
499        let p2 = Arc::new(NPath::extend(start, e1, v2));
500        let p3 = Arc::new(NPath::extend(p2, e2, v3));
501
502        let path = p3.to_path();
503
504        assert_eq!(path.len(), 2);
505        assert_eq!(
506            path.src.vid,
507            VertexId::try_from_int64(1).expect("test vertex id")
508        );
509    }
510
511    #[test]
512    fn test_npath_contains_vertex() {
513        let v1 = create_test_vertex(1);
514        let v2 = create_test_vertex(2);
515        let v3 = create_test_vertex(3);
516        let e1 = create_test_edge(1, 2, "friend");
517        let e2 = create_test_edge(2, 3, "friend");
518
519        let start = Arc::new(NPath::new(v1));
520        let p2 = Arc::new(NPath::extend(start, e1, v2));
521        let p3 = Arc::new(NPath::extend(p2, e2, v3));
522
523        assert!(p3.contains_vertex(&VertexId::try_from_int64(1).expect("test vertex id")));
524        assert!(p3.contains_vertex(&VertexId::try_from_int64(2).expect("test vertex id")));
525        assert!(p3.contains_vertex(&VertexId::try_from_int64(3).expect("test vertex id")));
526        assert!(!p3.contains_vertex(&VertexId::try_from_int64(4).expect("test vertex id")));
527    }
528
529    #[test]
530    fn test_npath_iter_vertices() {
531        let v1 = create_test_vertex(1);
532        let v2 = create_test_vertex(2);
533        let v3 = create_test_vertex(3);
534        let e1 = create_test_edge(1, 2, "friend");
535        let e2 = create_test_edge(2, 3, "friend");
536
537        let start = Arc::new(NPath::new(v1));
538        let p2 = Arc::new(NPath::extend(start, e1, v2));
539        let p3 = Arc::new(NPath::extend(p2, e2, v3));
540
541        let vertices: Vec<_> = p3.iter_vertices().collect();
542        assert_eq!(vertices.len(), 3);
543    }
544
545    #[test]
546    fn test_npath_equality() {
547        let v1 = create_test_vertex(1);
548        let v2 = create_test_vertex(2);
549        let e = create_test_edge(1, 2, "friend");
550
551        let start1 = Arc::new(NPath::new(v1.clone()));
552        let path1 = Arc::new(NPath::extend(start1, e.clone(), v2.clone()));
553
554        let start2 = Arc::new(NPath::new(v1));
555        let path2 = Arc::new(NPath::extend(start2, e, v2));
556
557        assert_eq!(path1.hash(), path2.hash());
558    }
559}