Skip to content

Commit 4155138

Browse files
committed
experiment with hash tree
1 parent 0c8f79e commit 4155138

File tree

1 file changed

+159
-0
lines changed

1 file changed

+159
-0
lines changed
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
from compas.datastructures import Tree
2+
from compas.datastructures import TreeNode
3+
from compas.data import json_dumps
4+
import hashlib
5+
6+
7+
def sha256(content):
8+
return hashlib.sha256(json_dumps(content).encode()).hexdigest()
9+
10+
11+
class HashNode(TreeNode):
12+
def __init__(self, path, value=None):
13+
super().__init__()
14+
self.path = path
15+
self.value = value
16+
self._signature = None
17+
18+
def __repr__(self):
19+
path = self.path or "ROOT"
20+
if self.value is not None:
21+
return "{}:{} @ {}".format(path, self.value, self.signature[:5])
22+
else:
23+
return "{} @ {}".format(path, self.signature[:5])
24+
25+
@property
26+
def absolute_path(self):
27+
if self.parent is None:
28+
return self.path
29+
return self.parent.absolute_path + self.path
30+
31+
@property
32+
def is_value(self):
33+
return self.value is not None
34+
35+
@property
36+
def signature(self):
37+
return self._signature
38+
39+
@property
40+
def children_dict(self):
41+
return {child.path: child for child in self.children}
42+
43+
@property
44+
def children_paths(self):
45+
return [child.path for child in self.children]
46+
47+
@classmethod
48+
def from_dict(cls, data_dict, path=""):
49+
node = cls(path)
50+
for key in data_dict:
51+
path = ".{}".format(key)
52+
if isinstance(data_dict[key], dict):
53+
child = cls.from_dict(data_dict[key], path=path)
54+
node.add(child)
55+
else:
56+
node.add(cls(path, value=data_dict[key]))
57+
58+
return node
59+
60+
61+
class HashTree(Tree):
62+
def __init__(self):
63+
super().__init__()
64+
self.signatures = {}
65+
66+
@classmethod
67+
def from_dict(cls, data_dict):
68+
tree = cls()
69+
root = HashNode.from_dict(data_dict)
70+
tree.add(root)
71+
tree.calculate_signatures()
72+
return tree
73+
74+
def calculate_signatures(self):
75+
self.node_signature(self.root)
76+
77+
def node_signature(self, node, parent_path=""):
78+
absolute_path = parent_path + node.path
79+
if absolute_path in self.signatures:
80+
return self.signatures[absolute_path]
81+
82+
signature = sha256(
83+
{
84+
"path": node.path,
85+
"value": node.value,
86+
"children": [self.node_signature(child, absolute_path) for child in node.children],
87+
}
88+
)
89+
90+
self.signatures[absolute_path] = signature
91+
node._signature = signature
92+
93+
return signature
94+
95+
def diff(self, other):
96+
added = []
97+
removed = []
98+
modified = []
99+
100+
def _diff(node1, node2):
101+
if node1.signature == node2.signature:
102+
return
103+
else:
104+
if node1.is_value or node2.is_value:
105+
modified.append({"path": node1.absolute_path, "old": node2.value, "new": node1.value})
106+
107+
for path in node1.children_paths:
108+
if path in node2.children_dict:
109+
_diff(node1.children_dict[path], node2.children_dict[path])
110+
else:
111+
added.append(
112+
{"path": node1.children_dict[path].absolute_path, "value": node1.children_dict[path].value}
113+
)
114+
115+
for path in node2.children_paths:
116+
if path not in node1.children_dict:
117+
removed.append(
118+
{"path": node2.children_dict[path].absolute_path, "value": node2.children_dict[path].value}
119+
)
120+
121+
_diff(self.root, other.root)
122+
123+
return {"added": added, "removed": removed, "modified": modified}
124+
125+
def print_diff(self, other):
126+
diff = self.diff(other)
127+
print("Added:")
128+
for item in diff["added"]:
129+
print(item)
130+
print("Removed:")
131+
for item in diff["removed"]:
132+
print(item)
133+
print("Modified:")
134+
for item in diff["modified"]:
135+
print(item)
136+
137+
138+
if __name__ == "__main__":
139+
140+
print("\nCOMPARE DICTS:")
141+
tree1 = HashTree.from_dict({"a": {"b": 1, "c": 3}, "d": [1, 2, 3], "e": 2})
142+
tree2 = HashTree.from_dict({"a": {"b": 1, "c": 2}, "d": [1, 2, 3], "f": 2})
143+
144+
tree1.print_hierarchy()
145+
tree2.print_hierarchy()
146+
147+
tree2.print_diff(tree1)
148+
149+
print("\nCOMPARE MESH CHANGE:")
150+
151+
from compas.datastructures import Mesh
152+
153+
mesh = Mesh.from_polyhedron(4)
154+
tree1 = HashTree.from_dict(mesh.data)
155+
mesh.vertex_attribute(0, "x", 1.0)
156+
tree2 = HashTree.from_dict(mesh.data)
157+
tree1.print_hierarchy()
158+
tree2.print_hierarchy()
159+
tree2.print_diff(tree1)

0 commit comments

Comments
 (0)