summaryrefslogtreecommitdiff
path: root/graph/src/lib.rs
blob: 2d23af34b5d9f8ee3066bd7a4f7e5107e059b2d9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
#![warn(missing_docs)]
//! This crate implements a trait API for graphs that the crate "rep"
//! needs.
//!
//! Also a default implementation for the trait is provided, so that
//! by default no external crates are needed, whereas it is easy to
//! use external crates, if so derired.

use std::hash::Hash;

use core::fmt::{Debug, Display};

pub mod error;

pub mod adset;

pub use adset::ASGraph;

pub mod adlist;

pub use adlist::ALGraph;

pub mod labelled;

pub use labelled::DLGraph;

pub mod builder;

pub use builder::Builder;

use error::Error;

/// The expected behaviour of an immutable graph.
pub trait Graph: Default {
    /// A type that iterates through the node indices.
    type Iter<'a>: Iterator<Item = usize> + 'a
    where
        Self: 'a;

    /// Return true if and only if the graph has no nodes.
    fn is_empty(&self) -> bool;

    /// Return the length of nodes in the graph.
    fn nodes_len(&self) -> usize;

    #[inline]
    /// Return the length of edges in the graph.
    ///
    /// This is optional.  Implementations need not support this.
    fn edges_len(&self) -> Option<usize> {
        None
    }

    #[inline]
    /// Return an iterator of nodes represented as unsigned integers.
    ///
    /// If a custom application needs to have labels on nodes, just
    /// associate the label to the node internally, and define an
    /// extension trait that allows querying those additional labels.
    ///
    /// This design choice is based on the idea that this library
    /// should be minimal and only provide the core of a graph.  As a
    /// label is not really core, it is not included here.
    fn nodes(&self) -> Box<dyn Iterator<Item = usize> + '_> {
        Box::new(0..self.nodes_len())
    }

    /// Return an iterator of edges going out of a node.
    ///
    /// Return an error if the node is not known to the graph.
    fn children_of(&self, node_id: usize) -> Result<Self::Iter<'_>, Error>;

    #[inline]
    /// Return an iterator of edges represented as pairs (FROM, TO).
    ///
    /// The default implementation iterates through the nodes and then
    /// iterates through their children.  If the implementation has a
    /// more efficient method, overwrite this method.
    fn edges(&self) -> Box<dyn Iterator<Item = (usize, usize)> + '_> {
        Box::new(self.nodes().flat_map(|node| {
            self.children_of(node)
                // If this node is invalid, this means the
                // implementation of `nodes` is wrong, so it is
                // appropriate to panic here.
                .unwrap()
                .map(move |child| (node, child))
        }))
    }

    #[inline]
    /// Return true if and only if the node is in the graph.
    fn has_node(&self, node_id: usize) -> bool {
        (0..self.nodes_len()).contains(&node_id)
    }

    /// Return the number of "children" of a node, or an error if the
    /// node is not a member of the graph.
    fn degree(&self, node_id: usize) -> Result<usize, Error>;

    /// Return a boolean indicating if the node has no children, or an
    /// error if the node is not a member of the graph.
    fn is_empty_node(&self, node_id: usize) -> Result<bool, Error>;

    /// Return true if and only if there is an edge from the source to
    /// the target.
    ///
    /// Return an error if either the source or the target is an
    /// invalid node.
    fn has_edge(&self, source: usize, target: usize) -> Result<bool, Error>;

    /// Output the edges into a graphviz file.
    fn print_viz(&self, filename: &str) -> Result<(), std::io::Error> {
        let preamble = "digraph nfa {
    fontname=\"Helvetica,Arial,sans-serif\"
    node [fontname=\"Helvetica,Arial,sans-serif\"]
    edge [fontname=\"Helvetica,Arial,sans-serif\"]
    rankdir=LR;\n";

        let mut post = String::new();

        use std::fmt::Write as FWrite;

        for (source, target) in self.edges() {
            let _ = writeln!(post, "{source} -> {target}");
        }

        post.push_str("}\n");

        let result = format!("{preamble}{post}");

        if std::fs::metadata(filename).is_ok() {
            std::fs::remove_file(filename)?;
        }

        let mut file = std::fs::File::options()
            .write(true)
            .create(true)
            .open(filename)?;

        use std::io::Write;

        file.write_all(result.as_bytes())
    }

    /// Returns whether or not the graph contains cycles.
    ///
    /// If, and only if, the graph contains invalid nodes, an error
    /// will be signalled.
    fn contains_cycles(&self) -> Result<bool, Error> {
        use std::collections::HashSet;

        let mut seen_nodes: HashSet<usize> = HashSet::with_capacity(self.nodes_len());

        for node in self.nodes() {
            if seen_nodes.contains(&node) {
                continue;
            }

            let mut edges_seen_nodes: HashSet<usize> = HashSet::with_capacity(self.nodes_len());

            let mut stack = Vec::with_capacity(self.nodes_len());
            stack.push(node);

            while let Some(top) = stack.pop() {
                if edges_seen_nodes.contains(&top) {
                    // a cycle is found
                    return Ok(true);
                }

                edges_seen_nodes.insert(top);

                let mut local_stack: Vec<usize> = self.children_of(top)?.collect();

                local_stack.reverse();

                stack.extend(local_stack);
            }

            seen_nodes.extend(edges_seen_nodes);
        }

        Ok(false)
    }

    /// Replace the contents of the graph by a builder.
    fn replace_by_builder(&mut self, builder: impl Builder<Result = Self>);
}

/// A graph that can be extended, but not mutated, in the sense that
/// existing nodes and edges will not be modified nor removed, but new
/// nodes can be added.  The index of the new node will be returned.
///
/// Implementations can choose to keep a set of sets of edges, so that
/// new nodes will not have duplicate edge sets.  In this case, the
/// returned new node index is not necessarily equal to
/// self.nodes_len() - 1, and hence the return type is designed in
/// this way.
pub trait ExtGraph: Graph {
    /// Add a new node with `edges`.
    ///
    /// If an edge from `edges` points to a non-existent node, return
    /// an error.
    fn extend(&mut self, edges: impl IntoIterator<Item = usize>) -> Result<usize, Error>;
}

/// The type of labels should be comparable and hashable.
pub trait GraphLabel:
    Hash + Eq + PartialEq + Clone + Copy + Ord + PartialOrd + Display + Debug
{
}

impl<T: Hash + Eq + PartialEq + Clone + Copy + Ord + PartialOrd + Display + Debug> GraphLabel
    for T
{
}

/// A labelled graph is just a graph with labels associated to
/// vertices and / or edges.
///
/// This trait defines what the package needs out of a labelled graph.
///
/// Any implementation should be able to handle a set of types for
/// labels, so this trait is generic over the label type.
pub trait LabelGraph<T: GraphLabel>: Graph {
    /// A type that iterates through the node indices.
    type Iter<'a>: Iterator<Item = usize> + 'a
    where
        Self: 'a;

    /// A type that iterates through labels.
    type LabelIter<'a>: Iterator<Item = (&'a T, <Self as LabelGraph<T>>::Iter<'a>)> + 'a
    where
        Self: 'a,
        T: 'a;

    #[inline]
    /// Return the label of a vertex or an error if the node is
    /// invalid.
    ///
    /// The default implementation always returns None for a valid
    /// node.
    fn vertex_label(&self, node_id: usize) -> Result<Option<T>, Error> {
        if self.has_node(node_id) {
            Ok(None)
        } else {
            Err(Error::IndexOutOfBounds(node_id, self.nodes_len()))
        }
    }

    #[inline]
    /// Return the label of an edge or an error if some node is
    /// invalid.
    ///
    /// The default implementation always returns an empty vector for
    /// valid nodes.
    fn edge_label(&self, source: usize, target: usize) -> Result<Vec<T>, Error> {
        self.has_edge(source, target).map(|_| Vec::new())
    }

    /// Return an iterator of edges out of a node, whose associated
    /// label is as given.
    ///
    /// The efficiency of this method matters in implementations.
    fn find_children_with_label(
        &self,
        node_id: usize,
        label: &T,
    ) -> Result<<Self as LabelGraph<T>>::Iter<'_>, Error>;

    /// Return an iterator of labels of edges out of a node.
    ///
    /// The efficiency of this method matters in implementations.
    fn labels_of(&self, node_id: usize) -> Result<Self::LabelIter<'_>, Error>;

    /// Return true if and only if the node has an edge with the given
    /// label and target.
    fn has_edge_label(&self, node_id: usize, label: &T, target: usize) -> Result<bool, Error>;
}

/// A labelled graph that can be extended, but not mutated, in the
/// sense that existing nodes and edges will not be modified nor
/// removed, but new nodes can be added.  The index of the new node
/// will be returned.
///
/// Implementations can choose to keep a set of sets of edges, so that
/// new nodes will not have duplicate edge sets.  In this case, the
/// returned new node index is not necessarily equal to
/// self.nodes_len() - 1, and hence the return type is designed in
/// this way.
pub trait LabelExtGraph<T: GraphLabel>: LabelGraph<T> {
    /// Add a new node with `edges`.
    ///
    /// If an edge from `edges` points to a non-existent node, return
    /// an error.
    fn extend(&mut self, edges: impl IntoIterator<Item = (T, usize)>) -> Result<usize, Error>;
}

#[cfg(test)]
mod test_cycle_detection {
    use super::{builder::Builder, labelled::DLGBuilder, Graph};

    #[test]
    fn test() -> Result<(), Box<dyn std::error::Error>> {
        let mut builder: DLGBuilder<usize> = DLGBuilder::default();

        // Add five nodes
        builder.add_vertex();
        builder.add_vertex();
        builder.add_vertex();
        builder.add_vertex();
        builder.add_vertex();

        // Link each node to its successor and link the last node with
        // the first one to form a cycle.
        for i in 0..5 {
            builder.add_edge(i, if i < 4 { i + 1 } else { 0 }, i)?;
        }

        let graph = builder.build_ref();

        assert_eq!(graph.contains_cycles()?, true);

        // Remove the link from the last node to the first node.
        builder.remove_edge(4, 0, |_| true)?;

        let graph = builder.build();

        assert_eq!(graph.contains_cycles()?, false);

        Ok(())
    }
}