diff --git a/src/detectmatelibrary/detectors/value_range_detector.py b/src/detectmatelibrary/detectors/value_range_detector.py index 7f61cc8..444663e 100644 --- a/src/detectmatelibrary/detectors/value_range_detector.py +++ b/src/detectmatelibrary/detectors/value_range_detector.py @@ -28,19 +28,28 @@ def __init__( self.config: ValueRangeDetectorConfig # type narrowing for IDE def add_value(self, tracker: SingleStabilityTracker, value: Any) -> None: - """Add a new value to the tracker (range semantics).""" + """Add a new value to the tracker (range semantics). + + Only the extremes are stored: ``unique_set`` holds at most two elements + ({min, max}), so storage is O(1) per variable instead of O(distinct + values). ``change_series`` and the min/max are byte-identical to storing + every value. The classifier's RANDOM branch can no longer fire for range + variables (size <= 2 never equals the change_series length); monotonic + counters stay excluded as UNSTABLE via their all-True series. + """ try: value = float(value) value = int(value) if value.is_integer() else value except ValueError: return - if len(tracker.unique_set) > 0: + if tracker.unique_set: min_ = min(tracker.unique_set) max_ = max(tracker.unique_set) tracker.change_series.append(value < min_ or value > max_) + tracker.unique_set = {min(min_, value), max(max_, value)} else: tracker.change_series.append(True) - tracker.unique_set.add(value) + tracker.unique_set = {value} def _event_data_kwargs(self) -> Optional[Dict[str, Any]]: return self._stability_kwargs() diff --git a/tests/test_detectors/test_value_range_detector.py b/tests/test_detectors/test_value_range_detector.py index 3ce7df8..e16379f 100644 --- a/tests/test_detectors/test_value_range_detector.py +++ b/tests/test_detectors/test_value_range_detector.py @@ -98,6 +98,21 @@ def test_add_value_updates_tracker(self): assert tracker.unique_set == {5, 7} assert list(tracker.change_series) == [True, True] + def test_add_value_stores_only_min_max(self): + """unique_set stays bounded to {min, max} regardless of how many + distinct values are seen; change detection still tracks the true + range.""" + detector = ValueRangeDetector() + tracker = SingleStabilityTracker() + + for v in [50, 10, 30, 90, 20, 70]: # min=10, max=90 + detector.add_value(tracker, v) + + assert tracker.unique_set == {10, 90} + # 50 (first, True), 10 (new min, True), 90 (new max, True) extend range; + # 30, 20, 70 fall inside -> False + assert list(tracker.change_series) == [True, True, False, True, False, False] + class TestValueRangeDetectorTraining: """Test ValueRangeDetector training functionality."""