Skip to content

Commit 09dc53c

Browse files
authored
Tree walker (#176)
* Update tree.py * more robust locale handling (for strings)
1 parent 80e4e38 commit 09dc53c

2 files changed

Lines changed: 38 additions & 6 deletions

File tree

syndiffix/microdata.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import unicodedata
12
from abc import ABC, abstractmethod
23
from bisect import bisect_left
34
from itertools import islice
@@ -178,11 +179,20 @@ def create_value_safe_set(self, values: pd.Series) -> None:
178179
class StringConvertor(DataConvertor):
179180
def __init__(self, values: Iterable[Value]) -> None:
180181
super().__init__()
181-
unique_values = set(v for v in values if not pd.isna(v))
182-
for value in unique_values:
183-
if not isinstance(value, str):
184-
raise TypeError(f"Not a `str` object in a string dtype column: {value}.")
185-
self.value_map = sorted(cast(Set[str], unique_values))
182+
unique_values = set()
183+
for v in values:
184+
if not pd.isna(v):
185+
if not isinstance(v, str):
186+
raise TypeError(f"Not a `str` object in a string dtype column: {v}.")
187+
188+
# Normalize to NFC form to handle composed vs decomposed Unicode consistently
189+
# This ensures "café" and "cafe\u0301" are treated as the same string
190+
normalized_value = unicodedata.normalize("NFC", v)
191+
unique_values.add(normalized_value)
192+
193+
# Use locale-independent binary sorting for consistent results across systems
194+
# This ensures the same ordering regardless of system locale settings
195+
self.value_map = sorted(unique_values, key=lambda x: x.encode("utf-8"))
186196

187197
# Note that self.safe_values is only used if self.value_safe_flag is False
188198
self.safe_values: Set[float] = set()
@@ -197,7 +207,9 @@ def column_type(self) -> ColumnType:
197207

198208
def to_float(self, value: Value) -> float:
199209
# Note that value here is the string itself, not an index.
200-
index = bisect_left(self.value_map, cast(str, value))
210+
# Normalize the lookup value the same way we normalized during initialization
211+
normalized_value = unicodedata.normalize("NFC", cast(str, value))
212+
index = bisect_left(self.value_map, normalized_value)
201213
assert index >= 0 and index < len(self.value_map)
202214
return float(index)
203215

syndiffix/tree.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,26 @@ def print(self) -> None:
282282
print(f" _noisy_count_cache: {self._noisy_count_cache}")
283283

284284

285+
def tree_walker(node: Node) -> Iterator[Node]:
286+
"""
287+
Walk through every node in the tree, yielding the current node and all descendants.
288+
289+
Args:
290+
node: The root node to start walking from
291+
292+
Yields:
293+
Every node in the tree including the starting node
294+
"""
295+
# Yield the current node first
296+
yield node
297+
298+
# Recursively yield children if this is a Branch
299+
if isinstance(node, Branch):
300+
for child_index in sorted(node.children.keys()):
301+
child = node.children[child_index]
302+
yield from tree_walker(child)
303+
304+
285305
def _dump_tree(node: Node, indent: int = 0) -> None:
286306
"""Display the tree structure with directory-like indentation."""
287307
indent_str = " " * indent

0 commit comments

Comments
 (0)