Skip to content

Commit d215375

Browse files
authored
Refactor tree building (#173)
* Rename materialize_tree to materialize_table * done and tested
1 parent a832458 commit d215375

6 files changed

Lines changed: 66 additions & 16 deletions

File tree

docs/development.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Activate `poetry` environment: `poetry shell`. (can skip, then prepend `poetry r
1111
- Check: `flake8 . && mypy . && black --check . && isort . --check`
1212

1313
To filter out acceptable lines:
14-
`poetry run flake8 . | findstr /v "E501" | findstr /v "F405" | findstr /v "F403"`
14+
`poetry run flake8 syndiffix | findstr /v "E501" | findstr /v "F405" | findstr /v "F403"`
1515

1616
### Creating a new release
1717

syndiffix/clustering/common.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def __repr__(self) -> str:
2424

2525

2626
# Owner, Stitch columns, Derived columns
27+
# The total set of columns is Stitch+Derived.
2728
DerivedCluster = tuple[StitchOwner, list[ColumnId], list[ColumnId]]
2829

2930

@@ -33,7 +34,9 @@ class Clusters:
3334
derived_clusters: list[DerivedCluster]
3435

3536

36-
TreeMaterializer = Callable[[Forest, list[ColumnId]], tuple[list[MicrodataRow], Combination]]
37+
TreeBuilder = Callable[[Forest, list[ColumnId]], None]
38+
39+
TableMaterializer = Callable[[Forest, list[ColumnId]], tuple[list[MicrodataRow], Combination]]
3740

3841

3942
def microdata_row_to_row(microdata_row: MicrodataRow) -> Row:

syndiffix/clustering/stitching.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -360,29 +360,36 @@ def _do_patch(
360360

361361

362362
def _stitch(
363-
materialize_tree: TreeMaterializer,
363+
materialize_table: TableMaterializer,
364364
forest: Forest,
365365
metadata: StitchingMetadata,
366366
left: tuple[list[MicrodataRow], Combination],
367367
derived_cluster: DerivedCluster,
368368
) -> tuple[list[MicrodataRow], Combination]:
369369
(_, stitch_columns, derived_columns) = derived_cluster
370370

371-
right = materialize_tree(forest, stitch_columns + derived_columns)
371+
right = materialize_table(forest, stitch_columns + derived_columns)
372372

373373
if len(stitch_columns) == 0:
374374
return _do_patch(forest.unsafe_rng, left, right)
375375
else:
376376
return _do_stitch(forest, metadata, left, right, derived_cluster)
377377

378378

379+
def build_forest(tree_builder: TreeBuilder, forest: Forest, clusters: Clusters) -> None:
380+
tree_builder(forest, clusters.initial_cluster)
381+
for derived_cluster in clusters.derived_clusters:
382+
(_, stitch_columns, derived_columns) = derived_cluster
383+
tree_builder(forest, stitch_columns + derived_columns)
384+
385+
379386
def build_table(
380-
materialize_tree: TreeMaterializer, forest: Forest, metadata: StitchingMetadata, clusters: Clusters
387+
materialize_table: TableMaterializer, forest: Forest, metadata: StitchingMetadata, clusters: Clusters
381388
) -> tuple[list[Row], Combination]:
382-
acc = materialize_tree(forest, clusters.initial_cluster)
389+
acc = materialize_table(forest, clusters.initial_cluster)
383390

384391
for derived_cluster in clusters.derived_clusters:
385-
acc = _stitch(materialize_tree, forest, metadata, acc, derived_cluster)
392+
acc = _stitch(materialize_table, forest, metadata, acc, derived_cluster)
386393

387394
rows, columns = acc
388395
return [microdata_row_to_row(row) for row in rows], columns

syndiffix/synthesizer.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from .bucket import harvest
1111
from .clustering.common import MicrodataRow
12-
from .clustering.stitching import StitchingMetadata, build_table
12+
from .clustering.stitching import StitchingMetadata, build_forest, build_table
1313
from .clustering.strategy import ClusteringStrategy, DefaultClustering, MlClustering
1414
from .common import *
1515
from .counters import (
@@ -118,8 +118,19 @@ def __init__(
118118
for col_id, converter in enumerate(self.column_convertors):
119119
converter.analyze_tree(self.forest.get_tree((ColumnId(col_id),)))
120120

121+
def tree_builder(forest: Forest, columns: list[ColumnId]) -> None:
122+
combination = tuple(sorted(columns))
123+
# get_tree builds the trees (except 1dim, which have already been built)
124+
_ = forest.get_tree(combination)
125+
126+
build_forest(
127+
tree_builder,
128+
self.forest,
129+
self.clusters,
130+
)
131+
121132
def sample(self) -> pd.DataFrame:
122-
def materialize_tree(forest: Forest, columns: list[ColumnId]) -> tuple[list[MicrodataRow], Combination]:
133+
def materialize_table(forest: Forest, columns: list[ColumnId]) -> tuple[list[MicrodataRow], Combination]:
123134
combination = tuple(sorted(columns))
124135
tree = forest.get_tree(combination)
125136
buckets = harvest(tree, self.forest.derive_unsafe_rng())
@@ -134,7 +145,7 @@ def materialize_tree(forest: Forest, columns: list[ColumnId]) -> tuple[list[Micr
134145
)
135146

136147
rows, root_combination = build_table(
137-
materialize_tree,
148+
materialize_table,
138149
self.forest,
139150
StitchingMetadata(self.column_is_integral, self.entropy_1dim),
140151
self.clusters,

tests/clustering/test_stitching.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def test_stitching() -> None:
3535
(ColumnId(2), ColumnId(3)): _build_rows(col_c_right, col_d),
3636
}
3737

38-
def materialize_tree(_forest: Forest, columns: list[ColumnId]) -> tuple[list[MicrodataRow], Combination]:
38+
def materialize_table(_forest: Forest, columns: list[ColumnId]) -> tuple[list[MicrodataRow], Combination]:
3939
combination = tuple(sorted(columns))
4040
return (microtables[combination], combination)
4141

@@ -47,7 +47,7 @@ def materialize_tree(_forest: Forest, columns: list[ColumnId]) -> tuple[list[Mic
4747
],
4848
)
4949

50-
rows, combination = build_table(materialize_tree, forest, _dummy_metadata(4), clusters)
50+
rows, combination = build_table(materialize_table, forest, _dummy_metadata(4), clusters)
5151

5252
assert combination == (0, 1, 2, 3)
5353
assert rows == [
@@ -67,7 +67,7 @@ def materialize_tree(_forest: Forest, columns: list[ColumnId]) -> tuple[list[Mic
6767
def test_empty_microtables() -> None:
6868
forest = load_forest("dummy.csv")
6969

70-
def materialize_tree(_forest: Forest, columns: list[ColumnId]) -> tuple[list[MicrodataRow], Combination]:
70+
def materialize_table(_forest: Forest, columns: list[ColumnId]) -> tuple[list[MicrodataRow], Combination]:
7171
combination = tuple(sorted(columns))
7272
return ([], combination)
7373

@@ -78,7 +78,7 @@ def materialize_tree(_forest: Forest, columns: list[ColumnId]) -> tuple[list[Mic
7878
],
7979
)
8080

81-
rows, combination = build_table(materialize_tree, forest, _dummy_metadata(3), clusters)
81+
rows, combination = build_table(materialize_table, forest, _dummy_metadata(3), clusters)
8282

8383
assert combination == (0, 1, 2)
8484
assert rows == []
@@ -98,7 +98,7 @@ def test_patching() -> None:
9898
(ColumnId(3),): _build_rows(col_d),
9999
}
100100

101-
def materialize_tree(_forest: Forest, columns: list[ColumnId]) -> tuple[list[MicrodataRow], Combination]:
101+
def materialize_table(_forest: Forest, columns: list[ColumnId]) -> tuple[list[MicrodataRow], Combination]:
102102
combination = tuple(sorted(columns))
103103
return (microtables[combination], combination)
104104

@@ -110,7 +110,7 @@ def materialize_tree(_forest: Forest, columns: list[ColumnId]) -> tuple[list[Mic
110110
],
111111
)
112112

113-
rows, combination = build_table(materialize_tree, forest, _dummy_metadata(4), clusters)
113+
rows, combination = build_table(materialize_table, forest, _dummy_metadata(4), clusters)
114114

115115
assert combination == (0, 1, 2, 3)
116116
assert rows == [

tests/test_synthesizer.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,3 +306,32 @@ def test_pid() -> None:
306306
# Check that every value in syn_data['c1'] begins with either 'a' or 'b'
307307
for value in syn_data["c1"]:
308308
assert value.startswith("a") or value.startswith("b"), f"Value '{value}' does not start with 'a' or 'b'"
309+
310+
311+
def test_tree_builder() -> None:
312+
# Create a dataframe with three columns
313+
np.random.seed(42) # For reproducible tests
314+
df = pd.DataFrame(
315+
{
316+
"col1": np.random.choice(["A", "B", "C"], size=100),
317+
"col2": np.random.randint(0, 10, size=100),
318+
"col3": np.random.uniform(0, 1, size=100),
319+
}
320+
)
321+
322+
# Create synthesizer (this should build all trees)
323+
syn = Synthesizer(df)
324+
325+
# Import necessary types for combinations
326+
from itertools import combinations
327+
328+
from syndiffix.common import ColumnId
329+
330+
# Test all possible combinations of columns (1, 2, and 3 columns)
331+
column_indices = [ColumnId(0), ColumnId(1), ColumnId(2)] # Indices for col1, col2, col3
332+
333+
for r in range(1, 4): # 1, 2, and 3 columns
334+
for combination in combinations(column_indices, r):
335+
# Convert to tuple as expected by the tree cache
336+
tree = syn.forest._tree_cache.get(combination)
337+
assert tree is not None, f"Tree not found for combination {combination}"

0 commit comments

Comments
 (0)