sophia_indexed/
graph.rs

1//! A utility trait for building graphs using indexed terms.
2
3use std::collections::hash_map::Entry;
4use std::collections::HashMap;
5use std::hash::Hash;
6
7use sophia_api::term::TTerm;
8use sophia_term::*;
9
10/// Symbols from other crates, re-exported for the sake of macros
11pub mod reexport {
12    pub use sophia_api::graph::MgResult;
13    pub use sophia_api::triple::stream::{StreamResult, TripleSource};
14    pub use sophia_api::triple::Triple;
15}
16
17/// A utility trait for implementing [`Graph`](sophia_api::graph::Graph)
18/// and [`MutableGraph`](sophia_api::dataset::Dataset)
19/// based on an internal [`TermIndexMap`](sophia_term::index_map::TermIndexMap)
20/// for efficient storage.
21///
22/// The [`impl_mutable_graph_for_indexed_graph!`] macro
23/// can be used to derive the `MutableGraph` implementation
24/// for any implementation of `IndexedGraph`.
25pub trait IndexedGraph {
26    /// The type used to represent terms internally.
27    type Index: Copy + Eq + Hash;
28
29    /// The type used to hold the term data.
30    type TermData: TermData + 'static;
31
32    /// Construct a new empty graph, provisioning for storing `capacity` triples.
33    fn with_capacity(capacity: usize) -> Self;
34
35    /// Shrink the memory consumption of the graph as much as possible.
36    fn shrink_to_fit(&mut self);
37
38    /// Return the index for the given term, if it exists.
39    fn get_index<T>(&self, t: &T) -> Option<Self::Index>
40    where
41        T: TTerm + ?Sized;
42
43    /// Return the term for the given index, if it exists.
44    fn get_term(&self, i: Self::Index) -> Option<&Term<Self::TermData>>;
45
46    /// Insert a triple in this Graph,
47    /// and return the corresponding tuple of indices.
48    fn insert_indexed<TS, TP, TO>(&mut self, s: &TS, p: &TP, o: &TO) -> Option<[Self::Index; 3]>
49    where
50        TS: TTerm + ?Sized,
51        TP: TTerm + ?Sized,
52        TO: TTerm + ?Sized;
53    /// Remove a triple from this Graph,
54    /// and return the corresponding tuple of indices.
55    fn remove_indexed<TS, TP, TO>(&mut self, s: &TS, p: &TP, o: &TO) -> Option<[Self::Index; 3]>
56    where
57        TS: TTerm + ?Sized,
58        TP: TTerm + ?Sized,
59        TO: TTerm + ?Sized;
60}
61
62/// Defines the implementation of [`CollectibleGraph`](sophia_api::graph::CollectibleGraph)
63/// for [`IndexedGraph`].
64#[macro_export]
65macro_rules! impl_collectible_graph_for_indexed_graph {
66    ($indexed_mutable_graph: ty) => {
67        impl CollectibleGraph for $indexed_mutable_graph {
68            impl_collectible_graph_for_indexed_graph!();
69        }
70    };
71    () => {
72        fn from_triple_source<TS: $crate::graph::reexport::TripleSource>(
73            mut triples: TS,
74        ) -> $crate::graph::reexport::StreamResult<Self, TS::Error, Self::Error> {
75            use $crate::graph::reexport::Triple;
76            let (tmin, tmax) = triples.size_hint_triples();
77            let cap = tmax.unwrap_or(tmin);
78            let mut g = Self::with_capacity(cap);
79            triples
80                .try_for_each_triple(|t| -> Result<(), Self::Error> {
81                    g.insert_indexed(t.s(), t.p(), t.o());
82                    Ok(())
83                })
84                .map(|_| g)
85        }
86    };
87}
88
89/// Defines the implementation of [`MutableGraph`](sophia_api::graph::MutableGraph)
90/// for [`IndexedGraph`].
91#[macro_export]
92macro_rules! impl_mutable_graph_for_indexed_graph {
93    ($indexed_mutable_graph: ty) => {
94        impl MutableGraph for $indexed_mutable_graph {
95            impl_mutable_graph_for_indexed_graph!();
96        }
97    };
98    () => {
99        type MutationError = std::convert::Infallible;
100
101        fn insert<TS_, TP_, TO_>(
102            &mut self,
103            s: &TS_,
104            p: &TP_,
105            o: &TO_,
106        ) -> $crate::graph::reexport::MgResult<Self, bool>
107        where
108            TS_: sophia_api::term::TTerm + ?Sized,
109            TP_: sophia_api::term::TTerm + ?Sized,
110            TO_: sophia_api::term::TTerm + ?Sized,
111        {
112            Ok(self.insert_indexed(s, p, o).is_some())
113        }
114        fn remove<TS_, TP_, TO_>(
115            &mut self,
116            s: &TS_,
117            p: &TP_,
118            o: &TO_,
119        ) -> $crate::graph::reexport::MgResult<Self, bool>
120        where
121            TS_: sophia_api::term::TTerm + ?Sized,
122            TP_: sophia_api::term::TTerm + ?Sized,
123            TO_: sophia_api::term::TTerm + ?Sized,
124        {
125            Ok(self.remove_indexed(s, p, o).is_some())
126        }
127    };
128}
129
130/// Insert an absent value in the Vec value of a HashMap,
131/// creating the Vec if it does not exist.
132///
133/// # Returns
134///
135/// `true` if the Vec was created,
136///  meaning that "parent" indexes need to be updated.
137///
138pub fn insert_in_index<K, W>(hm: &mut HashMap<K, Vec<W>>, k: K, w: W) -> bool
139where
140    K: Eq + Hash,
141    W: Copy + Eq,
142{
143    let mut ret = false;
144    hm.entry(k)
145        .or_insert_with(|| {
146            ret = true;
147            Vec::new()
148        })
149        .push(w);
150    ret
151}
152
153/// Remove an existing value in the Vec value of a HashMap,
154/// removing the entry completely if the Vec ends up empty.
155///
156/// # Returns
157///
158/// `true` if the entry was removed,
159///  meaning that "parent" indexes need to be updated.
160///
161/// # Panics
162///
163/// This function will panic if either
164/// * `k` is not a key of `hm`, or
165/// * `w` is not contained in the value associated to `k`.
166pub fn remove_from_index<K, W>(hm: &mut HashMap<K, Vec<W>>, k: K, w: W) -> bool
167where
168    K: Eq + Hash,
169    W: Copy + Eq,
170{
171    match hm.entry(k) {
172        Entry::Occupied(mut e) => {
173            {
174                let ws = e.get_mut();
175                if ws.len() > 1 {
176                    let wi = ws
177                        .iter()
178                        .enumerate()
179                        .filter_map(|(i, w2)| if *w2 == w { Some(i) } else { None })
180                        .next()
181                        .unwrap();
182                    ws.swap_remove(wi);
183                    return false;
184                }
185            }
186            e.remove_entry();
187            true
188        }
189        Entry::Vacant(_) => unreachable!(),
190    }
191}
192
193#[cfg(test)]
194mod test {
195    // Nothing really worth testing here
196}