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)
|
||||
|
||||
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
|
||||
# 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, (
|
||||
"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"
|
||||
@ -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))
|
||||
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
|
||||
# But since `operation` is already recursive, we only need to compress this level not all levels
|
||||
# Hence we use the non-recursive _compress method
|
||||
@ -161,7 +177,14 @@ def _operation(
|
||||
values=intersection.values,
|
||||
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
|
||||
if keep_just_A:
|
||||
|
@ -119,6 +119,9 @@ class WildcardGroup(ValueGroup):
|
||||
def __iter__(self):
|
||||
return ["*"]
|
||||
|
||||
def __bool__(self):
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def from_strings(cls, values: Iterable[str]) -> Sequence[ValueGroup]:
|
||||
return [WildcardGroup()]
|
||||
|
@ -3,6 +3,8 @@
|
||||
// #![allow(unused_variables)]
|
||||
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::wrap_pyfunction;
|
||||
use pyo3::types::{PyDict, PyInt, PyList, PyString};
|
||||
@ -18,6 +20,47 @@ fn rust(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
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::request::Request;
|
||||
|
@ -1,6 +1,95 @@
|
||||
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():
|
||||
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