fix bug add testcases
This commit is contained in:
parent
110046b251
commit
ed4a9055fa
@ -75,7 +75,11 @@ def node_intersection(
|
|||||||
return QEnum_intersection(A, B)
|
return QEnum_intersection(A, B)
|
||||||
|
|
||||||
if isinstance(A.values, WildcardGroup) and isinstance(B.values, WildcardGroup):
|
if isinstance(A.values, WildcardGroup) and isinstance(B.values, WildcardGroup):
|
||||||
return A, ValuesMetadata(WildcardGroup(), {}), B
|
return (
|
||||||
|
ValuesMetadata(QEnum([]), {}),
|
||||||
|
ValuesMetadata(WildcardGroup(), {}),
|
||||||
|
ValuesMetadata(QEnum([]), {}),
|
||||||
|
)
|
||||||
|
|
||||||
# If A is a wildcard matcher then the intersection is everything
|
# If A is a wildcard matcher then the intersection is everything
|
||||||
# just_A is still *
|
# just_A is still *
|
||||||
@ -92,7 +96,7 @@ def node_intersection(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def operation(A: Qube, B: Qube, operation_type: SetOperation, node_type) -> Qube:
|
def operation(A: Qube, B: Qube, operation_type: SetOperation, node_type) -> Qube | None:
|
||||||
assert A.key == B.key, (
|
assert A.key == B.key, (
|
||||||
"The two Qube root nodes must have the same key to perform set operations,"
|
"The two Qube root nodes must have the same key to perform set operations,"
|
||||||
f"would usually be two root nodes. They have {A.key} and {B.key} respectively"
|
f"would usually be two root nodes. They have {A.key} and {B.key} respectively"
|
||||||
@ -118,6 +122,18 @@ def operation(A: Qube, B: Qube, operation_type: SetOperation, node_type) -> Qube
|
|||||||
output = list(_operation(key, A_nodes, B_nodes, operation_type, node_type))
|
output = list(_operation(key, A_nodes, B_nodes, operation_type, node_type))
|
||||||
new_children.extend(output)
|
new_children.extend(output)
|
||||||
|
|
||||||
|
# print(f"operation {operation_type}: {A}, {B} {new_children = }")
|
||||||
|
# print(f"{A.children = }")
|
||||||
|
# print(f"{B.children = }")
|
||||||
|
# print(f"{new_children = }")
|
||||||
|
|
||||||
|
# If there are now no children as a result of the operation, return nothing.
|
||||||
|
if (A.children or B.children) and not new_children:
|
||||||
|
if A.key == "root":
|
||||||
|
return A.replace(children=())
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
# Whenever we modify children we should recompress them
|
# Whenever we modify children we should recompress them
|
||||||
# But since `operation` is already recursive, we only need to compress this level not all levels
|
# But since `operation` is already recursive, we only need to compress this level not all levels
|
||||||
# Hence we use the non-recursive _compress method
|
# Hence we use the non-recursive _compress method
|
||||||
@ -161,7 +177,14 @@ def _operation(
|
|||||||
values=intersection.values,
|
values=intersection.values,
|
||||||
metadata=intersection.metadata,
|
metadata=intersection.metadata,
|
||||||
)
|
)
|
||||||
yield operation(new_node_a, new_node_b, operation_type, node_type)
|
# print(f"{node_a = }")
|
||||||
|
# print(f"{node_b = }")
|
||||||
|
# print(f"{intersection.values =}")
|
||||||
|
result = operation(
|
||||||
|
new_node_a, new_node_b, operation_type, node_type
|
||||||
|
)
|
||||||
|
if result is not None:
|
||||||
|
yield result
|
||||||
|
|
||||||
# Now we've removed all the intersections we can yield the just_A and just_B parts if needed
|
# Now we've removed all the intersections we can yield the just_A and just_B parts if needed
|
||||||
if keep_just_A:
|
if keep_just_A:
|
||||||
|
@ -119,6 +119,9 @@ class WildcardGroup(ValueGroup):
|
|||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
return ["*"]
|
return ["*"]
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
return True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_strings(cls, values: Iterable[str]) -> Sequence[ValueGroup]:
|
def from_strings(cls, values: Iterable[str]) -> Sequence[ValueGroup]:
|
||||||
return [WildcardGroup()]
|
return [WildcardGroup()]
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
// #![allow(unused_variables)]
|
// #![allow(unused_variables)]
|
||||||
|
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::wrap_pyfunction;
|
use pyo3::wrap_pyfunction;
|
||||||
use pyo3::types::{PyDict, PyInt, PyList, PyString};
|
use pyo3::types::{PyDict, PyInt, PyList, PyString};
|
||||||
@ -18,6 +20,47 @@ fn rust(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
|
||||||
|
struct NodeId(usize);
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
|
||||||
|
struct StringId(usize);
|
||||||
|
|
||||||
|
struct Node {
|
||||||
|
key: StringId,
|
||||||
|
metadata: HashMap<StringId, Vec<String>>,
|
||||||
|
parent: NodeId,
|
||||||
|
values: Vec<String>,
|
||||||
|
children: HashMap<StringId, Vec<NodeId>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct Qube {
|
||||||
|
root: NodeId,
|
||||||
|
nodes: Vec<Node>,
|
||||||
|
strings: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::ops;
|
||||||
|
|
||||||
|
impl ops::Index<StringId> for Qube {
|
||||||
|
type Output = str;
|
||||||
|
|
||||||
|
fn index(&self, index: StringId) -> &str {
|
||||||
|
&self.strings[index.0]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ops::Index<NodeId> for Qube {
|
||||||
|
type Output = Node;
|
||||||
|
|
||||||
|
fn index(&self, index: NodeId) -> &Node {
|
||||||
|
&self.nodes[index.0]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// use rsfdb::listiterator::KeyValueLevel;
|
// use rsfdb::listiterator::KeyValueLevel;
|
||||||
// use rsfdb::request::Request;
|
// use rsfdb::request::Request;
|
||||||
|
@ -1,6 +1,95 @@
|
|||||||
from qubed import Qube
|
from qubed import Qube
|
||||||
|
|
||||||
|
|
||||||
|
def set_operation_testcase(testcase):
|
||||||
|
q1 = Qube.from_tree(testcase["q1"])
|
||||||
|
q2 = Qube.from_tree(testcase["q2"])
|
||||||
|
assert q1 | q2 == Qube.from_tree(testcase["union"])
|
||||||
|
assert q1 & q2 == Qube.from_tree(testcase["intersection"])
|
||||||
|
assert q1 - q2 == Qube.from_tree(testcase["q1 - q2"])
|
||||||
|
|
||||||
|
|
||||||
|
# These are a bunch of testcases where q1 and q2 are specified and then their union/intersection/difference are checked
|
||||||
|
# Generate them with code like:
|
||||||
|
# q1 = Qube.from_tree("root, frequency=*, levtype=*, param=*, levelist=*, domain=a/b/c/d")
|
||||||
|
# q2 = Qube.from_tree("root, frequency=*, levtype=*, param=*, domain=a/b/c/d")
|
||||||
|
|
||||||
|
# test = {
|
||||||
|
# "q1": str(q1),
|
||||||
|
# "q2": str(q2),
|
||||||
|
# "union": str(q1 | q2),
|
||||||
|
# "intersection": str(q1 & q2),
|
||||||
|
# "q1 - q2": str(q1 - q2),
|
||||||
|
# }
|
||||||
|
# BUT MANUALLY CHECK THE OUTPUT BEFORE ADDING IT AS A TEST CASE!
|
||||||
|
|
||||||
|
|
||||||
|
testcases = [
|
||||||
|
# Simplest case, only leaves differ
|
||||||
|
{
|
||||||
|
"q1": "root, a=1, b=1, c=1",
|
||||||
|
"q2": "root, a=1, b=1, c=2",
|
||||||
|
"union": "root, a=1, b=1, c=1/2",
|
||||||
|
"intersection": "root",
|
||||||
|
"q1 - q2": "root",
|
||||||
|
},
|
||||||
|
# Some overlap but also each tree has unique items
|
||||||
|
{
|
||||||
|
"q1": "root, a=1, b=1, c=1/2/3",
|
||||||
|
"q2": "root, a=1, b=1, c=2/3/4",
|
||||||
|
"union": "root, a=1, b=1, c=1/2/3/4",
|
||||||
|
"intersection": "root, a=1, b=1, c=2/3",
|
||||||
|
"q1 - q2": "root",
|
||||||
|
},
|
||||||
|
# Overlap at two levels
|
||||||
|
{
|
||||||
|
"q1": "root, a=1, b=1/2, c=1/2/3",
|
||||||
|
"q2": "root, a=1, b=2/3, c=2/3/4",
|
||||||
|
"union": """
|
||||||
|
root, a=1
|
||||||
|
├── b=1, c=1/2/3
|
||||||
|
├── b=2, c=1/2/3/4
|
||||||
|
└── b=3, c=2/3/4
|
||||||
|
""",
|
||||||
|
"intersection": "root, a=1, b=2, c=2/3",
|
||||||
|
"q1 - q2": "root",
|
||||||
|
},
|
||||||
|
# Check that we can merge even if the divergence point is higher
|
||||||
|
{
|
||||||
|
"q1": "root, a=1, b=1, c=1",
|
||||||
|
"q2": "root, a=2, b=1, c=1",
|
||||||
|
"union": "root, a=1/2, b=1, c=1",
|
||||||
|
"intersection": "root",
|
||||||
|
"q1 - q2": "root, a=1, b=1, c=1",
|
||||||
|
},
|
||||||
|
# Two equal qubes
|
||||||
|
{
|
||||||
|
"q1": "root, a=1, b=1, c=1",
|
||||||
|
"q2": "root, a=1, b=1, c=1",
|
||||||
|
"union": "root, a=1, b=1, c=1",
|
||||||
|
"intersection": "root, a=1, b=1, c=1",
|
||||||
|
"q1 - q2": "root",
|
||||||
|
},
|
||||||
|
# With wildcards
|
||||||
|
{
|
||||||
|
"q1": "root, frequency=*, levtype=*, param=*, levelist=*, domain=a/b/c/d",
|
||||||
|
"q2": "root, frequency=*, levtype=*, param=*, domain=a/b/c/d",
|
||||||
|
"union": """
|
||||||
|
root, frequency=*, levtype=*, param=*
|
||||||
|
├── domain=a/b/c/d
|
||||||
|
└── levelist=*, domain=a/b/c/d
|
||||||
|
""",
|
||||||
|
"intersection": "root",
|
||||||
|
"q1 - q2": "root",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_cases():
|
||||||
|
for case in testcases:
|
||||||
|
set_operation_testcase(case)
|
||||||
|
|
||||||
|
|
||||||
def test_leaf_conservation():
|
def test_leaf_conservation():
|
||||||
q = Qube.from_dict(
|
q = Qube.from_dict(
|
||||||
{
|
{
|
||||||
|
@ -34,3 +34,16 @@ def test_intersection():
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_wildcard_union():
|
||||||
|
q1 = Qube.from_tree(
|
||||||
|
"root, frequency=*, levtype=*, param=*, levelist=*, domain=a/b/c/d"
|
||||||
|
)
|
||||||
|
q2 = Qube.from_tree("root, frequency=*, levtype=*, param=*, domain=a/b/c/d")
|
||||||
|
expected = Qube.from_tree("""
|
||||||
|
root, frequency=*, levtype=*, param=*
|
||||||
|
├── domain=a/b/c/d
|
||||||
|
└── levelist=*, domain=a/b/c/d
|
||||||
|
""")
|
||||||
|
assert (q1 | q2) == expected
|
||||||
|
Loading…
x
Reference in New Issue
Block a user