Skip to content

Commit

Permalink
added feature namespace.toposort (#49)
Browse files Browse the repository at this point in the history
* added feature namespace.toposort

* namespace toposort, made suggested changes

* made requested changes to namespace.rs

* made `create_and_insert_values` topo-sort Values before iterating over them

* debugged topo-sort

* topos0rt now returs a valid vec of sorted namea

* added feature namespace.toposort

* namespace toposort, made suggested changes

* made requested changes to namespace.rs

* made `create_and_insert_values` topo-sort Values before iterating over them

* debugged topo-sort

* topos0rt now returs a valid vec of sorted namea

* fixed failing test

* ran cargo clippy

* made clippy happy

* fixed last clippy warning

* fixed clash with PR #61
  • Loading branch information
OLUWAMUYIWA authored Aug 25, 2021
1 parent 79f402b commit 0e0abdb
Show file tree
Hide file tree
Showing 24 changed files with 498 additions and 188 deletions.
6 changes: 1 addition & 5 deletions core/src/compile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -937,11 +937,7 @@ where
let (root, relative_what) = what.relativize(to);
let relative_to = to.as_in(&root).unwrap();
self.get_mut(&root)
.with_context(|| anyhow!(
"looking for the common root to {} and {}",
what,
to
))
.with_context(|| anyhow!("looking for the common root to {} and {}", what, to))
.and_then(|local_table| local_table.issue(&relative_what, &relative_to))
}
}
Expand Down
3 changes: 2 additions & 1 deletion core/src/graph/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ impl Generator for RandomArray {
}
}

type ArrayNodeInner = AndThenTry<OwnedDevaluize<Box<Graph>, u64>, Box<dyn Fn(u64) -> RandomArray>, RandomArray>;
type ArrayNodeInner =
AndThenTry<OwnedDevaluize<Box<Graph>, u64>, Box<dyn Fn(u64) -> RandomArray>, RandomArray>;

derive_generator! {
yield Token,
Expand Down
6 changes: 3 additions & 3 deletions core/src/graph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -639,9 +639,9 @@ pub mod tests {
let sample: f64 = dist.sample(&mut rng);
// Not using pattern matching here because of <https://github.com/rust-lang/rust/issues/41620>.
// As of 2020-12-01 it causes a linter warning which will be a compiler error in future releases.
if (sample - -2.5).abs() < error_margin ||
(sample - -1.0).abs() < error_margin ||
(sample - 0.5).abs() < error_margin
if (sample - -2.5).abs() < error_margin
|| (sample - -1.0).abs() < error_margin
|| (sample - 0.5).abs() < error_margin
{
} else {
panic!("Generated '{}' which should not happen", sample)
Expand Down
4 changes: 2 additions & 2 deletions core/src/graph/unique.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::hash::{BuildHasher, Hash, Hasher};
const MAX_RETRIES: usize = 64;

type ValueFilter =
TryFilterMap<Box<Graph>, Box<dyn FnMut(Value) -> Result<Option<Value>, Error>>, Value>;
TryFilterMap<Box<Graph>, Box<dyn FnMut(Value) -> Result<Option<Value>, Error>>, Value>;

derive_generator! {
yield Token,
Expand Down Expand Up @@ -79,7 +79,7 @@ pub mod tests {
high: NUM_GENERATED as u64,
step: 1,
})
.unwrap(),
.unwrap(),
));
let output = UniqueNode::hash(numbers, None)
.repeat(NUM_GENERATED)
Expand Down
4 changes: 2 additions & 2 deletions core/src/schema/content/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
//! - Things that belong to those submodules that also need to be exposed
//! to other parts of `synth` should be re-exported here.
use serde::{Deserialize, Serialize, de::IntoDeserializer};
use serde::{de::IntoDeserializer, Deserialize, Serialize};
use serde_json::Value;

mod r#bool;
Expand All @@ -25,7 +25,7 @@ pub use number::{number_content, NumberContent, NumberContentKind, NumberKindExt
mod string;
pub use string::{
ChronoValue, ChronoValueFormatter, ChronoValueType, DateTimeContent, FakerContent,
FakerContentArgument, RegexContent, StringContent, Uuid,
FakerContentArgument, FormatContent, RegexContent, StringContent, Uuid,
};

mod array;
Expand Down
2 changes: 1 addition & 1 deletion core/src/schema/content/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ pub struct TruncatedContent {
#[serde(rename_all = "lowercase")]
pub struct FormatContent {
format: String,
arguments: HashMap<String, Content>,
pub arguments: HashMap<String, Content>,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
Expand Down
281 changes: 273 additions & 8 deletions core/src/schema/namespace.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
use super::content::{FormatContent, ObjectContent, OneOfContent, StringContent};
use super::inference::MergeStrategy;
use super::{suggest_closest, ArrayContent, Content, FieldRef, Find, Name};
use crate::compile::{Compile, Compiler};
use crate::graph::prelude::OptionalMergeStrategy;
use crate::graph::{Graph, KeyValueOrNothing};
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use serde_json::{value::Value, Map};

use std::collections::BTreeMap;
use std::convert::AsRef;
use std::{default::Default, iter::FromIterator};

use super::inference::MergeStrategy;
use super::{suggest_closest, ArrayContent, Content, FieldRef, Find, Name};
use crate::compile::{Compile, Compiler};
use crate::graph::prelude::OptionalMergeStrategy;
use crate::graph::{Graph, KeyValueOrNothing};
use std::collections::{HashMap, VecDeque};
use std::ops::Index;

#[allow(dead_code)]
type JsonObject = Map<String, Value>;

//graph alies
type NameGraph = HashMap<Name, Vec<Name>>;

#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)]
pub struct Namespace {
#[serde(flatten)]
Expand Down Expand Up @@ -160,14 +165,148 @@ impl Namespace {
Err(failed!(target: Release, NotFound => "no such collection: '{}'{}", name, suggest))
}
}
fn get_adj_list(&self) -> Vec<(Name, Name)> {
let mut list = Vec::new();
for (n, c) in self.collections.iter() {
get_list((n, c), &mut list);
}
list
}

pub fn topo_sort(&self) -> Option<Vec<Name>> {
let lists: Vec<(Name, Name)> = self.get_adj_list();
let mut q: VecDeque<Name> = VecDeque::new();
let mut sorted: Vec<Name> = Vec::new();
let mut graph: NameGraph = NameGraph::new();
for v in &lists {
graph
.entry(v.0.clone())
.or_insert_with(Vec::new)
.push(v.1.clone());
}
log::info!("lists: {:?}", lists);
let mut in_degrees: BTreeMap<Name, usize> = BTreeMap::new();

for (p, c) in &lists {
*in_degrees.entry(c.clone()).or_insert(0) += 1;
in_degrees.entry(p.clone()).or_insert(0);
}

for n in &in_degrees {
if n.1 == &0 {
q.push_back(n.0.clone());
}
}

while let Some(name) = q.pop_front() {
sorted.push(name.clone());
log::info!("name: {:?}", name);
if graph.contains_key(&name) {
for out in graph.index(&name) {
in_degrees.entry(out.clone()).and_modify(|v| *v -= 1);
if in_degrees.iter().any(|v| v.1 == &0usize) {
q.push_back(out.clone());
}
}
};
}

if sorted.len() == in_degrees.keys().len() {
log::info!("{:?}", sorted);
//if the name has no same_as type field, it must still be in the list, but may come in any order, back or front
for name in self.collections.keys() {
if !sorted.contains(name) {
sorted.push(name.clone());
}
}
Some(sorted)
} else {
None
}
}
}

fn get_list((n, c): (&Name, &Content), list: &mut Vec<(Name, Name)>) {
match c {
Content::Object(ObjectContent { fields, .. }) => {
for cont in fields.values() {
if let Content::SameAs(same) = cont {
let (l, r) = (same.ref_.collection().clone(), n.clone());
if l != r && !list.contains(&(l.clone(), r.clone())) {
list.push((l, r));
};
} else {
get_list((n, cont), list);
}
}
}
Content::Array(ArrayContent { box content, .. }) => match content.clone() {
Content::Object(obj) => {
get_list((n, &Content::Object(obj)), list);
}
Content::Array(arr) => {
get_list((n, &Content::Array(arr)), list);
}
Content::SameAs(same) => {
get_list((n, &Content::SameAs(same)), list);
}
Content::OneOf(one) => {
get_list((n, &Content::OneOf(one)), list);
}
Content::String(s) => {
get_list((n, &Content::String(s)), list);
}
_ => {}
},
Content::OneOf(OneOfContent { variants }) => {
for variant in variants {
let box variant = variant.content.clone();
match variant {
Content::Array(arr) => {
get_list((n, &Content::Array(arr)), list);
}
Content::Object(obj) => {
get_list((n, &Content::Object(obj)), list);
}
Content::OneOf(one) => {
get_list((n, &Content::OneOf(one)), list);
}
Content::SameAs(same) => {
get_list((n, &Content::SameAs(same)), list);
}
Content::String(s) => {
get_list((n, &Content::String(s)), list);
}
_ => {}
}
}
}
Content::SameAs(same_as_content) => {
let pair = (same_as_content.ref_.collection().clone(), n.clone());
if pair.0 != pair.1 && !list.contains(&pair) {
list.push(pair);
};
}
Content::String(StringContent::Format(form)) => {
//the only variant of string that can hold a content
let FormatContent { ref arguments, .. } = form;
for (_, c) in arguments.iter() {
get_list((n, c), list);
}
}
_ => {}
}
}
impl Compile for Namespace {
fn compile<'a, C: Compiler<'a>>(&'a self, mut compiler: C) -> Result<Graph> {
// TODO: needs to wrap each top-level attribute in a variable size array model
let object_node = self
let sorted_ns = self
.topo_sort()
.ok_or_else(|| anyhow!("dependency is cyclic"))?;
let object_node = sorted_ns
.iter()
.map(|(name, field)| {
.map(|name| {
let field = self.collections.get(name).expect("field should be there");
compiler
.build(name.as_ref(), field)
.map(|graph| KeyValueOrNothing::always(name.as_ref(), graph))
Expand All @@ -176,3 +315,129 @@ impl Compile for Namespace {
Ok(Graph::Object(object_node))
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::schema::BoolContent;
use crate::schema::Content;
#[test]
fn test_sort_simple() {
let mut namespace = Namespace {
collections: BTreeMap::new(),
};
let ref1: FieldRef = "visitors.address.postcode".parse().unwrap();
let ref2: FieldRef = "daughters.address.postcode".parse().unwrap();

namespace
.put_collection(
&"users".parse::<Name>().unwrap(),
Content::SameAs(crate::schema::SameAsContent { ref_: ref1 }),
)
.unwrap();
namespace
.put_collection(
&"sons".parse::<Name>().unwrap(),
Content::SameAs(crate::schema::SameAsContent { ref_: ref2 }),
)
.unwrap();
namespace
.put_collection(
&"visitors".parse::<Name>().unwrap(),
Content::Bool(BoolContent::Constant(true)),
)
.unwrap();
namespace
.put_collection(
&"daughters".parse::<Name>().unwrap(),
Content::Bool(BoolContent::Constant(false)),
)
.unwrap();
println!("sorted: {:?}", namespace.topo_sort());
let sorted = namespace.topo_sort().unwrap();
let length = sorted.len();
for i in 0..length {
assert!(check_dep(&sorted[..i], &namespace))
}
}

#[test]
fn test_sort_complex() {
let mut namespace = Namespace {
collections: BTreeMap::new(),
};
let ref1: FieldRef = "visitors.address.postcode".parse().unwrap();

namespace
.put_collection(
&"users".parse::<Name>().unwrap(),
Content::SameAs(crate::schema::SameAsContent { ref_: ref1.clone() }),
)
.unwrap();
namespace
.put_collection(
&"sons".parse::<Name>().unwrap(),
Content::SameAs(crate::schema::SameAsContent { ref_: ref1 }),
)
.unwrap();
println!("sorted: {:?}", namespace.topo_sort());
namespace
.put_collection(
&"visitors".parse::<Name>().unwrap(),
Content::Bool(BoolContent::Constant(true)),
)
.unwrap();
namespace
.put_collection(
&"daughters".parse::<Name>().unwrap(),
Content::Bool(BoolContent::Constant(false)),
)
.unwrap();

println!("sorted: {:?}", namespace.topo_sort());
let sorted = namespace.topo_sort().unwrap();
let length = sorted.len();
for i in 0..length {
assert!(check_dep(&sorted[..i], &namespace))
}
}

#[test]
fn test_sort_cycle() {
let mut namespace = Namespace {
collections: BTreeMap::new(),
};
let ref1: FieldRef = "visitors.address.postcode".parse().unwrap();
let ref2: FieldRef = "users.address.postcode".parse().unwrap();
// let ref3: FieldRef = "winners.address.postcode".parse().unwrap();

namespace
.put_collection(
&"users".parse::<Name>().unwrap(),
Content::SameAs(crate::schema::SameAsContent { ref_: ref1 }),
)
.unwrap();
namespace
.put_collection(
&"visitors".parse::<Name>().unwrap(),
Content::SameAs(crate::schema::SameAsContent { ref_: ref2 }),
)
.unwrap();
println!("sorted: {:?}", namespace.topo_sort());
assert!(namespace.topo_sort().is_none());
}

//helper method for checking sorted dependencies
fn check_dep(list: &[Name], ns: &Namespace) -> bool {
let (last, list) = match list.split_last() {
Some(n) => n,
None => return true,
};
let c = ns.get_collection(last).unwrap();
if let Content::SameAs(same) = c {
list.contains(same.ref_.collection()) && check_dep(list, ns)
} else {
true
}
}
}
Loading

0 comments on commit 0e0abdb

Please sign in to comment.