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
use crate::shape::shex::Shape;
use std::collections::VecDeque;
pub type ShapeTreeItem = Vec<Shape>;
/// The `ShapeTree` struct contains a vector of `ShapeTreeItem` objects.
///
/// Properties:
///
/// * `shapes`: `shapes` is a vector of `ShapeTreeItem` structs that represents the
/// collection of shapes in the `ShapeTree`. Each `ShapeTreeItem` struct contains
/// information about a single shape, such as its type, position, and size.
#[derive(Clone)]
pub struct ShapeTree {
shapes: Vec<ShapeTreeItem>,
}
impl ShapeTree {
/// The approach to the problem is using Reverse Level Order Traversal and storing all
/// the levels in a 2D vector having each of the levels stored in a different row.
/// The steps to follow are described below:
///
/// 1. Create a vector `nodes` to store the nodes to be evaluated.
/// 2. Create the shapes `vector` to store the different levels.
/// 3. Push the `root` node, provided node, into the queue.
/// 4. Iterate over the `nodes` until there's no value left:
/// 4.1 Iterate over all the nodes that were there at the beginning of the iteration.
/// 4.2 Take the first node in the queue and match it against its Enum
/// 4.2.1 If it is a `TripleConstraint` => push it to the temporary vector for the current iteration
/// 4.2.2 If it is a `ShapeReference` => push it to the temporary vector and enqueue its child
/// 4.2.3 If it is a `ShapeComposite` => push it to the temporary vector and enqueue its children
/// 4.2.4 If it is a `ShapeLiteral` => push it to the temporary vector for the current iteration
/// 4.2.5 If it is a `NumericFacet` => push it to the temporary vector and enqueue its child
/// 4.3 Push the temporary results into the `shapes` vector
/// 4.4 Clear the temporary results.
/// 5. Return the `shapes` vector in reverse order
pub fn new(shape: Shape) -> Self {
let mut nodes = VecDeque::new(); // We create a queue of nodes
let mut shapes = Vec::<ShapeTreeItem>::new(); // We create the returning vector
let mut temp = Vec::<Shape>::new(); // We create a temporal vector
nodes.push_front(shape); // We add the root node to the queue
// Iterate over the nodes in the tree using a queue
while !nodes.is_empty() {
for _ in 0..nodes.len() {
match nodes.pop_front() {
Some(node) => match &node {
Shape::TripleConstraint(_) => temp.push(node),
Shape::ShapeReference(shape) => {
temp.push(node.to_owned());
nodes.push_back(shape.to_owned().get_reference());
}
Shape::ShapeComposite(shape) => {
temp.push(node.to_owned());
shape
.get_shapes()
.iter()
.for_each(|shape| nodes.push_back(shape.to_owned()));
}
Shape::ShapeLiteral(_) => temp.push(node),
Shape::Cardinality(shape) => {
temp.push(node.to_owned());
nodes.push_back(shape.to_owned().get_shape());
}
},
None => continue,
}
}
shapes.push(temp.to_owned());
temp.clear();
}
shapes.reverse();
ShapeTree { shapes }
}
/// The function returns the number of iterations needed to generate all possible
/// combinations of shapes in a given object. This is a Theorem than can be seen
/// in further detail in the paper associated with this project.
///
/// Returns:
///
/// The function `iterations` returns a `u8` value which represents the number of
/// iterations required to generate all possible combinations of shapes in the
/// `self` object. If the `self` object contains an n-ary shape, then the number of
/// iterations is equal to the number of shapes minus one, otherwise it is equal to
/// the number of shapes.
pub fn iterations(&self) -> u8 {
if self.contains_nary() {
self.shapes.len() as u8 - 1
} else {
self.shapes.len() as u8
}
}
/// The function checks if a given shape contains a composite shape or a cardinality
/// shape.
///
/// Returns:
///
/// a boolean value. It returns `true` if the `self` object contains at least one
/// `ShapeComposite` or `Cardinality` shape, and `false` otherwise.
fn contains_nary(&self) -> bool {
for shapes in self.shapes.iter() {
for shape in shapes.iter() {
match shape {
Shape::TripleConstraint(_) => continue,
Shape::ShapeReference(_) => continue,
Shape::ShapeComposite(_) => return true,
Shape::ShapeLiteral(_) => continue,
Shape::Cardinality(_) => return true,
};
}
}
false
}
}
impl IntoIterator for ShapeTree {
type Item = ShapeTreeItem;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.shapes.into_iter()
}
}
#[cfg(test)]
pub mod tests {
use crate::shape::shape_tree::ShapeTree;
use crate::utils::examples::*;
#[test]
fn simple_schema_test() {
assert_eq!(1, ShapeTree::new(simple_schema()).into_iter().count())
}
#[test]
fn paper_schema_test() {
assert_eq!(2, ShapeTree::new(paper_schema()).into_iter().count())
}
#[test]
fn complex_schema_test() {
assert_eq!(3, ShapeTree::new(complex_schema()).into_iter().count())
}
#[test]
fn reference_schema_test() {
assert_eq!(3, ShapeTree::new(reference_schema()).into_iter().count())
}
#[test]
fn optional_schema_test() {
assert_eq!(3, ShapeTree::new(optional_schema()).into_iter().count())
}
}