Skip to content

Alnoms Core API Reference (OSS)

This reference is the single source of truth for the Alnoms industrial standard open-core package. All documentation below is generated deterministically from the sovereign source code.


βš™οΈ Core Engine (alnoms.core)

The orchestration and performance profiling logic for Pre-Deployment Governance.

⏱️ Performance Profiling

Industrial‑grade performance analyzer for algorithm benchmarking.

The Profiler supports:

  • Precision timing using timeit.default_timer
  • Warmup runs to stabilize CPU cache and branch predictors
  • Statistical aggregation (min, mean, median)
  • Doubling‑test complexity estimation
  • Decorator‑based profiling for normal program flow
  • Stress‑suite benchmarking for head‑to‑head comparisons

Attributes:

Name Type Description
repeats int

Number of timed runs per benchmark.

warmup int

Number of untimed warmup runs.

mode str

Statistical mode for final timing ('min', 'mean', 'median').

Source code in src/alnoms/core/profiler.py
class Profiler:
    """Industrial‑grade performance analyzer for algorithm benchmarking.

    The Profiler supports:

    - Precision timing using `timeit.default_timer`
    - Warmup runs to stabilize CPU cache and branch predictors
    - Statistical aggregation (min, mean, median)
    - Doubling‑test complexity estimation
    - Decorator‑based profiling for normal program flow
    - Stress‑suite benchmarking for head‑to‑head comparisons

    Attributes:
        repeats (int): Number of timed runs per benchmark.
        warmup (int): Number of untimed warmup runs.
        mode (str): Statistical mode for final timing ('min', 'mean', 'median').
    """

    def __init__(self, repeats: int = 5, warmup: int = 1, mode: str = "min"):
        """Initialize the Profiler with benchmark settings.

        Args:
            repeats (int): Number of timed runs per benchmark.
            warmup (int): Number of warmup runs to prime CPU cache.
            mode (str): Statistical mode ('min', 'mean', 'median').

        Notes:
            - `repeats` is clamped to at least 1.
            - `warmup` is clamped to at least 0.
        """
        self.repeats = max(1, repeats)
        self.warmup = max(0, warmup)
        self.mode = mode
        self._profile_stats = {}

    @contextmanager
    def stopwatch(self, label: str = "Block") -> Generator[None, None, None]:
        """Context manager for precision timing of a code block.

        Args:
            label (str): Identifier for the timed block.

        Yields:
            None: Execution of the wrapped block.

        Side Effects:
            - Records elapsed time under `self._profile_stats[label]`.
        """
        start = timeit.default_timer()
        try:
            yield
        finally:
            end = timeit.default_timer()
            elapsed = end - start
            self._profile_stats.setdefault(label, []).append(elapsed)

    def benchmark(self, func: Callable, *args: Any) -> float:
        """Benchmark a function with GC disabled for timing purity.

        Args:
            func (Callable): Function to benchmark.
            *args (Any): Arguments passed to the function.

        Returns:
            float: Execution time in seconds, aggregated using the configured mode.

        Notes:
            - Deepcopies arguments to avoid mutation across runs.
            - Disables garbage collection to reduce jitter.
        """
        # Warmup runs
        for _ in range(self.warmup):
            safe_args = copy.deepcopy(args)
            func(*safe_args)

        times = []
        gc_old = gc.isenabled()
        gc.disable()
        try:
            for _ in range(self.repeats):
                safe_args = copy.deepcopy(args)
                start = timeit.default_timer()
                func(*safe_args)
                end = timeit.default_timer()
                times.append(end - start)
        finally:
            if gc_old:
                gc.enable()

        # Statistical mode selection
        if self.mode == "median":
            return statistics.median(times)
        elif self.mode == "mean":
            return statistics.mean(times)
        return min(times)

    def run_doubling_test(
        self,
        func: Callable,
        input_gen: Callable[[int], Any],
        start_n: int = 50,
        rounds: int = 3,
        timeout: float = 15.0,
    ) -> List[Dict[str, Any]]:
        """Perform doubling analysis to estimate algorithmic complexity.

        Args:
            func (Callable): Algorithm under test.
            input_gen (Callable): Function generating input for size N.
            start_n (int): Initial input size.
            rounds (int): Number of doubling iterations.
            timeout (float): Maximum allowed runtime for the entire test.

        Returns:
            List[Dict[str, Any]]: A list of records containing:
                - "N": Input size
                - "Time": Execution time
                - "Ratio": T(2N) / T(N)
                - "Complexity": Estimated Big‑O class

        Notes:
            - Automatically increases recursion limit for deep algorithms.
            - Stops early if timeout is exceeded.
        """
        sys.setrecursionlimit(max(3000, sys.getrecursionlimit()))
        results = []
        prev_time = 0.0
        n = start_n
        start_clock = time.perf_counter()

        for _ in range(rounds):
            if time.perf_counter() - start_clock > timeout:
                break

            data = input_gen(n)
            args = data if isinstance(data, tuple) else (data,)
            curr_time = self.benchmark(func, *args)

            ratio = curr_time / prev_time if prev_time > 0 else 0.0
            complexity = self._guess_complexity(ratio)

            results.append(
                {"N": n, "Time": curr_time, "Ratio": ratio, "Complexity": complexity}
            )
            prev_time = curr_time
            n *= 2

        return results

    def profile(self, func: Callable) -> Callable:
        """Decorator for lightweight profiling during normal execution.

        Args:
            func (Callable): Function to wrap.

        Returns:
            Callable: Wrapped function that records execution time.

        Notes:
            - Stores timing data under `self._profile_stats[func.__name__]`.
        """

        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            start = timeit.default_timer()
            result = func(*args, **kwargs)
            end = timeit.default_timer()
            elapsed = end - start
            self._profile_stats.setdefault(func.__name__, []).append(elapsed)
            return result

        return wrapper

    def print_decorator_report(self) -> None:
        """Print a summary table of all decorator‑tracked timings.

        Displays:
            - Function/block label
            - Number of calls
            - Average time
            - Total time
        """
        print("\nπŸ“ ALNOMS PROFILE REPORT")
        print(
            f"{'Label/Function':<20} | {'Calls':<6} | {'Avg Time (s)':<12} | {'Total Time'}"
        )
        print("-" * 65)
        for fname, times in self._profile_stats.items():
            avg_t = statistics.mean(times) if times else 0.0
            total_t = sum(times)
            print(f"{fname:<20} | {len(times):<6} | {avg_t:<12.5f} | {total_t:.5f}")

    def _guess_complexity(self, ratio: float) -> str:
        """Map doubling ratios to approximate Big‑O complexity classes.

        Args:
            ratio (float): Ratio T(2N) / T(N).

        Returns:
            str: Estimated complexity class.

        Notes:
            - Thresholds are widened to account for CPU jitter and frequency scaling.
        """
        if ratio <= 0:
            return "Initial Round"
        if ratio < 1.4:
            return "O(1) / O(log N)"
        if ratio < 2.8:
            return "O(N)"
        if ratio < 5.5:
            return "O(N^2)"
        if ratio < 10.0:
            return "O(N^3)"
        return "High Growth / Exponential"

    def print_analysis(self, func_name: str, results: List[Dict[str, Any]]) -> None:
        """Print a formatted table from a doubling test.

        Args:
            func_name (str): Name of the analyzed function.
            results (List[Dict[str, Any]]): Output from `run_doubling_test`.
        """
        print(f"\nπŸ”¬ ANALYSIS: {func_name} (Mode: {self.mode})")
        print(f"{'N':<10} | {'Time (s)':<12} | {'Ratio':<8} | {'Est. Complexity':<15}")
        print("-" * 55)
        for row in results:
            r_str = f"{row['Ratio']:.2f}" if row["Ratio"] > 0 else "-"
            print(
                f"{row['N']:<10} | {row['Time']:<12.5f} | {r_str:<8} | {row['Complexity']:<15}"
            )

    def run_stress_suite(
        self,
        funcs: Dict[str, Callable],
        input_gen: Callable[[int], Any],
        n_values: List[int] = [1000, 2000, 4000],
    ) -> Dict[int, Dict[str, float]]:
        """Run multiple algorithms across multiple input sizes.

        Useful for head‑to‑head comparisons in research, teaching, and
        performance governance.

        Args:
            funcs (Dict[str, Callable]): Mapping of function names to callables.
            input_gen (Callable): Data generator for size N.
            n_values (List[int]): Input sizes to test.

        Returns:
            Dict[int, Dict[str, float]]:
                Nested mapping of `{N: {FunctionName: Time}}`.
        """
        suite_results = {}
        for n in n_values:
            suite_results[n] = {}
            data = input_gen(n)
            args = data if isinstance(data, tuple) else (data,)

            for name, func in funcs.items():
                suite_results[n][name] = self.benchmark(func, *args)
        return suite_results

__init__(repeats=5, warmup=1, mode='min')

Initialize the Profiler with benchmark settings.

Parameters:

Name Type Description Default
repeats int

Number of timed runs per benchmark.

5
warmup int

Number of warmup runs to prime CPU cache.

1
mode str

Statistical mode ('min', 'mean', 'median').

'min'
Notes
  • repeats is clamped to at least 1.
  • warmup is clamped to at least 0.
Source code in src/alnoms/core/profiler.py
def __init__(self, repeats: int = 5, warmup: int = 1, mode: str = "min"):
    """Initialize the Profiler with benchmark settings.

    Args:
        repeats (int): Number of timed runs per benchmark.
        warmup (int): Number of warmup runs to prime CPU cache.
        mode (str): Statistical mode ('min', 'mean', 'median').

    Notes:
        - `repeats` is clamped to at least 1.
        - `warmup` is clamped to at least 0.
    """
    self.repeats = max(1, repeats)
    self.warmup = max(0, warmup)
    self.mode = mode
    self._profile_stats = {}

benchmark(func, *args)

Benchmark a function with GC disabled for timing purity.

Parameters:

Name Type Description Default
func Callable

Function to benchmark.

required
*args Any

Arguments passed to the function.

()

Returns:

Name Type Description
float float

Execution time in seconds, aggregated using the configured mode.

Notes
  • Deepcopies arguments to avoid mutation across runs.
  • Disables garbage collection to reduce jitter.
Source code in src/alnoms/core/profiler.py
def benchmark(self, func: Callable, *args: Any) -> float:
    """Benchmark a function with GC disabled for timing purity.

    Args:
        func (Callable): Function to benchmark.
        *args (Any): Arguments passed to the function.

    Returns:
        float: Execution time in seconds, aggregated using the configured mode.

    Notes:
        - Deepcopies arguments to avoid mutation across runs.
        - Disables garbage collection to reduce jitter.
    """
    # Warmup runs
    for _ in range(self.warmup):
        safe_args = copy.deepcopy(args)
        func(*safe_args)

    times = []
    gc_old = gc.isenabled()
    gc.disable()
    try:
        for _ in range(self.repeats):
            safe_args = copy.deepcopy(args)
            start = timeit.default_timer()
            func(*safe_args)
            end = timeit.default_timer()
            times.append(end - start)
    finally:
        if gc_old:
            gc.enable()

    # Statistical mode selection
    if self.mode == "median":
        return statistics.median(times)
    elif self.mode == "mean":
        return statistics.mean(times)
    return min(times)

print_analysis(func_name, results)

Print a formatted table from a doubling test.

Parameters:

Name Type Description Default
func_name str

Name of the analyzed function.

required
results List[Dict[str, Any]]

Output from run_doubling_test.

required
Source code in src/alnoms/core/profiler.py
def print_analysis(self, func_name: str, results: List[Dict[str, Any]]) -> None:
    """Print a formatted table from a doubling test.

    Args:
        func_name (str): Name of the analyzed function.
        results (List[Dict[str, Any]]): Output from `run_doubling_test`.
    """
    print(f"\nπŸ”¬ ANALYSIS: {func_name} (Mode: {self.mode})")
    print(f"{'N':<10} | {'Time (s)':<12} | {'Ratio':<8} | {'Est. Complexity':<15}")
    print("-" * 55)
    for row in results:
        r_str = f"{row['Ratio']:.2f}" if row["Ratio"] > 0 else "-"
        print(
            f"{row['N']:<10} | {row['Time']:<12.5f} | {r_str:<8} | {row['Complexity']:<15}"
        )

print_decorator_report()

Print a summary table of all decorator‑tracked timings.

Displays
  • Function/block label
  • Number of calls
  • Average time
  • Total time
Source code in src/alnoms/core/profiler.py
def print_decorator_report(self) -> None:
    """Print a summary table of all decorator‑tracked timings.

    Displays:
        - Function/block label
        - Number of calls
        - Average time
        - Total time
    """
    print("\nπŸ“ ALNOMS PROFILE REPORT")
    print(
        f"{'Label/Function':<20} | {'Calls':<6} | {'Avg Time (s)':<12} | {'Total Time'}"
    )
    print("-" * 65)
    for fname, times in self._profile_stats.items():
        avg_t = statistics.mean(times) if times else 0.0
        total_t = sum(times)
        print(f"{fname:<20} | {len(times):<6} | {avg_t:<12.5f} | {total_t:.5f}")

profile(func)

Decorator for lightweight profiling during normal execution.

Parameters:

Name Type Description Default
func Callable

Function to wrap.

required

Returns:

Name Type Description
Callable Callable

Wrapped function that records execution time.

Notes
  • Stores timing data under self._profile_stats[func.__name__].
Source code in src/alnoms/core/profiler.py
def profile(self, func: Callable) -> Callable:
    """Decorator for lightweight profiling during normal execution.

    Args:
        func (Callable): Function to wrap.

    Returns:
        Callable: Wrapped function that records execution time.

    Notes:
        - Stores timing data under `self._profile_stats[func.__name__]`.
    """

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = timeit.default_timer()
        result = func(*args, **kwargs)
        end = timeit.default_timer()
        elapsed = end - start
        self._profile_stats.setdefault(func.__name__, []).append(elapsed)
        return result

    return wrapper

run_doubling_test(func, input_gen, start_n=50, rounds=3, timeout=15.0)

Perform doubling analysis to estimate algorithmic complexity.

Parameters:

Name Type Description Default
func Callable

Algorithm under test.

required
input_gen Callable

Function generating input for size N.

required
start_n int

Initial input size.

50
rounds int

Number of doubling iterations.

3
timeout float

Maximum allowed runtime for the entire test.

15.0

Returns:

Type Description
List[Dict[str, Any]]

List[Dict[str, Any]]: A list of records containing: - "N": Input size - "Time": Execution time - "Ratio": T(2N) / T(N) - "Complexity": Estimated Big‑O class

Notes
  • Automatically increases recursion limit for deep algorithms.
  • Stops early if timeout is exceeded.
Source code in src/alnoms/core/profiler.py
def run_doubling_test(
    self,
    func: Callable,
    input_gen: Callable[[int], Any],
    start_n: int = 50,
    rounds: int = 3,
    timeout: float = 15.0,
) -> List[Dict[str, Any]]:
    """Perform doubling analysis to estimate algorithmic complexity.

    Args:
        func (Callable): Algorithm under test.
        input_gen (Callable): Function generating input for size N.
        start_n (int): Initial input size.
        rounds (int): Number of doubling iterations.
        timeout (float): Maximum allowed runtime for the entire test.

    Returns:
        List[Dict[str, Any]]: A list of records containing:
            - "N": Input size
            - "Time": Execution time
            - "Ratio": T(2N) / T(N)
            - "Complexity": Estimated Big‑O class

    Notes:
        - Automatically increases recursion limit for deep algorithms.
        - Stops early if timeout is exceeded.
    """
    sys.setrecursionlimit(max(3000, sys.getrecursionlimit()))
    results = []
    prev_time = 0.0
    n = start_n
    start_clock = time.perf_counter()

    for _ in range(rounds):
        if time.perf_counter() - start_clock > timeout:
            break

        data = input_gen(n)
        args = data if isinstance(data, tuple) else (data,)
        curr_time = self.benchmark(func, *args)

        ratio = curr_time / prev_time if prev_time > 0 else 0.0
        complexity = self._guess_complexity(ratio)

        results.append(
            {"N": n, "Time": curr_time, "Ratio": ratio, "Complexity": complexity}
        )
        prev_time = curr_time
        n *= 2

    return results

run_stress_suite(funcs, input_gen, n_values=[1000, 2000, 4000])

Run multiple algorithms across multiple input sizes.

Useful for head‑to‑head comparisons in research, teaching, and performance governance.

Parameters:

Name Type Description Default
funcs Dict[str, Callable]

Mapping of function names to callables.

required
input_gen Callable

Data generator for size N.

required
n_values List[int]

Input sizes to test.

[1000, 2000, 4000]

Returns:

Type Description
Dict[int, Dict[str, float]]

Dict[int, Dict[str, float]]: Nested mapping of {N: {FunctionName: Time}}.

Source code in src/alnoms/core/profiler.py
def run_stress_suite(
    self,
    funcs: Dict[str, Callable],
    input_gen: Callable[[int], Any],
    n_values: List[int] = [1000, 2000, 4000],
) -> Dict[int, Dict[str, float]]:
    """Run multiple algorithms across multiple input sizes.

    Useful for head‑to‑head comparisons in research, teaching, and
    performance governance.

    Args:
        funcs (Dict[str, Callable]): Mapping of function names to callables.
        input_gen (Callable): Data generator for size N.
        n_values (List[int]): Input sizes to test.

    Returns:
        Dict[int, Dict[str, float]]:
            Nested mapping of `{N: {FunctionName: Time}}`.
    """
    suite_results = {}
    for n in n_values:
        suite_results[n] = {}
        data = input_gen(n)
        args = data if isinstance(data, tuple) else (data,)

        for name, func in funcs.items():
            suite_results[n][name] = self.benchmark(func, *args)
    return suite_results

stopwatch(label='Block')

Context manager for precision timing of a code block.

Parameters:

Name Type Description Default
label str

Identifier for the timed block.

'Block'

Yields:

Name Type Description
None None

Execution of the wrapped block.

Side Effects
  • Records elapsed time under self._profile_stats[label].
Source code in src/alnoms/core/profiler.py
@contextmanager
def stopwatch(self, label: str = "Block") -> Generator[None, None, None]:
    """Context manager for precision timing of a code block.

    Args:
        label (str): Identifier for the timed block.

    Yields:
        None: Execution of the wrapped block.

    Side Effects:
        - Records elapsed time under `self._profile_stats[label]`.
    """
    start = timeit.default_timer()
    try:
        yield
    finally:
        end = timeit.default_timer()
        elapsed = end - start
        self._profile_stats.setdefault(label, []).append(elapsed)

🧠 Analysis & Decision Engine

Central orchestrator for the Alnoms governance pipeline.

This class coordinates:

  • Script execution and dynamic profiling
  • Static AST pattern detection
  • Loop‑depth and static complexity estimation
  • Optional empirical scaling tests
  • Metadata‑driven algorithmic recommendations
  • Fixer‑based prescriptive remediation

All methods are static and the class is stateless.

Source code in src/alnoms/core/analyzer.py
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
class ScriptAnalyzer:
    """Central orchestrator for the Alnoms governance pipeline.

    This class coordinates:

    - Script execution and dynamic profiling
    - Static AST pattern detection
    - Loop‑depth and static complexity estimation
    - Optional empirical scaling tests
    - Metadata‑driven algorithmic recommendations
    - Fixer‑based prescriptive remediation

    All methods are static and the class is stateless.
    """

    # ----------------------------------------------------------------------
    # LOOP DEPTH ANALYSIS
    # ----------------------------------------------------------------------
    @staticmethod
    def _get_loop_depth(node: ast.AST) -> int:
        """Recursively compute the maximum nesting depth of loops.

        Comprehensions (list, dict, set, generator) are ignored because they
        are optimized internally by CPython and do not represent explicit
        nested loops in the same semantic sense.

        Args:
            node (ast.AST): The AST node to inspect.

        Returns:
            int: Maximum loop nesting depth. Returns 0 if no loops are found.
        """
        if isinstance(
            node, (ast.ListComp, ast.DictComp, ast.SetComp, ast.GeneratorExp)
        ):
            return 0

        if not isinstance(node, (ast.For, ast.While)):
            return 0

        max_child = 0
        for child in getattr(node, "body", []):
            max_child = max(max_child, ScriptAnalyzer._get_loop_depth(child))

        return 1 + max_child

    @staticmethod
    def _find_target_loop_node(tree: ast.AST, lineno: int) -> Optional[ast.AST]:
        """Locate the loop node closest to a given line number.

        This is a Python‑version‑safe method that does not rely on `end_lineno`.
        It finds the deepest loop whose starting line is less than or equal to
        the pattern's line number.

        Args:
            tree (ast.AST): Parsed AST of the entire file.
            lineno (int): Line number associated with a detected pattern.

        Returns:
            Optional[ast.AST]: The best matching loop node, or None.
        """
        best_match = None
        for node in ast.walk(tree):
            if isinstance(node, (ast.For, ast.While)) and hasattr(node, "lineno"):
                if node.lineno <= lineno:
                    if best_match is None or node.lineno > best_match.lineno:
                        best_match = node
        return best_match

    # ----------------------------------------------------------------------
    # SCRIPT EXECUTION & PROFILING
    # ----------------------------------------------------------------------
    @staticmethod
    def run_script(path: str):
        """Execute a Python script in an isolated module namespace.

        Args:
            path (str): Path to the Python script.

        Returns:
            module: The executed module object.
        """
        spec = importlib.util.spec_from_file_location("__main__", path)
        module = importlib.util.module_from_spec(spec)
        sys.modules["__main__"] = module
        spec.loader.exec_module(module)
        return module

    @staticmethod
    def profile_script(path: str):
        """Profile a script and extract the top slowest developer functions.

        Uses `cProfile` to gather cumulative execution time and filters out
        non‑user code.

        Args:
            path (str): Path to the Python script.

        Returns:
            tuple: A tuple containing:
                - list: Top 5 slowest functions with timing info.
                - float: Total cumulative execution time.
                - module: The executed module object.
        """
        pr = cProfile.Profile()
        pr.enable()
        module = ScriptAnalyzer.run_script(path)
        pr.disable()

        s = io.StringIO()
        ps = pstats.Stats(pr, stream=s).sort_stats("cumulative")
        stats = ps.stats

        results = []
        total_time = sum([v[3] for v in stats.values()])
        target_filename = os.path.basename(path)

        for func, stat in stats.items():
            filename, lineno, funcname = func
            cumtime = stat[3]

            if target_filename not in filename:
                continue
            if funcname.startswith("<") and funcname.endswith(">"):
                continue

            results.append(
                {
                    "function": funcname,
                    "time": round(cumtime, 5),
                    "percent": round((cumtime / total_time) * 100, 2)
                    if total_time
                    else 0,
                }
            )

        results.sort(key=lambda x: x["time"], reverse=True)
        return results[:5], total_time, module

    # ----------------------------------------------------------------------
    # AST EXTRACTION
    # ----------------------------------------------------------------------
    @staticmethod
    def _get_function_ast(tree: ast.AST, func_name: str) -> Optional[ast.AST]:
        """Extract AST node for a given function name."""
        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef) and node.name == func_name:
                return node
        return None

    # ----------------------------------------------------------------------
    # EMPIRICAL SCALING TESTS
    # ----------------------------------------------------------------------
    @staticmethod
    def run_empirical_test(
        module: Any,
        slowest_func_name: str,
        gen_name: str = None,
        data_file: str = None,
        start_n: int = 50,
        rounds: int = 3,
        func_ast: Optional[ast.AST] = None,  # βœ… ADDED
    ) -> Optional[List[Dict[str, Any]]]:
        """Run empirical doubling tests on a target function.

        Input data can come from:

        - A script-defined `data_gen()`
        - A standard generator in `alnoms.core.generators`
        - A data file loaded via `DataReader`

        Args:
            module (Any): The executed script module.
            slowest_func_name (str): Function selected for empirical testing.
            gen_name (str, optional): Name of a standard generator.
            data_file (str, optional): Path to a data file.
            start_n (int): Initial input size.
            rounds (int): Number of doubling rounds.

        Returns:
            Optional[List[Dict[str, Any]]]: Empirical results or None.
        """
        input_gen = None

        # File-based generator
        if data_file:
            try:
                file_data = std_io.read_all_ints(data_file)
            except ValueError:
                file_data = std_io.read_lines(data_file)

            def input_gen(n):
                if isinstance(n, (list, tuple)):
                    n = len(n)
                return (file_data[:n],)

        # Standard generator
        elif gen_name:
            raw_gen = getattr(std_gen, gen_name, None)

            def input_gen(n):
                if isinstance(n, (list, tuple)):
                    n = len(n)
                res = raw_gen(n)
                return res if isinstance(res, tuple) else (res,)

        # Script-defined generator OR AutoGen fallback
        else:
            if hasattr(module, "data_gen"):
                raw_gen = module.data_gen

                def input_gen(n):
                    # If n is a list or tuple, convert to its length
                    if isinstance(n, (list, tuple)):
                        n = len(n)

                    out = raw_gen(n)

                    # Always return a tuple
                    return out if isinstance(out, tuple) else (out,)
            else:
                # βœ… SAFE AUTOGEN FALLBACK
                if func_ast:
                    pattern = AutoGen._classify(func_ast)

                    def input_gen(n):
                        if isinstance(n, (list, tuple)):
                            n = len(n)
                        try:
                            samples = AutoGen.generate(pattern, n)
                            return samples

                        except Exception:
                            pass

                        # πŸ”₯ HARD FALLBACK (guaranteed to work)
                        return ("a" * n,)
                else:
                    # πŸ”₯ LAST RESORT FALLBACK
                    def input_gen(n):
                        if isinstance(n, (list, tuple)):
                            n = len(n)
                        return ("a" * n,)

        if not input_gen:
            return None

        # Detect config overrides
        sample_data = input_gen(start_n)
        config = sample_data if isinstance(sample_data, dict) else {}
        final_start_n = config.get("start_n", start_n)
        final_rounds = config.get("rounds", rounds)

        # Determine target function
        if isinstance(sample_data, dict) and "target" in sample_data:
            target_name = sample_data["target"]

            def effective_gen(n):
                return input_gen(n)["args"]
        else:
            target_name = slowest_func_name

            def effective_gen(n):
                data = input_gen(n)
                return data if isinstance(data, tuple) else (data,)

        target_func = getattr(module, target_name, None)
        if not target_func:
            return None

        # Validate argument count
        sig = inspect.signature(target_func)
        required_params = [
            p
            for p in sig.parameters.values()
            if p.default == p.empty and p.kind not in (p.VAR_POSITIONAL, p.VAR_KEYWORD)
        ]

        test_args = effective_gen(final_start_n)
        args_count = len(test_args) if isinstance(test_args, tuple) else 1

        if args_count < len(required_params):
            return None

        prof = Profiler(repeats=3, warmup=1, mode="min")
        try:
            return prof.run_doubling_test(
                target_func, effective_gen, start_n=final_start_n, rounds=final_rounds
            )
        except Exception:
            # If empirical scaling fails (signature mismatch, bad generator, etc.),
            # degrade gracefully and skip empirical results.
            return None

    # ----------------------------------------------------------------------
    # FULL PIPELINE ORCHESTRATION
    # ----------------------------------------------------------------------
    @staticmethod
    def analyze_file(
        path: str,
        deep: bool = False,
        target_override: str = None,
        gen_name: str = None,
        data_file: str = None,
        start_n: int = 50,
        rounds: int = 3,
    ) -> dict:
        """Perform full governance analysis on a Python script.

        Pipeline:
            1. Execute + profile the script
            2. Run static AST pattern detection
            3. Compute loop depth and static complexity
            4. Optionally run empirical scaling tests
            5. Integrate DecisionEngine metadata
            6. Integrate Fixers for prescriptive remediation
            7. Produce a unified governance report

        Args:
            path (str): Path to the Python script.
            deep (bool): Whether to run empirical scaling tests.
            target_override (str, optional): Explicit function name for empirical tests.
            gen_name (str, optional): Name of a standard generator.
            data_file (str, optional): Path to a data file.
            start_n (int): Initial input size for empirical tests.
            rounds (int): Number of doubling rounds.

        Returns:
            dict: A complete governance analysis report.
        """
        # 1. Profile and Execute
        profile_results, total_time, module = ScriptAnalyzer.profile_script(path)

        # 2. Static Analysis
        raw_patterns = analyze_code(path)
        with open(path, "r", encoding="utf-8") as f:
            full_tree = ast.parse(f.read())
            # Attach parent pointers for AST classification
            for node in ast.walk(full_tree):
                for child in ast.iter_child_nodes(node):
                    child.parent = node

        empirical_results = None
        slowest_func_name = None
        for entry in profile_results:
            if entry["function"] != "data_gen":
                slowest_func_name = entry["function"]
                break
        if not slowest_func_name:
            for node in ast.walk(full_tree):
                if isinstance(node, ast.FunctionDef) and node.name != "data_gen":
                    slowest_func_name = node.name
                    break
        empirical_target = target_override or slowest_func_name
        print("DEBUG deep =", deep)
        print("DEBUG empirical_target =", empirical_target)

        func_ast = ScriptAnalyzer._get_function_ast(full_tree, empirical_target)

        if deep and empirical_target:
            empirical_results = ScriptAnalyzer.run_empirical_test(
                module,
                empirical_target,
                gen_name,
                data_file,
                start_n,
                rounds,
                func_ast=func_ast,
            )

        # 3. Decision Engine
        engine = DecisionEngine(MetadataRegistry.get_all())
        aggregated_findings = {}

        detected_complexity = (
            empirical_results[-1].get("Complexity", "Unknown")
            if empirical_results
            else "Unknown"
        )

        # 4. Remediation Orchestration
        for finding in raw_patterns:
            func = finding.get("function", "global")
            pid = finding.get("pattern_id", "unknown")
            line_no = finding.get("line")

            key = (func, pid)

            # Aggregate occurrences
            if key not in aggregated_findings:
                aggregated_findings[key] = finding
                finding["occurrence_count"] = 1
                finding["occurrence_lines"] = [line_no]
            else:
                aggregated_findings[key]["occurrence_count"] += 1
                aggregated_findings[key]["occurrence_lines"].append(line_no)
                continue

            # Static loop depth
            static_depth = 1
            if pid == "nested_loops":
                target_node = ScriptAnalyzer._find_target_loop_node(full_tree, line_no)
                if target_node:
                    static_depth = ScriptAnalyzer._get_loop_depth(target_node)

            finding["loop_depth"] = static_depth

            # Static vs empirical complexity
            finding["static_complexity"] = (
                f"O(N^{static_depth})" if pid == "nested_loops" else None
            )
            finding["empirical_complexity"] = detected_complexity

            # Decision Engine metadata
            is_cubic = (
                pid == "nested_loops" and static_depth >= 3
            ) or detected_complexity == "O(N^3)"

            if not is_cubic:
                recommended_algo = engine.decide_algorithm(pid)
                if recommended_algo:
                    finding["dsa_meta"] = engine.decide_metadata(recommended_algo)
            else:
                finding["dsa_meta"] = None
                finding["is_domain_override"] = True

            # Fixer integration
            fixer = get_fixer(pid)
            if fixer:
                finding["cure_type"] = fixer.cure_type()
                finding["explanation"] = fixer.explain(finding, detected_complexity)
                finding["cost_estimate"] = fixer.cost_estimate(
                    finding, detected_complexity
                )
                finding["snippets"] = fixer.snippet_before_after(
                    finding, detected_complexity
                )

        return {
            "file": path,
            "profile": profile_results,
            "patterns": list(aggregated_findings.values()),
            "total_time": round(total_time, 4),
            "empirical": empirical_results,
            "empirical_target": empirical_target,
            "meta": {
                "version": "0.1.3",
                "timestamp": datetime.now(timezone.utc)
                .isoformat()
                .replace("+00:00", "Z"),
            },
        }

analyze_file(path, deep=False, target_override=None, gen_name=None, data_file=None, start_n=50, rounds=3) staticmethod

Perform full governance analysis on a Python script.

Pipeline
  1. Execute + profile the script
  2. Run static AST pattern detection
  3. Compute loop depth and static complexity
  4. Optionally run empirical scaling tests
  5. Integrate DecisionEngine metadata
  6. Integrate Fixers for prescriptive remediation
  7. Produce a unified governance report

Parameters:

Name Type Description Default
path str

Path to the Python script.

required
deep bool

Whether to run empirical scaling tests.

False
target_override str

Explicit function name for empirical tests.

None
gen_name str

Name of a standard generator.

None
data_file str

Path to a data file.

None
start_n int

Initial input size for empirical tests.

50
rounds int

Number of doubling rounds.

3

Returns:

Name Type Description
dict dict

A complete governance analysis report.

Source code in src/alnoms/core/analyzer.py
@staticmethod
def analyze_file(
    path: str,
    deep: bool = False,
    target_override: str = None,
    gen_name: str = None,
    data_file: str = None,
    start_n: int = 50,
    rounds: int = 3,
) -> dict:
    """Perform full governance analysis on a Python script.

    Pipeline:
        1. Execute + profile the script
        2. Run static AST pattern detection
        3. Compute loop depth and static complexity
        4. Optionally run empirical scaling tests
        5. Integrate DecisionEngine metadata
        6. Integrate Fixers for prescriptive remediation
        7. Produce a unified governance report

    Args:
        path (str): Path to the Python script.
        deep (bool): Whether to run empirical scaling tests.
        target_override (str, optional): Explicit function name for empirical tests.
        gen_name (str, optional): Name of a standard generator.
        data_file (str, optional): Path to a data file.
        start_n (int): Initial input size for empirical tests.
        rounds (int): Number of doubling rounds.

    Returns:
        dict: A complete governance analysis report.
    """
    # 1. Profile and Execute
    profile_results, total_time, module = ScriptAnalyzer.profile_script(path)

    # 2. Static Analysis
    raw_patterns = analyze_code(path)
    with open(path, "r", encoding="utf-8") as f:
        full_tree = ast.parse(f.read())
        # Attach parent pointers for AST classification
        for node in ast.walk(full_tree):
            for child in ast.iter_child_nodes(node):
                child.parent = node

    empirical_results = None
    slowest_func_name = None
    for entry in profile_results:
        if entry["function"] != "data_gen":
            slowest_func_name = entry["function"]
            break
    if not slowest_func_name:
        for node in ast.walk(full_tree):
            if isinstance(node, ast.FunctionDef) and node.name != "data_gen":
                slowest_func_name = node.name
                break
    empirical_target = target_override or slowest_func_name
    print("DEBUG deep =", deep)
    print("DEBUG empirical_target =", empirical_target)

    func_ast = ScriptAnalyzer._get_function_ast(full_tree, empirical_target)

    if deep and empirical_target:
        empirical_results = ScriptAnalyzer.run_empirical_test(
            module,
            empirical_target,
            gen_name,
            data_file,
            start_n,
            rounds,
            func_ast=func_ast,
        )

    # 3. Decision Engine
    engine = DecisionEngine(MetadataRegistry.get_all())
    aggregated_findings = {}

    detected_complexity = (
        empirical_results[-1].get("Complexity", "Unknown")
        if empirical_results
        else "Unknown"
    )

    # 4. Remediation Orchestration
    for finding in raw_patterns:
        func = finding.get("function", "global")
        pid = finding.get("pattern_id", "unknown")
        line_no = finding.get("line")

        key = (func, pid)

        # Aggregate occurrences
        if key not in aggregated_findings:
            aggregated_findings[key] = finding
            finding["occurrence_count"] = 1
            finding["occurrence_lines"] = [line_no]
        else:
            aggregated_findings[key]["occurrence_count"] += 1
            aggregated_findings[key]["occurrence_lines"].append(line_no)
            continue

        # Static loop depth
        static_depth = 1
        if pid == "nested_loops":
            target_node = ScriptAnalyzer._find_target_loop_node(full_tree, line_no)
            if target_node:
                static_depth = ScriptAnalyzer._get_loop_depth(target_node)

        finding["loop_depth"] = static_depth

        # Static vs empirical complexity
        finding["static_complexity"] = (
            f"O(N^{static_depth})" if pid == "nested_loops" else None
        )
        finding["empirical_complexity"] = detected_complexity

        # Decision Engine metadata
        is_cubic = (
            pid == "nested_loops" and static_depth >= 3
        ) or detected_complexity == "O(N^3)"

        if not is_cubic:
            recommended_algo = engine.decide_algorithm(pid)
            if recommended_algo:
                finding["dsa_meta"] = engine.decide_metadata(recommended_algo)
        else:
            finding["dsa_meta"] = None
            finding["is_domain_override"] = True

        # Fixer integration
        fixer = get_fixer(pid)
        if fixer:
            finding["cure_type"] = fixer.cure_type()
            finding["explanation"] = fixer.explain(finding, detected_complexity)
            finding["cost_estimate"] = fixer.cost_estimate(
                finding, detected_complexity
            )
            finding["snippets"] = fixer.snippet_before_after(
                finding, detected_complexity
            )

    return {
        "file": path,
        "profile": profile_results,
        "patterns": list(aggregated_findings.values()),
        "total_time": round(total_time, 4),
        "empirical": empirical_results,
        "empirical_target": empirical_target,
        "meta": {
            "version": "0.1.3",
            "timestamp": datetime.now(timezone.utc)
            .isoformat()
            .replace("+00:00", "Z"),
        },
    }

profile_script(path) staticmethod

Profile a script and extract the top slowest developer functions.

Uses cProfile to gather cumulative execution time and filters out non‑user code.

Parameters:

Name Type Description Default
path str

Path to the Python script.

required

Returns:

Name Type Description
tuple

A tuple containing: - list: Top 5 slowest functions with timing info. - float: Total cumulative execution time. - module: The executed module object.

Source code in src/alnoms/core/analyzer.py
@staticmethod
def profile_script(path: str):
    """Profile a script and extract the top slowest developer functions.

    Uses `cProfile` to gather cumulative execution time and filters out
    non‑user code.

    Args:
        path (str): Path to the Python script.

    Returns:
        tuple: A tuple containing:
            - list: Top 5 slowest functions with timing info.
            - float: Total cumulative execution time.
            - module: The executed module object.
    """
    pr = cProfile.Profile()
    pr.enable()
    module = ScriptAnalyzer.run_script(path)
    pr.disable()

    s = io.StringIO()
    ps = pstats.Stats(pr, stream=s).sort_stats("cumulative")
    stats = ps.stats

    results = []
    total_time = sum([v[3] for v in stats.values()])
    target_filename = os.path.basename(path)

    for func, stat in stats.items():
        filename, lineno, funcname = func
        cumtime = stat[3]

        if target_filename not in filename:
            continue
        if funcname.startswith("<") and funcname.endswith(">"):
            continue

        results.append(
            {
                "function": funcname,
                "time": round(cumtime, 5),
                "percent": round((cumtime / total_time) * 100, 2)
                if total_time
                else 0,
            }
        )

    results.sort(key=lambda x: x["time"], reverse=True)
    return results[:5], total_time, module

run_empirical_test(module, slowest_func_name, gen_name=None, data_file=None, start_n=50, rounds=3, func_ast=None) staticmethod

Run empirical doubling tests on a target function.

Input data can come from:

  • A script-defined data_gen()
  • A standard generator in alnoms.core.generators
  • A data file loaded via DataReader

Parameters:

Name Type Description Default
module Any

The executed script module.

required
slowest_func_name str

Function selected for empirical testing.

required
gen_name str

Name of a standard generator.

None
data_file str

Path to a data file.

None
start_n int

Initial input size.

50
rounds int

Number of doubling rounds.

3

Returns:

Type Description
Optional[List[Dict[str, Any]]]

Optional[List[Dict[str, Any]]]: Empirical results or None.

Source code in src/alnoms/core/analyzer.py
@staticmethod
def run_empirical_test(
    module: Any,
    slowest_func_name: str,
    gen_name: str = None,
    data_file: str = None,
    start_n: int = 50,
    rounds: int = 3,
    func_ast: Optional[ast.AST] = None,  # βœ… ADDED
) -> Optional[List[Dict[str, Any]]]:
    """Run empirical doubling tests on a target function.

    Input data can come from:

    - A script-defined `data_gen()`
    - A standard generator in `alnoms.core.generators`
    - A data file loaded via `DataReader`

    Args:
        module (Any): The executed script module.
        slowest_func_name (str): Function selected for empirical testing.
        gen_name (str, optional): Name of a standard generator.
        data_file (str, optional): Path to a data file.
        start_n (int): Initial input size.
        rounds (int): Number of doubling rounds.

    Returns:
        Optional[List[Dict[str, Any]]]: Empirical results or None.
    """
    input_gen = None

    # File-based generator
    if data_file:
        try:
            file_data = std_io.read_all_ints(data_file)
        except ValueError:
            file_data = std_io.read_lines(data_file)

        def input_gen(n):
            if isinstance(n, (list, tuple)):
                n = len(n)
            return (file_data[:n],)

    # Standard generator
    elif gen_name:
        raw_gen = getattr(std_gen, gen_name, None)

        def input_gen(n):
            if isinstance(n, (list, tuple)):
                n = len(n)
            res = raw_gen(n)
            return res if isinstance(res, tuple) else (res,)

    # Script-defined generator OR AutoGen fallback
    else:
        if hasattr(module, "data_gen"):
            raw_gen = module.data_gen

            def input_gen(n):
                # If n is a list or tuple, convert to its length
                if isinstance(n, (list, tuple)):
                    n = len(n)

                out = raw_gen(n)

                # Always return a tuple
                return out if isinstance(out, tuple) else (out,)
        else:
            # βœ… SAFE AUTOGEN FALLBACK
            if func_ast:
                pattern = AutoGen._classify(func_ast)

                def input_gen(n):
                    if isinstance(n, (list, tuple)):
                        n = len(n)
                    try:
                        samples = AutoGen.generate(pattern, n)
                        return samples

                    except Exception:
                        pass

                    # πŸ”₯ HARD FALLBACK (guaranteed to work)
                    return ("a" * n,)
            else:
                # πŸ”₯ LAST RESORT FALLBACK
                def input_gen(n):
                    if isinstance(n, (list, tuple)):
                        n = len(n)
                    return ("a" * n,)

    if not input_gen:
        return None

    # Detect config overrides
    sample_data = input_gen(start_n)
    config = sample_data if isinstance(sample_data, dict) else {}
    final_start_n = config.get("start_n", start_n)
    final_rounds = config.get("rounds", rounds)

    # Determine target function
    if isinstance(sample_data, dict) and "target" in sample_data:
        target_name = sample_data["target"]

        def effective_gen(n):
            return input_gen(n)["args"]
    else:
        target_name = slowest_func_name

        def effective_gen(n):
            data = input_gen(n)
            return data if isinstance(data, tuple) else (data,)

    target_func = getattr(module, target_name, None)
    if not target_func:
        return None

    # Validate argument count
    sig = inspect.signature(target_func)
    required_params = [
        p
        for p in sig.parameters.values()
        if p.default == p.empty and p.kind not in (p.VAR_POSITIONAL, p.VAR_KEYWORD)
    ]

    test_args = effective_gen(final_start_n)
    args_count = len(test_args) if isinstance(test_args, tuple) else 1

    if args_count < len(required_params):
        return None

    prof = Profiler(repeats=3, warmup=1, mode="min")
    try:
        return prof.run_doubling_test(
            target_func, effective_gen, start_n=final_start_n, rounds=final_rounds
        )
    except Exception:
        # If empirical scaling fails (signature mismatch, bad generator, etc.),
        # degrade gracefully and skip empirical results.
        return None

run_script(path) staticmethod

Execute a Python script in an isolated module namespace.

Parameters:

Name Type Description Default
path str

Path to the Python script.

required

Returns:

Name Type Description
module

The executed module object.

Source code in src/alnoms/core/analyzer.py
@staticmethod
def run_script(path: str):
    """Execute a Python script in an isolated module namespace.

    Args:
        path (str): Path to the Python script.

    Returns:
        module: The executed module object.
    """
    spec = importlib.util.spec_from_file_location("__main__", path)
    module = importlib.util.module_from_spec(spec)
    sys.modules["__main__"] = module
    spec.loader.exec_module(module)
    return module

Deterministic rule‑based mapping for OSS‑tier algorithm selection.

The DecisionEngine provides a stable, non‑adaptive mapping from detected performance patterns to recommended data‑structure or algorithmic remedies. All identifiers returned by this engine use snake_case to satisfy OSS‑tier test and governance requirements.

Metadata lookup is also performed using snake_case keys, matching the canonical identifiers stored in the MetadataRegistry.

Source code in src/alnoms/core/decision_engine.py
class DecisionEngine:
    """Deterministic rule‑based mapping for OSS‑tier algorithm selection.

    The DecisionEngine provides a stable, non‑adaptive mapping from detected
    performance patterns to recommended data‑structure or algorithmic
    remedies. All identifiers returned by this engine use **snake_case**
    to satisfy OSS‑tier test and governance requirements.

    Metadata lookup is also performed using snake_case keys, matching the
    canonical identifiers stored in the MetadataRegistry.
    """

    def __init__(self, metadata: Dict[str, dict]):
        """Initialize the decision engine with metadata.

        Args:
            metadata (Dict[str, dict]):
                Mapping of snake_case algorithm identifiers to metadata
                dictionaries. Each metadata entry typically includes
                complexity, category, tier, and module import path.
        """
        self.metadata = metadata

        # Base rules for non‑nested‑loop patterns (snake_case outward)
        self.rule_map = {
            "inefficient_membership": "separate_chaining_hash_st",
            "redundant_sort": "merge_sort",
            "inplace_concat": "list_concat",
            "expensive_calls": "memoization",
            "high_freq_io": "buffered_io",
        }

        # Intent‑aware rules for nested loops (snake_case outward)
        self.nested_loop_rules = {
            "membership": "separate_chaining_hash_st",
            "sorting": "merge_sort",
            "dfs": "graph_traversal",
            "generic": "pruning",
        }

    def decide_algorithm(
        self, pattern: str, intent: Optional[str] = None
    ) -> Optional[str]:
        """Return the recommended algorithm identifier (snake_case).

        Args:
            pattern (str):
                Detected performance pattern identifier.
            intent (Optional[str]):
                Developer intent extracted from AST heuristics. Relevant only
                for nested‑loop patterns. Examples include:
                `"membership"`, `"sorting"`, `"dfs"`, `"generic"`.

        Returns:
            Optional[str]:
                Snake_case algorithm identifier, or None if no mapping exists.
        """
        if pattern == "nested_loops":
            if intent:
                return self.nested_loop_rules.get(intent, "pruning")
            return "pruning"

        return self.rule_map.get(pattern)

    def decide_metadata(self, algorithm: str) -> Optional[dict]:
        """Retrieve metadata for a recommended algorithm.

        Args:
            algorithm (str):
                Snake_case algorithm identifier returned by `decide_algorithm`.
                If a caller passes a non‑canonical identifier, it is normalized
                to snake_case before lookup.

        Returns:
            Optional[dict]:
                Metadata dictionary for the algorithm, or None if not found.
        """
        algo_key = algorithm.lower()

        # Normalize PascalCase β†’ snake_case if needed
        if algo_key not in self.metadata:
            # Example: "MergeSort" β†’ "merge_sort"
            normalized = []
            for c in algorithm:
                if c.isupper() and normalized:
                    normalized.append("_")
                normalized.append(c.lower())
            algo_key = "".join(normalized)

        return self.metadata.get(algo_key)

    def decide_fix(self, pattern: str, intent: Optional[str] = None) -> Optional[str]:
        """Return a human‑readable fix recommendation.

        Args:
            pattern (str):
                Detected performance pattern.
            intent (Optional[str]):
                Developer intent for nested loops.

        Returns:
            Optional[str]:
                Short prescriptive recommendation string, or None.
        """
        algo = self.decide_algorithm(pattern, intent)
        if algo:
            return f"Use {algo} to reduce complexity."
        return None

    def decide(self, pattern: str, intent: Optional[str] = None) -> Optional[str]:
        """Primary OSS entrypoint for algorithm selection.

        Args:
            pattern (str):
                Detected performance pattern.
            intent (Optional[str]):
                Developer intent for nested loops.

        Returns:
            Optional[str]:
                Snake_case recommended algorithm identifier.
        """
        return self.decide_algorithm(pattern, intent)

__init__(metadata)

Initialize the decision engine with metadata.

Parameters:

Name Type Description Default
metadata Dict[str, dict]

Mapping of snake_case algorithm identifiers to metadata dictionaries. Each metadata entry typically includes complexity, category, tier, and module import path.

required
Source code in src/alnoms/core/decision_engine.py
def __init__(self, metadata: Dict[str, dict]):
    """Initialize the decision engine with metadata.

    Args:
        metadata (Dict[str, dict]):
            Mapping of snake_case algorithm identifiers to metadata
            dictionaries. Each metadata entry typically includes
            complexity, category, tier, and module import path.
    """
    self.metadata = metadata

    # Base rules for non‑nested‑loop patterns (snake_case outward)
    self.rule_map = {
        "inefficient_membership": "separate_chaining_hash_st",
        "redundant_sort": "merge_sort",
        "inplace_concat": "list_concat",
        "expensive_calls": "memoization",
        "high_freq_io": "buffered_io",
    }

    # Intent‑aware rules for nested loops (snake_case outward)
    self.nested_loop_rules = {
        "membership": "separate_chaining_hash_st",
        "sorting": "merge_sort",
        "dfs": "graph_traversal",
        "generic": "pruning",
    }

decide(pattern, intent=None)

Primary OSS entrypoint for algorithm selection.

Parameters:

Name Type Description Default
pattern str

Detected performance pattern.

required
intent Optional[str]

Developer intent for nested loops.

None

Returns:

Type Description
Optional[str]

Optional[str]: Snake_case recommended algorithm identifier.

Source code in src/alnoms/core/decision_engine.py
def decide(self, pattern: str, intent: Optional[str] = None) -> Optional[str]:
    """Primary OSS entrypoint for algorithm selection.

    Args:
        pattern (str):
            Detected performance pattern.
        intent (Optional[str]):
            Developer intent for nested loops.

    Returns:
        Optional[str]:
            Snake_case recommended algorithm identifier.
    """
    return self.decide_algorithm(pattern, intent)

decide_algorithm(pattern, intent=None)

Return the recommended algorithm identifier (snake_case).

Parameters:

Name Type Description Default
pattern str

Detected performance pattern identifier.

required
intent Optional[str]

Developer intent extracted from AST heuristics. Relevant only for nested‑loop patterns. Examples include: "membership", "sorting", "dfs", "generic".

None

Returns:

Type Description
Optional[str]

Optional[str]: Snake_case algorithm identifier, or None if no mapping exists.

Source code in src/alnoms/core/decision_engine.py
def decide_algorithm(
    self, pattern: str, intent: Optional[str] = None
) -> Optional[str]:
    """Return the recommended algorithm identifier (snake_case).

    Args:
        pattern (str):
            Detected performance pattern identifier.
        intent (Optional[str]):
            Developer intent extracted from AST heuristics. Relevant only
            for nested‑loop patterns. Examples include:
            `"membership"`, `"sorting"`, `"dfs"`, `"generic"`.

    Returns:
        Optional[str]:
            Snake_case algorithm identifier, or None if no mapping exists.
    """
    if pattern == "nested_loops":
        if intent:
            return self.nested_loop_rules.get(intent, "pruning")
        return "pruning"

    return self.rule_map.get(pattern)

decide_fix(pattern, intent=None)

Return a human‑readable fix recommendation.

Parameters:

Name Type Description Default
pattern str

Detected performance pattern.

required
intent Optional[str]

Developer intent for nested loops.

None

Returns:

Type Description
Optional[str]

Optional[str]: Short prescriptive recommendation string, or None.

Source code in src/alnoms/core/decision_engine.py
def decide_fix(self, pattern: str, intent: Optional[str] = None) -> Optional[str]:
    """Return a human‑readable fix recommendation.

    Args:
        pattern (str):
            Detected performance pattern.
        intent (Optional[str]):
            Developer intent for nested loops.

    Returns:
        Optional[str]:
            Short prescriptive recommendation string, or None.
    """
    algo = self.decide_algorithm(pattern, intent)
    if algo:
        return f"Use {algo} to reduce complexity."
    return None

decide_metadata(algorithm)

Retrieve metadata for a recommended algorithm.

Parameters:

Name Type Description Default
algorithm str

Snake_case algorithm identifier returned by decide_algorithm. If a caller passes a non‑canonical identifier, it is normalized to snake_case before lookup.

required

Returns:

Type Description
Optional[dict]

Optional[dict]: Metadata dictionary for the algorithm, or None if not found.

Source code in src/alnoms/core/decision_engine.py
def decide_metadata(self, algorithm: str) -> Optional[dict]:
    """Retrieve metadata for a recommended algorithm.

    Args:
        algorithm (str):
            Snake_case algorithm identifier returned by `decide_algorithm`.
            If a caller passes a non‑canonical identifier, it is normalized
            to snake_case before lookup.

    Returns:
        Optional[dict]:
            Metadata dictionary for the algorithm, or None if not found.
    """
    algo_key = algorithm.lower()

    # Normalize PascalCase β†’ snake_case if needed
    if algo_key not in self.metadata:
        # Example: "MergeSort" β†’ "merge_sort"
        normalized = []
        for c in algorithm:
            if c.isupper() and normalized:
                normalized.append("_")
            normalized.append(c.lower())
        algo_key = "".join(normalized)

    return self.metadata.get(algo_key)

🎲 Data Generators & I/O

Collection of deterministic and high‑performance dataset generators.

These generators are used throughout the Alnoms ecosystem for:

  • Algorithm benchmarking
  • Worst‑case and best‑case scenario construction
  • Empirical scaling tests (doubling tests)
  • Teaching and demonstration notebooks
  • Reproducible research workflows

All methods are static and side‑effect‑free.

Source code in src/alnoms/core/generators.py
class DataGenerator:
    """Collection of deterministic and high‑performance dataset generators.

    These generators are used throughout the Alnoms ecosystem for:

    - Algorithm benchmarking
    - Worst‑case and best‑case scenario construction
    - Empirical scaling tests (doubling tests)
    - Teaching and demonstration notebooks
    - Reproducible research workflows

    All methods are static and side‑effect‑free.
    """

    @staticmethod
    def random_array(n: int, lo: int = 0, hi: int = 1000) -> List[int]:
        """Generate an array of random integers.

        This is the default dependency‑free generator used across the OSS tier.
        It relies solely on Python's built‑in `random` module and is suitable
        for lightweight benchmarking or environments where NumPy is unavailable.

        Args:
            n (int): Number of integers to generate.
            lo (int): Lower bound of the random range (inclusive).
            hi (int): Upper bound of the random range (inclusive).

        Returns:
            List[int]: A list of `n` random integers.
        """
        return [random.randint(lo, hi) for _ in range(n)]

    @staticmethod
    def sorted_array(n: int, reverse: bool = False) -> List[int]:
        """Generate a sorted array of integers from 0 to n‑1.

        Useful for constructing best‑case or worst‑case inputs for sorting
        algorithms and search routines.

        Args:
            n (int): Number of elements to generate.
            reverse (bool): If True, return the array in descending order.

        Returns:
            List[int]: A sorted list of integers.
        """
        arr = list(range(n))
        if reverse:
            arr.reverse()
        return arr

    @staticmethod
    def reverse_sorted_array(n: int) -> List[int]:
        """Generate a descending array from n‑1 to 0.

        This is a convenience wrapper around `sorted_array(reverse=True)` and
        is frequently used to construct worst‑case inputs for algorithms such
        as insertion sort or bubble sort.

        Args:
            n (int): Number of elements to generate.

        Returns:
            List[int]: A descending list of integers.
        """
        return DataGenerator.sorted_array(n, reverse=True)

    @staticmethod
    def large_scale_dataset(n: int) -> List[int]:
        """Generate a large dataset optimized for high‑volume research.

        Attempts to use NumPy for high‑throughput integer generation. If NumPy
        is unavailable, falls back to the pure‑Python `random_array` generator.

        Args:
            n (int): Number of integers to generate.

        Returns:
            List[int]: A list of random integers suitable for large‑scale tests.
        """
        try:
            import numpy as np

            return np.random.randint(0, 1000, n).tolist()  # pragma: no cover
        except ImportError:
            return DataGenerator.random_array(n)

    @staticmethod
    def square_matrices(n: int) -> tuple:
        """Generate a pair of NΓ—N matrices filled with constant values.

        Designed for benchmarking matrix multiplication algorithms where the
        computational complexityβ€”not the numerical valuesβ€”is the primary focus.

        Complexity:
            - Time: O(NΒ²) to initialize both matrices.
            - Space: O(NΒ²) for storage.

        Args:
            n (int): Dimension of each square matrix.

        Returns:
            tuple: A tuple `(matrix_a, matrix_b)` where:
                - `matrix_a` is filled with 1s
                - `matrix_b` is filled with 2s
        """
        matrix_a = [[1 for _ in range(n)] for _ in range(n)]
        matrix_b = [[2 for _ in range(n)] for _ in range(n)]
        return (matrix_a, matrix_b)

    @staticmethod
    def random_string(n: int, alphabet="abcdefghijklmnopqrstuvwxyz") -> str:
        return "".join(random.choice(alphabet) for _ in range(n))

large_scale_dataset(n) staticmethod

Generate a large dataset optimized for high‑volume research.

Attempts to use NumPy for high‑throughput integer generation. If NumPy is unavailable, falls back to the pure‑Python random_array generator.

Parameters:

Name Type Description Default
n int

Number of integers to generate.

required

Returns:

Type Description
List[int]

List[int]: A list of random integers suitable for large‑scale tests.

Source code in src/alnoms/core/generators.py
@staticmethod
def large_scale_dataset(n: int) -> List[int]:
    """Generate a large dataset optimized for high‑volume research.

    Attempts to use NumPy for high‑throughput integer generation. If NumPy
    is unavailable, falls back to the pure‑Python `random_array` generator.

    Args:
        n (int): Number of integers to generate.

    Returns:
        List[int]: A list of random integers suitable for large‑scale tests.
    """
    try:
        import numpy as np

        return np.random.randint(0, 1000, n).tolist()  # pragma: no cover
    except ImportError:
        return DataGenerator.random_array(n)

random_array(n, lo=0, hi=1000) staticmethod

Generate an array of random integers.

This is the default dependency‑free generator used across the OSS tier. It relies solely on Python's built‑in random module and is suitable for lightweight benchmarking or environments where NumPy is unavailable.

Parameters:

Name Type Description Default
n int

Number of integers to generate.

required
lo int

Lower bound of the random range (inclusive).

0
hi int

Upper bound of the random range (inclusive).

1000

Returns:

Type Description
List[int]

List[int]: A list of n random integers.

Source code in src/alnoms/core/generators.py
@staticmethod
def random_array(n: int, lo: int = 0, hi: int = 1000) -> List[int]:
    """Generate an array of random integers.

    This is the default dependency‑free generator used across the OSS tier.
    It relies solely on Python's built‑in `random` module and is suitable
    for lightweight benchmarking or environments where NumPy is unavailable.

    Args:
        n (int): Number of integers to generate.
        lo (int): Lower bound of the random range (inclusive).
        hi (int): Upper bound of the random range (inclusive).

    Returns:
        List[int]: A list of `n` random integers.
    """
    return [random.randint(lo, hi) for _ in range(n)]

reverse_sorted_array(n) staticmethod

Generate a descending array from n‑1 to 0.

This is a convenience wrapper around sorted_array(reverse=True) and is frequently used to construct worst‑case inputs for algorithms such as insertion sort or bubble sort.

Parameters:

Name Type Description Default
n int

Number of elements to generate.

required

Returns:

Type Description
List[int]

List[int]: A descending list of integers.

Source code in src/alnoms/core/generators.py
@staticmethod
def reverse_sorted_array(n: int) -> List[int]:
    """Generate a descending array from n‑1 to 0.

    This is a convenience wrapper around `sorted_array(reverse=True)` and
    is frequently used to construct worst‑case inputs for algorithms such
    as insertion sort or bubble sort.

    Args:
        n (int): Number of elements to generate.

    Returns:
        List[int]: A descending list of integers.
    """
    return DataGenerator.sorted_array(n, reverse=True)

sorted_array(n, reverse=False) staticmethod

Generate a sorted array of integers from 0 to n‑1.

Useful for constructing best‑case or worst‑case inputs for sorting algorithms and search routines.

Parameters:

Name Type Description Default
n int

Number of elements to generate.

required
reverse bool

If True, return the array in descending order.

False

Returns:

Type Description
List[int]

List[int]: A sorted list of integers.

Source code in src/alnoms/core/generators.py
@staticmethod
def sorted_array(n: int, reverse: bool = False) -> List[int]:
    """Generate a sorted array of integers from 0 to n‑1.

    Useful for constructing best‑case or worst‑case inputs for sorting
    algorithms and search routines.

    Args:
        n (int): Number of elements to generate.
        reverse (bool): If True, return the array in descending order.

    Returns:
        List[int]: A sorted list of integers.
    """
    arr = list(range(n))
    if reverse:
        arr.reverse()
    return arr

square_matrices(n) staticmethod

Generate a pair of NΓ—N matrices filled with constant values.

Designed for benchmarking matrix multiplication algorithms where the computational complexityβ€”not the numerical valuesβ€”is the primary focus.

Complexity
  • Time: O(NΒ²) to initialize both matrices.
  • Space: O(NΒ²) for storage.

Parameters:

Name Type Description Default
n int

Dimension of each square matrix.

required

Returns:

Name Type Description
tuple tuple

A tuple (matrix_a, matrix_b) where: - matrix_a is filled with 1s - matrix_b is filled with 2s

Source code in src/alnoms/core/generators.py
@staticmethod
def square_matrices(n: int) -> tuple:
    """Generate a pair of NΓ—N matrices filled with constant values.

    Designed for benchmarking matrix multiplication algorithms where the
    computational complexityβ€”not the numerical valuesβ€”is the primary focus.

    Complexity:
        - Time: O(NΒ²) to initialize both matrices.
        - Space: O(NΒ²) for storage.

    Args:
        n (int): Dimension of each square matrix.

    Returns:
        tuple: A tuple `(matrix_a, matrix_b)` where:
            - `matrix_a` is filled with 1s
            - `matrix_b` is filled with 2s
    """
    matrix_a = [[1 for _ in range(n)] for _ in range(n)]
    matrix_b = [[2 for _ in range(n)] for _ in range(n)]
    return (matrix_a, matrix_b)

Utility functions for loading test datasets from files.

All methods are static and designed for predictable, dependency‑free behavior. They support common formats used in algorithm benchmarking, including whitespace‑separated integers, tokens, and raw lines.

Source code in src/alnoms/core/io.py
class DataReader:
    """Utility functions for loading test datasets from files.

    All methods are static and designed for predictable, dependency‑free
    behavior. They support common formats used in algorithm benchmarking,
    including whitespace‑separated integers, tokens, and raw lines.
    """

    @staticmethod
    def read_all_ints(path: str) -> List[int]:
        """Read all whitespace‑separated integers from a file.

        The file may contain integers separated by spaces, tabs, or newlines.
        This format is commonly used for sorting and searching benchmarks.

        Args:
            path (str): Absolute or relative path to the input file.

        Returns:
            List[int]: A list of parsed integers.

        Raises:
            FileNotFoundError: If the file does not exist.
            ValueError: If any token cannot be parsed as an integer.
        """
        DataReader._validate_path(path)
        with open(path, "r", encoding="utf-8") as f:
            content = f.read()
            tokens = content.split()
            return [int(token) for token in tokens]

    @staticmethod
    def read_all_strings(path: str) -> List[str]:
        """Read all whitespace‑separated tokens from a file.

        Useful for loading datasets for Trie benchmarks, MSD/LSD string sorts,
        and token‑based algorithm tests.

        Args:
            path (str): Absolute or relative path to the input file.

        Returns:
            List[str]: A list of string tokens.

        Raises:
            FileNotFoundError: If the file does not exist.
        """
        DataReader._validate_path(path)
        with open(path, "r", encoding="utf-8") as f:
            return f.read().split()

    @staticmethod
    def read_lines(path: str) -> List[str]:
        """Read all lines from a file, stripping leading and trailing whitespace.

        Empty lines are preserved as empty strings. This is useful for
        line‑oriented algorithms, text processing, and structured input formats.

        Args:
            path (str): Absolute or relative path to the input file.

        Returns:
            List[str]: A list of cleaned lines.

        Raises:
            FileNotFoundError: If the file does not exist.
        """
        DataReader._validate_path(path)
        lines = []
        with open(path, "r", encoding="utf-8") as f:
            for line in f:
                lines.append(line.strip())
        return lines

    @staticmethod
    def _validate_path(path: str) -> None:
        """Validate that a file exists before attempting to read it.

        Args:
            path (str): Path to validate.

        Raises:
            FileNotFoundError: If the file does not exist.
        """
        if not os.path.exists(path):
            raise FileNotFoundError(f"File not found: {path}")

read_all_ints(path) staticmethod

Read all whitespace‑separated integers from a file.

The file may contain integers separated by spaces, tabs, or newlines. This format is commonly used for sorting and searching benchmarks.

Parameters:

Name Type Description Default
path str

Absolute or relative path to the input file.

required

Returns:

Type Description
List[int]

List[int]: A list of parsed integers.

Raises:

Type Description
FileNotFoundError

If the file does not exist.

ValueError

If any token cannot be parsed as an integer.

Source code in src/alnoms/core/io.py
@staticmethod
def read_all_ints(path: str) -> List[int]:
    """Read all whitespace‑separated integers from a file.

    The file may contain integers separated by spaces, tabs, or newlines.
    This format is commonly used for sorting and searching benchmarks.

    Args:
        path (str): Absolute or relative path to the input file.

    Returns:
        List[int]: A list of parsed integers.

    Raises:
        FileNotFoundError: If the file does not exist.
        ValueError: If any token cannot be parsed as an integer.
    """
    DataReader._validate_path(path)
    with open(path, "r", encoding="utf-8") as f:
        content = f.read()
        tokens = content.split()
        return [int(token) for token in tokens]

read_all_strings(path) staticmethod

Read all whitespace‑separated tokens from a file.

Useful for loading datasets for Trie benchmarks, MSD/LSD string sorts, and token‑based algorithm tests.

Parameters:

Name Type Description Default
path str

Absolute or relative path to the input file.

required

Returns:

Type Description
List[str]

List[str]: A list of string tokens.

Raises:

Type Description
FileNotFoundError

If the file does not exist.

Source code in src/alnoms/core/io.py
@staticmethod
def read_all_strings(path: str) -> List[str]:
    """Read all whitespace‑separated tokens from a file.

    Useful for loading datasets for Trie benchmarks, MSD/LSD string sorts,
    and token‑based algorithm tests.

    Args:
        path (str): Absolute or relative path to the input file.

    Returns:
        List[str]: A list of string tokens.

    Raises:
        FileNotFoundError: If the file does not exist.
    """
    DataReader._validate_path(path)
    with open(path, "r", encoding="utf-8") as f:
        return f.read().split()

read_lines(path) staticmethod

Read all lines from a file, stripping leading and trailing whitespace.

Empty lines are preserved as empty strings. This is useful for line‑oriented algorithms, text processing, and structured input formats.

Parameters:

Name Type Description Default
path str

Absolute or relative path to the input file.

required

Returns:

Type Description
List[str]

List[str]: A list of cleaned lines.

Raises:

Type Description
FileNotFoundError

If the file does not exist.

Source code in src/alnoms/core/io.py
@staticmethod
def read_lines(path: str) -> List[str]:
    """Read all lines from a file, stripping leading and trailing whitespace.

    Empty lines are preserved as empty strings. This is useful for
    line‑oriented algorithms, text processing, and structured input formats.

    Args:
        path (str): Absolute or relative path to the input file.

    Returns:
        List[str]: A list of cleaned lines.

    Raises:
        FileNotFoundError: If the file does not exist.
    """
    DataReader._validate_path(path)
    lines = []
    with open(path, "r", encoding="utf-8") as f:
        for line in f:
            lines.append(line.strip())
    return lines

🧬 Algorithms (alnoms.dsa.algorithms)

The algorithm pillar provides precision implementations for complex computational problems.

πŸ•ΈοΈ Graph Theory

Breadth‑First Search Paths.

Provides a breadth‑first search (BFS)–based shortest‑path finder for undirected graphs. BFS computes the shortest path in terms of number of edges from a designated source vertex to all reachable vertices.

Design Characteristics: - Computes unweighted shortest paths - Produces predecessor links and distance levels - Time Complexity: O(V + E) - Space Complexity: O(V)

BreadthFirstPaths

Computes BFS‑based shortest paths from a single source vertex.

This class performs a breadth‑first search from a specified source vertex and records both predecessor links and distance levels. BFS guarantees shortest paths in unweighted graphs.

Attributes:

Name Type Description
_s int

The source vertex.

_marked List[bool]

Marks whether each vertex has been visited.

_edge_to List[int]

Predecessor links for path reconstruction.

_dist_to List[float]

Number of edges in the shortest path.

Source code in src/alnoms/dsa/algorithms/graph/breadth_first_paths.py
class BreadthFirstPaths:
    """Computes BFS‑based shortest paths from a single source vertex.

    This class performs a breadth‑first search from a specified source
    vertex and records both predecessor links and distance levels. BFS
    guarantees shortest paths in unweighted graphs.

    Attributes:
        _s (int): The source vertex.
        _marked (List[bool]): Marks whether each vertex has been visited.
        _edge_to (List[int]): Predecessor links for path reconstruction.
        _dist_to (List[float]): Number of edges in the shortest path.
    """

    def __init__(self, G: Graph, s: int):
        """Initializes BFS search from a source vertex.

        Args:
            G (Graph): The graph to search.
            s (int): The source vertex.

        Raises:
            IndexError: If the source vertex is out of bounds.
        """
        self._s = s
        self._marked = [False] * G.V()
        self._edge_to = [0] * G.V()
        self._dist_to = [float("inf")] * G.V()

        self._validate_vertex(s, G.V())
        self._bfs(G, s)

    def _bfs(self, G: Graph, s: int) -> None:
        """Performs BFS from the source vertex.

        Args:
            G (Graph): The graph being searched.
            s (int): The source vertex.
        """
        q: Deque[int] = deque()
        self._marked[s] = True
        self._dist_to[s] = 0
        q.append(s)

        while q:
            v = q.popleft()
            for w in G.adj(v):
                if not self._marked[w]:
                    self._marked[w] = True
                    self._edge_to[w] = v
                    self._dist_to[w] = self._dist_to[v] + 1
                    q.append(w)

    def has_path_to(self, v: int) -> bool:
        """Checks whether a path exists from the source to a vertex.

        Args:
            v (int): The vertex to check.

        Returns:
            bool: True if reachable, otherwise False.

        Raises:
            IndexError: If the vertex is out of bounds.
        """
        self._validate_vertex(v, len(self._marked))
        return self._marked[v]

    def dist_to(self, v: int) -> float:
        """Returns the BFS shortest‑path distance to a vertex.

        Args:
            v (int): The vertex whose distance is requested.

        Returns:
            float: Number of edges in the shortest path.

        Raises:
            IndexError: If the vertex is out of bounds.
        """
        self._validate_vertex(v, len(self._marked))
        return self._dist_to[v]

    def path_to(self, v: int) -> Optional[Iterable[int]]:
        """Returns the shortest path from the source to a vertex.

        Args:
            v (int): The destination vertex.

        Returns:
            Optional[Iterable[int]]: A sequence of vertices forming the
            shortest path, or None if no path exists.

        Raises:
            IndexError: If the vertex is out of bounds.
        """
        self._validate_vertex(v, len(self._marked))
        if not self.has_path_to(v):
            return None

        path: Deque[int] = deque()
        x = v
        while x != self._s:
            path.appendleft(x)
            x = self._edge_to[x]
        path.appendleft(self._s)
        return path

    def _validate_vertex(self, v: int, V: int) -> None:
        """Validates that a vertex index is within bounds.

        Args:
            v (int): The vertex to validate.
            V (int): Total number of vertices.

        Raises:
            IndexError: If the vertex is out of bounds.
        """
        if v < 0 or v >= V:
            raise IndexError(f"Vertex {v} is out of bounds")
__init__(G, s)

Initializes BFS search from a source vertex.

Parameters:

Name Type Description Default
G Graph

The graph to search.

required
s int

The source vertex.

required

Raises:

Type Description
IndexError

If the source vertex is out of bounds.

Source code in src/alnoms/dsa/algorithms/graph/breadth_first_paths.py
def __init__(self, G: Graph, s: int):
    """Initializes BFS search from a source vertex.

    Args:
        G (Graph): The graph to search.
        s (int): The source vertex.

    Raises:
        IndexError: If the source vertex is out of bounds.
    """
    self._s = s
    self._marked = [False] * G.V()
    self._edge_to = [0] * G.V()
    self._dist_to = [float("inf")] * G.V()

    self._validate_vertex(s, G.V())
    self._bfs(G, s)
dist_to(v)

Returns the BFS shortest‑path distance to a vertex.

Parameters:

Name Type Description Default
v int

The vertex whose distance is requested.

required

Returns:

Name Type Description
float float

Number of edges in the shortest path.

Raises:

Type Description
IndexError

If the vertex is out of bounds.

Source code in src/alnoms/dsa/algorithms/graph/breadth_first_paths.py
def dist_to(self, v: int) -> float:
    """Returns the BFS shortest‑path distance to a vertex.

    Args:
        v (int): The vertex whose distance is requested.

    Returns:
        float: Number of edges in the shortest path.

    Raises:
        IndexError: If the vertex is out of bounds.
    """
    self._validate_vertex(v, len(self._marked))
    return self._dist_to[v]
has_path_to(v)

Checks whether a path exists from the source to a vertex.

Parameters:

Name Type Description Default
v int

The vertex to check.

required

Returns:

Name Type Description
bool bool

True if reachable, otherwise False.

Raises:

Type Description
IndexError

If the vertex is out of bounds.

Source code in src/alnoms/dsa/algorithms/graph/breadth_first_paths.py
def has_path_to(self, v: int) -> bool:
    """Checks whether a path exists from the source to a vertex.

    Args:
        v (int): The vertex to check.

    Returns:
        bool: True if reachable, otherwise False.

    Raises:
        IndexError: If the vertex is out of bounds.
    """
    self._validate_vertex(v, len(self._marked))
    return self._marked[v]
path_to(v)

Returns the shortest path from the source to a vertex.

Parameters:

Name Type Description Default
v int

The destination vertex.

required

Returns:

Type Description
Optional[Iterable[int]]

Optional[Iterable[int]]: A sequence of vertices forming the

Optional[Iterable[int]]

shortest path, or None if no path exists.

Raises:

Type Description
IndexError

If the vertex is out of bounds.

Source code in src/alnoms/dsa/algorithms/graph/breadth_first_paths.py
def path_to(self, v: int) -> Optional[Iterable[int]]:
    """Returns the shortest path from the source to a vertex.

    Args:
        v (int): The destination vertex.

    Returns:
        Optional[Iterable[int]]: A sequence of vertices forming the
        shortest path, or None if no path exists.

    Raises:
        IndexError: If the vertex is out of bounds.
    """
    self._validate_vertex(v, len(self._marked))
    if not self.has_path_to(v):
        return None

    path: Deque[int] = deque()
    x = v
    while x != self._s:
        path.appendleft(x)
        x = self._edge_to[x]
    path.appendleft(self._s)
    return path

Depth‑First Search Orderings.

Computes preorder, postorder, and reverse‑postorder vertex orderings for a directed graph using depth‑first search (DFS). These orderings are fundamental in algorithms such as topological sorting and strongly connected components.

Design Characteristics: - DFS‑based vertex ordering - Reverse postorder supports topological sorting in DAGs - Time Complexity: O(V + E) - Space Complexity: O(V)

DepthFirstOrder

Computes DFS‑based vertex orderings for a directed graph.

This class performs a full DFS over all vertices of a directed graph and records the reverse postorder sequence. Reverse postorder is particularly useful for topological sorting in directed acyclic graphs (DAGs).

Attributes:

Name Type Description
_marked List[bool]

Marks whether each vertex has been visited.

_reverse_post Deque[int]

Vertices in reverse postorder.

Source code in src/alnoms/dsa/algorithms/graph/depth_first_order.py
class DepthFirstOrder:
    """Computes DFS‑based vertex orderings for a directed graph.

    This class performs a full DFS over all vertices of a directed graph
    and records the reverse postorder sequence. Reverse postorder is
    particularly useful for topological sorting in directed acyclic
    graphs (DAGs).

    Attributes:
        _marked (List[bool]): Marks whether each vertex has been visited.
        _reverse_post (Deque[int]): Vertices in reverse postorder.
    """

    def __init__(self, G: Digraph):
        """Initializes DFS order computation.

        Args:
            G (Digraph): The directed graph whose orderings are computed.
        """
        self._marked = [False] * G.V()
        self._reverse_post: Deque[int] = deque()

        for v in range(G.V()):
            if not self._marked[v]:
                self._dfs(G, v)

    def _dfs(self, G: Digraph, v: int) -> None:
        """Performs DFS from a vertex and records postorder.

        Args:
            G (Digraph): The graph being searched.
            v (int): The current vertex.
        """
        self._marked[v] = True
        for w in G.adj(v):
            if not self._marked[w]:
                self._dfs(G, w)

        # Postorder: add after exploring all descendants
        self._reverse_post.appendleft(v)

    def reverse_post(self) -> Iterable[int]:
        """Returns vertices in reverse postorder.

        Returns:
            Iterable[int]: A sequence of vertices in reverse postorder.
        """
        return self._reverse_post
__init__(G)

Initializes DFS order computation.

Parameters:

Name Type Description Default
G Digraph

The directed graph whose orderings are computed.

required
Source code in src/alnoms/dsa/algorithms/graph/depth_first_order.py
def __init__(self, G: Digraph):
    """Initializes DFS order computation.

    Args:
        G (Digraph): The directed graph whose orderings are computed.
    """
    self._marked = [False] * G.V()
    self._reverse_post: Deque[int] = deque()

    for v in range(G.V()):
        if not self._marked[v]:
            self._dfs(G, v)
reverse_post()

Returns vertices in reverse postorder.

Returns:

Type Description
Iterable[int]

Iterable[int]: A sequence of vertices in reverse postorder.

Source code in src/alnoms/dsa/algorithms/graph/depth_first_order.py
def reverse_post(self) -> Iterable[int]:
    """Returns vertices in reverse postorder.

    Returns:
        Iterable[int]: A sequence of vertices in reverse postorder.
    """
    return self._reverse_post

Depth‑First Search Paths.

Provides a depth‑first search (DFS)–based path finder for undirected graphs. Starting from a designated source vertex, the algorithm explores all reachable vertices and records predecessor links that allow path reconstruction.

Design Characteristics: - Computes reachability from a single source - Produces DFS tree predecessor links - Paths are not guaranteed to be shortest - Time Complexity: O(V + E) - Space Complexity: O(V)

DepthFirstPaths

Computes DFS‑based paths from a single source vertex.

This class performs a depth‑first search from a specified source vertex and records the predecessor of each visited vertex. These predecessor links allow reconstruction of any path from the source to a reachable vertex.

Attributes:

Name Type Description
_s int

The source vertex.

_marked List[bool]

Marks whether each vertex has been visited.

_edge_to List[int]

Predecessor links for path reconstruction.

Source code in src/alnoms/dsa/algorithms/graph/depth_first_paths.py
class DepthFirstPaths:
    """Computes DFS‑based paths from a single source vertex.

    This class performs a depth‑first search from a specified source
    vertex and records the predecessor of each visited vertex. These
    predecessor links allow reconstruction of any path from the source
    to a reachable vertex.

    Attributes:
        _s (int): The source vertex.
        _marked (List[bool]): Marks whether each vertex has been visited.
        _edge_to (List[int]): Predecessor links for path reconstruction.
    """

    def __init__(self, G: Graph, s: int):
        """Initializes DFS search from a source vertex.

        Args:
            G (Graph): The graph to search.
            s (int): The source vertex.

        Raises:
            IndexError: If the source vertex is out of bounds.
        """
        self._s = s
        self._marked = [False] * G.V()
        self._edge_to = [0] * G.V()

        self._validate_vertex(s, G.V())
        self._dfs(G, s)

    def _dfs(self, G: Graph, v: int) -> None:
        """Performs recursive DFS from a vertex.

        Args:
            G (Graph): The graph being searched.
            v (int): The current vertex.
        """
        self._marked[v] = True
        for w in G.adj(v):
            if not self._marked[w]:
                self._edge_to[w] = v
                self._dfs(G, w)

    def has_path_to(self, v: int) -> bool:
        """Checks whether a path exists from the source to a vertex.

        Args:
            v (int): The vertex to check.

        Returns:
            bool: True if reachable, otherwise False.

        Raises:
            IndexError: If the vertex is out of bounds.
        """
        self._validate_vertex(v, len(self._marked))
        return self._marked[v]

    def path_to(self, v: int) -> Optional[Iterable[int]]:
        """Returns a path from the source to a vertex.

        Args:
            v (int): The destination vertex.

        Returns:
            Optional[Iterable[int]]: A sequence of vertices forming a path
            from the source to `v`, or None if no such path exists.

        Raises:
            IndexError: If the vertex is out of bounds.
        """
        self._validate_vertex(v, len(self._marked))
        if not self.has_path_to(v):
            return None

        path: Deque[int] = deque()
        x = v
        while x != self._s:
            path.appendleft(x)
            x = self._edge_to[x]
        path.appendleft(self._s)
        return path

    def _validate_vertex(self, v: int, V: int) -> None:
        """Validates that a vertex index is within bounds.

        Args:
            v (int): The vertex to validate.
            V (int): Total number of vertices.

        Raises:
            IndexError: If the vertex is out of bounds.
        """
        if v < 0 or v >= V:
            raise IndexError(f"Vertex {v} is out of bounds")
__init__(G, s)

Initializes DFS search from a source vertex.

Parameters:

Name Type Description Default
G Graph

The graph to search.

required
s int

The source vertex.

required

Raises:

Type Description
IndexError

If the source vertex is out of bounds.

Source code in src/alnoms/dsa/algorithms/graph/depth_first_paths.py
def __init__(self, G: Graph, s: int):
    """Initializes DFS search from a source vertex.

    Args:
        G (Graph): The graph to search.
        s (int): The source vertex.

    Raises:
        IndexError: If the source vertex is out of bounds.
    """
    self._s = s
    self._marked = [False] * G.V()
    self._edge_to = [0] * G.V()

    self._validate_vertex(s, G.V())
    self._dfs(G, s)
has_path_to(v)

Checks whether a path exists from the source to a vertex.

Parameters:

Name Type Description Default
v int

The vertex to check.

required

Returns:

Name Type Description
bool bool

True if reachable, otherwise False.

Raises:

Type Description
IndexError

If the vertex is out of bounds.

Source code in src/alnoms/dsa/algorithms/graph/depth_first_paths.py
def has_path_to(self, v: int) -> bool:
    """Checks whether a path exists from the source to a vertex.

    Args:
        v (int): The vertex to check.

    Returns:
        bool: True if reachable, otherwise False.

    Raises:
        IndexError: If the vertex is out of bounds.
    """
    self._validate_vertex(v, len(self._marked))
    return self._marked[v]
path_to(v)

Returns a path from the source to a vertex.

Parameters:

Name Type Description Default
v int

The destination vertex.

required

Returns:

Type Description
Optional[Iterable[int]]

Optional[Iterable[int]]: A sequence of vertices forming a path

Optional[Iterable[int]]

from the source to v, or None if no such path exists.

Raises:

Type Description
IndexError

If the vertex is out of bounds.

Source code in src/alnoms/dsa/algorithms/graph/depth_first_paths.py
def path_to(self, v: int) -> Optional[Iterable[int]]:
    """Returns a path from the source to a vertex.

    Args:
        v (int): The destination vertex.

    Returns:
        Optional[Iterable[int]]: A sequence of vertices forming a path
        from the source to `v`, or None if no such path exists.

    Raises:
        IndexError: If the vertex is out of bounds.
    """
    self._validate_vertex(v, len(self._marked))
    if not self.has_path_to(v):
        return None

    path: Deque[int] = deque()
    x = v
    while x != self._s:
        path.appendleft(x)
        x = self._edge_to[x]
    path.appendleft(self._s)
    return path

Dijkstra's Shortest Paths.

Implements the single‑source shortest‑path algorithm for edge‑weighted directed graphs with non‑negative edge weights. The algorithm computes the shortest path tree rooted at a specified source vertex using a binary heap–based priority queue.

Design Characteristics: - Supports arbitrary directed graphs with non‑negative weights - Produces shortest‑path distances and predecessor edges - Uses lazy deletion in the priority queue - Time Complexity: O(E log V) - Space Complexity: O(V)

DijkstraSP

Computes shortest paths in a weighted directed graph.

This class computes the shortest path from a single source vertex to all other vertices in an edge‑weighted digraph with non‑negative edge weights. Distances and predecessor edges are stored for subsequent queries.

Attributes:

Name Type Description
_dist_to List[float]

Shortest known distance to each vertex.

_edge_to List[Optional[DirectedEdge]]

Last edge on the shortest known path to each vertex.

_pq List[tuple]

Min‑heap storing (distance, vertex) pairs.

Source code in src/alnoms/dsa/algorithms/graph/dijkstra_sp.py
class DijkstraSP:
    """Computes shortest paths in a weighted directed graph.

    This class computes the shortest path from a single source vertex to
    all other vertices in an edge‑weighted digraph with non‑negative edge
    weights. Distances and predecessor edges are stored for subsequent
    queries.

    Attributes:
        _dist_to (List[float]): Shortest known distance to each vertex.
        _edge_to (List[Optional[DirectedEdge]]): Last edge on the shortest
            known path to each vertex.
        _pq (List[tuple]): Min‑heap storing (distance, vertex) pairs.
    """

    def __init__(self, G: EdgeWeightedDigraph, s: int):
        """Initializes the shortest‑path computation from a source vertex.

        Args:
            G (EdgeWeightedDigraph): The input directed graph.
            s (int): The source vertex.

        Raises:
            ValueError: If any edge in the graph has a negative weight.
        """
        self._validate_edges(G)

        self._dist_to = [float("inf")] * G.V()
        self._edge_to: List[Optional[DirectedEdge]] = [None] * G.V()
        self._pq: List[tuple] = []

        self._dist_to[s] = 0.0
        heapq.heappush(self._pq, (0.0, s))

        while self._pq:
            dist, v = heapq.heappop(self._pq)

            # Skip stale entries
            if dist > self._dist_to[v]:
                continue

            for e in G.adj(v):
                self._relax(e)

    def _relax(self, e: DirectedEdge) -> None:
        """Relaxes an edge and updates state if a shorter path is found.

        Args:
            e (DirectedEdge): The directed edge to relax.
        """
        v, w = e.from_vertex(), e.to_vertex()
        new_dist = self._dist_to[v] + e.weight

        if self._dist_to[w] > new_dist:
            self._dist_to[w] = new_dist
            self._edge_to[w] = e
            heapq.heappush(self._pq, (new_dist, w))

    def _validate_edges(self, G: EdgeWeightedDigraph) -> None:
        """Ensures that the graph contains no negative‑weight edges.

        Args:
            G (EdgeWeightedDigraph): The graph to validate.

        Raises:
            ValueError: If any edge weight is negative.
        """
        for e in G.edges():
            if e.weight < 0:
                raise ValueError(f"Edge has negative weight: {e}")

    def has_path_to(self, v: int) -> bool:
        """Checks whether a path exists from the source to a vertex.

        Args:
            v (int): The vertex to check.

        Returns:
            bool: True if a path exists, otherwise False.
        """
        return self._dist_to[v] < float("inf")

    def dist_to(self, v: int) -> float:
        """Returns the shortest‑path distance to a vertex.

        Args:
            v (int): The vertex whose distance is requested.

        Returns:
            float: The shortest distance, or infinity if unreachable.
        """
        return self._dist_to[v]

    def path_to(self, v: int) -> Optional[Iterable[DirectedEdge]]:
        """Returns the shortest path from the source to a vertex.

        Args:
            v (int): The destination vertex.

        Returns:
            Optional[Iterable[DirectedEdge]]: A sequence of edges forming
            the shortest path, or None if no path exists.
        """
        if not self.has_path_to(v):
            return None

        path: Deque[DirectedEdge] = deque()
        e = self._edge_to[v]

        while e is not None:
            path.appendleft(e)
            e = self._edge_to[e.from_vertex()]

        return path
__init__(G, s)

Initializes the shortest‑path computation from a source vertex.

Parameters:

Name Type Description Default
G EdgeWeightedDigraph

The input directed graph.

required
s int

The source vertex.

required

Raises:

Type Description
ValueError

If any edge in the graph has a negative weight.

Source code in src/alnoms/dsa/algorithms/graph/dijkstra_sp.py
def __init__(self, G: EdgeWeightedDigraph, s: int):
    """Initializes the shortest‑path computation from a source vertex.

    Args:
        G (EdgeWeightedDigraph): The input directed graph.
        s (int): The source vertex.

    Raises:
        ValueError: If any edge in the graph has a negative weight.
    """
    self._validate_edges(G)

    self._dist_to = [float("inf")] * G.V()
    self._edge_to: List[Optional[DirectedEdge]] = [None] * G.V()
    self._pq: List[tuple] = []

    self._dist_to[s] = 0.0
    heapq.heappush(self._pq, (0.0, s))

    while self._pq:
        dist, v = heapq.heappop(self._pq)

        # Skip stale entries
        if dist > self._dist_to[v]:
            continue

        for e in G.adj(v):
            self._relax(e)
dist_to(v)

Returns the shortest‑path distance to a vertex.

Parameters:

Name Type Description Default
v int

The vertex whose distance is requested.

required

Returns:

Name Type Description
float float

The shortest distance, or infinity if unreachable.

Source code in src/alnoms/dsa/algorithms/graph/dijkstra_sp.py
def dist_to(self, v: int) -> float:
    """Returns the shortest‑path distance to a vertex.

    Args:
        v (int): The vertex whose distance is requested.

    Returns:
        float: The shortest distance, or infinity if unreachable.
    """
    return self._dist_to[v]
has_path_to(v)

Checks whether a path exists from the source to a vertex.

Parameters:

Name Type Description Default
v int

The vertex to check.

required

Returns:

Name Type Description
bool bool

True if a path exists, otherwise False.

Source code in src/alnoms/dsa/algorithms/graph/dijkstra_sp.py
def has_path_to(self, v: int) -> bool:
    """Checks whether a path exists from the source to a vertex.

    Args:
        v (int): The vertex to check.

    Returns:
        bool: True if a path exists, otherwise False.
    """
    return self._dist_to[v] < float("inf")
path_to(v)

Returns the shortest path from the source to a vertex.

Parameters:

Name Type Description Default
v int

The destination vertex.

required

Returns:

Type Description
Optional[Iterable[DirectedEdge]]

Optional[Iterable[DirectedEdge]]: A sequence of edges forming

Optional[Iterable[DirectedEdge]]

the shortest path, or None if no path exists.

Source code in src/alnoms/dsa/algorithms/graph/dijkstra_sp.py
def path_to(self, v: int) -> Optional[Iterable[DirectedEdge]]:
    """Returns the shortest path from the source to a vertex.

    Args:
        v (int): The destination vertex.

    Returns:
        Optional[Iterable[DirectedEdge]]: A sequence of edges forming
        the shortest path, or None if no path exists.
    """
    if not self.has_path_to(v):
        return None

    path: Deque[DirectedEdge] = deque()
    e = self._edge_to[v]

    while e is not None:
        path.appendleft(e)
        e = self._edge_to[e.from_vertex()]

    return path

Topological Sorting.

Provides a topological ordering of a directed acyclic graph (DAG) using depth‑first search finishing times. The implementation relies on the reverse postorder of a DFS traversal. If the graph contains a cycle, the resulting order is not a valid topological sort, but the algorithm still produces an ordering based on DFS structure.

Design Characteristics: - O(V + E) time - Uses DFS reverse‑postorder - Assumes the input graph is a DAG unless validated externally

Classes:

Name Description
Topological

Computes a topological order of a directed graph.

Topological

Computes a topological order of a directed graph.

The ordering is derived from the reverse postorder of a depth‑first search. This yields a valid topological order if and only if the graph is acyclic. Cycle detection is not performed here; callers should validate DAG properties externally if correctness is required.

Source code in src/alnoms/dsa/algorithms/graph/topological.py
class Topological:
    """Computes a topological order of a directed graph.

    The ordering is derived from the reverse postorder of a depth‑first
    search. This yields a valid topological order if and only if the
    graph is acyclic. Cycle detection is not performed here; callers
    should validate DAG properties externally if correctness is required.
    """

    def __init__(self, G: Digraph):
        """Initializes the topological sort computation.

        Args:
            G (Digraph): Directed graph on which to compute the ordering.
        """
        finder = DepthFirstOrder(G)
        self._order: Optional[Iterable[int]] = finder.reverse_post()

    def has_order(self) -> bool:
        """Indicates whether a topological order is available.

        Returns:
            bool: True if an order was computed. This does not guarantee
            that the graph is acyclic; it only reflects that an ordering
            exists based on DFS finishing times.
        """
        return self._order is not None

    def order(self) -> Iterable[int]:
        """Returns the computed topological order.

        Returns:
            Iterable[int]: Vertices in topological order.
        """
        return self._order
__init__(G)

Initializes the topological sort computation.

Parameters:

Name Type Description Default
G Digraph

Directed graph on which to compute the ordering.

required
Source code in src/alnoms/dsa/algorithms/graph/topological.py
def __init__(self, G: Digraph):
    """Initializes the topological sort computation.

    Args:
        G (Digraph): Directed graph on which to compute the ordering.
    """
    finder = DepthFirstOrder(G)
    self._order: Optional[Iterable[int]] = finder.reverse_post()
has_order()

Indicates whether a topological order is available.

Returns:

Name Type Description
bool bool

True if an order was computed. This does not guarantee

bool

that the graph is acyclic; it only reflects that an ordering

bool

exists based on DFS finishing times.

Source code in src/alnoms/dsa/algorithms/graph/topological.py
def has_order(self) -> bool:
    """Indicates whether a topological order is available.

    Returns:
        bool: True if an order was computed. This does not guarantee
        that the graph is acyclic; it only reflects that an ordering
        exists based on DFS finishing times.
    """
    return self._order is not None
order()

Returns the computed topological order.

Returns:

Type Description
Iterable[int]

Iterable[int]: Vertices in topological order.

Source code in src/alnoms/dsa/algorithms/graph/topological.py
def order(self) -> Iterable[int]:
    """Returns the computed topological order.

    Returns:
        Iterable[int]: Vertices in topological order.
    """
    return self._order

πŸ”’ Sorting & Searching

Bubble Sort.

Implements the classic bubble sort algorithm, which repeatedly scans the array and swaps adjacent elements that are out of order. This version includes the standard early‑termination optimization: if a full pass completes with no swaps, the array is already sorted. An optional visualization mode yields intermediate array states after each swap.

Design Characteristics: - In‑place sorting - Stable behavior - O(NΒ²) average and worst‑case time - O(N) best‑case time on already sorted input - O(1) auxiliary space - Visualization mode yields swap‑step snapshots

Classes:

Name Description
BubbleSort

Static implementation of bubble sort.

BubbleSort

Bubble sort implementation.

Repeatedly compares adjacent elements and swaps them if they are in the wrong order. The algorithm terminates early if a full pass completes without any swaps, making it efficient for nearly sorted input.

Source code in src/alnoms/dsa/algorithms/sorting/bubble_sort.py
class BubbleSort:
    """Bubble sort implementation.

    Repeatedly compares adjacent elements and swaps them if they are in
    the wrong order. The algorithm terminates early if a full pass
    completes without any swaps, making it efficient for nearly sorted
    input.
    """

    @staticmethod
    def bubble_sort(
        arr: List[Any], visualize: bool = False
    ) -> Union[List[Any], Generator[List[Any], None, None]]:
        """Sorts the input list using bubble sort.

        If ``visualize`` is True, the function returns a generator that
        yields the array state after each swap. Otherwise, it returns the
        fully sorted list.

        Args:
            arr (List[Any]): The list to sort.
            visualize (bool): Whether to yield intermediate states.

        Returns:
            Union[List[Any], Generator[List[Any], None, None]]:
                Sorted list or generator of intermediate states.

        Complexity:
            Time: O(NΒ²) average/worst, O(N) best case
            Space: O(1)
        """
        data = list(arr)
        n = len(data)

        def _algo():
            for i in range(n):
                swapped = False
                for j in range(0, n - i - 1):
                    if data[j] > data[j + 1]:
                        data[j], data[j + 1] = data[j + 1], data[j]
                        swapped = True
                        if visualize:
                            yield list(data)
                if not swapped:
                    break
            if not visualize:
                yield list(data)

        gen = _algo()
        return gen if visualize else list(gen)[-1]
bubble_sort(arr, visualize=False) staticmethod

Sorts the input list using bubble sort.

If visualize is True, the function returns a generator that yields the array state after each swap. Otherwise, it returns the fully sorted list.

Parameters:

Name Type Description Default
arr List[Any]

The list to sort.

required
visualize bool

Whether to yield intermediate states.

False

Returns:

Type Description
Union[List[Any], Generator[List[Any], None, None]]

Union[List[Any], Generator[List[Any], None, None]]: Sorted list or generator of intermediate states.

Complexity
Source code in src/alnoms/dsa/algorithms/sorting/bubble_sort.py
@staticmethod
def bubble_sort(
    arr: List[Any], visualize: bool = False
) -> Union[List[Any], Generator[List[Any], None, None]]:
    """Sorts the input list using bubble sort.

    If ``visualize`` is True, the function returns a generator that
    yields the array state after each swap. Otherwise, it returns the
    fully sorted list.

    Args:
        arr (List[Any]): The list to sort.
        visualize (bool): Whether to yield intermediate states.

    Returns:
        Union[List[Any], Generator[List[Any], None, None]]:
            Sorted list or generator of intermediate states.

    Complexity:
        Time: O(NΒ²) average/worst, O(N) best case
        Space: O(1)
    """
    data = list(arr)
    n = len(data)

    def _algo():
        for i in range(n):
            swapped = False
            for j in range(0, n - i - 1):
                if data[j] > data[j + 1]:
                    data[j], data[j + 1] = data[j + 1], data[j]
                    swapped = True
                    if visualize:
                        yield list(data)
            if not swapped:
                break
        if not visualize:
            yield list(data)

    gen = _algo()
    return gen if visualize else list(gen)[-1]

Insertion Sort.

Implements the classic insertion sort algorithm, which builds the sorted array incrementally by inserting each element into its correct position relative to the already‑sorted prefix. The algorithm performs extremely well on partially sorted arrays and small subarrays. An optional visualization mode yields intermediate array states after each swap.

Design Characteristics: - In‑place sorting - Stable behavior - O(N) best‑case time on nearly sorted input - O(NΒ²) worst‑case time - O(1) auxiliary space - Visualization mode yields swap‑step snapshots

Classes:

Name Description
InsertionSort

Static implementation of insertion sort.

InsertionSort

Insertion sort implementation.

Builds the sorted array one element at a time by shifting larger elements to the right and inserting the current element into its correct position. Particularly effective for small or partially sorted datasets.

Source code in src/alnoms/dsa/algorithms/sorting/insertion_sort.py
class InsertionSort:
    """Insertion sort implementation.

    Builds the sorted array one element at a time by shifting larger
    elements to the right and inserting the current element into its
    correct position. Particularly effective for small or partially
    sorted datasets.
    """

    @staticmethod
    def insertion_sort(
        arr: List[Any], visualize: bool = False
    ) -> Union[List[Any], Generator[List[Any], None, None]]:
        """Sorts the input list using insertion sort.

        If ``visualize`` is True, the function returns a generator that
        yields the array state after each swap. Otherwise, it returns the
        fully sorted list.

        Args:
            arr (List[Any]): The list to sort.
            visualize (bool): Whether to yield intermediate states.

        Returns:
            Union[List[Any], Generator[List[Any], None, None]]:
                Sorted list or generator of intermediate states.

        Complexity:
            Time: O(NΒ²) worst case, O(N) best case
            Space: O(1)
        """
        data = list(arr)
        n = len(data)

        def _algo():
            for i in range(1, n):
                for j in range(i, 0, -1):
                    if data[j] < data[j - 1]:
                        data[j], data[j - 1] = data[j - 1], data[j]
                        if visualize:
                            yield list(data)
                    else:
                        break
            if not visualize:
                yield list(data)

        gen = _algo()
        return gen if visualize else list(gen)[-1]
insertion_sort(arr, visualize=False) staticmethod

Sorts the input list using insertion sort.

If visualize is True, the function returns a generator that yields the array state after each swap. Otherwise, it returns the fully sorted list.

Parameters:

Name Type Description Default
arr List[Any]

The list to sort.

required
visualize bool

Whether to yield intermediate states.

False

Returns:

Type Description
Union[List[Any], Generator[List[Any], None, None]]

Union[List[Any], Generator[List[Any], None, None]]: Sorted list or generator of intermediate states.

Complexity
Source code in src/alnoms/dsa/algorithms/sorting/insertion_sort.py
@staticmethod
def insertion_sort(
    arr: List[Any], visualize: bool = False
) -> Union[List[Any], Generator[List[Any], None, None]]:
    """Sorts the input list using insertion sort.

    If ``visualize`` is True, the function returns a generator that
    yields the array state after each swap. Otherwise, it returns the
    fully sorted list.

    Args:
        arr (List[Any]): The list to sort.
        visualize (bool): Whether to yield intermediate states.

    Returns:
        Union[List[Any], Generator[List[Any], None, None]]:
            Sorted list or generator of intermediate states.

    Complexity:
        Time: O(NΒ²) worst case, O(N) best case
        Space: O(1)
    """
    data = list(arr)
    n = len(data)

    def _algo():
        for i in range(1, n):
            for j in range(i, 0, -1):
                if data[j] < data[j - 1]:
                    data[j], data[j - 1] = data[j - 1], data[j]
                    if visualize:
                        yield list(data)
                else:
                    break
        if not visualize:
            yield list(data)

    gen = _algo()
    return gen if visualize else list(gen)[-1]

Merge Sort.

Implements the classic divide‑and‑conquer merge sort algorithm using an auxiliary array for merging. The algorithm guarantees O(N log N) time regardless of input distribution and is stable by construction. An optional visualization mode yields intermediate array states after each merge operation.

Design Characteristics: - Stable sorting - Deterministic O(N log N) time - Requires O(N) auxiliary space - Visualization mode yields merge‑step snapshots

Classes:

Name Description
MergeSort

Static implementation of top‑down merge sort.

MergeSort

Top‑down recursive merge sort implementation.

The algorithm recursively divides the array into halves, sorts each half, and merges them using an auxiliary array. This version supports visualization by yielding intermediate states after each merge step.

Source code in src/alnoms/dsa/algorithms/sorting/merge_sort.py
class MergeSort:
    """Top‑down recursive merge sort implementation.

    The algorithm recursively divides the array into halves, sorts each
    half, and merges them using an auxiliary array. This version supports
    visualization by yielding intermediate states after each merge step.
    """

    @staticmethod
    def merge_sort(
        arr: List[Any], visualize: bool = False
    ) -> Union[List[Any], Generator[List[Any], None, None]]:
        """Sorts the input list using merge sort.

        If ``visualize`` is True, the function returns a generator that
        yields the array state after each merge operation. Otherwise, it
        returns the fully sorted list.

        Args:
            arr (List[Any]): The list to sort.
            visualize (bool): Whether to yield intermediate states.

        Returns:
            Union[List[Any], Generator[List[Any], None, None]]:
                Sorted list or generator of intermediate states.

        Complexity:
            Time: O(N log N)
            Space: O(N) auxiliary array
        """
        data = list(arr)
        aux = list(arr)

        def _merge(lo: int, mid: int, hi: int):
            # Copy to auxiliary array
            for k in range(lo, hi + 1):
                aux[k] = data[k]

            i, j = lo, mid + 1

            for k in range(lo, hi + 1):
                if i > mid:
                    data[k] = aux[j]
                    j += 1
                elif j > hi:
                    data[k] = aux[i]
                    i += 1
                elif aux[j] < aux[i]:
                    data[k] = aux[j]
                    j += 1
                else:
                    data[k] = aux[i]
                    i += 1

                if visualize:
                    yield list(data)

        def _sort(lo: int, hi: int):
            if hi <= lo:
                return
            mid = lo + (hi - lo) // 2
            yield from _sort(lo, mid)
            yield from _sort(mid + 1, hi)
            yield from _merge(lo, mid, hi)

        def _wrapper():
            yield from _sort(0, len(data) - 1)
            if not visualize:
                yield data

        gen = _wrapper()
        return gen if visualize else list(gen)[-1]
merge_sort(arr, visualize=False) staticmethod

Sorts the input list using merge sort.

If visualize is True, the function returns a generator that yields the array state after each merge operation. Otherwise, it returns the fully sorted list.

Parameters:

Name Type Description Default
arr List[Any]

The list to sort.

required
visualize bool

Whether to yield intermediate states.

False

Returns:

Type Description
Union[List[Any], Generator[List[Any], None, None]]

Union[List[Any], Generator[List[Any], None, None]]: Sorted list or generator of intermediate states.

Complexity
Source code in src/alnoms/dsa/algorithms/sorting/merge_sort.py
@staticmethod
def merge_sort(
    arr: List[Any], visualize: bool = False
) -> Union[List[Any], Generator[List[Any], None, None]]:
    """Sorts the input list using merge sort.

    If ``visualize`` is True, the function returns a generator that
    yields the array state after each merge operation. Otherwise, it
    returns the fully sorted list.

    Args:
        arr (List[Any]): The list to sort.
        visualize (bool): Whether to yield intermediate states.

    Returns:
        Union[List[Any], Generator[List[Any], None, None]]:
            Sorted list or generator of intermediate states.

    Complexity:
        Time: O(N log N)
        Space: O(N) auxiliary array
    """
    data = list(arr)
    aux = list(arr)

    def _merge(lo: int, mid: int, hi: int):
        # Copy to auxiliary array
        for k in range(lo, hi + 1):
            aux[k] = data[k]

        i, j = lo, mid + 1

        for k in range(lo, hi + 1):
            if i > mid:
                data[k] = aux[j]
                j += 1
            elif j > hi:
                data[k] = aux[i]
                i += 1
            elif aux[j] < aux[i]:
                data[k] = aux[j]
                j += 1
            else:
                data[k] = aux[i]
                i += 1

            if visualize:
                yield list(data)

    def _sort(lo: int, hi: int):
        if hi <= lo:
            return
        mid = lo + (hi - lo) // 2
        yield from _sort(lo, mid)
        yield from _sort(mid + 1, hi)
        yield from _merge(lo, mid, hi)

    def _wrapper():
        yield from _sort(0, len(data) - 1)
        if not visualize:
            yield data

    gen = _wrapper()
    return gen if visualize else list(gen)[-1]

Quick Sort (3‑Way Partitioning).

Implements Dijkstra’s 3‑way partitioning variant of Quicksort, which handles duplicate keys efficiently by dividing the array into three regions: less than, equal to, and greater than the pivot. This approach improves performance on inputs with many repeated values.

Design Characteristics: - In‑place sorting - Average‑case O(N log N) time - Worst‑case O(NΒ²) time (rare with randomized input) - O(log N) recursion depth - Optional visualization mode yielding intermediate array states

Classes:

Name Description
QuickSort

Static implementation of 3‑way partitioning Quicksort.

QuickSort

3‑way partitioning Quicksort implementation.

Uses Dijkstra’s partitioning strategy to efficiently handle arrays with many duplicate keys. The algorithm recursively sorts the subarrays containing elements less than and greater than the pivot.

Source code in src/alnoms/dsa/algorithms/sorting/quick_sort.py
class QuickSort:
    """3‑way partitioning Quicksort implementation.

    Uses Dijkstra’s partitioning strategy to efficiently handle arrays
    with many duplicate keys. The algorithm recursively sorts the
    subarrays containing elements less than and greater than the pivot.
    """

    @staticmethod
    def quick_sort(
        arr: List[Any], visualize: bool = False
    ) -> Union[List[Any], Generator[List[Any], None, None]]:
        """Sorts the input list using 3‑way partitioning Quicksort.

        If ``visualize`` is True, the function returns a generator that
        yields the array state after each swap or partitioning step.
        Otherwise, it returns the fully sorted list.

        Args:
            arr (List[Any]): The list to sort.
            visualize (bool): Whether to yield intermediate states.

        Returns:
            Union[List[Any], Generator[List[Any], None, None]]:
                Sorted list or generator of intermediate states.

        Complexity:
            Time: O(N log N) average, O(NΒ²) worst‑case
            Space: O(log N) recursion
        """
        data = list(arr)

        def _sort(lo: int, hi: int):
            if hi <= lo:
                return

            lt, i, gt = lo, lo + 1, hi
            pivot = data[lo]

            while i <= gt:
                if data[i] < pivot:
                    data[lt], data[i] = data[i], data[lt]
                    lt += 1
                    i += 1
                    if visualize:
                        yield list(data)
                elif data[i] > pivot:
                    data[i], data[gt] = data[gt], data[i]
                    gt -= 1
                    if visualize:
                        yield list(data)
                else:
                    i += 1

            yield from _sort(lo, lt - 1)
            yield from _sort(gt + 1, hi)

        def _wrapper():
            yield from _sort(0, len(data) - 1)
            if not visualize:
                yield data

        gen = _wrapper()
        return gen if visualize else list(gen)[-1]
quick_sort(arr, visualize=False) staticmethod

Sorts the input list using 3‑way partitioning Quicksort.

If visualize is True, the function returns a generator that yields the array state after each swap or partitioning step. Otherwise, it returns the fully sorted list.

Parameters:

Name Type Description Default
arr List[Any]

The list to sort.

required
visualize bool

Whether to yield intermediate states.

False

Returns:

Type Description
Union[List[Any], Generator[List[Any], None, None]]

Union[List[Any], Generator[List[Any], None, None]]: Sorted list or generator of intermediate states.

Complexity
Source code in src/alnoms/dsa/algorithms/sorting/quick_sort.py
@staticmethod
def quick_sort(
    arr: List[Any], visualize: bool = False
) -> Union[List[Any], Generator[List[Any], None, None]]:
    """Sorts the input list using 3‑way partitioning Quicksort.

    If ``visualize`` is True, the function returns a generator that
    yields the array state after each swap or partitioning step.
    Otherwise, it returns the fully sorted list.

    Args:
        arr (List[Any]): The list to sort.
        visualize (bool): Whether to yield intermediate states.

    Returns:
        Union[List[Any], Generator[List[Any], None, None]]:
            Sorted list or generator of intermediate states.

    Complexity:
        Time: O(N log N) average, O(NΒ²) worst‑case
        Space: O(log N) recursion
    """
    data = list(arr)

    def _sort(lo: int, hi: int):
        if hi <= lo:
            return

        lt, i, gt = lo, lo + 1, hi
        pivot = data[lo]

        while i <= gt:
            if data[i] < pivot:
                data[lt], data[i] = data[i], data[lt]
                lt += 1
                i += 1
                if visualize:
                    yield list(data)
            elif data[i] > pivot:
                data[i], data[gt] = data[gt], data[i]
                gt -= 1
                if visualize:
                    yield list(data)
            else:
                i += 1

        yield from _sort(lo, lt - 1)
        yield from _sort(gt + 1, hi)

    def _wrapper():
        yield from _sort(0, len(data) - 1)
        if not visualize:
            yield data

    gen = _wrapper()
    return gen if visualize else list(gen)[-1]

Selection Sort.

Provides a simple comparison‑based sorting algorithm that repeatedly selects the minimum element and swaps it into its correct position. This implementation supports optional step‑by‑step visualization via a generator interface.

Design Characteristics: - In‑place sorting - Deterministic O(NΒ²) time - O(1) auxiliary space - Visualization mode yields intermediate array states

Classes:

Name Description
SelectionSort

Static implementation of the selection sort algorithm.

SelectionSort

Selection sort implementation.

Repeatedly scans the unsorted portion of the array to find the minimum element and swaps it into its correct position. The algorithm is simple, stable in behavior, and useful for educational and demonstrative purposes.

Source code in src/alnoms/dsa/algorithms/sorting/selection_sort.py
class SelectionSort:
    """Selection sort implementation.

    Repeatedly scans the unsorted portion of the array to find the
    minimum element and swaps it into its correct position. The algorithm
    is simple, stable in behavior, and useful for educational and
    demonstrative purposes.
    """

    @staticmethod
    def selection_sort(
        arr: List[Any], visualize: bool = False
    ) -> Union[List[Any], Generator[List[Any], None, None]]:
        """Sorts the input list using selection sort.

        If ``visualize`` is True, the function returns a generator that
        yields the array state after each swap. Otherwise, it returns the
        fully sorted list.

        Args:
            arr (List[Any]): The list to sort.
            visualize (bool): Whether to yield intermediate states.

        Returns:
            Union[List[Any], Generator[List[Any], None, None]]:
                Sorted list or generator of intermediate states.

        Complexity:
            Time: O(NΒ²)
            Space: O(1)
        """
        data = list(arr)
        n = len(data)

        def _algo():
            for i in range(n):
                min_idx = i
                for j in range(i + 1, n):
                    if data[j] < data[min_idx]:
                        min_idx = j
                data[i], data[min_idx] = data[min_idx], data[i]
                if visualize:
                    yield list(data)
            if not visualize:
                yield list(data)

        gen = _algo()
        return gen if visualize else list(gen)[-1]
selection_sort(arr, visualize=False) staticmethod

Sorts the input list using selection sort.

If visualize is True, the function returns a generator that yields the array state after each swap. Otherwise, it returns the fully sorted list.

Parameters:

Name Type Description Default
arr List[Any]

The list to sort.

required
visualize bool

Whether to yield intermediate states.

False

Returns:

Type Description
Union[List[Any], Generator[List[Any], None, None]]

Union[List[Any], Generator[List[Any], None, None]]: Sorted list or generator of intermediate states.

Complexity
Source code in src/alnoms/dsa/algorithms/sorting/selection_sort.py
@staticmethod
def selection_sort(
    arr: List[Any], visualize: bool = False
) -> Union[List[Any], Generator[List[Any], None, None]]:
    """Sorts the input list using selection sort.

    If ``visualize`` is True, the function returns a generator that
    yields the array state after each swap. Otherwise, it returns the
    fully sorted list.

    Args:
        arr (List[Any]): The list to sort.
        visualize (bool): Whether to yield intermediate states.

    Returns:
        Union[List[Any], Generator[List[Any], None, None]]:
            Sorted list or generator of intermediate states.

    Complexity:
        Time: O(NΒ²)
        Space: O(1)
    """
    data = list(arr)
    n = len(data)

    def _algo():
        for i in range(n):
            min_idx = i
            for j in range(i + 1, n):
                if data[j] < data[min_idx]:
                    min_idx = j
            data[i], data[min_idx] = data[min_idx], data[i]
            if visualize:
                yield list(data)
        if not visualize:
            yield list(data)

    gen = _algo()
    return gen if visualize else list(gen)[-1]

Binary Search Utilities.

Provides classic binary search operations on sorted arrays, including exact‑match search and rank computation. Both operations run in O(log N) time and assume that the input list is already sorted in ascending order.

Design Characteristics: - Deterministic O(log N) time - Works on any comparable key type - Rank operation supports arrays with duplicates - No side effects; functions operate on the input list as read‑only

Classes:

Name Description
BinarySearch

Static utilities for binary search and rank.

BinarySearch

Binary search and rank utilities.

Implements exact‑match binary search and a left‑leaning rank operation that counts how many elements are strictly less than a given key. Both methods assume the input list is sorted.

Source code in src/alnoms/dsa/algorithms/searching/binary_search.py
class BinarySearch:
    """Binary search and rank utilities.

    Implements exact‑match binary search and a left‑leaning rank
    operation that counts how many elements are strictly less than a
    given key. Both methods assume the input list is sorted.
    """

    @staticmethod
    def search(a: List[Any], key: Any) -> int:
        """Performs binary search for ``key`` in a sorted list.

        Args:
            a (List[Any]): Sorted list to search.
            key (Any): Target key.

        Returns:
            int: Index of ``key`` if found, otherwise -1.

        Complexity:
            Time: O(log N)
            Space: O(1)
        """
        lo = 0
        hi = len(a) - 1

        while lo <= hi:
            mid = lo + (hi - lo) // 2
            if key < a[mid]:
                hi = mid - 1
            elif key > a[mid]:
                lo = mid + 1
            else:
                return mid
        return -1

    @staticmethod
    def rank(a: List[Any], key: Any) -> int:
        """Returns the number of elements strictly less than ``key``.

        Uses a left‑leaning binary search to find the first index at
        which ``key`` could be inserted while maintaining sorted order.
        This yields the count of elements smaller than ``key`` and works
        efficiently even with many duplicates.

        Args:
            a (List[Any]): Sorted list to examine.
            key (Any): Target key.

        Returns:
            int: Count of elements strictly less than ``key``.

        Complexity:
            Time: O(log N)
            Space: O(1)
        """
        lo = 0
        hi = len(a) - 1

        while lo <= hi:
            mid = lo + (hi - lo) // 2
            if key <= a[mid]:
                hi = mid - 1
            else:
                lo = mid + 1
        return lo
rank(a, key) staticmethod

Returns the number of elements strictly less than key.

Uses a left‑leaning binary search to find the first index at which key could be inserted while maintaining sorted order. This yields the count of elements smaller than key and works efficiently even with many duplicates.

Parameters:

Name Type Description Default
a List[Any]

Sorted list to examine.

required
key Any

Target key.

required

Returns:

Name Type Description
int int

Count of elements strictly less than key.

Complexity
Source code in src/alnoms/dsa/algorithms/searching/binary_search.py
@staticmethod
def rank(a: List[Any], key: Any) -> int:
    """Returns the number of elements strictly less than ``key``.

    Uses a left‑leaning binary search to find the first index at
    which ``key`` could be inserted while maintaining sorted order.
    This yields the count of elements smaller than ``key`` and works
    efficiently even with many duplicates.

    Args:
        a (List[Any]): Sorted list to examine.
        key (Any): Target key.

    Returns:
        int: Count of elements strictly less than ``key``.

    Complexity:
        Time: O(log N)
        Space: O(1)
    """
    lo = 0
    hi = len(a) - 1

    while lo <= hi:
        mid = lo + (hi - lo) // 2
        if key <= a[mid]:
            hi = mid - 1
        else:
            lo = mid + 1
    return lo
search(a, key) staticmethod

Performs binary search for key in a sorted list.

Parameters:

Name Type Description Default
a List[Any]

Sorted list to search.

required
key Any

Target key.

required

Returns:

Name Type Description
int int

Index of key if found, otherwise -1.

Complexity
Source code in src/alnoms/dsa/algorithms/searching/binary_search.py
@staticmethod
def search(a: List[Any], key: Any) -> int:
    """Performs binary search for ``key`` in a sorted list.

    Args:
        a (List[Any]): Sorted list to search.
        key (Any): Target key.

    Returns:
        int: Index of ``key`` if found, otherwise -1.

    Complexity:
        Time: O(log N)
        Space: O(1)
    """
    lo = 0
    hi = len(a) - 1

    while lo <= hi:
        mid = lo + (hi - lo) // 2
        if key < a[mid]:
            hi = mid - 1
        elif key > a[mid]:
            lo = mid + 1
        else:
            return mid
    return -1

🎯 Pointers

Pointer‑Based Algorithms.

Provides classic two‑pointer techniques, including Floyd’s Tortoise‑and‑Hare cycle detection algorithm for singly linked lists. These algorithms rely on constant‑space pointer manipulation and are widely used in linked‑list processing and fast/slow traversal patterns.

Design Characteristics: - O(1) auxiliary space - O(N) cycle detection - Works on any singly linked list with next references

Classes:

Name Description
CycleDetector

Implements Floyd’s cycle‑finding algorithm.

CycleDetector

Cycle detection using Floyd’s Tortoise‑and‑Hare algorithm.

Uses two pointers moving at different speeds to determine whether a singly linked list contains a cycle. If the fast pointer ever meets the slow pointer, a cycle exists.

Source code in src/alnoms/dsa/algorithms/pointers/cycle_detector.py
class CycleDetector:
    """Cycle detection using Floyd’s Tortoise‑and‑Hare algorithm.

    Uses two pointers moving at different speeds to determine whether a
    singly linked list contains a cycle. If the fast pointer ever meets
    the slow pointer, a cycle exists.
    """

    @staticmethod
    def has_cycle(head: Optional[Node]) -> bool:
        """Detects whether a singly linked list contains a cycle.

        The algorithm advances a slow pointer by one step and a fast
        pointer by two steps. If the list contains a cycle, the two
        pointers eventually meet. If the fast pointer reaches the end of
        the list, no cycle exists.

        Args:
            head (Optional[Node]): Head of the singly linked list.

        Returns:
            bool: True if a cycle exists, otherwise False.

        Complexity:
            Time: O(N)
            Space: O(1)
        """
        if not head or not head.next:
            return False

        slow = head
        fast = head

        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next

            if slow is fast:
                return True

        return False
has_cycle(head) staticmethod

Detects whether a singly linked list contains a cycle.

The algorithm advances a slow pointer by one step and a fast pointer by two steps. If the list contains a cycle, the two pointers eventually meet. If the fast pointer reaches the end of the list, no cycle exists.

Parameters:

Name Type Description Default
head Optional[Node]

Head of the singly linked list.

required

Returns:

Name Type Description
bool bool

True if a cycle exists, otherwise False.

Complexity
Source code in src/alnoms/dsa/algorithms/pointers/cycle_detector.py
@staticmethod
def has_cycle(head: Optional[Node]) -> bool:
    """Detects whether a singly linked list contains a cycle.

    The algorithm advances a slow pointer by one step and a fast
    pointer by two steps. If the list contains a cycle, the two
    pointers eventually meet. If the fast pointer reaches the end of
    the list, no cycle exists.

    Args:
        head (Optional[Node]): Head of the singly linked list.

    Returns:
        bool: True if a cycle exists, otherwise False.

    Complexity:
        Time: O(N)
        Space: O(1)
    """
    if not head or not head.next:
        return False

    slow = head
    fast = head

    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next

        if slow is fast:
            return True

    return False

πŸ“¦ Data Structures (alnoms.dsa.structures)

Low-latency, memory-efficient structures designed for high-scale environments.

πŸ“ Linear Structures

Bag (Unordered Collection).

Defines a lightweight collection type that supports insertion but does not support removal. A Bag is useful for collecting items and iterating over them without imposing any ordering guarantees. Internally, the Bag is backed by a singly linked list, enabling O(1) insertion.

Design Characteristics: - Unordered collection - O(1) insertion at the front - O(N) iteration - No removal operations

Classes:

Name Description
Bag

A simple unordered collection supporting fast insertion.

Bag

Bases: Generic[T]

An unordered collection supporting fast insertion.

Items are stored in a singly linked list. The Bag does not support removal; its purpose is to collect items and allow iteration over them. The iteration order is LIFO due to front insertion, but the order is not semantically meaningful.

Source code in src/alnoms/dsa/structures/bag.py
class Bag(Generic[T]):
    """An unordered collection supporting fast insertion.

    Items are stored in a singly linked list. The Bag does not support
    removal; its purpose is to collect items and allow iteration over
    them. The iteration order is LIFO due to front insertion, but the
    order is not semantically meaningful.
    """

    def __init__(self):
        """Initializes an empty Bag.

        Attributes:
            _first (Optional[Node]): Reference to the first node.
            _n (int): Number of items stored.
        """
        self._first: Optional[Node] = None
        self._n: int = 0

    def is_empty(self) -> bool:
        """Checks whether the Bag is empty.

        Returns:
            bool: True if the Bag contains no items.
        """
        return self._first is None

    def size(self) -> int:
        """Returns the number of items in the Bag.

        Returns:
            int: Total number of stored items.
        """
        return self._n

    def add(self, item: T) -> None:
        """Adds an item to the Bag.

        This operation runs in O(1) time by inserting the new item at the
        front of the linked list.

        Args:
            item (T): The item to add.
        """
        old_first = self._first
        self._first = Node(item)
        self._first.next = old_first
        self._n += 1

    def __iter__(self) -> Iterator[T]:
        """Iterates over the items in the Bag.

        Yields:
            T: Items stored in the Bag. Order is LIFO but not meaningful.
        """
        current = self._first
        while current:
            yield current.data
            current = current.next
__init__()

Initializes an empty Bag.

Attributes:

Name Type Description
_first Optional[Node]

Reference to the first node.

_n int

Number of items stored.

Source code in src/alnoms/dsa/structures/bag.py
def __init__(self):
    """Initializes an empty Bag.

    Attributes:
        _first (Optional[Node]): Reference to the first node.
        _n (int): Number of items stored.
    """
    self._first: Optional[Node] = None
    self._n: int = 0
__iter__()

Iterates over the items in the Bag.

Yields:

Name Type Description
T T

Items stored in the Bag. Order is LIFO but not meaningful.

Source code in src/alnoms/dsa/structures/bag.py
def __iter__(self) -> Iterator[T]:
    """Iterates over the items in the Bag.

    Yields:
        T: Items stored in the Bag. Order is LIFO but not meaningful.
    """
    current = self._first
    while current:
        yield current.data
        current = current.next
add(item)

Adds an item to the Bag.

This operation runs in O(1) time by inserting the new item at the front of the linked list.

Parameters:

Name Type Description Default
item T

The item to add.

required
Source code in src/alnoms/dsa/structures/bag.py
def add(self, item: T) -> None:
    """Adds an item to the Bag.

    This operation runs in O(1) time by inserting the new item at the
    front of the linked list.

    Args:
        item (T): The item to add.
    """
    old_first = self._first
    self._first = Node(item)
    self._first.next = old_first
    self._n += 1
is_empty()

Checks whether the Bag is empty.

Returns:

Name Type Description
bool bool

True if the Bag contains no items.

Source code in src/alnoms/dsa/structures/bag.py
def is_empty(self) -> bool:
    """Checks whether the Bag is empty.

    Returns:
        bool: True if the Bag contains no items.
    """
    return self._first is None
size()

Returns the number of items in the Bag.

Returns:

Name Type Description
int int

Total number of stored items.

Source code in src/alnoms/dsa/structures/bag.py
def size(self) -> int:
    """Returns the number of items in the Bag.

    Returns:
        int: Total number of stored items.
    """
    return self._n

Alnoms Data Structure: FIFO Queue (Linked‑List Backed).

Provides a generic, type‑safe FIFO (First‑In First‑Out) queue implementation using a singly linked list with explicit head and tail references. This design guarantees O(1) enqueue and dequeue operations without requiring array resizing or circular‑buffer bookkeeping.

The queue supports:

  • O(1) enqueue at the tail
  • O(1) dequeue from the head
  • Safe iteration from front to back
  • Deterministic memory behavior with no hidden reallocations

This module is part of the Alnoms OSS data‑structures suite and is optimized for teaching, benchmarking, and algorithmic experimentation. All operations are implemented with predictable worst‑case complexity.

Queue

Bases: Generic[T]

A FIFO (First‑In First‑Out) queue implemented using a linked list.

This implementation maintains references to both the head (_first) and tail (_last) nodes, ensuring O(1) enqueue and dequeue operations. The queue grows dynamically without requiring array resizing, making it suitable for workloads requiring predictable constant‑time behavior.

Source code in src/alnoms/dsa/structures/queue.py
class Queue(Generic[T]):
    """A FIFO (First‑In First‑Out) queue implemented using a linked list.

    This implementation maintains references to both the head (``_first``)
    and tail (``_last``) nodes, ensuring O(1) enqueue and dequeue
    operations. The queue grows dynamically without requiring array
    resizing, making it suitable for workloads requiring predictable
    constant‑time behavior.
    """

    def __init__(self):
        """Initializes an empty queue.

        Attributes:
            _first (Optional[Node]): Reference to the front of the queue.
            _last (Optional[Node]): Reference to the end of the queue.
            _n (int): Number of items currently stored.
        """
        self._first: Optional[Node] = None
        self._last: Optional[Node] = None
        self._n: int = 0

    def is_empty(self) -> bool:
        """Checks whether the queue is empty.

        Returns:
            bool: True if the queue contains no items, otherwise False.
        """
        return self._first is None

    def size(self) -> int:
        """Returns the number of items in the queue.

        Returns:
            int: The number of elements stored.
        """
        return self._n

    def enqueue(self, item: T) -> None:
        """Adds an item to the end of the queue.

        This operation runs in O(1) time by inserting a new node at the
        tail of the linked list.

        Args:
            item (T): The item to add to the queue.
        """
        old_last = self._last
        self._last = Node(item)
        self._last.next = None

        if self.is_empty():
            self._first = self._last
        else:
            old_last.next = self._last

        self._n += 1

    def dequeue(self) -> T:
        """Removes and returns the item at the front of the queue.

        This operation runs in O(1) time by removing the head node.

        Returns:
            T: The item previously at the front of the queue.

        Raises:
            IndexError: If the queue is empty.
        """
        if self.is_empty():
            raise IndexError("Queue underflow")

        item = self._first.data
        self._first = self._first.next
        self._n -= 1

        if self.is_empty():
            self._last = None  # Avoid loitering

        return item

    def peek(self) -> T:
        """Returns the item at the front of the queue without removing it.

        Returns:
            T: The item currently at the front.

        Raises:
            IndexError: If the queue is empty.
        """
        if self.is_empty():
            raise IndexError("Queue underflow")
        return self._first.data

    def __iter__(self) -> Iterator[T]:
        """Iterates over the queue from front to back.

        Yields:
            T: Items in FIFO order, starting from the front.
        """
        current = self._first
        while current:
            yield current.data
            current = current.next
__init__()

Initializes an empty queue.

Attributes:

Name Type Description
_first Optional[Node]

Reference to the front of the queue.

_last Optional[Node]

Reference to the end of the queue.

_n int

Number of items currently stored.

Source code in src/alnoms/dsa/structures/queue.py
def __init__(self):
    """Initializes an empty queue.

    Attributes:
        _first (Optional[Node]): Reference to the front of the queue.
        _last (Optional[Node]): Reference to the end of the queue.
        _n (int): Number of items currently stored.
    """
    self._first: Optional[Node] = None
    self._last: Optional[Node] = None
    self._n: int = 0
__iter__()

Iterates over the queue from front to back.

Yields:

Name Type Description
T T

Items in FIFO order, starting from the front.

Source code in src/alnoms/dsa/structures/queue.py
def __iter__(self) -> Iterator[T]:
    """Iterates over the queue from front to back.

    Yields:
        T: Items in FIFO order, starting from the front.
    """
    current = self._first
    while current:
        yield current.data
        current = current.next
dequeue()

Removes and returns the item at the front of the queue.

This operation runs in O(1) time by removing the head node.

Returns:

Name Type Description
T T

The item previously at the front of the queue.

Raises:

Type Description
IndexError

If the queue is empty.

Source code in src/alnoms/dsa/structures/queue.py
def dequeue(self) -> T:
    """Removes and returns the item at the front of the queue.

    This operation runs in O(1) time by removing the head node.

    Returns:
        T: The item previously at the front of the queue.

    Raises:
        IndexError: If the queue is empty.
    """
    if self.is_empty():
        raise IndexError("Queue underflow")

    item = self._first.data
    self._first = self._first.next
    self._n -= 1

    if self.is_empty():
        self._last = None  # Avoid loitering

    return item
enqueue(item)

Adds an item to the end of the queue.

This operation runs in O(1) time by inserting a new node at the tail of the linked list.

Parameters:

Name Type Description Default
item T

The item to add to the queue.

required
Source code in src/alnoms/dsa/structures/queue.py
def enqueue(self, item: T) -> None:
    """Adds an item to the end of the queue.

    This operation runs in O(1) time by inserting a new node at the
    tail of the linked list.

    Args:
        item (T): The item to add to the queue.
    """
    old_last = self._last
    self._last = Node(item)
    self._last.next = None

    if self.is_empty():
        self._first = self._last
    else:
        old_last.next = self._last

    self._n += 1
is_empty()

Checks whether the queue is empty.

Returns:

Name Type Description
bool bool

True if the queue contains no items, otherwise False.

Source code in src/alnoms/dsa/structures/queue.py
def is_empty(self) -> bool:
    """Checks whether the queue is empty.

    Returns:
        bool: True if the queue contains no items, otherwise False.
    """
    return self._first is None
peek()

Returns the item at the front of the queue without removing it.

Returns:

Name Type Description
T T

The item currently at the front.

Raises:

Type Description
IndexError

If the queue is empty.

Source code in src/alnoms/dsa/structures/queue.py
def peek(self) -> T:
    """Returns the item at the front of the queue without removing it.

    Returns:
        T: The item currently at the front.

    Raises:
        IndexError: If the queue is empty.
    """
    if self.is_empty():
        raise IndexError("Queue underflow")
    return self._first.data
size()

Returns the number of items in the queue.

Returns:

Name Type Description
int int

The number of elements stored.

Source code in src/alnoms/dsa/structures/queue.py
def size(self) -> int:
    """Returns the number of items in the queue.

    Returns:
        int: The number of elements stored.
    """
    return self._n

Linked-List-Based Stack.

Implements a generic LIFO (Last-In First-Out) stack using a linked list backing. This design guarantees O(1) worst-case push and pop operations by avoiding array resizing and capacity management.

Features
  • O(1) push and pop
  • Dynamic, node-based storage
  • Type-parameterized (generic) API
  • Safe iteration from top to bottom

Stack

Bases: Generic[T]

A LIFO (Last‑In First‑Out) stack implemented using a linked list.

This implementation guarantees O(1) worst‑case time for both push and pop operations by avoiding the resizing overhead associated with array‑based stacks. The stack grows dynamically through linked nodes, making it suitable for workloads requiring predictable constant‑time operations.

Source code in src/alnoms/dsa/structures/stack.py
class Stack(Generic[T]):
    """A LIFO (Last‑In First‑Out) stack implemented using a linked list.

    This implementation guarantees O(1) worst‑case time for both `push`
    and `pop` operations by avoiding the resizing overhead associated
    with array‑based stacks. The stack grows dynamically through linked
    nodes, making it suitable for workloads requiring predictable
    constant‑time operations.
    """

    def __init__(self):
        """Initializes an empty stack.

        Attributes:
            _first (Optional[Node]): Reference to the top node.
            _n (int): Number of items currently stored.
        """
        self._first: Optional[Node] = None
        self._n: int = 0

    def is_empty(self) -> bool:
        """Checks whether the stack is empty.

        Returns:
            bool: True if the stack contains no items, otherwise False.
        """
        return self._first is None

    def size(self) -> int:
        """Returns the number of items in the stack.

        Returns:
            int: The number of elements stored.
        """
        return self._n

    def push(self, item: T) -> None:
        """Pushes an item onto the top of the stack.

        This operation runs in O(1) time by inserting a new node at the
        head of the linked list.

        Args:
            item (T): The item to push onto the stack.
        """
        old_first = self._first
        self._first = Node(item)
        self._first.next = old_first
        self._n += 1

    def pop(self) -> T:
        """Removes and returns the most recently added item.

        This operation runs in O(1) time by removing the head node.

        Returns:
            T: The item previously at the top of the stack.

        Raises:
            IndexError: If the stack is empty.
        """
        if self.is_empty():
            raise IndexError("Stack underflow")

        item = self._first.data
        self._first = self._first.next
        self._n -= 1
        return item

    def peek(self) -> T:
        """Returns the item at the top of the stack without removing it.

        Returns:
            T: The item currently at the top.

        Raises:
            IndexError: If the stack is empty.
        """
        if self.is_empty():
            raise IndexError("Stack underflow")
        return self._first.data

    def __iter__(self) -> Iterator[T]:
        """Iterates over the stack from top to bottom.

        Yields:
            T: Items in LIFO order, starting from the top.
        """
        current = self._first
        while current:
            yield current.data
            current = current.next
__init__()

Initializes an empty stack.

Attributes:

Name Type Description
_first Optional[Node]

Reference to the top node.

_n int

Number of items currently stored.

Source code in src/alnoms/dsa/structures/stack.py
def __init__(self):
    """Initializes an empty stack.

    Attributes:
        _first (Optional[Node]): Reference to the top node.
        _n (int): Number of items currently stored.
    """
    self._first: Optional[Node] = None
    self._n: int = 0
__iter__()

Iterates over the stack from top to bottom.

Yields:

Name Type Description
T T

Items in LIFO order, starting from the top.

Source code in src/alnoms/dsa/structures/stack.py
def __iter__(self) -> Iterator[T]:
    """Iterates over the stack from top to bottom.

    Yields:
        T: Items in LIFO order, starting from the top.
    """
    current = self._first
    while current:
        yield current.data
        current = current.next
is_empty()

Checks whether the stack is empty.

Returns:

Name Type Description
bool bool

True if the stack contains no items, otherwise False.

Source code in src/alnoms/dsa/structures/stack.py
def is_empty(self) -> bool:
    """Checks whether the stack is empty.

    Returns:
        bool: True if the stack contains no items, otherwise False.
    """
    return self._first is None
peek()

Returns the item at the top of the stack without removing it.

Returns:

Name Type Description
T T

The item currently at the top.

Raises:

Type Description
IndexError

If the stack is empty.

Source code in src/alnoms/dsa/structures/stack.py
def peek(self) -> T:
    """Returns the item at the top of the stack without removing it.

    Returns:
        T: The item currently at the top.

    Raises:
        IndexError: If the stack is empty.
    """
    if self.is_empty():
        raise IndexError("Stack underflow")
    return self._first.data
pop()

Removes and returns the most recently added item.

This operation runs in O(1) time by removing the head node.

Returns:

Name Type Description
T T

The item previously at the top of the stack.

Raises:

Type Description
IndexError

If the stack is empty.

Source code in src/alnoms/dsa/structures/stack.py
def pop(self) -> T:
    """Removes and returns the most recently added item.

    This operation runs in O(1) time by removing the head node.

    Returns:
        T: The item previously at the top of the stack.

    Raises:
        IndexError: If the stack is empty.
    """
    if self.is_empty():
        raise IndexError("Stack underflow")

    item = self._first.data
    self._first = self._first.next
    self._n -= 1
    return item
push(item)

Pushes an item onto the top of the stack.

This operation runs in O(1) time by inserting a new node at the head of the linked list.

Parameters:

Name Type Description Default
item T

The item to push onto the stack.

required
Source code in src/alnoms/dsa/structures/stack.py
def push(self, item: T) -> None:
    """Pushes an item onto the top of the stack.

    This operation runs in O(1) time by inserting a new node at the
    head of the linked list.

    Args:
        item (T): The item to push onto the stack.
    """
    old_first = self._first
    self._first = Node(item)
    self._first.next = old_first
    self._n += 1
size()

Returns the number of items in the stack.

Returns:

Name Type Description
int int

The number of elements stored.

Source code in src/alnoms/dsa/structures/stack.py
def size(self) -> int:
    """Returns the number of items in the stack.

    Returns:
        int: The number of elements stored.
    """
    return self._n

Singly Linked List (Foundational Data Structure).

Provides a minimal, textbook‑grade implementation of a singly linked list supporting O(1) insertions at the head and linear‑time traversal. This structure is useful for workloads requiring predictable insertion performance without the resizing overhead of array‑backed lists.

Features

β€’ O(1) insertion at the head β€’ Linear‑time traversal and search β€’ Node‑based dynamic memory allocation β€’ Pythonic iteration support

This module is part of the Alnoms Data Structures suite and is designed for clarity, determinism, and mkdocstrings‑compatible documentation.

SinglyLinkedList

A foundational singly linked list implementation.

This structure supports O(1) insertions at the head and linear-time traversal from head to tail. It is suitable for workloads requiring predictable insertion performance without the resizing overhead of array-backed lists.

Source code in src/alnoms/dsa/structures/singly_linked_list.py
class SinglyLinkedList:
    """A foundational singly linked list implementation.

    This structure supports O(1) insertions at the head and linear-time
    traversal from head to tail. It is suitable for workloads requiring
    predictable insertion performance without the resizing overhead of
    array-backed lists.
    """

    def __init__(self):
        """Initializes an empty singly linked list.

        Attributes:
            head (Optional[Node]): Reference to the first node in the list.
            _size (int): Number of nodes currently stored.
        """
        self.head: Optional[Node] = None
        self._size: int = 0

    def __len__(self) -> int:
        """Returns the number of nodes in the list.

        Returns:
            int: The total number of elements.
        """
        return self._size

    def __iter__(self) -> Iterator[Any]:
        """Iterates through the list from head to tail.

        Yields:
            Any: The data stored in each node.
        """
        current = self.head
        while current:
            yield current.data
            current = current.next

    def is_empty(self) -> bool:
        """Checks whether the list contains any elements.

        Returns:
            bool: True if the list is empty, otherwise False.
        """
        return self.head is None

    def insert_at_head(self, data: Any) -> None:
        """Inserts a new node at the beginning of the list.

        This operation runs in O(1) time by updating the head pointer.

        Args:
            data (Any): The value to insert.
        """
        new_node = Node(data)
        new_node.next = self.head
        self.head = new_node
        self._size += 1

    def append(self, data: Any) -> None:
        """Appends a new node to the end of the list.

        This operation runs in O(N) time due to the need to traverse
        to the tail.

        Args:
            data (Any): The value to append.
        """
        new_node = Node(data)
        if not self.head:
            self.head = new_node
            self._size += 1
            return

        current = self.head
        while current.next:
            current = current.next

        current.next = new_node
        self._size += 1

    def remove(self, data: Any) -> bool:
        """Removes the first occurrence of a value from the list.

        This operation runs in O(N) time due to linear search.

        Args:
            data (Any): The value to remove.

        Returns:
            bool: True if a node was removed, otherwise False.
        """
        if not self.head:
            return False

        if self.head.data == data:
            self.head = self.head.next
            self._size -= 1
            return True

        current = self.head
        while current.next:
            if current.next.data == data:
                current.next = current.next.next
                self._size -= 1
                return True
            current = current.next

        return False

    def display(self) -> str:
        """Returns a string representation of the list.

        Useful for debugging and visualization.

        Returns:
            str: A formatted representation such as ``"1 -> 2 -> NULL"``.
        """
        elements = [str(data) for data in self]
        return " -> ".join(elements) + " -> NULL" if elements else "EMPTY"
__init__()

Initializes an empty singly linked list.

Attributes:

Name Type Description
head Optional[Node]

Reference to the first node in the list.

_size int

Number of nodes currently stored.

Source code in src/alnoms/dsa/structures/singly_linked_list.py
def __init__(self):
    """Initializes an empty singly linked list.

    Attributes:
        head (Optional[Node]): Reference to the first node in the list.
        _size (int): Number of nodes currently stored.
    """
    self.head: Optional[Node] = None
    self._size: int = 0
__iter__()

Iterates through the list from head to tail.

Yields:

Name Type Description
Any Any

The data stored in each node.

Source code in src/alnoms/dsa/structures/singly_linked_list.py
def __iter__(self) -> Iterator[Any]:
    """Iterates through the list from head to tail.

    Yields:
        Any: The data stored in each node.
    """
    current = self.head
    while current:
        yield current.data
        current = current.next
__len__()

Returns the number of nodes in the list.

Returns:

Name Type Description
int int

The total number of elements.

Source code in src/alnoms/dsa/structures/singly_linked_list.py
def __len__(self) -> int:
    """Returns the number of nodes in the list.

    Returns:
        int: The total number of elements.
    """
    return self._size
append(data)

Appends a new node to the end of the list.

This operation runs in O(N) time due to the need to traverse to the tail.

Parameters:

Name Type Description Default
data Any

The value to append.

required
Source code in src/alnoms/dsa/structures/singly_linked_list.py
def append(self, data: Any) -> None:
    """Appends a new node to the end of the list.

    This operation runs in O(N) time due to the need to traverse
    to the tail.

    Args:
        data (Any): The value to append.
    """
    new_node = Node(data)
    if not self.head:
        self.head = new_node
        self._size += 1
        return

    current = self.head
    while current.next:
        current = current.next

    current.next = new_node
    self._size += 1
display()

Returns a string representation of the list.

Useful for debugging and visualization.

Returns:

Name Type Description
str str

A formatted representation such as "1 -> 2 -> NULL".

Source code in src/alnoms/dsa/structures/singly_linked_list.py
def display(self) -> str:
    """Returns a string representation of the list.

    Useful for debugging and visualization.

    Returns:
        str: A formatted representation such as ``"1 -> 2 -> NULL"``.
    """
    elements = [str(data) for data in self]
    return " -> ".join(elements) + " -> NULL" if elements else "EMPTY"
insert_at_head(data)

Inserts a new node at the beginning of the list.

This operation runs in O(1) time by updating the head pointer.

Parameters:

Name Type Description Default
data Any

The value to insert.

required
Source code in src/alnoms/dsa/structures/singly_linked_list.py
def insert_at_head(self, data: Any) -> None:
    """Inserts a new node at the beginning of the list.

    This operation runs in O(1) time by updating the head pointer.

    Args:
        data (Any): The value to insert.
    """
    new_node = Node(data)
    new_node.next = self.head
    self.head = new_node
    self._size += 1
is_empty()

Checks whether the list contains any elements.

Returns:

Name Type Description
bool bool

True if the list is empty, otherwise False.

Source code in src/alnoms/dsa/structures/singly_linked_list.py
def is_empty(self) -> bool:
    """Checks whether the list contains any elements.

    Returns:
        bool: True if the list is empty, otherwise False.
    """
    return self.head is None
remove(data)

Removes the first occurrence of a value from the list.

This operation runs in O(N) time due to linear search.

Parameters:

Name Type Description Default
data Any

The value to remove.

required

Returns:

Name Type Description
bool bool

True if a node was removed, otherwise False.

Source code in src/alnoms/dsa/structures/singly_linked_list.py
def remove(self, data: Any) -> bool:
    """Removes the first occurrence of a value from the list.

    This operation runs in O(N) time due to linear search.

    Args:
        data (Any): The value to remove.

    Returns:
        bool: True if a node was removed, otherwise False.
    """
    if not self.head:
        return False

    if self.head.data == data:
        self.head = self.head.next
        self._size -= 1
        return True

    current = self.head
    while current.next:
        if current.next.data == data:
            current.next = current.next.next
            self._size -= 1
            return True
        current = current.next

    return False

Doubly Linked List.

Provides a bidirectional linked list supporting O(1) insertion at both the head and tail. Each node maintains references to its predecessor and successor, enabling efficient traversal and structural updates.

Design Characteristics: - Bidirectional traversal - O(1) prepend and append - O(N) search and removal - Deterministic memory behavior

Classes:

Name Description
DoublyLinkedList

A doubly linked list supporting head/tail operations.

DoublyLinkedList

Bases: Generic[T]

A doubly linked list supporting bidirectional traversal.

The list maintains references to both the head and tail nodes, enabling O(1) insertion at either end. Removal of arbitrary values runs in O(N) time due to linear search.

Source code in src/alnoms/dsa/structures/doubly_linked_list.py
class DoublyLinkedList(Generic[T]):
    """A doubly linked list supporting bidirectional traversal.

    The list maintains references to both the head and tail nodes,
    enabling O(1) insertion at either end. Removal of arbitrary values
    runs in O(N) time due to linear search.
    """

    def __init__(self):
        """Initializes an empty doubly linked list.

        Attributes:
            head (Optional[DoublyNode]): First node in the list.
            tail (Optional[DoublyNode]): Last node in the list.
            _size (int): Number of nodes in the list.
        """
        self.head: Optional[DoublyNode] = None
        self.tail: Optional[DoublyNode] = None
        self._size: int = 0

    def __len__(self) -> int:
        """Returns the number of nodes in the list.

        Returns:
            int: The size of the list.
        """
        return self._size

    def __iter__(self) -> Iterator[Any]:
        """Iterates through the list from head to tail.

        Yields:
            Any: The data stored in each node.
        """
        current = self.head
        while current:
            yield current.data
            current = current.next

    def is_empty(self) -> bool:
        """Checks whether the list is empty.

        Returns:
            bool: True if the list contains no elements.
        """
        return self.head is None

    def append(self, data: Any) -> None:
        """Appends a node to the end of the list.

        This operation runs in O(1) time.

        Args:
            data (Any): The value to append.
        """
        new_node = DoublyNode(data)

        if not self.head:
            self.head = new_node
            self.tail = new_node
        else:
            new_node.prev = self.tail
            if self.tail:
                self.tail.next = new_node
            self.tail = new_node

        self._size += 1

    def prepend(self, data: Any) -> None:
        """Inserts a node at the beginning of the list.

        This operation runs in O(1) time.

        Args:
            data (Any): The value to prepend.
        """
        new_node = DoublyNode(data)

        if not self.head:
            self.head = new_node
            self.tail = new_node
        else:
            new_node.next = self.head
            self.head.prev = new_node
            self.head = new_node

        self._size += 1

    def remove(self, data: Any) -> bool:
        """Removes the first occurrence of a specific value.

        This operation runs in O(N) time due to linear search.

        Args:
            data (Any): The value to remove.

        Returns:
            bool: True if a node was removed, otherwise False.
        """
        current = self.head

        while current:
            if current.data == data:
                # Update previous link
                if current.prev:
                    current.prev.next = current.next
                else:
                    self.head = current.next

                # Update next link
                if current.next:
                    current.next.prev = current.prev
                else:
                    self.tail = current.prev

                self._size -= 1
                return True

            current = current.next

        return False

    def display_forward(self) -> str:
        """Returns a string representation from head to tail.

        Returns:
            str: A formatted representation of the list.
        """
        elements = [str(data) for data in self]
        return " <-> ".join(elements) if elements else "EMPTY"
__init__()

Initializes an empty doubly linked list.

Attributes:

Name Type Description
head Optional[DoublyNode]

First node in the list.

tail Optional[DoublyNode]

Last node in the list.

_size int

Number of nodes in the list.

Source code in src/alnoms/dsa/structures/doubly_linked_list.py
def __init__(self):
    """Initializes an empty doubly linked list.

    Attributes:
        head (Optional[DoublyNode]): First node in the list.
        tail (Optional[DoublyNode]): Last node in the list.
        _size (int): Number of nodes in the list.
    """
    self.head: Optional[DoublyNode] = None
    self.tail: Optional[DoublyNode] = None
    self._size: int = 0
__iter__()

Iterates through the list from head to tail.

Yields:

Name Type Description
Any Any

The data stored in each node.

Source code in src/alnoms/dsa/structures/doubly_linked_list.py
def __iter__(self) -> Iterator[Any]:
    """Iterates through the list from head to tail.

    Yields:
        Any: The data stored in each node.
    """
    current = self.head
    while current:
        yield current.data
        current = current.next
__len__()

Returns the number of nodes in the list.

Returns:

Name Type Description
int int

The size of the list.

Source code in src/alnoms/dsa/structures/doubly_linked_list.py
def __len__(self) -> int:
    """Returns the number of nodes in the list.

    Returns:
        int: The size of the list.
    """
    return self._size
append(data)

Appends a node to the end of the list.

This operation runs in O(1) time.

Parameters:

Name Type Description Default
data Any

The value to append.

required
Source code in src/alnoms/dsa/structures/doubly_linked_list.py
def append(self, data: Any) -> None:
    """Appends a node to the end of the list.

    This operation runs in O(1) time.

    Args:
        data (Any): The value to append.
    """
    new_node = DoublyNode(data)

    if not self.head:
        self.head = new_node
        self.tail = new_node
    else:
        new_node.prev = self.tail
        if self.tail:
            self.tail.next = new_node
        self.tail = new_node

    self._size += 1
display_forward()

Returns a string representation from head to tail.

Returns:

Name Type Description
str str

A formatted representation of the list.

Source code in src/alnoms/dsa/structures/doubly_linked_list.py
def display_forward(self) -> str:
    """Returns a string representation from head to tail.

    Returns:
        str: A formatted representation of the list.
    """
    elements = [str(data) for data in self]
    return " <-> ".join(elements) if elements else "EMPTY"
is_empty()

Checks whether the list is empty.

Returns:

Name Type Description
bool bool

True if the list contains no elements.

Source code in src/alnoms/dsa/structures/doubly_linked_list.py
def is_empty(self) -> bool:
    """Checks whether the list is empty.

    Returns:
        bool: True if the list contains no elements.
    """
    return self.head is None
prepend(data)

Inserts a node at the beginning of the list.

This operation runs in O(1) time.

Parameters:

Name Type Description Default
data Any

The value to prepend.

required
Source code in src/alnoms/dsa/structures/doubly_linked_list.py
def prepend(self, data: Any) -> None:
    """Inserts a node at the beginning of the list.

    This operation runs in O(1) time.

    Args:
        data (Any): The value to prepend.
    """
    new_node = DoublyNode(data)

    if not self.head:
        self.head = new_node
        self.tail = new_node
    else:
        new_node.next = self.head
        self.head.prev = new_node
        self.head = new_node

    self._size += 1
remove(data)

Removes the first occurrence of a specific value.

This operation runs in O(N) time due to linear search.

Parameters:

Name Type Description Default
data Any

The value to remove.

required

Returns:

Name Type Description
bool bool

True if a node was removed, otherwise False.

Source code in src/alnoms/dsa/structures/doubly_linked_list.py
def remove(self, data: Any) -> bool:
    """Removes the first occurrence of a specific value.

    This operation runs in O(N) time due to linear search.

    Args:
        data (Any): The value to remove.

    Returns:
        bool: True if a node was removed, otherwise False.
    """
    current = self.head

    while current:
        if current.data == data:
            # Update previous link
            if current.prev:
                current.prev.next = current.next
            else:
                self.head = current.next

            # Update next link
            if current.next:
                current.next.prev = current.prev
            else:
                self.tail = current.prev

            self._size -= 1
            return True

        current = current.next

    return False

🌳 Trees & Graphs

Binary Search Tree (BST).

Provides a recursive implementation of an ordered symbol table using a binary search tree. Keys must be comparable, and the structure supports ordered operations such as min, max, floor, rank, and sorted iteration. Deletion uses Hibbard's algorithm.

Design Characteristics: - Ordered key‑value storage - In‑order traversal yields sorted keys - Hibbard deletion for node removal - Rank and selection operations supported - Worst‑case height O(N) if unbalanced

Classes:

Name Description
BinarySearchTree

Ordered symbol table implemented using a BST.

BinarySearchTree

Binary Search Tree (BST) symbol table.

Keys are maintained in sorted order. Supports search, insertion, deletion (Hibbard), and ordered operations such as min, max, and floor. All operations are implemented recursively.

Source code in src/alnoms/dsa/structures/binary_search_tree.py
class BinarySearchTree:
    """Binary Search Tree (BST) symbol table.

    Keys are maintained in sorted order. Supports search, insertion,
    deletion (Hibbard), and ordered operations such as min, max, and
    floor. All operations are implemented recursively.
    """

    def __init__(self):
        """Initializes an empty BST."""
        self._root: Optional[_Node] = None

    def size(self) -> int:
        """Returns the number of key‑value pairs in the table.

        Returns:
            int: Total number of stored entries.
        """
        return self._size(self._root)

    def _size(self, x: Optional[_Node]) -> int:
        """Returns the size of the subtree rooted at ``x``."""
        return 0 if x is None else x.size

    def is_empty(self) -> bool:
        """Checks whether the BST is empty.

        Returns:
            bool: True if empty, otherwise False.
        """
        return self.size() == 0

    def get(self, key: Any) -> Optional[Any]:
        """Returns the value associated with ``key``.

        Args:
            key (Any): Key to search for.

        Returns:
            Optional[Any]: Value if found, otherwise None.
        """
        return self._get(self._root, key)

    def _get(self, x: Optional[_Node], key: Any) -> Optional[Any]:
        if x is None:
            return None

        if key < x.key:
            return self._get(x.left, key)
        if key > x.key:
            return self._get(x.right, key)
        return x.val

    def contains(self, key: Any) -> bool:
        """Checks whether the BST contains ``key``.

        Returns:
            bool: True if present, otherwise False.
        """
        return self.get(key) is not None

    def put(self, key: Any, val: Any) -> None:
        """Inserts or updates a key‑value pair.

        If ``val`` is None, the key is deleted.

        Args:
            key (Any): Key to insert.
            val (Any): Value to associate.
        """
        if val is None:
            self.delete(key)
            return
        self._root = self._put(self._root, key, val)

    def _put(self, x: Optional[_Node], key: Any, val: Any) -> _Node:
        if x is None:
            return _Node(key, val, 1)

        if key < x.key:
            x.left = self._put(x.left, key, val)
        elif key > x.key:
            x.right = self._put(x.right, key, val)
        else:
            x.val = val

        x.size = 1 + self._size(x.left) + self._size(x.right)
        return x

    def min(self) -> Any:
        """Returns the smallest key.

        Raises:
            ValueError: If the BST is empty.
        """
        if self.is_empty():
            raise ValueError("min() called on empty BST")
        return self._min(self._root).key

    def _min(self, x: _Node) -> _Node:
        return x if x.left is None else self._min(x.left)

    def max(self) -> Any:
        """Returns the largest key.

        Raises:
            ValueError: If the BST is empty.
        """
        if self.is_empty():
            raise ValueError("max() called on empty BST")
        return self._max(self._root).key

    def _max(self, x: _Node) -> _Node:
        return x if x.right is None else self._max(x.right)

    def floor(self, key: Any) -> Optional[Any]:
        """Returns the largest key ≀ ``key``.

        Args:
            key (Any): Target key.

        Returns:
            Optional[Any]: Floor key, or None if no such key exists.
        """
        if self.is_empty():
            return None
        x = self._floor(self._root, key)
        return None if x is None else x.key

    def _floor(self, x: Optional[_Node], key: Any) -> Optional[_Node]:
        if x is None:
            return None

        if key == x.key:
            return x
        if key < x.key:
            return self._floor(x.left, key)

        t = self._floor(x.right, key)
        return t if t is not None else x

    def delete_min(self) -> None:
        """Deletes the smallest key."""
        if self.is_empty():
            raise ValueError("delete_min() called on empty BST")
        self._root = self._delete_min(self._root)

    def _delete_min(self, x: _Node) -> Optional[_Node]:
        if x.left is None:
            return x.right
        x.left = self._delete_min(x.left)
        x.size = 1 + self._size(x.left) + self._size(x.right)
        return x

    def delete(self, key: Any) -> None:
        """Deletes ``key`` from the BST using Hibbard deletion."""
        if not self.contains(key):
            return
        self._root = self._delete(self._root, key)

    def _delete(self, x: _Node, key: Any) -> Optional[_Node]:
        if x is None:
            return None

        if key < x.key:
            x.left = self._delete(x.left, key)
        elif key > x.key:
            x.right = self._delete(x.right, key)
        else:
            # Node with one or zero children
            if x.right is None:
                return x.left
            if x.left is None:
                return x.right

            # Node with two children: replace with successor
            t = x
            x = self._min(t.right)
            x.right = self._delete_min(t.right)
            x.left = t.left

        x.size = 1 + self._size(x.left) + self._size(x.right)
        return x

    def keys(self) -> List[Any]:
        """Returns all keys in sorted order.

        Returns:
            List[Any]: Sorted list of keys.
        """
        result: List[Any] = []
        self._keys(self._root, result)
        return result

    def _keys(self, x: Optional[_Node], result: List[Any]) -> None:
        if x is None:
            return
        self._keys(x.left, result)
        result.append(x.key)
        self._keys(x.right, result)
__init__()

Initializes an empty BST.

Source code in src/alnoms/dsa/structures/binary_search_tree.py
def __init__(self):
    """Initializes an empty BST."""
    self._root: Optional[_Node] = None
contains(key)

Checks whether the BST contains key.

Returns:

Name Type Description
bool bool

True if present, otherwise False.

Source code in src/alnoms/dsa/structures/binary_search_tree.py
def contains(self, key: Any) -> bool:
    """Checks whether the BST contains ``key``.

    Returns:
        bool: True if present, otherwise False.
    """
    return self.get(key) is not None
delete(key)

Deletes key from the BST using Hibbard deletion.

Source code in src/alnoms/dsa/structures/binary_search_tree.py
def delete(self, key: Any) -> None:
    """Deletes ``key`` from the BST using Hibbard deletion."""
    if not self.contains(key):
        return
    self._root = self._delete(self._root, key)
delete_min()

Deletes the smallest key.

Source code in src/alnoms/dsa/structures/binary_search_tree.py
def delete_min(self) -> None:
    """Deletes the smallest key."""
    if self.is_empty():
        raise ValueError("delete_min() called on empty BST")
    self._root = self._delete_min(self._root)
floor(key)

Returns the largest key ≀ key.

Parameters:

Name Type Description Default
key Any

Target key.

required

Returns:

Type Description
Optional[Any]

Optional[Any]: Floor key, or None if no such key exists.

Source code in src/alnoms/dsa/structures/binary_search_tree.py
def floor(self, key: Any) -> Optional[Any]:
    """Returns the largest key ≀ ``key``.

    Args:
        key (Any): Target key.

    Returns:
        Optional[Any]: Floor key, or None if no such key exists.
    """
    if self.is_empty():
        return None
    x = self._floor(self._root, key)
    return None if x is None else x.key
get(key)

Returns the value associated with key.

Parameters:

Name Type Description Default
key Any

Key to search for.

required

Returns:

Type Description
Optional[Any]

Optional[Any]: Value if found, otherwise None.

Source code in src/alnoms/dsa/structures/binary_search_tree.py
def get(self, key: Any) -> Optional[Any]:
    """Returns the value associated with ``key``.

    Args:
        key (Any): Key to search for.

    Returns:
        Optional[Any]: Value if found, otherwise None.
    """
    return self._get(self._root, key)
is_empty()

Checks whether the BST is empty.

Returns:

Name Type Description
bool bool

True if empty, otherwise False.

Source code in src/alnoms/dsa/structures/binary_search_tree.py
def is_empty(self) -> bool:
    """Checks whether the BST is empty.

    Returns:
        bool: True if empty, otherwise False.
    """
    return self.size() == 0
keys()

Returns all keys in sorted order.

Returns:

Type Description
List[Any]

List[Any]: Sorted list of keys.

Source code in src/alnoms/dsa/structures/binary_search_tree.py
def keys(self) -> List[Any]:
    """Returns all keys in sorted order.

    Returns:
        List[Any]: Sorted list of keys.
    """
    result: List[Any] = []
    self._keys(self._root, result)
    return result
max()

Returns the largest key.

Raises:

Type Description
ValueError

If the BST is empty.

Source code in src/alnoms/dsa/structures/binary_search_tree.py
def max(self) -> Any:
    """Returns the largest key.

    Raises:
        ValueError: If the BST is empty.
    """
    if self.is_empty():
        raise ValueError("max() called on empty BST")
    return self._max(self._root).key
min()

Returns the smallest key.

Raises:

Type Description
ValueError

If the BST is empty.

Source code in src/alnoms/dsa/structures/binary_search_tree.py
def min(self) -> Any:
    """Returns the smallest key.

    Raises:
        ValueError: If the BST is empty.
    """
    if self.is_empty():
        raise ValueError("min() called on empty BST")
    return self._min(self._root).key
put(key, val)

Inserts or updates a key‑value pair.

If val is None, the key is deleted.

Parameters:

Name Type Description Default
key Any

Key to insert.

required
val Any

Value to associate.

required
Source code in src/alnoms/dsa/structures/binary_search_tree.py
def put(self, key: Any, val: Any) -> None:
    """Inserts or updates a key‑value pair.

    If ``val`` is None, the key is deleted.

    Args:
        key (Any): Key to insert.
        val (Any): Value to associate.
    """
    if val is None:
        self.delete(key)
        return
    self._root = self._put(self._root, key, val)
size()

Returns the number of key‑value pairs in the table.

Returns:

Name Type Description
int int

Total number of stored entries.

Source code in src/alnoms/dsa/structures/binary_search_tree.py
def size(self) -> int:
    """Returns the number of key‑value pairs in the table.

    Returns:
        int: Total number of stored entries.
    """
    return self._size(self._root)

Graph Data Structures.

Provides a lightweight, adjacency‑list–based undirected graph optimized for algorithmic experimentation, teaching, and benchmarking. The graph supports efficient edge insertion and adjacency iteration while maintaining predictable memory usage proportional to V + E.

Design Characteristics: - Representation: Adjacency lists (space complexity O(V + E)). - Performance: - Add edge: O(1) - Iterate adjacency: O(degree(v)) - Check membership: O(degree(v)) - Self‑loops and parallel edges are permitted by default.

Classes:

Name Description
Graph

Undirected graph implemented using adjacency lists.

Graph

Undirected graph implemented using adjacency lists.

Each vertex maintains a list of its adjacent vertices. The structure is optimized for sparse graphs and supports efficient adjacency iteration and constant‑time edge insertion.

Source code in src/alnoms/dsa/structures/graphs.py
class Graph:
    """Undirected graph implemented using adjacency lists.

    Each vertex maintains a list of its adjacent vertices. The structure
    is optimized for sparse graphs and supports efficient adjacency
    iteration and constant‑time edge insertion.
    """

    def __init__(self, V: int):
        """Initializes an empty graph with ``V`` vertices and zero edges.

        Args:
            V (int): Number of vertices in the graph.

        Raises:
            ValueError: If ``V`` is negative.
        """
        if V < 0:
            raise ValueError("Number of vertices must be non-negative")

        self._V = V
        self._E = 0
        self._adj: List[List[int]] = [[] for _ in range(V)]

    def V(self) -> int:
        """Returns the number of vertices in the graph.

        Returns:
            int: Total vertex count.
        """
        return self._V

    def E(self) -> int:
        """Returns the number of edges in the graph.

        Returns:
            int: Total edge count.
        """
        return self._E

    def add_edge(self, v: int, w: int) -> None:
        """Adds an undirected edge between vertices ``v`` and ``w``.

        Both vertices must be valid indices in the range ``0`` to
        ``V - 1``. Parallel edges and self‑loops are allowed.

        Args:
            v (int): First vertex.
            w (int): Second vertex.

        Raises:
            IndexError: If either vertex index is out of bounds.
        """
        self._validate_vertex(v)
        self._validate_vertex(w)

        self._adj[v].append(w)
        self._adj[w].append(v)
        self._E += 1

    def adj(self, v: int) -> Iterable[int]:
        """Returns the vertices adjacent to ``v``.

        Args:
            v (int): The vertex whose neighbors are requested.

        Returns:
            Iterable[int]: A list of adjacent vertices.
        """
        self._validate_vertex(v)
        return self._adj[v]

    def degree(self, v: int) -> int:
        """Returns the degree of vertex ``v``.

        Args:
            v (int): The vertex whose degree is requested.

        Returns:
            int: Number of adjacent vertices.
        """
        self._validate_vertex(v)
        return len(self._adj[v])

    def _validate_vertex(self, v: int) -> None:
        """Validates that ``v`` is a legal vertex index.

        Args:
            v (int): Vertex index to validate.

        Raises:
            IndexError: If ``v`` is outside the valid range.
        """
        if v < 0 or v >= self._V:
            raise IndexError(f"Vertex {v} is not between 0 and {self._V - 1}")

    def __repr__(self) -> str:
        """Returns a string representation of the graph.

        The format lists each vertex followed by its adjacency list.

        Returns:
            str: Human‑readable representation of the graph.
        """
        lines = [f"{self._V} vertices, {self._E} edges\n"]
        for v in range(self._V):
            neighbors = " ".join(str(w) for w in self._adj[v])
            lines.append(f"{v}: {neighbors}\n")
        return "".join(lines)
E()

Returns the number of edges in the graph.

Returns:

Name Type Description
int int

Total edge count.

Source code in src/alnoms/dsa/structures/graphs.py
def E(self) -> int:
    """Returns the number of edges in the graph.

    Returns:
        int: Total edge count.
    """
    return self._E
V()

Returns the number of vertices in the graph.

Returns:

Name Type Description
int int

Total vertex count.

Source code in src/alnoms/dsa/structures/graphs.py
def V(self) -> int:
    """Returns the number of vertices in the graph.

    Returns:
        int: Total vertex count.
    """
    return self._V
__init__(V)

Initializes an empty graph with V vertices and zero edges.

Parameters:

Name Type Description Default
V int

Number of vertices in the graph.

required

Raises:

Type Description
ValueError

If V is negative.

Source code in src/alnoms/dsa/structures/graphs.py
def __init__(self, V: int):
    """Initializes an empty graph with ``V`` vertices and zero edges.

    Args:
        V (int): Number of vertices in the graph.

    Raises:
        ValueError: If ``V`` is negative.
    """
    if V < 0:
        raise ValueError("Number of vertices must be non-negative")

    self._V = V
    self._E = 0
    self._adj: List[List[int]] = [[] for _ in range(V)]
__repr__()

Returns a string representation of the graph.

The format lists each vertex followed by its adjacency list.

Returns:

Name Type Description
str str

Human‑readable representation of the graph.

Source code in src/alnoms/dsa/structures/graphs.py
def __repr__(self) -> str:
    """Returns a string representation of the graph.

    The format lists each vertex followed by its adjacency list.

    Returns:
        str: Human‑readable representation of the graph.
    """
    lines = [f"{self._V} vertices, {self._E} edges\n"]
    for v in range(self._V):
        neighbors = " ".join(str(w) for w in self._adj[v])
        lines.append(f"{v}: {neighbors}\n")
    return "".join(lines)
add_edge(v, w)

Adds an undirected edge between vertices v and w.

Both vertices must be valid indices in the range 0 to V - 1. Parallel edges and self‑loops are allowed.

Parameters:

Name Type Description Default
v int

First vertex.

required
w int

Second vertex.

required

Raises:

Type Description
IndexError

If either vertex index is out of bounds.

Source code in src/alnoms/dsa/structures/graphs.py
def add_edge(self, v: int, w: int) -> None:
    """Adds an undirected edge between vertices ``v`` and ``w``.

    Both vertices must be valid indices in the range ``0`` to
    ``V - 1``. Parallel edges and self‑loops are allowed.

    Args:
        v (int): First vertex.
        w (int): Second vertex.

    Raises:
        IndexError: If either vertex index is out of bounds.
    """
    self._validate_vertex(v)
    self._validate_vertex(w)

    self._adj[v].append(w)
    self._adj[w].append(v)
    self._E += 1
adj(v)

Returns the vertices adjacent to v.

Parameters:

Name Type Description Default
v int

The vertex whose neighbors are requested.

required

Returns:

Type Description
Iterable[int]

Iterable[int]: A list of adjacent vertices.

Source code in src/alnoms/dsa/structures/graphs.py
def adj(self, v: int) -> Iterable[int]:
    """Returns the vertices adjacent to ``v``.

    Args:
        v (int): The vertex whose neighbors are requested.

    Returns:
        Iterable[int]: A list of adjacent vertices.
    """
    self._validate_vertex(v)
    return self._adj[v]
degree(v)

Returns the degree of vertex v.

Parameters:

Name Type Description Default
v int

The vertex whose degree is requested.

required

Returns:

Name Type Description
int int

Number of adjacent vertices.

Source code in src/alnoms/dsa/structures/graphs.py
def degree(self, v: int) -> int:
    """Returns the degree of vertex ``v``.

    Args:
        v (int): The vertex whose degree is requested.

    Returns:
        int: Number of adjacent vertices.
    """
    self._validate_vertex(v)
    return len(self._adj[v])

Directed Graph (Digraph).

Defines a lightweight adjacency‑list–based directed graph. Each vertex maintains a list of outgoing edges, and indegree counts are tracked explicitly. The structure supports efficient adjacency iteration, constant‑time edge insertion, and construction of the graph's reverse.

Design Characteristics: - Representation: Adjacency lists of integers. - Performance: - Add edge: O(1) - Iterate outgoing edges: O(outdegree(v)) - Compute reverse graph: O(V + E) - Self‑loops and parallel edges are permitted.

Classes:

Name Description
Digraph

Directed graph with adjacency lists and indegree tracking.

Digraph

Directed graph implemented using adjacency lists.

Each vertex maintains a list of outgoing edges. Indegree counts are stored explicitly to support algorithms that rely on incoming edge information. The structure is suitable for topological sorting, SCC algorithms, and general directed graph processing.

Source code in src/alnoms/dsa/structures/digraph.py
class Digraph:
    """Directed graph implemented using adjacency lists.

    Each vertex maintains a list of outgoing edges. Indegree counts are
    stored explicitly to support algorithms that rely on incoming edge
    information. The structure is suitable for topological sorting,
    SCC algorithms, and general directed graph processing.
    """

    def __init__(self, V: int):
        """Initializes an empty digraph with ``V`` vertices.

        Args:
            V (int): Number of vertices.

        Raises:
            ValueError: If ``V`` is negative.
        """
        if V < 0:
            raise ValueError("Number of vertices must be non-negative")

        self._V = V
        self._E = 0
        self._adj: List[List[int]] = [[] for _ in range(V)]
        self._indegree: List[int] = [0] * V

    def V(self) -> int:
        """Returns the number of vertices in the digraph.

        Returns:
            int: Total vertex count.
        """
        return self._V

    def E(self) -> int:
        """Returns the number of edges in the digraph.

        Returns:
            int: Total edge count.
        """
        return self._E

    def add_edge(self, v: int, w: int) -> None:
        """Adds a directed edge from ``v`` to ``w``.

        Args:
            v (int): Source vertex.
            w (int): Destination vertex.

        Raises:
            IndexError: If either vertex index is out of bounds.
        """
        self._validate_vertex(v)
        self._validate_vertex(w)

        self._adj[v].append(w)
        self._indegree[w] += 1
        self._E += 1

    def adj(self, v: int) -> Iterable[int]:
        """Returns all vertices reachable from ``v`` via outgoing edges.

        Args:
            v (int): The source vertex.

        Returns:
            Iterable[int]: Outgoing neighbors of ``v``.
        """
        self._validate_vertex(v)
        return self._adj[v]

    def out_degree(self, v: int) -> int:
        """Returns the number of edges leaving vertex ``v``.

        Args:
            v (int): The vertex.

        Returns:
            int: Outdegree of ``v``.
        """
        self._validate_vertex(v)
        return len(self._adj[v])

    def in_degree(self, v: int) -> int:
        """Returns the number of edges entering vertex ``v``.

        Args:
            v (int): The vertex.

        Returns:
            int: Indegree of ``v``.
        """
        self._validate_vertex(v)
        return self._indegree[v]

    def reverse(self) -> "Digraph":
        """Returns a new digraph with all edges reversed.

        Useful for algorithms such as strongly connected components (SCC)
        where reverse graph traversal is required.

        Returns:
            Digraph: A new digraph with reversed edge directions.
        """
        R = Digraph(self._V)
        for v in range(self._V):
            for w in self._adj[v]:
                R.add_edge(w, v)
        return R

    def _validate_vertex(self, v: int) -> None:
        """Validates that ``v`` is a legal vertex index.

        Args:
            v (int): Vertex index to validate.

        Raises:
            IndexError: If ``v`` is outside the valid range.
        """
        if v < 0 or v >= self._V:
            raise IndexError(f"Vertex {v} is not between 0 and {self._V - 1}")
E()

Returns the number of edges in the digraph.

Returns:

Name Type Description
int int

Total edge count.

Source code in src/alnoms/dsa/structures/digraph.py
def E(self) -> int:
    """Returns the number of edges in the digraph.

    Returns:
        int: Total edge count.
    """
    return self._E
V()

Returns the number of vertices in the digraph.

Returns:

Name Type Description
int int

Total vertex count.

Source code in src/alnoms/dsa/structures/digraph.py
def V(self) -> int:
    """Returns the number of vertices in the digraph.

    Returns:
        int: Total vertex count.
    """
    return self._V
__init__(V)

Initializes an empty digraph with V vertices.

Parameters:

Name Type Description Default
V int

Number of vertices.

required

Raises:

Type Description
ValueError

If V is negative.

Source code in src/alnoms/dsa/structures/digraph.py
def __init__(self, V: int):
    """Initializes an empty digraph with ``V`` vertices.

    Args:
        V (int): Number of vertices.

    Raises:
        ValueError: If ``V`` is negative.
    """
    if V < 0:
        raise ValueError("Number of vertices must be non-negative")

    self._V = V
    self._E = 0
    self._adj: List[List[int]] = [[] for _ in range(V)]
    self._indegree: List[int] = [0] * V
add_edge(v, w)

Adds a directed edge from v to w.

Parameters:

Name Type Description Default
v int

Source vertex.

required
w int

Destination vertex.

required

Raises:

Type Description
IndexError

If either vertex index is out of bounds.

Source code in src/alnoms/dsa/structures/digraph.py
def add_edge(self, v: int, w: int) -> None:
    """Adds a directed edge from ``v`` to ``w``.

    Args:
        v (int): Source vertex.
        w (int): Destination vertex.

    Raises:
        IndexError: If either vertex index is out of bounds.
    """
    self._validate_vertex(v)
    self._validate_vertex(w)

    self._adj[v].append(w)
    self._indegree[w] += 1
    self._E += 1
adj(v)

Returns all vertices reachable from v via outgoing edges.

Parameters:

Name Type Description Default
v int

The source vertex.

required

Returns:

Type Description
Iterable[int]

Iterable[int]: Outgoing neighbors of v.

Source code in src/alnoms/dsa/structures/digraph.py
def adj(self, v: int) -> Iterable[int]:
    """Returns all vertices reachable from ``v`` via outgoing edges.

    Args:
        v (int): The source vertex.

    Returns:
        Iterable[int]: Outgoing neighbors of ``v``.
    """
    self._validate_vertex(v)
    return self._adj[v]
in_degree(v)

Returns the number of edges entering vertex v.

Parameters:

Name Type Description Default
v int

The vertex.

required

Returns:

Name Type Description
int int

Indegree of v.

Source code in src/alnoms/dsa/structures/digraph.py
def in_degree(self, v: int) -> int:
    """Returns the number of edges entering vertex ``v``.

    Args:
        v (int): The vertex.

    Returns:
        int: Indegree of ``v``.
    """
    self._validate_vertex(v)
    return self._indegree[v]
out_degree(v)

Returns the number of edges leaving vertex v.

Parameters:

Name Type Description Default
v int

The vertex.

required

Returns:

Name Type Description
int int

Outdegree of v.

Source code in src/alnoms/dsa/structures/digraph.py
def out_degree(self, v: int) -> int:
    """Returns the number of edges leaving vertex ``v``.

    Args:
        v (int): The vertex.

    Returns:
        int: Outdegree of ``v``.
    """
    self._validate_vertex(v)
    return len(self._adj[v])
reverse()

Returns a new digraph with all edges reversed.

Useful for algorithms such as strongly connected components (SCC) where reverse graph traversal is required.

Returns:

Name Type Description
Digraph Digraph

A new digraph with reversed edge directions.

Source code in src/alnoms/dsa/structures/digraph.py
def reverse(self) -> "Digraph":
    """Returns a new digraph with all edges reversed.

    Useful for algorithms such as strongly connected components (SCC)
    where reverse graph traversal is required.

    Returns:
        Digraph: A new digraph with reversed edge directions.
    """
    R = Digraph(self._V)
    for v in range(self._V):
        for w in self._adj[v]:
            R.add_edge(w, v)
    return R

Edge‑Weighted Graph.

Provides an undirected graph where each edge carries an associated weight. This structure is used in classical graph algorithms such as Minimum Spanning Trees (MST) and Shortest Paths. The graph is stored using adjacency lists, with each vertex maintaining a list of incident weighted edges.

Design Characteristics: - Representation: Adjacency lists of Edge objects. - Performance: - Add edge: O(1) - Iterate adjacency: O(degree(v)) - Parallel edges and self‑loops are permitted.

Classes:

Name Description
EdgeWeightedGraph

Undirected weighted graph backed by adjacency lists.

EdgeWeightedGraph

Undirected weighted graph implemented using adjacency lists.

Each vertex maintains a list of incident Edge objects. The graph supports efficient adjacency iteration and constant‑time edge insertion. This structure is suitable for MST and shortest‑path algorithms that operate on weighted graphs.

Source code in src/alnoms/dsa/structures/edge_weighted_graph.py
class EdgeWeightedGraph:
    """Undirected weighted graph implemented using adjacency lists.

    Each vertex maintains a list of incident ``Edge`` objects. The graph
    supports efficient adjacency iteration and constant‑time edge
    insertion. This structure is suitable for MST and shortest‑path
    algorithms that operate on weighted graphs.
    """

    def __init__(self, V: int):
        """Initializes an empty weighted graph with ``V`` vertices.

        Args:
            V (int): Number of vertices.

        Raises:
            ValueError: If ``V`` is negative.
        """
        if V < 0:
            raise ValueError("Number of vertices must be non-negative")

        self._V = V
        self._E = 0
        self._adj: List[List[Edge]] = [[] for _ in range(V)]

    def V(self) -> int:
        """Returns the number of vertices in the graph.

        Returns:
            int: Total vertex count.
        """
        return self._V

    def E(self) -> int:
        """Returns the number of edges in the graph.

        Returns:
            int: Total edge count.
        """
        return self._E

    def add_edge(self, e: Edge) -> None:
        """Adds a weighted undirected edge to the graph.

        The edge is inserted into the adjacency lists of both endpoints.
        Parallel edges and self‑loops are allowed.

        Args:
            e (Edge): The edge to add.

        Raises:
            IndexError: If either endpoint is out of bounds.
        """
        v = e.either()
        w = e.other(v)

        self._validate_vertex(v)
        self._validate_vertex(w)

        self._adj[v].append(e)
        self._adj[w].append(e)
        self._E += 1

    def adj(self, v: int) -> Iterable[Edge]:
        """Returns all weighted edges incident to vertex ``v``.

        Args:
            v (int): The vertex whose incident edges are requested.

        Returns:
            Iterable[Edge]: List of edges adjacent to ``v``.
        """
        self._validate_vertex(v)
        return self._adj[v]

    def edges(self) -> Iterable[Edge]:
        """Returns all edges in the graph without duplicates.

        For each undirected edge, only the instance where the opposite
        endpoint is greater than the current vertex is included.

        Returns:
            Iterable[Edge]: All unique edges in the graph.
        """
        all_edges = []
        for v in range(self._V):
            for e in self._adj[v]:
                if e.other(v) > v:
                    all_edges.append(e)
        return all_edges

    def _validate_vertex(self, v: int) -> None:
        """Validates that ``v`` is a legal vertex index.

        Args:
            v (int): Vertex index to validate.

        Raises:
            IndexError: If ``v`` is outside the valid range.
        """
        if v < 0 or v >= self._V:
            raise IndexError(f"Vertex {v} is not between 0 and {self._V - 1}")
E()

Returns the number of edges in the graph.

Returns:

Name Type Description
int int

Total edge count.

Source code in src/alnoms/dsa/structures/edge_weighted_graph.py
def E(self) -> int:
    """Returns the number of edges in the graph.

    Returns:
        int: Total edge count.
    """
    return self._E
V()

Returns the number of vertices in the graph.

Returns:

Name Type Description
int int

Total vertex count.

Source code in src/alnoms/dsa/structures/edge_weighted_graph.py
def V(self) -> int:
    """Returns the number of vertices in the graph.

    Returns:
        int: Total vertex count.
    """
    return self._V
__init__(V)

Initializes an empty weighted graph with V vertices.

Parameters:

Name Type Description Default
V int

Number of vertices.

required

Raises:

Type Description
ValueError

If V is negative.

Source code in src/alnoms/dsa/structures/edge_weighted_graph.py
def __init__(self, V: int):
    """Initializes an empty weighted graph with ``V`` vertices.

    Args:
        V (int): Number of vertices.

    Raises:
        ValueError: If ``V`` is negative.
    """
    if V < 0:
        raise ValueError("Number of vertices must be non-negative")

    self._V = V
    self._E = 0
    self._adj: List[List[Edge]] = [[] for _ in range(V)]
add_edge(e)

Adds a weighted undirected edge to the graph.

The edge is inserted into the adjacency lists of both endpoints. Parallel edges and self‑loops are allowed.

Parameters:

Name Type Description Default
e Edge

The edge to add.

required

Raises:

Type Description
IndexError

If either endpoint is out of bounds.

Source code in src/alnoms/dsa/structures/edge_weighted_graph.py
def add_edge(self, e: Edge) -> None:
    """Adds a weighted undirected edge to the graph.

    The edge is inserted into the adjacency lists of both endpoints.
    Parallel edges and self‑loops are allowed.

    Args:
        e (Edge): The edge to add.

    Raises:
        IndexError: If either endpoint is out of bounds.
    """
    v = e.either()
    w = e.other(v)

    self._validate_vertex(v)
    self._validate_vertex(w)

    self._adj[v].append(e)
    self._adj[w].append(e)
    self._E += 1
adj(v)

Returns all weighted edges incident to vertex v.

Parameters:

Name Type Description Default
v int

The vertex whose incident edges are requested.

required

Returns:

Type Description
Iterable[Edge]

Iterable[Edge]: List of edges adjacent to v.

Source code in src/alnoms/dsa/structures/edge_weighted_graph.py
def adj(self, v: int) -> Iterable[Edge]:
    """Returns all weighted edges incident to vertex ``v``.

    Args:
        v (int): The vertex whose incident edges are requested.

    Returns:
        Iterable[Edge]: List of edges adjacent to ``v``.
    """
    self._validate_vertex(v)
    return self._adj[v]
edges()

Returns all edges in the graph without duplicates.

For each undirected edge, only the instance where the opposite endpoint is greater than the current vertex is included.

Returns:

Type Description
Iterable[Edge]

Iterable[Edge]: All unique edges in the graph.

Source code in src/alnoms/dsa/structures/edge_weighted_graph.py
def edges(self) -> Iterable[Edge]:
    """Returns all edges in the graph without duplicates.

    For each undirected edge, only the instance where the opposite
    endpoint is greater than the current vertex is included.

    Returns:
        Iterable[Edge]: All unique edges in the graph.
    """
    all_edges = []
    for v in range(self._V):
        for e in self._adj[v]:
            if e.other(v) > v:
                all_edges.append(e)
    return all_edges

Edge‑Weighted Directed Graph.

Provides a directed graph where each edge carries an associated weight. The structure is implemented using adjacency lists, with each vertex maintaining a list of outgoing DirectedEdge objects. This design supports efficient adjacency iteration and constant‑time edge insertion.

Design Characteristics: - Representation: Adjacency lists of DirectedEdge objects. - Performance: - Add edge: O(1) - Iterate outgoing edges: O(outdegree(v)) - Parallel edges and self‑loops are permitted.

Classes:

Name Description
EdgeWeightedDigraph

Directed weighted graph backed by adjacency lists.

EdgeWeightedDigraph

Directed weighted graph implemented using adjacency lists.

Each vertex maintains a list of outgoing DirectedEdge objects. The structure is suitable for shortest‑path algorithms such as Dijkstra, Bellman‑Ford, and DAG relaxations.

Source code in src/alnoms/dsa/structures/edge_weighted_digraph.py
class EdgeWeightedDigraph:
    """Directed weighted graph implemented using adjacency lists.

    Each vertex maintains a list of outgoing ``DirectedEdge`` objects.
    The structure is suitable for shortest‑path algorithms such as
    Dijkstra, Bellman‑Ford, and DAG relaxations.
    """

    def __init__(self, V: int):
        """Initializes an empty edge‑weighted digraph with ``V`` vertices.

        Args:
            V (int): Number of vertices.

        Raises:
            ValueError: If ``V`` is negative.
        """
        if V < 0:
            raise ValueError("Number of vertices must be non-negative")

        self._V = V
        self._E = 0
        self._adj: List[List[DirectedEdge]] = [[] for _ in range(V)]

    def V(self) -> int:
        """Returns the number of vertices in the digraph.

        Returns:
            int: Total vertex count.
        """
        return self._V

    def E(self) -> int:
        """Returns the number of edges in the digraph.

        Returns:
            int: Total edge count.
        """
        return self._E

    def add_edge(self, e: DirectedEdge) -> None:
        """Adds a directed weighted edge to the digraph.

        The edge is inserted into the adjacency list of its source
        vertex. Parallel edges and self‑loops are allowed.

        Args:
            e (DirectedEdge): The edge to add.

        Raises:
            IndexError: If either endpoint is out of bounds.
        """
        v = e.from_vertex()
        w = e.to_vertex()

        self._validate_vertex(v)
        self._validate_vertex(w)

        self._adj[v].append(e)
        self._E += 1

    def adj(self, v: int) -> Iterable[DirectedEdge]:
        """Returns all outgoing edges from vertex ``v``.

        Args:
            v (int): The source vertex.

        Returns:
            Iterable[DirectedEdge]: Outgoing edges from ``v``.
        """
        self._validate_vertex(v)
        return self._adj[v]

    def edges(self) -> Iterable[DirectedEdge]:
        """Returns all edges in the digraph.

        Returns:
            Iterable[DirectedEdge]: All directed edges.
        """
        all_edges = []
        for v in range(self._V):
            all_edges.extend(self._adj[v])
        return all_edges

    def _validate_vertex(self, v: int) -> None:
        """Validates that ``v`` is a legal vertex index.

        Args:
            v (int): Vertex index to validate.

        Raises:
            IndexError: If ``v`` is outside the valid range.
        """
        if v < 0 or v >= self._V:
            raise IndexError(f"Vertex {v} is not between 0 and {self._V - 1}")
E()

Returns the number of edges in the digraph.

Returns:

Name Type Description
int int

Total edge count.

Source code in src/alnoms/dsa/structures/edge_weighted_digraph.py
def E(self) -> int:
    """Returns the number of edges in the digraph.

    Returns:
        int: Total edge count.
    """
    return self._E
V()

Returns the number of vertices in the digraph.

Returns:

Name Type Description
int int

Total vertex count.

Source code in src/alnoms/dsa/structures/edge_weighted_digraph.py
def V(self) -> int:
    """Returns the number of vertices in the digraph.

    Returns:
        int: Total vertex count.
    """
    return self._V
__init__(V)

Initializes an empty edge‑weighted digraph with V vertices.

Parameters:

Name Type Description Default
V int

Number of vertices.

required

Raises:

Type Description
ValueError

If V is negative.

Source code in src/alnoms/dsa/structures/edge_weighted_digraph.py
def __init__(self, V: int):
    """Initializes an empty edge‑weighted digraph with ``V`` vertices.

    Args:
        V (int): Number of vertices.

    Raises:
        ValueError: If ``V`` is negative.
    """
    if V < 0:
        raise ValueError("Number of vertices must be non-negative")

    self._V = V
    self._E = 0
    self._adj: List[List[DirectedEdge]] = [[] for _ in range(V)]
add_edge(e)

Adds a directed weighted edge to the digraph.

The edge is inserted into the adjacency list of its source vertex. Parallel edges and self‑loops are allowed.

Parameters:

Name Type Description Default
e DirectedEdge

The edge to add.

required

Raises:

Type Description
IndexError

If either endpoint is out of bounds.

Source code in src/alnoms/dsa/structures/edge_weighted_digraph.py
def add_edge(self, e: DirectedEdge) -> None:
    """Adds a directed weighted edge to the digraph.

    The edge is inserted into the adjacency list of its source
    vertex. Parallel edges and self‑loops are allowed.

    Args:
        e (DirectedEdge): The edge to add.

    Raises:
        IndexError: If either endpoint is out of bounds.
    """
    v = e.from_vertex()
    w = e.to_vertex()

    self._validate_vertex(v)
    self._validate_vertex(w)

    self._adj[v].append(e)
    self._E += 1
adj(v)

Returns all outgoing edges from vertex v.

Parameters:

Name Type Description Default
v int

The source vertex.

required

Returns:

Type Description
Iterable[DirectedEdge]

Iterable[DirectedEdge]: Outgoing edges from v.

Source code in src/alnoms/dsa/structures/edge_weighted_digraph.py
def adj(self, v: int) -> Iterable[DirectedEdge]:
    """Returns all outgoing edges from vertex ``v``.

    Args:
        v (int): The source vertex.

    Returns:
        Iterable[DirectedEdge]: Outgoing edges from ``v``.
    """
    self._validate_vertex(v)
    return self._adj[v]
edges()

Returns all edges in the digraph.

Returns:

Type Description
Iterable[DirectedEdge]

Iterable[DirectedEdge]: All directed edges.

Source code in src/alnoms/dsa/structures/edge_weighted_digraph.py
def edges(self) -> Iterable[DirectedEdge]:
    """Returns all edges in the digraph.

    Returns:
        Iterable[DirectedEdge]: All directed edges.
    """
    all_edges = []
    for v in range(self._V):
        all_edges.extend(self._adj[v])
    return all_edges

πŸ”‘ Hash Tables

Hash Table Implementations.

Provides classic symbol table implementations based on hashing, including separate chaining and (optionally) linear probing variants. Designed as textbook-grade, inspectable data structures for algorithmic education and performance governance experiments.

Features
  • Hash-based key-value storage
  • Separate chaining collision handling
  • Hashable keys, arbitrary values
  • Deterministic, reference-friendly implementation

SeparateChainingHashST

Hash table implementation using separate chaining.

This symbol table stores key–value pairs in an array of buckets, where each bucket is a Python list of (key, value) tuples. Collisions are resolved by chaining, and average search time is proportional to O(N / M) where:

β€’ ``N`` = number of key–value pairs
β€’ ``M`` = number of buckets

This implementation is simple, predictable, and suitable for workloads where hash distribution is reasonably uniform.

Source code in src/alnoms/dsa/structures/separate_chaining_hash_st.py
class SeparateChainingHashST:
    """Hash table implementation using separate chaining.

    This symbol table stores key–value pairs in an array of buckets,
    where each bucket is a Python list of ``(key, value)`` tuples.
    Collisions are resolved by chaining, and average search time is
    proportional to ``O(N / M)`` where:

        β€’ ``N`` = number of key–value pairs
        β€’ ``M`` = number of buckets

    This implementation is simple, predictable, and suitable for workloads
    where hash distribution is reasonably uniform.
    """

    def __init__(self, m: int = 997):
        """Initializes the hash table.

        Args:
            m (int): Number of buckets (chains). Defaults to 997, a prime
                number that helps reduce clustering.

        Attributes:
            _m (int): Number of buckets.
            _n (int): Number of stored key–value pairs.
            _st (List[List[Tuple[Any, Any]]]): Array of buckets.
        """
        self._m = m
        self._n = 0
        self._st: List[List[Tuple[Any, Any]]] = [[] for _ in range(m)]

    def _hash(self, key: Any) -> int:
        """Computes the bucket index for a given key.

        Args:
            key (Any): A hashable key.

        Returns:
            int: The bucket index in the range ``[0, M)``.
        """
        return (hash(key) & 0x7FFFFFFF) % self._m

    def size(self) -> int:
        """Returns the number of key–value pairs stored.

        Returns:
            int: Total number of entries.
        """
        return self._n

    def is_empty(self) -> int:
        """Checks whether the table contains any entries.

        Returns:
            bool: True if the table is empty, otherwise False.
        """
        return self._n == 0

    def contains(self, key: Any) -> bool:
        """Checks whether the table contains the given key.

        Args:
            key (Any): The key to search for.

        Returns:
            bool: True if the key exists, otherwise False.
        """
        return self.get(key) is not None

    def get(self, key: Any) -> Optional[Any]:
        """Retrieves the value associated with a key.

        Args:
            key (Any): The key to search for.

        Returns:
            Optional[Any]: The associated value if found, otherwise None.
        """
        i = self._hash(key)
        for k, v in self._st[i]:
            if k == key:
                return v
        return None

    def put(self, key: Any, val: Any) -> None:
        """Inserts or updates a key–value pair.

        If the key already exists, its value is updated.
        If ``val`` is ``None``, the key is removed.

        Args:
            key (Any): The key to insert.
            val (Any): The value to associate with the key.
        """
        if val is None:
            self.delete(key)
            return

        i = self._hash(key)

        # Update existing key
        for idx, (k, v) in enumerate(self._st[i]):
            if k == key:
                self._st[i][idx] = (key, val)
                return

        # Insert new key–value pair
        self._st[i].append((key, val))
        self._n += 1

    def delete(self, key: Any) -> None:
        """Removes a key and its associated value.

        Args:
            key (Any): The key to remove.
        """
        i = self._hash(key)
        bucket = self._st[i]

        for idx, (k, v) in enumerate(bucket):
            if k == key:
                del bucket[idx]
                self._n -= 1
                return

    def keys(self) -> List[Any]:
        """Returns all keys stored in the table.

        Returns:
            List[Any]: A list containing all keys.
        """
        all_keys = []
        for bucket in self._st:
            for k, _ in bucket:
                all_keys.append(k)
        return all_keys
__init__(m=997)

Initializes the hash table.

Parameters:

Name Type Description Default
m int

Number of buckets (chains). Defaults to 997, a prime number that helps reduce clustering.

997

Attributes:

Name Type Description
_m int

Number of buckets.

_n int

Number of stored key–value pairs.

_st List[List[Tuple[Any, Any]]]

Array of buckets.

Source code in src/alnoms/dsa/structures/separate_chaining_hash_st.py
def __init__(self, m: int = 997):
    """Initializes the hash table.

    Args:
        m (int): Number of buckets (chains). Defaults to 997, a prime
            number that helps reduce clustering.

    Attributes:
        _m (int): Number of buckets.
        _n (int): Number of stored key–value pairs.
        _st (List[List[Tuple[Any, Any]]]): Array of buckets.
    """
    self._m = m
    self._n = 0
    self._st: List[List[Tuple[Any, Any]]] = [[] for _ in range(m)]
contains(key)

Checks whether the table contains the given key.

Parameters:

Name Type Description Default
key Any

The key to search for.

required

Returns:

Name Type Description
bool bool

True if the key exists, otherwise False.

Source code in src/alnoms/dsa/structures/separate_chaining_hash_st.py
def contains(self, key: Any) -> bool:
    """Checks whether the table contains the given key.

    Args:
        key (Any): The key to search for.

    Returns:
        bool: True if the key exists, otherwise False.
    """
    return self.get(key) is not None
delete(key)

Removes a key and its associated value.

Parameters:

Name Type Description Default
key Any

The key to remove.

required
Source code in src/alnoms/dsa/structures/separate_chaining_hash_st.py
def delete(self, key: Any) -> None:
    """Removes a key and its associated value.

    Args:
        key (Any): The key to remove.
    """
    i = self._hash(key)
    bucket = self._st[i]

    for idx, (k, v) in enumerate(bucket):
        if k == key:
            del bucket[idx]
            self._n -= 1
            return
get(key)

Retrieves the value associated with a key.

Parameters:

Name Type Description Default
key Any

The key to search for.

required

Returns:

Type Description
Optional[Any]

Optional[Any]: The associated value if found, otherwise None.

Source code in src/alnoms/dsa/structures/separate_chaining_hash_st.py
def get(self, key: Any) -> Optional[Any]:
    """Retrieves the value associated with a key.

    Args:
        key (Any): The key to search for.

    Returns:
        Optional[Any]: The associated value if found, otherwise None.
    """
    i = self._hash(key)
    for k, v in self._st[i]:
        if k == key:
            return v
    return None
is_empty()

Checks whether the table contains any entries.

Returns:

Name Type Description
bool int

True if the table is empty, otherwise False.

Source code in src/alnoms/dsa/structures/separate_chaining_hash_st.py
def is_empty(self) -> int:
    """Checks whether the table contains any entries.

    Returns:
        bool: True if the table is empty, otherwise False.
    """
    return self._n == 0
keys()

Returns all keys stored in the table.

Returns:

Type Description
List[Any]

List[Any]: A list containing all keys.

Source code in src/alnoms/dsa/structures/separate_chaining_hash_st.py
def keys(self) -> List[Any]:
    """Returns all keys stored in the table.

    Returns:
        List[Any]: A list containing all keys.
    """
    all_keys = []
    for bucket in self._st:
        for k, _ in bucket:
            all_keys.append(k)
    return all_keys
put(key, val)

Inserts or updates a key–value pair.

If the key already exists, its value is updated. If val is None, the key is removed.

Parameters:

Name Type Description Default
key Any

The key to insert.

required
val Any

The value to associate with the key.

required
Source code in src/alnoms/dsa/structures/separate_chaining_hash_st.py
def put(self, key: Any, val: Any) -> None:
    """Inserts or updates a key–value pair.

    If the key already exists, its value is updated.
    If ``val`` is ``None``, the key is removed.

    Args:
        key (Any): The key to insert.
        val (Any): The value to associate with the key.
    """
    if val is None:
        self.delete(key)
        return

    i = self._hash(key)

    # Update existing key
    for idx, (k, v) in enumerate(self._st[i]):
        if k == key:
            self._st[i][idx] = (key, val)
            return

    # Insert new key–value pair
    self._st[i].append((key, val))
    self._n += 1
size()

Returns the number of key–value pairs stored.

Returns:

Name Type Description
int int

Total number of entries.

Source code in src/alnoms/dsa/structures/separate_chaining_hash_st.py
def size(self) -> int:
    """Returns the number of key–value pairs stored.

    Returns:
        int: Total number of entries.
    """
    return self._n

πŸ•΅οΈ Pattern Heuristics & Fixes (alnoms.patterns & alnoms.fixes)

The internal diagnostic rule engine that identifies computational bottlenecks.

πŸ” Detectors

Orchestrates AST analysis using the registered pattern detectors.

The engine loads all OSS detectors by default and conditionally extends the registry with PRO and ENTERPRISE detectors based on environment feature flags. Each detector is responsible for identifying a specific algorithmic anti‑pattern.

This class exposes a single static method, analyze_code, which parses the file into an AST and dispatches it to all registered detectors.

Source code in src/alnoms/patterns/heuristics.py
class HeuristicsEngine:
    """Orchestrates AST analysis using the registered pattern detectors.

    The engine loads all OSS detectors by default and conditionally extends
    the registry with PRO and ENTERPRISE detectors based on environment
    feature flags. Each detector is responsible for identifying a specific
    algorithmic anti‑pattern.

    This class exposes a single static method, `analyze_code`, which parses
    the file into an AST and dispatches it to all registered detectors.
    """

    @staticmethod
    def analyze_code(path: str) -> List[Dict[str, Any]]:
        """Analyze a Python file using all registered pattern detectors.

        Args:
            path (str): Path to the Python source file.

        Returns:
            List[Dict[str, Any]]:
                A flat list of findings from all detectors. Each finding is a
                dictionary containing detector‑specific metadata such as:
                    - "function": Function where the issue occurs
                    - "pattern_id": Identifier of the detector
                    - "issue": Description of the anti‑pattern
                    - "line": Line number of the issue
                    - Additional detector‑specific fields

        Notes:
            - Empty files return an empty list.
            - Syntax errors or unexpected exceptions are captured and returned
              as a single file‑level finding rather than raising an exception.
        """
        try:
            with open(path, "r", encoding="utf-8", errors="ignore") as f:
                source = f.read()
                if not source.strip():
                    return []
                tree = ast.parse(source)

            all_findings = []
            # Dispatch AST to all registered detectors
            for detector in REGISTRY:
                findings = detector.detect(tree)
                all_findings.extend(findings)

            return all_findings

        except Exception as e:
            return [
                {
                    "function": "file_level",
                    "issue": f"Static Analysis Error: {str(e)}",
                    "complexity": "Unknown",
                    "suggestion": "Check file syntax. Empirical tests will proceed.",
                    "line": 0,
                }
            ]

analyze_code(path) staticmethod

Analyze a Python file using all registered pattern detectors.

Parameters:

Name Type Description Default
path str

Path to the Python source file.

required

Returns:

Type Description
List[Dict[str, Any]]

List[Dict[str, Any]]: A flat list of findings from all detectors. Each finding is a dictionary containing detector‑specific metadata such as: - "function": Function where the issue occurs - "pattern_id": Identifier of the detector - "issue": Description of the anti‑pattern - "line": Line number of the issue - Additional detector‑specific fields

Notes
  • Empty files return an empty list.
  • Syntax errors or unexpected exceptions are captured and returned as a single file‑level finding rather than raising an exception.
Source code in src/alnoms/patterns/heuristics.py
@staticmethod
def analyze_code(path: str) -> List[Dict[str, Any]]:
    """Analyze a Python file using all registered pattern detectors.

    Args:
        path (str): Path to the Python source file.

    Returns:
        List[Dict[str, Any]]:
            A flat list of findings from all detectors. Each finding is a
            dictionary containing detector‑specific metadata such as:
                - "function": Function where the issue occurs
                - "pattern_id": Identifier of the detector
                - "issue": Description of the anti‑pattern
                - "line": Line number of the issue
                - Additional detector‑specific fields

    Notes:
        - Empty files return an empty list.
        - Syntax errors or unexpected exceptions are captured and returned
          as a single file‑level finding rather than raising an exception.
    """
    try:
        with open(path, "r", encoding="utf-8", errors="ignore") as f:
            source = f.read()
            if not source.strip():
                return []
            tree = ast.parse(source)

        all_findings = []
        # Dispatch AST to all registered detectors
        for detector in REGISTRY:
            findings = detector.detect(tree)
            all_findings.extend(findings)

        return all_findings

    except Exception as e:
        return [
            {
                "function": "file_level",
                "issue": f"Static Analysis Error: {str(e)}",
                "complexity": "Unknown",
                "suggestion": "Check file syntax. Empirical tests will proceed.",
                "line": 0,
            }
        ]

Bases: PatternDetector

Detects nested loops and classifies their algorithmic intent.

This detector identifies functions containing nested for or while loops and applies lightweight heuristics to infer the likely purpose of the nested structure. The intent classification enables downstream fixers to provide context‑aware remediation strategies.

Supported intent categories

β€’ membership β€” equality checks, membership tests, pairwise scans β€’ sorting β€” range(len(...)) patterns resembling selection/bubble sort β€’ dfs β€” nested iteration over adjacency lists or graph structures β€’ generic β€” fallback for unclassified quadratic scans

Source code in src/alnoms/patterns/nested_loops.py
class NestedLoopDetector(PatternDetector):
    """Detects nested loops and classifies their algorithmic intent.

    This detector identifies functions containing nested `for` or `while`
    loops and applies lightweight heuristics to infer the likely purpose
    of the nested structure. The intent classification enables downstream
    fixers to provide context‑aware remediation strategies.

    Supported intent categories:
        β€’ **membership** β€” equality checks, membership tests, pairwise scans
        β€’ **sorting** β€” range(len(...)) patterns resembling selection/bubble sort
        β€’ **dfs** β€” nested iteration over adjacency lists or graph structures
        β€’ **generic** β€” fallback for unclassified quadratic scans
    """

    id = "nested_loops"
    name = "Nested Loop Detection"
    description = "Detects loops nested inside other loops."

    def _classify_intent(self, loop: ast.AST) -> str:
        """Infers the likely algorithmic intent of a nested loop.

        This heuristic inspects the body of a loop to detect common
        patterns associated with membership checks, sorting‑like scans,
        or DFS‑style neighbor traversal. If no recognizable pattern is
        found, the loop is classified as ``generic``.

        Args:
            loop (ast.AST): The AST node representing the outer loop.

        Returns:
            str: One of ``"membership"``, ``"sorting"``, ``"dfs"``, or
            ``"generic"``.
        """
        for child in ast.walk(loop):
            # Membership / equality checks
            if isinstance(child, ast.Compare):
                for op in child.ops:
                    if isinstance(op, (ast.Eq, ast.In, ast.NotIn)):
                        return "membership"

            # Sorting-like: range(len(...))
            if isinstance(child, ast.Call) and isinstance(child.func, ast.Name):
                if child.func.id == "range":
                    for arg in child.args:
                        if isinstance(arg, ast.Call) and isinstance(arg.func, ast.Name):
                            if arg.func.id == "len":
                                return "sorting"

            # DFS-like: adjacency traversal
            if isinstance(child, ast.Name) and child.id.lower() in {
                "neighbors",
                "adj",
                "graph",
            }:
                return "dfs"

        return "generic"

    def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
        """Detects nested loops within function bodies.

        Traverses the AST to locate functions containing nested ``for`` or
        ``while`` loops. When a nested loop is found, the detector assigns
        an intent classification and records a single finding per function
        to avoid noise.

        Args:
            tree (ast.AST): The parsed AST of the module being analyzed.

        Returns:
            List[Dict[str, Any]]: A list of findings, where each finding
            includes:
                - function (str): Name of the function containing the loop.
                - pattern_id (str): Identifier for this detector.
                - issue (str): Human-readable description of the problem.
                - complexity (str): Complexity classification (always O(N^2)).
                - intent (str): Classified nested-loop intent.
                - suggestion (str): Guidance for remediation.
                - line (int): Line number of the outer loop.
        """
        findings = []

        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                loops = [
                    c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
                ]

                for loop in loops:
                    sub_loops = [
                        c
                        for c in ast.walk(loop)
                        if isinstance(c, (ast.For, ast.While)) and c is not loop
                    ]

                    if sub_loops:
                        intent = self._classify_intent(loop)

                        findings.append(
                            {
                                "function": node.name,
                                "pattern_id": self.id,
                                "issue": "Nested loop detected",
                                "complexity": "O(N^2)",
                                "intent": intent,
                                "suggestion": (
                                    "Nested loop detected; see fixer for pattern-specific remediation."
                                ),
                                "line": loop.lineno,
                            }
                        )
                        break  # one finding per function

        return findings

detect(tree)

Detects nested loops within function bodies.

Traverses the AST to locate functions containing nested for or while loops. When a nested loop is found, the detector assigns an intent classification and records a single finding per function to avoid noise.

Parameters:

Name Type Description Default
tree AST

The parsed AST of the module being analyzed.

required

Returns:

Name Type Description
List[Dict[str, Any]]

List[Dict[str, Any]]: A list of findings, where each finding

includes List[Dict[str, Any]]
  • function (str): Name of the function containing the loop.
  • pattern_id (str): Identifier for this detector.
  • issue (str): Human-readable description of the problem.
  • complexity (str): Complexity classification (always O(N^2)).
  • intent (str): Classified nested-loop intent.
  • suggestion (str): Guidance for remediation.
  • line (int): Line number of the outer loop.
Source code in src/alnoms/patterns/nested_loops.py
def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
    """Detects nested loops within function bodies.

    Traverses the AST to locate functions containing nested ``for`` or
    ``while`` loops. When a nested loop is found, the detector assigns
    an intent classification and records a single finding per function
    to avoid noise.

    Args:
        tree (ast.AST): The parsed AST of the module being analyzed.

    Returns:
        List[Dict[str, Any]]: A list of findings, where each finding
        includes:
            - function (str): Name of the function containing the loop.
            - pattern_id (str): Identifier for this detector.
            - issue (str): Human-readable description of the problem.
            - complexity (str): Complexity classification (always O(N^2)).
            - intent (str): Classified nested-loop intent.
            - suggestion (str): Guidance for remediation.
            - line (int): Line number of the outer loop.
    """
    findings = []

    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef):
            loops = [
                c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
            ]

            for loop in loops:
                sub_loops = [
                    c
                    for c in ast.walk(loop)
                    if isinstance(c, (ast.For, ast.While)) and c is not loop
                ]

                if sub_loops:
                    intent = self._classify_intent(loop)

                    findings.append(
                        {
                            "function": node.name,
                            "pattern_id": self.id,
                            "issue": "Nested loop detected",
                            "complexity": "O(N^2)",
                            "intent": intent,
                            "suggestion": (
                                "Nested loop detected; see fixer for pattern-specific remediation."
                            ),
                            "line": loop.lineno,
                        }
                    )
                    break  # one finding per function

    return findings

Bases: PatternDetector

Detect inefficient membership tests inside loops.

This detector flags occurrences of:

- `x in some_list`
- `x not in some_list`
- membership checks on literal lists/tuples
- membership checks on variables that are likely lists

When these appear inside loops, they create a hidden O(NΒ²) pattern due to repeated linear scans. The detector uses heuristics to avoid false positives on safe containers such as sets, dicts, ranges, and variables whose names imply O(1) lookup semantics (e.g., visited, cache, lookup).

Attributes:

Name Type Description
SAFE_CONTAINER_HINTS set[str]

Variable‑name substrings that imply O(1) membership semantics.

Source code in src/alnoms/patterns/inefficient_membership.py
class MembershipDetector(PatternDetector):
    """Detect inefficient membership tests inside loops.

    This detector flags occurrences of:

        - `x in some_list`
        - `x not in some_list`
        - membership checks on literal lists/tuples
        - membership checks on variables that are likely lists

    When these appear inside loops, they create a hidden O(NΒ²) pattern due to
    repeated linear scans. The detector uses heuristics to avoid false positives
    on safe containers such as sets, dicts, ranges, and variables whose names
    imply O(1) lookup semantics (e.g., `visited`, `cache`, `lookup`).

    Attributes:
        SAFE_CONTAINER_HINTS (set[str]):
            Variable‑name substrings that imply O(1) membership semantics.
    """

    id = "inefficient_membership"
    name = "Inefficient Membership Detection"
    description = "Detects 'in' operator usage on list-like containers inside loops."

    SAFE_CONTAINER_HINTS = {
        "set",
        "dict",
        "map",
        "cache",
        "visited",
        "seen",
        "lookup",
        "index",
    }

    def _is_safe_container_name(self, name: str) -> bool:
        """Return True if the variable name implies O(1) membership.

        Args:
            name (str): Variable name extracted from the AST.

        Returns:
            bool: True if the name suggests a set/dict‑like container.
        """
        name = name.lower()
        return any(hint in name for hint in self.SAFE_CONTAINER_HINTS)

    def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
        """Analyze the AST and flag inefficient membership tests inside loops.

        Args:
            tree (ast.AST): Parsed AST of the target Python file.

        Returns:
            List[Dict[str, Any]]:
                A list of findings. Each finding includes:
                    - "function": Function where the issue occurs
                    - "pattern_id": Identifier for this detector
                    - "issue": Description of the membership test
                    - "complexity": Estimated complexity impact
                    - "suggestion": Recommended remediation
                    - "line": Line number of the issue

        Notes:
            - Literal lists/tuples of size ≀ 3 are ignored as they are cheap.
            - Safe containers (set/dict/range) are skipped.
            - Variable names are heuristically classified for safety.
        """
        findings = []

        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                loops = [
                    c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
                ]

                for loop in loops:
                    for child in ast.walk(loop):
                        # Look for "x in Y" or "x not in Y"
                        if isinstance(child, ast.Compare):
                            for op in child.ops:
                                if isinstance(op, (ast.In, ast.NotIn)):
                                    container = child.comparators[0]

                                    # --- CASE 1: Literal list/tuple ---
                                    if isinstance(container, (ast.List, ast.Tuple)):
                                        # Ignore tiny constant lists (<= 3)
                                        if len(container.elts) <= 3:
                                            continue

                                        findings.append(
                                            {
                                                "function": node.name,
                                                "pattern_id": self.id,
                                                "issue": (
                                                    "Membership test on list literal "
                                                    "inside loop"
                                                ),
                                                "complexity": "Potential O(N^2)",
                                                "suggestion": (
                                                    "Convert the list literal to a set "
                                                    "for O(1) lookups."
                                                ),
                                                "line": child.lineno,
                                            }
                                        )
                                        continue

                                    # --- CASE 2: Variable name ---
                                    if isinstance(container, ast.Name):
                                        var_name = container.id

                                        # Skip safe containers by name
                                        if self._is_safe_container_name(var_name):
                                            continue

                                        findings.append(
                                            {
                                                "function": node.name,
                                                "pattern_id": self.id,
                                                "issue": (
                                                    f"Membership test ('in {var_name}') "
                                                    "inside loop"
                                                ),
                                                "complexity": "Potential O(N^2)",
                                                "suggestion": (
                                                    f"Ensure '{var_name}' is a Set or Dict "
                                                    "for O(1) lookups, not a List."
                                                ),
                                                "line": child.lineno,
                                            }
                                        )
                                        continue

                                    # --- CASE 3: Calls like set(...), dict(...), range(...) ---
                                    if isinstance(container, ast.Call):
                                        if isinstance(container.func, ast.Name):
                                            if container.func.id in {
                                                "set",
                                                "dict",
                                                "range",
                                            }:
                                                continue  # safe

                                    # --- DEFAULT: Unknown container type ---
                                    findings.append(
                                        {
                                            "function": node.name,
                                            "pattern_id": self.id,
                                            "issue": "Membership test inside loop",
                                            "complexity": "Potential O(N^2)",
                                            "suggestion": (
                                                "Ensure the target collection is a Set or Dict."
                                            ),
                                            "line": child.lineno,
                                        }
                                    )

        return findings

detect(tree)

Analyze the AST and flag inefficient membership tests inside loops.

Parameters:

Name Type Description Default
tree AST

Parsed AST of the target Python file.

required

Returns:

Type Description
List[Dict[str, Any]]

List[Dict[str, Any]]: A list of findings. Each finding includes: - "function": Function where the issue occurs - "pattern_id": Identifier for this detector - "issue": Description of the membership test - "complexity": Estimated complexity impact - "suggestion": Recommended remediation - "line": Line number of the issue

Notes
  • Literal lists/tuples of size ≀ 3 are ignored as they are cheap.
  • Safe containers (set/dict/range) are skipped.
  • Variable names are heuristically classified for safety.
Source code in src/alnoms/patterns/inefficient_membership.py
def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
    """Analyze the AST and flag inefficient membership tests inside loops.

    Args:
        tree (ast.AST): Parsed AST of the target Python file.

    Returns:
        List[Dict[str, Any]]:
            A list of findings. Each finding includes:
                - "function": Function where the issue occurs
                - "pattern_id": Identifier for this detector
                - "issue": Description of the membership test
                - "complexity": Estimated complexity impact
                - "suggestion": Recommended remediation
                - "line": Line number of the issue

    Notes:
        - Literal lists/tuples of size ≀ 3 are ignored as they are cheap.
        - Safe containers (set/dict/range) are skipped.
        - Variable names are heuristically classified for safety.
    """
    findings = []

    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef):
            loops = [
                c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
            ]

            for loop in loops:
                for child in ast.walk(loop):
                    # Look for "x in Y" or "x not in Y"
                    if isinstance(child, ast.Compare):
                        for op in child.ops:
                            if isinstance(op, (ast.In, ast.NotIn)):
                                container = child.comparators[0]

                                # --- CASE 1: Literal list/tuple ---
                                if isinstance(container, (ast.List, ast.Tuple)):
                                    # Ignore tiny constant lists (<= 3)
                                    if len(container.elts) <= 3:
                                        continue

                                    findings.append(
                                        {
                                            "function": node.name,
                                            "pattern_id": self.id,
                                            "issue": (
                                                "Membership test on list literal "
                                                "inside loop"
                                            ),
                                            "complexity": "Potential O(N^2)",
                                            "suggestion": (
                                                "Convert the list literal to a set "
                                                "for O(1) lookups."
                                            ),
                                            "line": child.lineno,
                                        }
                                    )
                                    continue

                                # --- CASE 2: Variable name ---
                                if isinstance(container, ast.Name):
                                    var_name = container.id

                                    # Skip safe containers by name
                                    if self._is_safe_container_name(var_name):
                                        continue

                                    findings.append(
                                        {
                                            "function": node.name,
                                            "pattern_id": self.id,
                                            "issue": (
                                                f"Membership test ('in {var_name}') "
                                                "inside loop"
                                            ),
                                            "complexity": "Potential O(N^2)",
                                            "suggestion": (
                                                f"Ensure '{var_name}' is a Set or Dict "
                                                "for O(1) lookups, not a List."
                                            ),
                                            "line": child.lineno,
                                        }
                                    )
                                    continue

                                # --- CASE 3: Calls like set(...), dict(...), range(...) ---
                                if isinstance(container, ast.Call):
                                    if isinstance(container.func, ast.Name):
                                        if container.func.id in {
                                            "set",
                                            "dict",
                                            "range",
                                        }:
                                            continue  # safe

                                # --- DEFAULT: Unknown container type ---
                                findings.append(
                                    {
                                        "function": node.name,
                                        "pattern_id": self.id,
                                        "issue": "Membership test inside loop",
                                        "complexity": "Potential O(N^2)",
                                        "suggestion": (
                                            "Ensure the target collection is a Set or Dict."
                                        ),
                                        "line": child.lineno,
                                    }
                                )

    return findings

Bases: PatternDetector

Detect potentially expensive function calls inside loops.

This detector walks the AST of each function and inspects all for and while loops. Any function call inside a loop that is not part of the SAFE_CALLS whitelist is flagged as a potential performance risk.

The whitelist includes:

  • Common O(1) built‑ins (len, range, int, etc.)
  • Common O(1) container methods (append, pop, get, etc.)
  • Sorting and I/O calls, which are handled by dedicated detectors

Attributes:

Name Type Description
SAFE_CALLS set[str]

Set of known O(1) or trivial operations that should not be flagged.

Source code in src/alnoms/patterns/expensive_calls.py
class ExpensiveCallDetector(PatternDetector):
    """Detect potentially expensive function calls inside loops.

    This detector walks the AST of each function and inspects all `for` and
    `while` loops. Any function call inside a loop that is *not* part of the
    `SAFE_CALLS` whitelist is flagged as a potential performance risk.

    The whitelist includes:

    - Common O(1) built‑ins (`len`, `range`, `int`, etc.)
    - Common O(1) container methods (`append`, `pop`, `get`, etc.)
    - Sorting and I/O calls, which are handled by dedicated detectors

    Attributes:
        SAFE_CALLS (set[str]): Set of known O(1) or trivial operations that
            should not be flagged.
    """

    id = "expensive_calls"
    name = "Expensive Call Detection"
    description = "Detects potentially expensive function calls inside loops."

    # --- SAFE CALLS (O(1) or trivial) ---
    SAFE_CALLS = {
        # Builtins
        "range",
        "len",
        "print",
        "enumerate",
        "int",
        "str",
        "float",
        "list",
        "dict",
        "set",
        "tuple",
        # Common O(1) methods
        "append",
        "pop",
        "add",
        "get",
        "update",
        "remove",
        "clear",
        # Sorting is handled by a separate detector
        "sort",
        "sorted",
        # IO is handled separately
        "open",
        "read",
        "write",
    }

    def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
        """Analyze the AST and flag non‑whitelisted calls inside loops.

        Args:
            tree (ast.AST): Parsed AST of the target Python file.

        Returns:
            List[Dict[str, Any]]:
                A list of findings. Each finding includes:
                    - "function": Name of the function containing the loop.
                    - "pattern_id": Identifier for this detector.
                    - "issue": Description of the detected call.
                    - "complexity": Estimated complexity impact.
                    - "suggestion": Recommended remediation.
                    - "line": Line number of the call.

        Notes:
            - Only explicit `ast.Call` nodes inside `for`/`while` loops are inspected.
            - Calls to safe O(1) operations are ignored.
            - Sorting and I/O calls are delegated to other detectors.
        """
        findings = []

        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                loops = [
                    c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
                ]

                for loop in loops:
                    for child in ast.walk(loop):
                        if isinstance(child, ast.Call):
                            # Extract function name
                            func_name = ""
                            if isinstance(child.func, ast.Name):
                                func_name = child.func.id
                            elif isinstance(child.func, ast.Attribute):
                                func_name = child.func.attr

                            # Skip safe calls
                            if func_name in self.SAFE_CALLS:
                                continue

                            # Skip empty or unknown names
                            if not func_name:
                                continue

                            # Flag everything else as potentially expensive
                            findings.append(
                                {
                                    "function": node.name,
                                    "pattern_id": self.id,
                                    "issue": (
                                        f"Potentially expensive call '{func_name}()' "
                                        "inside loop"
                                    ),
                                    "complexity": "O(N * K)",
                                    "suggestion": (
                                        "Consider hoisting or caching this call outside "
                                        "the loop to avoid repeated execution."
                                    ),
                                    "line": child.lineno,
                                }
                            )

        return findings

detect(tree)

Analyze the AST and flag non‑whitelisted calls inside loops.

Parameters:

Name Type Description Default
tree AST

Parsed AST of the target Python file.

required

Returns:

Type Description
List[Dict[str, Any]]

List[Dict[str, Any]]: A list of findings. Each finding includes: - "function": Name of the function containing the loop. - "pattern_id": Identifier for this detector. - "issue": Description of the detected call. - "complexity": Estimated complexity impact. - "suggestion": Recommended remediation. - "line": Line number of the call.

Notes
  • Only explicit ast.Call nodes inside for/while loops are inspected.
  • Calls to safe O(1) operations are ignored.
  • Sorting and I/O calls are delegated to other detectors.
Source code in src/alnoms/patterns/expensive_calls.py
def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
    """Analyze the AST and flag non‑whitelisted calls inside loops.

    Args:
        tree (ast.AST): Parsed AST of the target Python file.

    Returns:
        List[Dict[str, Any]]:
            A list of findings. Each finding includes:
                - "function": Name of the function containing the loop.
                - "pattern_id": Identifier for this detector.
                - "issue": Description of the detected call.
                - "complexity": Estimated complexity impact.
                - "suggestion": Recommended remediation.
                - "line": Line number of the call.

    Notes:
        - Only explicit `ast.Call` nodes inside `for`/`while` loops are inspected.
        - Calls to safe O(1) operations are ignored.
        - Sorting and I/O calls are delegated to other detectors.
    """
    findings = []

    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef):
            loops = [
                c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
            ]

            for loop in loops:
                for child in ast.walk(loop):
                    if isinstance(child, ast.Call):
                        # Extract function name
                        func_name = ""
                        if isinstance(child.func, ast.Name):
                            func_name = child.func.id
                        elif isinstance(child.func, ast.Attribute):
                            func_name = child.func.attr

                        # Skip safe calls
                        if func_name in self.SAFE_CALLS:
                            continue

                        # Skip empty or unknown names
                        if not func_name:
                            continue

                        # Flag everything else as potentially expensive
                        findings.append(
                            {
                                "function": node.name,
                                "pattern_id": self.id,
                                "issue": (
                                    f"Potentially expensive call '{func_name}()' "
                                    "inside loop"
                                ),
                                "complexity": "O(N * K)",
                                "suggestion": (
                                    "Consider hoisting or caching this call outside "
                                    "the loop to avoid repeated execution."
                                ),
                                "line": child.lineno,
                            }
                        )

    return findings

Bases: PatternDetector

Detect high‑frequency I/O operations inside loops.

This detector walks the AST of each function and inspects all for and while loops. Any call to open, read, or write inside a loop is flagged as a performance risk due to:

  • Excessive system calls
  • Kernel context switching
  • Disk or network latency
  • Reduced throughput compared to buffered operations

Attributes:

Name Type Description
id str

Unique identifier for this detector.

name str

Human‑readable name for reporting.

description str

Short description of the detector's purpose.

Source code in src/alnoms/patterns/high_freq_io.py
class HighFrequencyIODetector(PatternDetector):
    """Detect high‑frequency I/O operations inside loops.

    This detector walks the AST of each function and inspects all `for` and
    `while` loops. Any call to `open`, `read`, or `write` inside a loop is
    flagged as a performance risk due to:

    - Excessive system calls
    - Kernel context switching
    - Disk or network latency
    - Reduced throughput compared to buffered operations

    Attributes:
        id (str): Unique identifier for this detector.
        name (str): Human‑readable name for reporting.
        description (str): Short description of the detector's purpose.
    """

    id = "high_freq_io"
    name = "High-Frequency I/O Detection"
    description = "Detects file or network I/O operations inside loops."

    def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
        """Analyze the AST and flag I/O operations inside loops.

        Args:
            tree (ast.AST): Parsed AST of the target Python file.

        Returns:
            List[Dict[str, Any]]:
                A list of findings. Each finding includes:
                    - "function": Name of the function containing the loop.
                    - "pattern_id": Identifier for this detector.
                    - "issue": Description of the detected I/O call.
                    - "complexity": Qualitative complexity impact.
                    - "suggestion": Recommended remediation.
                    - "line": Line number of the call.

        Notes:
            - Only explicit `ast.Call` nodes inside `for`/`while` loops are inspected.
            - This detector focuses on file and network I/O primitives.
            - Buffered or batched I/O is recommended for performance.
        """
        findings = []
        io_funcs = {"open", "read", "write"}

        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                loops = [
                    c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
                ]

                for loop in loops:
                    for child in ast.walk(loop):
                        if isinstance(child, ast.Call):
                            func_name = ""
                            if isinstance(child.func, ast.Name):
                                func_name = child.func.id
                            elif isinstance(child.func, ast.Attribute):
                                func_name = child.func.attr

                            if func_name in io_funcs:
                                findings.append(
                                    {
                                        "function": node.name,
                                        "pattern_id": self.id,
                                        "issue": (
                                            f"High-frequency I/O ('{func_name}') inside loop"
                                        ),
                                        "complexity": "High I/O Wait",
                                        "suggestion": (
                                            "Buffer data in memory and perform a single "
                                            "batch I/O operation outside the loop."
                                        ),
                                        "line": child.lineno,
                                    }
                                )

        return findings

detect(tree)

Analyze the AST and flag I/O operations inside loops.

Parameters:

Name Type Description Default
tree AST

Parsed AST of the target Python file.

required

Returns:

Type Description
List[Dict[str, Any]]

List[Dict[str, Any]]: A list of findings. Each finding includes: - "function": Name of the function containing the loop. - "pattern_id": Identifier for this detector. - "issue": Description of the detected I/O call. - "complexity": Qualitative complexity impact. - "suggestion": Recommended remediation. - "line": Line number of the call.

Notes
  • Only explicit ast.Call nodes inside for/while loops are inspected.
  • This detector focuses on file and network I/O primitives.
  • Buffered or batched I/O is recommended for performance.
Source code in src/alnoms/patterns/high_freq_io.py
def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
    """Analyze the AST and flag I/O operations inside loops.

    Args:
        tree (ast.AST): Parsed AST of the target Python file.

    Returns:
        List[Dict[str, Any]]:
            A list of findings. Each finding includes:
                - "function": Name of the function containing the loop.
                - "pattern_id": Identifier for this detector.
                - "issue": Description of the detected I/O call.
                - "complexity": Qualitative complexity impact.
                - "suggestion": Recommended remediation.
                - "line": Line number of the call.

    Notes:
        - Only explicit `ast.Call` nodes inside `for`/`while` loops are inspected.
        - This detector focuses on file and network I/O primitives.
        - Buffered or batched I/O is recommended for performance.
    """
    findings = []
    io_funcs = {"open", "read", "write"}

    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef):
            loops = [
                c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
            ]

            for loop in loops:
                for child in ast.walk(loop):
                    if isinstance(child, ast.Call):
                        func_name = ""
                        if isinstance(child.func, ast.Name):
                            func_name = child.func.id
                        elif isinstance(child.func, ast.Attribute):
                            func_name = child.func.attr

                        if func_name in io_funcs:
                            findings.append(
                                {
                                    "function": node.name,
                                    "pattern_id": self.id,
                                    "issue": (
                                        f"High-frequency I/O ('{func_name}') inside loop"
                                    ),
                                    "complexity": "High I/O Wait",
                                    "suggestion": (
                                        "Buffer data in memory and perform a single "
                                        "batch I/O operation outside the loop."
                                    ),
                                    "line": child.lineno,
                                }
                            )

    return findings

Bases: PatternDetector

Detects in-place string/list concatenation inside loops.

This detector identifies patterns where += or *= are used on potentially non-numeric variables inside for or while loops. Such operations can trigger repeated memory reallocations, resulting in O(N^2) scaling.

The detector includes heuristics to avoid false positives on numeric accumulation patterns (e.g., counters, totals, index updates).

Source code in src/alnoms/patterns/inplace_concat.py
class InplaceConcatDetector(PatternDetector):
    """Detects in-place string/list concatenation inside loops.

    This detector identifies patterns where `+=` or `*=` are used on
    potentially non-numeric variables inside `for` or `while` loops.
    Such operations can trigger repeated memory reallocations, resulting
    in O(N^2) scaling.

    The detector includes heuristics to avoid false positives on numeric
    accumulation patterns (e.g., counters, totals, index updates).
    """

    id = "inplace_concat"
    name = "In-Place Concatenation Detection"
    description = "Detects memory-heavy string or list concatenation inside loops."

    NUMERIC_HINTS = {
        "i",
        "j",
        "k",
        "n",
        "m",
        "idx",
        "count",
        "total",
        "sum",
        "acc",
        "value",
    }

    def _looks_numeric(self, node: ast.AST) -> bool:
        """Determines whether an AST node represents numeric-like intent.

        This heuristic helps avoid false positives by identifying RHS
        expressions that are likely numeric accumulations rather than
        string/list concatenations.

        Args:
            node (ast.AST): The AST node representing the right-hand side
                of an augmented assignment.

        Returns:
            bool: True if the node appears numeric (literal number,
            multiplication, or numeric-like variable name). False otherwise.
        """
        # Literal number
        if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
            return True

        # RHS contains multiplication β†’ numeric accumulation
        if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Mult):
            return True

        # RHS is a variable with numeric-like name
        if isinstance(node, ast.Name) and node.id.lower() in self.NUMERIC_HINTS:
            return True

        return False

    def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
        """Detects in-place concatenation patterns inside loops.

        Traverses the AST to find augmented assignments (`+=`, `*=`) that
        occur within `for` or `while` loops. The detector filters out
        numeric accumulation patterns and flags only those operations that
        are likely to cause O(N^2) memory behavior due to repeated
        reallocation of strings or lists.

        Args:
            tree (ast.AST): The parsed AST of the target Python module or
                function.

        Returns:
            List[Dict[str, Any]]: A list of findings, where each finding
            includes:
                - function (str): Name of the function containing the issue.
                - pattern_id (str): Identifier for this detector.
                - issue (str): Human-readable description of the problem.
                - complexity (str): Complexity classification.
                - suggestion (str): Recommended remediation strategy.
                - line (int): Line number where the issue occurs.
        """
        findings = []

        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                loops = [
                    c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
                ]

                for loop in loops:
                    for child in ast.walk(loop):
                        # Detect += or *=
                        if isinstance(child, ast.AugAssign) and isinstance(
                            child.op, (ast.Add, ast.Mult)
                        ):
                            target = child.target
                            value = child.value

                            # -----------------------------------------------------
                            # 1. Skip numeric accumulation (LHS or RHS numeric)
                            # -----------------------------------------------------
                            # Case: C[i][j] += ...
                            if isinstance(target, ast.Subscript):
                                continue

                            # Case: RHS looks numeric
                            if self._looks_numeric(value):
                                continue

                            # Case: LHS variable name suggests numeric intent
                            if (
                                isinstance(target, ast.Name)
                                and target.id.lower() in self.NUMERIC_HINTS
                            ):
                                continue

                            # -----------------------------------------------------
                            # 2. Detect REAL concatenation
                            # -----------------------------------------------------
                            # Case: literal string/list on RHS
                            if isinstance(
                                value,
                                (ast.List, ast.Tuple, ast.Constant, ast.JoinedStr),
                            ):
                                pass  # real concatenation

                            # Case: variable on LHS that is not numeric-like
                            elif (
                                isinstance(target, ast.Name)
                                and target.id.lower() not in self.NUMERIC_HINTS
                            ):
                                pass

                            else:
                                # Not enough evidence β†’ skip
                                continue

                            # -----------------------------------------------------
                            # 3. Record finding
                            # -----------------------------------------------------
                            findings.append(
                                {
                                    "function": node.name,
                                    "pattern_id": self.id,
                                    "issue": "In-place concatenation inside loop",
                                    "complexity": "O(N^2) Memory Risk",
                                    "suggestion": (
                                        "Collect items in a list and use ''.join() or list.extend() "
                                        "for linear building."
                                    ),
                                    "line": child.lineno,
                                }
                            )

        return findings

detect(tree)

Detects in-place concatenation patterns inside loops.

Traverses the AST to find augmented assignments (+=, *=) that occur within for or while loops. The detector filters out numeric accumulation patterns and flags only those operations that are likely to cause O(N^2) memory behavior due to repeated reallocation of strings or lists.

Parameters:

Name Type Description Default
tree AST

The parsed AST of the target Python module or function.

required

Returns:

Name Type Description
List[Dict[str, Any]]

List[Dict[str, Any]]: A list of findings, where each finding

includes List[Dict[str, Any]]
  • function (str): Name of the function containing the issue.
  • pattern_id (str): Identifier for this detector.
  • issue (str): Human-readable description of the problem.
  • complexity (str): Complexity classification.
  • suggestion (str): Recommended remediation strategy.
  • line (int): Line number where the issue occurs.
Source code in src/alnoms/patterns/inplace_concat.py
def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
    """Detects in-place concatenation patterns inside loops.

    Traverses the AST to find augmented assignments (`+=`, `*=`) that
    occur within `for` or `while` loops. The detector filters out
    numeric accumulation patterns and flags only those operations that
    are likely to cause O(N^2) memory behavior due to repeated
    reallocation of strings or lists.

    Args:
        tree (ast.AST): The parsed AST of the target Python module or
            function.

    Returns:
        List[Dict[str, Any]]: A list of findings, where each finding
        includes:
            - function (str): Name of the function containing the issue.
            - pattern_id (str): Identifier for this detector.
            - issue (str): Human-readable description of the problem.
            - complexity (str): Complexity classification.
            - suggestion (str): Recommended remediation strategy.
            - line (int): Line number where the issue occurs.
    """
    findings = []

    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef):
            loops = [
                c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
            ]

            for loop in loops:
                for child in ast.walk(loop):
                    # Detect += or *=
                    if isinstance(child, ast.AugAssign) and isinstance(
                        child.op, (ast.Add, ast.Mult)
                    ):
                        target = child.target
                        value = child.value

                        # -----------------------------------------------------
                        # 1. Skip numeric accumulation (LHS or RHS numeric)
                        # -----------------------------------------------------
                        # Case: C[i][j] += ...
                        if isinstance(target, ast.Subscript):
                            continue

                        # Case: RHS looks numeric
                        if self._looks_numeric(value):
                            continue

                        # Case: LHS variable name suggests numeric intent
                        if (
                            isinstance(target, ast.Name)
                            and target.id.lower() in self.NUMERIC_HINTS
                        ):
                            continue

                        # -----------------------------------------------------
                        # 2. Detect REAL concatenation
                        # -----------------------------------------------------
                        # Case: literal string/list on RHS
                        if isinstance(
                            value,
                            (ast.List, ast.Tuple, ast.Constant, ast.JoinedStr),
                        ):
                            pass  # real concatenation

                        # Case: variable on LHS that is not numeric-like
                        elif (
                            isinstance(target, ast.Name)
                            and target.id.lower() not in self.NUMERIC_HINTS
                        ):
                            pass

                        else:
                            # Not enough evidence β†’ skip
                            continue

                        # -----------------------------------------------------
                        # 3. Record finding
                        # -----------------------------------------------------
                        findings.append(
                            {
                                "function": node.name,
                                "pattern_id": self.id,
                                "issue": "In-place concatenation inside loop",
                                "complexity": "O(N^2) Memory Risk",
                                "suggestion": (
                                    "Collect items in a list and use ''.join() or list.extend() "
                                    "for linear building."
                                ),
                                "line": child.lineno,
                            }
                        )

    return findings

Bases: PatternDetector

Detects repeated sorting operations executed inside loops.

This detector inspects loop bodies for calls to Python's built‑in sorting mechanisms (sorted() or list .sort()). When these calls appear inside for or while loops, they can introduce O(N^2 log N) behavior due to repeated sorting of the same or similar data structures.

Sorting is typically intended to be performed once before iteration, or once after data collection, rather than on every loop iteration.

Source code in src/alnoms/patterns/redundant_sort.py
class RedundantSortDetector(PatternDetector):
    """Detects repeated sorting operations executed inside loops.

    This detector inspects loop bodies for calls to Python's built‑in
    sorting mechanisms (`sorted()` or list `.sort()`). When these calls
    appear inside `for` or `while` loops, they can introduce
    O(N^2 log N) behavior due to repeated sorting of the same or similar
    data structures.

    Sorting is typically intended to be performed once before iteration,
    or once after data collection, rather than on every loop iteration.
    """

    id = "redundant_sort"
    name = "Redundant Sort Detection"
    description = "Detects sorting operations executed inside loops."

    def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
        """Detects sorting calls (`sort`, `sorted`) inside loop bodies.

        Traverses the AST to locate function definitions and inspects all
        nested loops for calls to Python's standard sorting functions.
        When found, the detector records a finding indicating that sorting
        should be moved outside the loop to avoid repeated O(N log N)
        operations.

        Args:
            tree (ast.AST): The parsed AST of the module being analyzed.

        Returns:
            List[Dict[str, Any]]: A list of findings, where each finding
            includes:
                - function (str): Name of the function containing the issue.
                - pattern_id (str): Identifier for this detector.
                - issue (str): Description of the redundant sorting pattern.
                - complexity (str): Complexity classification (O(N^2 log N)).
                - suggestion (str): Recommended remediation strategy.
                - line (int): Line number where the sorting call occurs.
        """
        findings = []
        sorting_funcs = {"sort", "sorted"}

        for node in ast.walk(tree):
            if isinstance(node, ast.FunctionDef):
                loops = [
                    c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
                ]

                for loop in loops:
                    for child in ast.walk(loop):
                        if isinstance(child, ast.Call):
                            func_name = ""

                            # Case: sorted(...)
                            if isinstance(child.func, ast.Name):
                                func_name = child.func.id

                            # Case: list.sort(...)
                            elif isinstance(child.func, ast.Attribute):
                                func_name = child.func.attr

                            if func_name in sorting_funcs:
                                findings.append(
                                    {
                                        "function": node.name,
                                        "pattern_id": self.id,
                                        "issue": "Redundant sorting inside loop",
                                        "complexity": "O(N^2 log N)",
                                        "suggestion": (
                                            "Move sorting logic outside the loop to sort data only once."
                                        ),
                                        "line": child.lineno,
                                    }
                                )

        return findings

detect(tree)

Detects sorting calls (sort, sorted) inside loop bodies.

Traverses the AST to locate function definitions and inspects all nested loops for calls to Python's standard sorting functions. When found, the detector records a finding indicating that sorting should be moved outside the loop to avoid repeated O(N log N) operations.

Parameters:

Name Type Description Default
tree AST

The parsed AST of the module being analyzed.

required

Returns:

Name Type Description
List[Dict[str, Any]]

List[Dict[str, Any]]: A list of findings, where each finding

includes List[Dict[str, Any]]
  • function (str): Name of the function containing the issue.
  • pattern_id (str): Identifier for this detector.
  • issue (str): Description of the redundant sorting pattern.
  • complexity (str): Complexity classification (O(N^2 log N)).
  • suggestion (str): Recommended remediation strategy.
  • line (int): Line number where the sorting call occurs.
Source code in src/alnoms/patterns/redundant_sort.py
def detect(self, tree: ast.AST) -> List[Dict[str, Any]]:
    """Detects sorting calls (`sort`, `sorted`) inside loop bodies.

    Traverses the AST to locate function definitions and inspects all
    nested loops for calls to Python's standard sorting functions.
    When found, the detector records a finding indicating that sorting
    should be moved outside the loop to avoid repeated O(N log N)
    operations.

    Args:
        tree (ast.AST): The parsed AST of the module being analyzed.

    Returns:
        List[Dict[str, Any]]: A list of findings, where each finding
        includes:
            - function (str): Name of the function containing the issue.
            - pattern_id (str): Identifier for this detector.
            - issue (str): Description of the redundant sorting pattern.
            - complexity (str): Complexity classification (O(N^2 log N)).
            - suggestion (str): Recommended remediation strategy.
            - line (int): Line number where the sorting call occurs.
    """
    findings = []
    sorting_funcs = {"sort", "sorted"}

    for node in ast.walk(tree):
        if isinstance(node, ast.FunctionDef):
            loops = [
                c for c in ast.walk(node) if isinstance(c, (ast.For, ast.While))
            ]

            for loop in loops:
                for child in ast.walk(loop):
                    if isinstance(child, ast.Call):
                        func_name = ""

                        # Case: sorted(...)
                        if isinstance(child.func, ast.Name):
                            func_name = child.func.id

                        # Case: list.sort(...)
                        elif isinstance(child.func, ast.Attribute):
                            func_name = child.func.attr

                        if func_name in sorting_funcs:
                            findings.append(
                                {
                                    "function": node.name,
                                    "pattern_id": self.id,
                                    "issue": "Redundant sorting inside loop",
                                    "complexity": "O(N^2 log N)",
                                    "suggestion": (
                                        "Move sorting logic outside the loop to sort data only once."
                                    ),
                                    "line": child.lineno,
                                }
                            )

    return findings

πŸ’Š Fixers

Bases: Fixer

Remediation strategy for nested loop patterns.

This fixer provides intent‑aware remediation for nested loops, distinguishing between cubic patterns (e.g., matrix multiplication) and quadratic patterns such as membership scans, manual sorting, and DFS‑like adjacency traversal. Recommendations include algorithmic redesign, vectorization, hashing, and use of efficient data structures.

Attributes:

Name Type Description
pattern_id str

Identifier for the associated detector pattern.

Source code in src/alnoms/fixes/nested_loops_fixer.py
class NestedLoopFixer(Fixer):
    """Remediation strategy for nested loop patterns.

    This fixer provides intent‑aware remediation for nested loops,
    distinguishing between cubic patterns (e.g., matrix multiplication)
    and quadratic patterns such as membership scans, manual sorting, and
    DFS‑like adjacency traversal. Recommendations include algorithmic
    redesign, vectorization, hashing, and use of efficient data
    structures.

    Attributes:
        pattern_id (str): Identifier for the associated detector pattern.
    """

    pattern_id = "nested_loops"

    # -----------------------------
    # Heuristics
    # -----------------------------

    def _looks_like_matrix_multiply(self, finding):
        """Detects matrix‑multiplication‑like triple‑nested loops.

        This heuristic checks for patterns resembling:

            A[i][k] * B[k][j]

        It is intentionally conservative to avoid false positives.

        Args:
            finding (Dict): The detector finding containing source context.

        Returns:
            bool: True if the code resembles matrix multiplication.
        """
        code = finding.get("source", "") or ""
        code_lower = code.lower()

        return (
            "*" in code
            and "[" in code
            and "]" in code
            and ("a[" in code_lower or "matrix" in code_lower)
            and ("b[" in code_lower or "matrix" in code_lower)
        )

    # -----------------------------
    # Explanation
    # -----------------------------

    def explain(self, finding, detected_complexity="Unknown"):
        """Provides a human‑readable explanation of the nested loop issue.

        This method generates intent‑aware explanations for nested loops,
        distinguishing between cubic and quadratic patterns. It also
        provides domain‑specific guidance for matrix multiplication,
        membership scans, sorting‑like routines, and DFS‑style traversal.

        Args:
            finding (Dict): The detector finding describing the nested loop.
            detected_complexity (str): Static or empirical complexity.

        Returns:
            str: A narrative explanation describing the issue and the
            recommended remediation strategy.
        """
        depth = finding.get("loop_depth", 1)
        intent = finding.get("intent", "generic")

        # --- Cubic Cases ---
        if depth >= 3:
            if self._looks_like_matrix_multiply(finding):
                return (
                    "Cubic complexity detected (Matrix Multiplication Pattern). "
                    "This is a classic O(N^3) algorithm. High risk for production. "
                    "Use vectorized linear algebra (NumPy / BLAS) for 10x–100x speedups."
                )
            return (
                "Cubic complexity detected. This triple-nested loop is a high-risk "
                "algorithmic bottleneck. Consider pruning, memoization, or reducing "
                "the search space."
            )

        # --- Quadratic Cases (Intent-Aware) ---
        if intent == "membership":
            return (
                "This nested loop appears to perform membership or pairwise comparison. "
                "Use a hash-based index (set or dict) to reduce O(N^2) lookups to O(N)."
            )

        if intent == "sorting":
            return (
                "This nested loop resembles a manual sorting routine (e.g., bubble sort). "
                "Replace with an O(N log N) sorting algorithm such as merge sort."
            )

        if intent == "dfs":
            return (
                "This nested loop resembles a graph traversal pattern. "
                "Use a visited set and adjacency list to avoid redundant scanning."
            )

        return (
            "This nested loop likely causes O(N^2) behavior. "
            "Consider algorithmic redesign, pruning, or using more efficient data structures."
        )

    # -----------------------------
    # Snippets
    # -----------------------------

    def snippet_before_after(self, finding, detected_complexity="Unknown"):
        """Returns before/after code snippets illustrating the fix.

        Snippets are intent‑aware and tailored to the specific nested loop
        pattern detected. Cubic patterns receive vectorization or pruning
        guidance, while quadratic patterns receive hashing, sorting, or
        DFS‑related improvements.

        Args:
            finding (Dict): The detector finding describing the nested loop.
            detected_complexity (str): Complexity classification.

        Returns:
            Dict[str, str]: A dictionary containing:
                - ``before``: Example of the inefficient nested loop.
                - ``after``: Example of the recommended remediation.
        """
        depth = finding.get("loop_depth", 1)
        intent = finding.get("intent", "generic")

        # --- Cubic + Matrix Multiplication ---
        if depth >= 3 and self._looks_like_matrix_multiply(finding):
            before = (
                "# Triple-nested loop matrix multiplication\n"
                "for i in range(N):\n"
                "    for j in range(N):\n"
                "        for k in range(N):\n"
                "            C[i][j] += A[i][k] * B[k][j]"
            )
            after = (
                "# Use Vectorized Library (NumPy) or BLAS\n"
                "import numpy as np\n"
                "C = np.matmul(A, B)"
            )
            return {"before": before, "after": after}

        # --- Generic Cubic Fallback ---
        if depth >= 3:
            before = (
                "# Triple-nested loop\n"
                "for i in range(N):\n"
                "    for j in range(N):\n"
                "        for k in range(N):\n"
                "            work(i, j, k)"
            )
            after = (
                "# Consider pruning, memoization, or reducing search space\n"
                "optimized = optimized_algorithm(data)"
            )
            return {"before": before, "after": after}

        # --- Quadratic Membership Fix ---
        if intent == "membership":
            before = (
                "for a in A:\n"
                "    for b in B:\n"
                "        if a == b:\n"
                "            process(a)"
            )
            after = (
                "index = set(B)\nfor a in A:\n    if a in index:\n        process(a)"
            )
            return {"before": before, "after": after}

        # --- Sorting-Like Fix ---
        if intent == "sorting":
            before = (
                "# Manual quadratic sorting (e.g., bubble sort)\n"
                "for i in range(len(arr)):\n"
                "    for j in range(len(arr) - 1):\n"
                "        if arr[j] > arr[j + 1]:\n"
                "            arr[j], arr[j + 1] = arr[j + 1], arr[j]"
            )
            after = "# Replace with O(N log N) sort\narr = sorted(arr)"
            return {"before": before, "after": after}

        # --- DFS-Like Fix ---
        if intent == "dfs":
            before = (
                "# DFS-like nested loop\n"
                "for node in graph:\n"
                "    for neighbor in graph[node]:\n"
                "        process(node, neighbor)"
            )
            after = (
                "# Use visited set to avoid redundant scanning\n"
                "visited = set()\n"
                "def dfs(node):\n"
                "    if node in visited:\n"
                "        return\n"
                "    visited.add(node)\n"
                "    for neighbor in graph[node]:\n"
                "        dfs(neighbor)"
            )
            return {"before": before, "after": after}

        # --- Generic Quadratic Fallback ---
        before = (
            "# Quadratic nested loop\n"
            "for i in range(N):\n"
            "    for j in range(N):\n"
            "        work(i, j)"
        )
        after = (
            "# Consider algorithmic redesign or pruning\n"
            "optimized = optimized_algorithm(data)"
        )
        return {"before": before, "after": after}

    # -----------------------------
    # Cost Estimate
    # -----------------------------

    def cost_estimate(self, finding, detected_complexity="Unknown"):
        """Provides a qualitative estimate of the complexity improvement.

        Args:
            finding (Dict): The detector finding associated with the issue.
            detected_complexity (str): The complexity classification.

        Returns:
            Dict[str, str]: A dictionary describing expected improvements
            in time and memory complexity for cubic and quadratic cases.
        """
        depth = finding.get("loop_depth", 1)

        if depth >= 3:
            return {
                "time": "O(N^3) β†’ O(N^2.8) or Hardware Vectorized",
                "memory": "Implementation Dependent",
            }

        return {
            "time": "O(N^2) β†’ O(N)",
            "memory": "O(N) extra for set (if membership pattern)",
        }

cost_estimate(finding, detected_complexity='Unknown')

Provides a qualitative estimate of the complexity improvement.

Parameters:

Name Type Description Default
finding Dict

The detector finding associated with the issue.

required
detected_complexity str

The complexity classification.

'Unknown'

Returns:

Type Description

Dict[str, str]: A dictionary describing expected improvements

in time and memory complexity for cubic and quadratic cases.

Source code in src/alnoms/fixes/nested_loops_fixer.py
def cost_estimate(self, finding, detected_complexity="Unknown"):
    """Provides a qualitative estimate of the complexity improvement.

    Args:
        finding (Dict): The detector finding associated with the issue.
        detected_complexity (str): The complexity classification.

    Returns:
        Dict[str, str]: A dictionary describing expected improvements
        in time and memory complexity for cubic and quadratic cases.
    """
    depth = finding.get("loop_depth", 1)

    if depth >= 3:
        return {
            "time": "O(N^3) β†’ O(N^2.8) or Hardware Vectorized",
            "memory": "Implementation Dependent",
        }

    return {
        "time": "O(N^2) β†’ O(N)",
        "memory": "O(N) extra for set (if membership pattern)",
    }

explain(finding, detected_complexity='Unknown')

Provides a human‑readable explanation of the nested loop issue.

This method generates intent‑aware explanations for nested loops, distinguishing between cubic and quadratic patterns. It also provides domain‑specific guidance for matrix multiplication, membership scans, sorting‑like routines, and DFS‑style traversal.

Parameters:

Name Type Description Default
finding Dict

The detector finding describing the nested loop.

required
detected_complexity str

Static or empirical complexity.

'Unknown'

Returns:

Name Type Description
str

A narrative explanation describing the issue and the

recommended remediation strategy.

Source code in src/alnoms/fixes/nested_loops_fixer.py
def explain(self, finding, detected_complexity="Unknown"):
    """Provides a human‑readable explanation of the nested loop issue.

    This method generates intent‑aware explanations for nested loops,
    distinguishing between cubic and quadratic patterns. It also
    provides domain‑specific guidance for matrix multiplication,
    membership scans, sorting‑like routines, and DFS‑style traversal.

    Args:
        finding (Dict): The detector finding describing the nested loop.
        detected_complexity (str): Static or empirical complexity.

    Returns:
        str: A narrative explanation describing the issue and the
        recommended remediation strategy.
    """
    depth = finding.get("loop_depth", 1)
    intent = finding.get("intent", "generic")

    # --- Cubic Cases ---
    if depth >= 3:
        if self._looks_like_matrix_multiply(finding):
            return (
                "Cubic complexity detected (Matrix Multiplication Pattern). "
                "This is a classic O(N^3) algorithm. High risk for production. "
                "Use vectorized linear algebra (NumPy / BLAS) for 10x–100x speedups."
            )
        return (
            "Cubic complexity detected. This triple-nested loop is a high-risk "
            "algorithmic bottleneck. Consider pruning, memoization, or reducing "
            "the search space."
        )

    # --- Quadratic Cases (Intent-Aware) ---
    if intent == "membership":
        return (
            "This nested loop appears to perform membership or pairwise comparison. "
            "Use a hash-based index (set or dict) to reduce O(N^2) lookups to O(N)."
        )

    if intent == "sorting":
        return (
            "This nested loop resembles a manual sorting routine (e.g., bubble sort). "
            "Replace with an O(N log N) sorting algorithm such as merge sort."
        )

    if intent == "dfs":
        return (
            "This nested loop resembles a graph traversal pattern. "
            "Use a visited set and adjacency list to avoid redundant scanning."
        )

    return (
        "This nested loop likely causes O(N^2) behavior. "
        "Consider algorithmic redesign, pruning, or using more efficient data structures."
    )

snippet_before_after(finding, detected_complexity='Unknown')

Returns before/after code snippets illustrating the fix.

Snippets are intent‑aware and tailored to the specific nested loop pattern detected. Cubic patterns receive vectorization or pruning guidance, while quadratic patterns receive hashing, sorting, or DFS‑related improvements.

Parameters:

Name Type Description Default
finding Dict

The detector finding describing the nested loop.

required
detected_complexity str

Complexity classification.

'Unknown'

Returns:

Type Description

Dict[str, str]: A dictionary containing: - before: Example of the inefficient nested loop. - after: Example of the recommended remediation.

Source code in src/alnoms/fixes/nested_loops_fixer.py
def snippet_before_after(self, finding, detected_complexity="Unknown"):
    """Returns before/after code snippets illustrating the fix.

    Snippets are intent‑aware and tailored to the specific nested loop
    pattern detected. Cubic patterns receive vectorization or pruning
    guidance, while quadratic patterns receive hashing, sorting, or
    DFS‑related improvements.

    Args:
        finding (Dict): The detector finding describing the nested loop.
        detected_complexity (str): Complexity classification.

    Returns:
        Dict[str, str]: A dictionary containing:
            - ``before``: Example of the inefficient nested loop.
            - ``after``: Example of the recommended remediation.
    """
    depth = finding.get("loop_depth", 1)
    intent = finding.get("intent", "generic")

    # --- Cubic + Matrix Multiplication ---
    if depth >= 3 and self._looks_like_matrix_multiply(finding):
        before = (
            "# Triple-nested loop matrix multiplication\n"
            "for i in range(N):\n"
            "    for j in range(N):\n"
            "        for k in range(N):\n"
            "            C[i][j] += A[i][k] * B[k][j]"
        )
        after = (
            "# Use Vectorized Library (NumPy) or BLAS\n"
            "import numpy as np\n"
            "C = np.matmul(A, B)"
        )
        return {"before": before, "after": after}

    # --- Generic Cubic Fallback ---
    if depth >= 3:
        before = (
            "# Triple-nested loop\n"
            "for i in range(N):\n"
            "    for j in range(N):\n"
            "        for k in range(N):\n"
            "            work(i, j, k)"
        )
        after = (
            "# Consider pruning, memoization, or reducing search space\n"
            "optimized = optimized_algorithm(data)"
        )
        return {"before": before, "after": after}

    # --- Quadratic Membership Fix ---
    if intent == "membership":
        before = (
            "for a in A:\n"
            "    for b in B:\n"
            "        if a == b:\n"
            "            process(a)"
        )
        after = (
            "index = set(B)\nfor a in A:\n    if a in index:\n        process(a)"
        )
        return {"before": before, "after": after}

    # --- Sorting-Like Fix ---
    if intent == "sorting":
        before = (
            "# Manual quadratic sorting (e.g., bubble sort)\n"
            "for i in range(len(arr)):\n"
            "    for j in range(len(arr) - 1):\n"
            "        if arr[j] > arr[j + 1]:\n"
            "            arr[j], arr[j + 1] = arr[j + 1], arr[j]"
        )
        after = "# Replace with O(N log N) sort\narr = sorted(arr)"
        return {"before": before, "after": after}

    # --- DFS-Like Fix ---
    if intent == "dfs":
        before = (
            "# DFS-like nested loop\n"
            "for node in graph:\n"
            "    for neighbor in graph[node]:\n"
            "        process(node, neighbor)"
        )
        after = (
            "# Use visited set to avoid redundant scanning\n"
            "visited = set()\n"
            "def dfs(node):\n"
            "    if node in visited:\n"
            "        return\n"
            "    visited.add(node)\n"
            "    for neighbor in graph[node]:\n"
            "        dfs(neighbor)"
        )
        return {"before": before, "after": after}

    # --- Generic Quadratic Fallback ---
    before = (
        "# Quadratic nested loop\n"
        "for i in range(N):\n"
        "    for j in range(N):\n"
        "        work(i, j)"
    )
    after = (
        "# Consider algorithmic redesign or pruning\n"
        "optimized = optimized_algorithm(data)"
    )
    return {"before": before, "after": after}

Bases: Fixer

Remediation strategy for inefficient membership tests inside loops.

This fixer addresses patterns where membership checks such as x in list or x in tuple occur inside a loop. These operations are O(N) per lookup, leading to accidental O(N*M) or O(NΒ²) behavior. The recommended remediation is to convert the container to a set to achieve O(1) average‑case membership checks.

Attributes:

Name Type Description
pattern_id str

Identifier for the associated detector pattern.

Source code in src/alnoms/fixes/inefficient_membership_fixer.py
class InefficientMembershipFixer(Fixer):
    """Remediation strategy for inefficient membership tests inside loops.

    This fixer addresses patterns where membership checks such as
    ``x in list`` or ``x in tuple`` occur inside a loop. These operations
    are O(N) per lookup, leading to accidental O(N*M) or O(NΒ²) behavior.
    The recommended remediation is to convert the container to a ``set``
    to achieve O(1) average‑case membership checks.

    Attributes:
        pattern_id (str): Identifier for the associated detector pattern.
    """

    pattern_id = "inefficient_membership"

    def explain(self, finding, detected_complexity="Unknown"):
        """Provides a human‑readable explanation of the optimization.

        Args:
            finding (Dict): The detector finding describing the membership pattern.
            detected_complexity (str): The static or empirical complexity
                associated with the anti‑pattern.

        Returns:
            str: A narrative explanation describing why list‑based membership
            checks inside loops are slow and how converting to a set improves
            performance.
        """
        return (
            "Membership checks on lists inside loops are O(N). "
            "Convert to a set for O(1) lookups."
        )

    def snippet_before_after(self, finding, detected_complexity="Unknown"):
        """Returns before/after code snippets illustrating the fix.

        Args:
            finding (Dict): The detector output for the inefficient membership test.
            detected_complexity (str): Complexity classification used to
                contextualize the snippet.

        Returns:
            Dict[str, str]: A dictionary containing:
                - ``before``: Example of repeated list membership checks.
                - ``after``: Example using a set for constant‑time lookups.
        """
        before = "for k in keys:\n    if k in items:\n        process(k)"
        after = (
            "items_set = set(items)\n"
            "for k in keys:\n"
            "    if k in items_set:\n"
            "        process(k)"
        )
        return {"before": before, "after": after}

    def cost_estimate(self, finding, detected_complexity="Unknown"):
        """Provides a qualitative estimate of the complexity improvement.

        Args:
            finding (Dict): The detector finding associated with the issue.
            detected_complexity (str): The complexity classification.

        Returns:
            Dict[str, str]: A dictionary describing expected improvements
            in time and memory complexity. Converting to a set reduces
            repeated O(N) scans to O(1) lookups.
        """
        return {"time": "O(N*M) β†’ O(N + M)", "memory": "O(M)"}

cost_estimate(finding, detected_complexity='Unknown')

Provides a qualitative estimate of the complexity improvement.

Parameters:

Name Type Description Default
finding Dict

The detector finding associated with the issue.

required
detected_complexity str

The complexity classification.

'Unknown'

Returns:

Type Description

Dict[str, str]: A dictionary describing expected improvements

in time and memory complexity. Converting to a set reduces

repeated O(N) scans to O(1) lookups.

Source code in src/alnoms/fixes/inefficient_membership_fixer.py
def cost_estimate(self, finding, detected_complexity="Unknown"):
    """Provides a qualitative estimate of the complexity improvement.

    Args:
        finding (Dict): The detector finding associated with the issue.
        detected_complexity (str): The complexity classification.

    Returns:
        Dict[str, str]: A dictionary describing expected improvements
        in time and memory complexity. Converting to a set reduces
        repeated O(N) scans to O(1) lookups.
    """
    return {"time": "O(N*M) β†’ O(N + M)", "memory": "O(M)"}

explain(finding, detected_complexity='Unknown')

Provides a human‑readable explanation of the optimization.

Parameters:

Name Type Description Default
finding Dict

The detector finding describing the membership pattern.

required
detected_complexity str

The static or empirical complexity associated with the anti‑pattern.

'Unknown'

Returns:

Name Type Description
str

A narrative explanation describing why list‑based membership

checks inside loops are slow and how converting to a set improves

performance.

Source code in src/alnoms/fixes/inefficient_membership_fixer.py
def explain(self, finding, detected_complexity="Unknown"):
    """Provides a human‑readable explanation of the optimization.

    Args:
        finding (Dict): The detector finding describing the membership pattern.
        detected_complexity (str): The static or empirical complexity
            associated with the anti‑pattern.

    Returns:
        str: A narrative explanation describing why list‑based membership
        checks inside loops are slow and how converting to a set improves
        performance.
    """
    return (
        "Membership checks on lists inside loops are O(N). "
        "Convert to a set for O(1) lookups."
    )

snippet_before_after(finding, detected_complexity='Unknown')

Returns before/after code snippets illustrating the fix.

Parameters:

Name Type Description Default
finding Dict

The detector output for the inefficient membership test.

required
detected_complexity str

Complexity classification used to contextualize the snippet.

'Unknown'

Returns:

Type Description

Dict[str, str]: A dictionary containing: - before: Example of repeated list membership checks. - after: Example using a set for constant‑time lookups.

Source code in src/alnoms/fixes/inefficient_membership_fixer.py
def snippet_before_after(self, finding, detected_complexity="Unknown"):
    """Returns before/after code snippets illustrating the fix.

    Args:
        finding (Dict): The detector output for the inefficient membership test.
        detected_complexity (str): Complexity classification used to
            contextualize the snippet.

    Returns:
        Dict[str, str]: A dictionary containing:
            - ``before``: Example of repeated list membership checks.
            - ``after``: Example using a set for constant‑time lookups.
    """
    before = "for k in keys:\n    if k in items:\n        process(k)"
    after = (
        "items_set = set(items)\n"
        "for k in keys:\n"
        "    if k in items_set:\n"
        "        process(k)"
    )
    return {"before": before, "after": after}

πŸ›‘οΈ Pre-Deployment Governance (CLI)

Provides the command-line interface for the Alnoms performance intelligence system. The CLI orchestrates static AST analysis, prescriptive remediation suggestions, dynamic profiling, and empirical doubling tests to detect performance bottlenecks before production.

Features
  • CI/CD Ready: Supports raw and pretty JSON outputs for automation pipelines.
  • Static Analysis: Detects inefficient loops, API calls, and performance traps.
  • Prescriptive Suggestions: Recommends O(1) fixes and optimized patterns.
  • Empirical Analysis: Integrates with Arprax Profiler for Big-O scaling analysis.

PerformanceCLI

Performance Intelligence CLI for complexity analysis and profiling.

Acts as the primary entrypoint for the Alnoms performance intelligence system. Provides a terminal interface for static analysis, dynamic profiling, empirical scaling tests, and structured performance reporting. Designed for both human-readable output and CI/CD automation.

Source code in src/alnoms/cli.py
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
class PerformanceCLI:
    """Performance Intelligence CLI for complexity analysis and profiling.

    Acts as the primary entrypoint for the Alnoms performance intelligence system.
    Provides a terminal interface for static analysis, dynamic profiling,
    empirical scaling tests, and structured performance reporting. Designed for
    both human-readable output and CI/CD automation.
    """

    @staticmethod
    def print_report(result: dict) -> None:
        """Renders a full performance intelligence report in human-readable format.

        This method produces a structured analysis report that includes detected
        performance patterns, static diagnostics, dynamic profiling bottlenecks,
        empirical scaling analysis, performance verdict, and simulated fixes.

        Args:
            result (dict): Output from `ScriptAnalyzer.analyze_file()` containing:
                - 'patterns' (list): Detected performance patterns.
                - 'profile' (list): Dynamic profiling data.
                - 'empirical' (list): Empirical scaling analysis data.
                - 'meta' (dict): Metadata including timestamp and version.
                - 'empirical_target' (str): The target function for scaling tests.
                - 'total_time' (float): Total execution time.
                - 'file' (str): The analyzed file path.
        """
        meta = result.get("meta", {})
        patterns = result.get("patterns", [])
        profile_data = result.get("profile", [])
        empirical_data = result.get("empirical")
        target_name = result.get("empirical_target")

        print("\n==================================================")
        print("βš–οΈ PERFORMANCE REPORT")
        print("==================================================")
        print(f"File: {result.get('file', 'Unknown')}")
        print(f"Timestamp (UTC): {meta.get('timestamp', 'Unknown')}")
        print(f"Total Execution Time: {result.get('total_time', 0)}s\n")

        # ---------------------------------------------------------
        # 0. DETECTED INTENT
        # ---------------------------------------------------------
        if patterns:
            primary = patterns[0]
            intent = (
                primary.get("intent")
                or primary.get("issue")
                or primary.get("pattern_id")
            )
            print("🧠 DETECTED INTENT:")
            print(f"   {intent}\n")

        # ---------------------------------------------------------
        # 1. STATIC ANALYSIS & SUGGESTIONS
        # ---------------------------------------------------------
        print("🚨 STATIC ANALYSIS (Diagnostics & Suggestions)")
        print("-" * 50)

        if not patterns:
            print(
                "   βœ… No performance issues detected. Code is structurally efficient.\n"
            )
        else:
            for i, p in enumerate(patterns, 1):
                func_name = p.get("function", "global")
                line_no = p.get("line", "??")
                issue = p.get("issue", p.get("pattern_id"))

                print(
                    f"{i}. ⚠️ ISSUE: {issue} (Function: {func_name} | Line: {line_no})"
                )

                if "explanation" in p:
                    print(f"   πŸ“– Explanation: {p['explanation']}")

                    dsa = p.get("dsa_meta")
                    if dsa:
                        complexity = dsa.get("complexity", "O(N)")
                        module_path = dsa.get("module", "builtin")
                        tier = dsa.get("tier", "OSS")

                        print(f"   πŸ’Š RECOMMENDED OPTIMIZATION: {complexity}")
                        print(f"   πŸ—οΈ IMPLEMENTATION: {module_path}")
                        print(f"   πŸ” ACCESS TIER: {tier}")

                    costs = p.get("cost_estimate", {})
                    if "time" in costs:
                        print(f"   ⏱️ Complexity Shift: {costs['time']}")

                    snip = p.get("snippets")
                    if snip:
                        print("\n   πŸ’‘ SUGGESTED FIX:")
                        print("   --- BEFORE ---")
                        for line in snip["before"].split("\n"):
                            print(f"   |  {line}")
                        print("   --- AFTER ---")
                        for line in snip["after"].split("\n"):
                            print(f"   |  {line}")

                print()

        # ---------------------------------------------------------
        # 2. DYNAMIC PROFILING
        # ---------------------------------------------------------
        print("⏱️ DYNAMIC PROFILING (Top Execution Bottlenecks)")
        print("-" * 50)

        if not profile_data:
            print("   βœ… No performance bottlenecks detected in execution.\n")
        else:
            for i, func in enumerate(profile_data, 1):
                print(
                    f"   {i}. {func['function']}() -> {func['time']}s ({func['percent']}%)"
                )
            print()

        # ---------------------------------------------------------
        # 3. EMPIRICAL SCALING
        # ---------------------------------------------------------
        if empirical_data:
            print(f"πŸ“ˆ EMPIRICAL SCALING ANALYSIS: {target_name}()")
            print("-" * 50)
            print(
                f"{'N':<10} | {'Time (s)':<12} | {'Ratio':<8} | {'Est. Complexity':<15}"
            )
            print("-" * 50)

            final_complexity = "O(1)"
            for row in empirical_data:
                r_str = f"{row['Ratio']:.2f}" if row["Ratio"] > 0 else "-"
                print(
                    f"{row['N']:<10} | {row['Time']:<12.5f} | {r_str:<8} | {row['Complexity']:<15}"
                )
                final_complexity = row["Complexity"]

            # ---------------------------------------------------------
            # 4. VERDICT
            # ---------------------------------------------------------
            print("\nβš–οΈ VERDICT:")

            safe_tiers = ["O(1)", "O(log N)", "O(N)", "O(N log N)"]

            if final_complexity in safe_tiers:
                print(
                    f"βœ… PASSED: Function operates at {final_complexity}. Safe for scaling."
                )
            elif final_complexity == "O(N^2)":
                print(
                    f"⚠️ WARNING: Function operates at {final_complexity}. May not scale efficiently."
                )
            else:
                print(
                    f"❌ RISK: Function operates at {final_complexity}. Review recommended."
                )

            # ---------------------------------------------------------
            # 5. CONTEXT
            # ---------------------------------------------------------
            print("\nπŸ“Œ CONTEXT")
            print("-" * 50)
            print(
                "   Empirical scaling validates asymptotic behavior under increasing load.\n"
            )

            # ---------------------------------------------------------
            # 6. IMPACT ESTIMATION
            # ---------------------------------------------------------
            print("πŸš€ EXPECTED IMPACT")
            print("-" * 50)
            print("   For N = 10,000:")
            print("     β€’ O(NΒ²) β†’ ~100,000,000 operations")
            print("     β€’ O(N)  β†’ ~10,000 operations")
            print("   Estimated improvement: 100×–1000Γ— depending on workload.\n")

            # ---------------------------------------------------------
            # 7. CONFIDENCE
            # ---------------------------------------------------------
            print("πŸ€– CONFIDENCE")
            print("-" * 50)

            if final_complexity == "O(N^2)":
                print("   High β€” static and empirical signals agree.\n")
            else:
                print("   Medium β€” mixed signals between analysis methods.\n")

            # ---------------------------------------------------------
            # 8. SIMULATED FIX
            # ---------------------------------------------------------
            print("πŸ” AFTER OPTIMIZATION (SIMULATED)")
            print("-" * 50)
            print("   Expected Complexity: O(N)")
            print("   Behavior: Linear scaling with stable performance.")
            print("   Suggested Implementation:")
            print("       s = set(arr)")
            print("       for x in arr:")
            print("           if x in s:")
            print("               total += x\n")

        else:
            print("ℹ️ EMPIRICAL ANALYSIS SKIPPED\n")

        print("==================================================\n")

    @staticmethod
    def main() -> None:
        """Primary entry point for the Alnoms Command-Line Interface.

        Configures the argument parser and routes execution based on the selected
        subcommand. Supports two primary modes:
        1. `analyze`: Human-readable performance reporting with deep profiling options.
        2. `ci`: Headless execution mode that accepts multiple files, outputs strict
           JSON, and enforces Big-O complexity guardrails via system exit codes.

        Raises:
            SystemExit:
                - Exits with 0 on successful execution and clean compliance.
                - Exits with 1 if an internal error occurs or if the `ci` mode detects
                  a performance bottleneck that breaches the `--fail-on` threshold.
        """
        parser = argparse.ArgumentParser(
            prog="alnoms",
            description="πŸ”¬ Alnoms: Performance Intelligence System",
            formatter_class=argparse.RawDescriptionHelpFormatter,
            epilog="""
    Example Usage:
      alnoms analyze script.py
      alnoms analyze script.py --deep
      alnoms analyze script.py --deep --start-n 50 --rounds 3
      alnoms analyze script.py --json

      # CI/CD Execution
      alnoms ci file1.py file2.py --fail-on "O(N^3)"
                """,
        )

        # Global version flag
        parser.add_argument("-v", "--version", action="version", version="Alnoms 1.1.2")

        subparsers = parser.add_subparsers(dest="command", help="Commands")

        # --- 1. THE HUMAN COMMAND (analyze) ---
        analyze_parser = subparsers.add_parser(
            "analyze", help="Analyze a single Python file"
        )
        analyze_parser.add_argument("file", help="Python file path")

        audit_group = analyze_parser.add_argument_group("Analysis Options")
        audit_group.add_argument("--deep", action="store_true")
        audit_group.add_argument("--function", dest="target_override")
        audit_group.add_argument("--gen", dest="gen")
        audit_group.add_argument("--data", dest="data")
        audit_group.add_argument("--start-n", type=int, default=50)
        audit_group.add_argument("--rounds", type=int, default=3)

        output_group = analyze_parser.add_argument_group("Output Options")
        output_group.add_argument("--json", action="store_true")
        output_group.add_argument("--pretty", action="store_true")

        # --- 2. THE ROBOT COMMAND (ci) ---
        ci_parser = subparsers.add_parser(
            "ci", help="Headless execution for CI/CD pipelines"
        )
        ci_parser.add_argument("files", nargs="+", help="List of Python files to scan")
        ci_parser.add_argument(
            "--fail-on",
            default="",
            help="Complexity threshold to fail the build (e.g., O(N^3))",
        )

        args = parser.parse_args()

        if args.command == "analyze":
            try:
                result = ScriptAnalyzer.analyze_file(
                    path=args.file,
                    deep=args.deep,
                    target_override=args.target_override,
                    gen_name=args.gen,
                    data_file=args.data,
                    start_n=args.start_n,
                    rounds=args.rounds,
                )

                if args.json or args.pretty:
                    print(json.dumps(result, indent=2 if args.pretty else None))
                    return

                PerformanceCLI.print_report(result)

            except Exception as e:
                print(f"❌ Error analyzing {args.file}: {str(e)}")
                sys.exit(1)

        elif args.command == "ci":
            # --- SEVERITY & SCORING MODEL ---

            severity_scoring = {
                "O(2^N)": {"level": "CRITICAL", "score": 7},
                "O(N^3)": {"level": "CRITICAL", "score": 6},
                "O(N^2)": {"level": "HIGH", "score": 5},
                "O(N log N)": {"level": "MEDIUM", "score": 4},
                "O(N)": {"level": "LOW", "score": 3},
                "O(log N)": {"level": "LOW", "score": 2},
                "O(1)": {"level": "LOW", "score": 1},
            }

            fail_score = severity_scoring.get(args.fail_on, {}).get("score", 99)

            raw_issues = []

            # --- 1. GATHER EVIDENCE ---

            for file_path in args.files:
                try:
                    with contextlib.redirect_stdout(io.StringIO()):
                        result = ScriptAnalyzer.analyze_file(path=file_path, deep=False)

                    patterns = result.get("patterns", [])

                    for p in patterns:
                        comp = p.get("static_complexity") or "O(N^2)"

                        meta = severity_scoring.get(comp, {"level": "HIGH", "score": 5})

                        raw_issues.append(
                            {
                                "file": file_path,
                                "function": p.get("function", "global"),
                                "complexity": comp,
                                "severity": meta["level"],
                                "_score": meta["score"],  # Internal sorting key
                                "issue": p.get("issue", "Unknown Bottleneck"),
                                "suggestion": p.get("explanation", ""),
                            }
                        )

                except Exception as e:
                    # Handle internal engine crashes gracefully in the new schema

                    raw_issues.append(
                        {
                            "file": file_path,
                            "function": "unknown",
                            "complexity": "Unknown",
                            "severity": "CRITICAL",
                            "_score": 99,
                            "issue": "Engine Crash",
                            "suggestion": str(e),
                        }
                    )

            # --- 2. SORT & PRIORITIZE ---

            # Sort by highest severity score first. If tied, sort alphabetically by file to ensure determinism.

            raw_issues.sort(key=lambda x: (x["_score"], x["file"]), reverse=True)

            # Strip the internal sorting key for clean JSON

            for issue in raw_issues:
                del issue["_score"]

            # --- 3. BUILD THE PAYLOAD ---

            payload = {}

            if not raw_issues:
                # Clean Pass

                payload = {
                    "decision": {
                        "status": "PASS",
                        "reason": "No performance regressions detected",
                        "confidence": "HIGH",
                    },
                    "primary_trigger": None,
                    "summary": {
                        "total_issues": 0,
                        "by_severity": {
                            "CRITICAL": 0,
                            "HIGH": 0,
                            "MEDIUM": 0,
                            "LOW": 0,
                        },
                        "worst_complexity": "O(1)",
                        "risk_level": "LOW",
                    },
                    "issues": [],
                    "metadata": {
                        "scanned_files": len(args.files),
                        "analysis_mode": "fast",
                        "timestamp": datetime.now(timezone.utc)
                        .isoformat()
                        .replace("+00:00", "Z"),
                    },
                }

            else:
                # Issues Found

                primary = raw_issues[0]

                # --- 3. BUILD THE PAYLOAD ---

                # Aggregate Summary
                summary = {
                    "total_issues": len(raw_issues),
                    "by_severity": {"CRITICAL": 0, "HIGH": 0, "MEDIUM": 0, "LOW": 0},
                    "worst_complexity": primary["complexity"],
                    "risk_level": primary["severity"],
                }
                for issue in raw_issues:
                    if issue["severity"] in summary["by_severity"]:
                        summary["by_severity"][issue["severity"]] += 1

                # --- THE STRICT GATE DECISION MODEL ---
                primary_score = severity_scoring.get(primary["complexity"], {}).get(
                    "score", 99
                )

                if primary["severity"] == "CRITICAL":
                    # Hard Rule: CRITICAL issues ALWAYS block. They cannot be bypassed.
                    is_blocked = True
                    reason = f"CRITICAL performance risk detected ({primary['complexity']} scaling)"
                elif primary_score >= fail_score:
                    # Threshold Rule: HIGH/MEDIUM issues block if they cross the user's defined limit.
                    is_blocked = True
                    reason = f"Performance regression threshold exceeded ({primary['complexity']} scaling)"
                else:
                    # Clean Pass
                    is_blocked = False
                    reason = "Code scales efficiently within acceptable limits."

                payload = {
                    "decision": {
                        "status": "BLOCK" if is_blocked else "PASS",
                        "reason": reason,
                        "confidence": "HIGH",
                    },
                    "primary_trigger": primary,
                    "summary": summary,
                    "issues": raw_issues,
                    "metadata": {
                        "scanned_files": len(args.files),
                        "analysis_mode": "fast",
                        "timestamp": datetime.now(timezone.utc)
                        .isoformat()
                        .replace("+00:00", "Z"),
                    },
                }

            # --- 4. OUTPUT ---

            print(json.dumps(payload, indent=2))

            if payload.get("decision", {}).get("status") == "BLOCK":
                sys.exit(1)

            sys.exit(0)
        else:
            parser.print_help()
main() staticmethod

Primary entry point for the Alnoms Command-Line Interface.

Configures the argument parser and routes execution based on the selected subcommand. Supports two primary modes: 1. analyze: Human-readable performance reporting with deep profiling options. 2. ci: Headless execution mode that accepts multiple files, outputs strict JSON, and enforces Big-O complexity guardrails via system exit codes.

Raises:

Type Description
SystemExit
  • Exits with 0 on successful execution and clean compliance.
  • Exits with 1 if an internal error occurs or if the ci mode detects a performance bottleneck that breaches the --fail-on threshold.
Source code in src/alnoms/cli.py
@staticmethod
def main() -> None:
    """Primary entry point for the Alnoms Command-Line Interface.

    Configures the argument parser and routes execution based on the selected
    subcommand. Supports two primary modes:
    1. `analyze`: Human-readable performance reporting with deep profiling options.
    2. `ci`: Headless execution mode that accepts multiple files, outputs strict
       JSON, and enforces Big-O complexity guardrails via system exit codes.

    Raises:
        SystemExit:
            - Exits with 0 on successful execution and clean compliance.
            - Exits with 1 if an internal error occurs or if the `ci` mode detects
              a performance bottleneck that breaches the `--fail-on` threshold.
    """
    parser = argparse.ArgumentParser(
        prog="alnoms",
        description="πŸ”¬ Alnoms: Performance Intelligence System",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Example Usage:
  alnoms analyze script.py
  alnoms analyze script.py --deep
  alnoms analyze script.py --deep --start-n 50 --rounds 3
  alnoms analyze script.py --json

  # CI/CD Execution
  alnoms ci file1.py file2.py --fail-on "O(N^3)"
            """,
    )

    # Global version flag
    parser.add_argument("-v", "--version", action="version", version="Alnoms 1.1.2")

    subparsers = parser.add_subparsers(dest="command", help="Commands")

    # --- 1. THE HUMAN COMMAND (analyze) ---
    analyze_parser = subparsers.add_parser(
        "analyze", help="Analyze a single Python file"
    )
    analyze_parser.add_argument("file", help="Python file path")

    audit_group = analyze_parser.add_argument_group("Analysis Options")
    audit_group.add_argument("--deep", action="store_true")
    audit_group.add_argument("--function", dest="target_override")
    audit_group.add_argument("--gen", dest="gen")
    audit_group.add_argument("--data", dest="data")
    audit_group.add_argument("--start-n", type=int, default=50)
    audit_group.add_argument("--rounds", type=int, default=3)

    output_group = analyze_parser.add_argument_group("Output Options")
    output_group.add_argument("--json", action="store_true")
    output_group.add_argument("--pretty", action="store_true")

    # --- 2. THE ROBOT COMMAND (ci) ---
    ci_parser = subparsers.add_parser(
        "ci", help="Headless execution for CI/CD pipelines"
    )
    ci_parser.add_argument("files", nargs="+", help="List of Python files to scan")
    ci_parser.add_argument(
        "--fail-on",
        default="",
        help="Complexity threshold to fail the build (e.g., O(N^3))",
    )

    args = parser.parse_args()

    if args.command == "analyze":
        try:
            result = ScriptAnalyzer.analyze_file(
                path=args.file,
                deep=args.deep,
                target_override=args.target_override,
                gen_name=args.gen,
                data_file=args.data,
                start_n=args.start_n,
                rounds=args.rounds,
            )

            if args.json or args.pretty:
                print(json.dumps(result, indent=2 if args.pretty else None))
                return

            PerformanceCLI.print_report(result)

        except Exception as e:
            print(f"❌ Error analyzing {args.file}: {str(e)}")
            sys.exit(1)

    elif args.command == "ci":
        # --- SEVERITY & SCORING MODEL ---

        severity_scoring = {
            "O(2^N)": {"level": "CRITICAL", "score": 7},
            "O(N^3)": {"level": "CRITICAL", "score": 6},
            "O(N^2)": {"level": "HIGH", "score": 5},
            "O(N log N)": {"level": "MEDIUM", "score": 4},
            "O(N)": {"level": "LOW", "score": 3},
            "O(log N)": {"level": "LOW", "score": 2},
            "O(1)": {"level": "LOW", "score": 1},
        }

        fail_score = severity_scoring.get(args.fail_on, {}).get("score", 99)

        raw_issues = []

        # --- 1. GATHER EVIDENCE ---

        for file_path in args.files:
            try:
                with contextlib.redirect_stdout(io.StringIO()):
                    result = ScriptAnalyzer.analyze_file(path=file_path, deep=False)

                patterns = result.get("patterns", [])

                for p in patterns:
                    comp = p.get("static_complexity") or "O(N^2)"

                    meta = severity_scoring.get(comp, {"level": "HIGH", "score": 5})

                    raw_issues.append(
                        {
                            "file": file_path,
                            "function": p.get("function", "global"),
                            "complexity": comp,
                            "severity": meta["level"],
                            "_score": meta["score"],  # Internal sorting key
                            "issue": p.get("issue", "Unknown Bottleneck"),
                            "suggestion": p.get("explanation", ""),
                        }
                    )

            except Exception as e:
                # Handle internal engine crashes gracefully in the new schema

                raw_issues.append(
                    {
                        "file": file_path,
                        "function": "unknown",
                        "complexity": "Unknown",
                        "severity": "CRITICAL",
                        "_score": 99,
                        "issue": "Engine Crash",
                        "suggestion": str(e),
                    }
                )

        # --- 2. SORT & PRIORITIZE ---

        # Sort by highest severity score first. If tied, sort alphabetically by file to ensure determinism.

        raw_issues.sort(key=lambda x: (x["_score"], x["file"]), reverse=True)

        # Strip the internal sorting key for clean JSON

        for issue in raw_issues:
            del issue["_score"]

        # --- 3. BUILD THE PAYLOAD ---

        payload = {}

        if not raw_issues:
            # Clean Pass

            payload = {
                "decision": {
                    "status": "PASS",
                    "reason": "No performance regressions detected",
                    "confidence": "HIGH",
                },
                "primary_trigger": None,
                "summary": {
                    "total_issues": 0,
                    "by_severity": {
                        "CRITICAL": 0,
                        "HIGH": 0,
                        "MEDIUM": 0,
                        "LOW": 0,
                    },
                    "worst_complexity": "O(1)",
                    "risk_level": "LOW",
                },
                "issues": [],
                "metadata": {
                    "scanned_files": len(args.files),
                    "analysis_mode": "fast",
                    "timestamp": datetime.now(timezone.utc)
                    .isoformat()
                    .replace("+00:00", "Z"),
                },
            }

        else:
            # Issues Found

            primary = raw_issues[0]

            # --- 3. BUILD THE PAYLOAD ---

            # Aggregate Summary
            summary = {
                "total_issues": len(raw_issues),
                "by_severity": {"CRITICAL": 0, "HIGH": 0, "MEDIUM": 0, "LOW": 0},
                "worst_complexity": primary["complexity"],
                "risk_level": primary["severity"],
            }
            for issue in raw_issues:
                if issue["severity"] in summary["by_severity"]:
                    summary["by_severity"][issue["severity"]] += 1

            # --- THE STRICT GATE DECISION MODEL ---
            primary_score = severity_scoring.get(primary["complexity"], {}).get(
                "score", 99
            )

            if primary["severity"] == "CRITICAL":
                # Hard Rule: CRITICAL issues ALWAYS block. They cannot be bypassed.
                is_blocked = True
                reason = f"CRITICAL performance risk detected ({primary['complexity']} scaling)"
            elif primary_score >= fail_score:
                # Threshold Rule: HIGH/MEDIUM issues block if they cross the user's defined limit.
                is_blocked = True
                reason = f"Performance regression threshold exceeded ({primary['complexity']} scaling)"
            else:
                # Clean Pass
                is_blocked = False
                reason = "Code scales efficiently within acceptable limits."

            payload = {
                "decision": {
                    "status": "BLOCK" if is_blocked else "PASS",
                    "reason": reason,
                    "confidence": "HIGH",
                },
                "primary_trigger": primary,
                "summary": summary,
                "issues": raw_issues,
                "metadata": {
                    "scanned_files": len(args.files),
                    "analysis_mode": "fast",
                    "timestamp": datetime.now(timezone.utc)
                    .isoformat()
                    .replace("+00:00", "Z"),
                },
            }

        # --- 4. OUTPUT ---

        print(json.dumps(payload, indent=2))

        if payload.get("decision", {}).get("status") == "BLOCK":
            sys.exit(1)

        sys.exit(0)
    else:
        parser.print_help()
print_report(result) staticmethod

Renders a full performance intelligence report in human-readable format.

This method produces a structured analysis report that includes detected performance patterns, static diagnostics, dynamic profiling bottlenecks, empirical scaling analysis, performance verdict, and simulated fixes.

Parameters:

Name Type Description Default
result dict

Output from ScriptAnalyzer.analyze_file() containing: - 'patterns' (list): Detected performance patterns. - 'profile' (list): Dynamic profiling data. - 'empirical' (list): Empirical scaling analysis data. - 'meta' (dict): Metadata including timestamp and version. - 'empirical_target' (str): The target function for scaling tests. - 'total_time' (float): Total execution time. - 'file' (str): The analyzed file path.

required
Source code in src/alnoms/cli.py
@staticmethod
def print_report(result: dict) -> None:
    """Renders a full performance intelligence report in human-readable format.

    This method produces a structured analysis report that includes detected
    performance patterns, static diagnostics, dynamic profiling bottlenecks,
    empirical scaling analysis, performance verdict, and simulated fixes.

    Args:
        result (dict): Output from `ScriptAnalyzer.analyze_file()` containing:
            - 'patterns' (list): Detected performance patterns.
            - 'profile' (list): Dynamic profiling data.
            - 'empirical' (list): Empirical scaling analysis data.
            - 'meta' (dict): Metadata including timestamp and version.
            - 'empirical_target' (str): The target function for scaling tests.
            - 'total_time' (float): Total execution time.
            - 'file' (str): The analyzed file path.
    """
    meta = result.get("meta", {})
    patterns = result.get("patterns", [])
    profile_data = result.get("profile", [])
    empirical_data = result.get("empirical")
    target_name = result.get("empirical_target")

    print("\n==================================================")
    print("βš–οΈ PERFORMANCE REPORT")
    print("==================================================")
    print(f"File: {result.get('file', 'Unknown')}")
    print(f"Timestamp (UTC): {meta.get('timestamp', 'Unknown')}")
    print(f"Total Execution Time: {result.get('total_time', 0)}s\n")

    # ---------------------------------------------------------
    # 0. DETECTED INTENT
    # ---------------------------------------------------------
    if patterns:
        primary = patterns[0]
        intent = (
            primary.get("intent")
            or primary.get("issue")
            or primary.get("pattern_id")
        )
        print("🧠 DETECTED INTENT:")
        print(f"   {intent}\n")

    # ---------------------------------------------------------
    # 1. STATIC ANALYSIS & SUGGESTIONS
    # ---------------------------------------------------------
    print("🚨 STATIC ANALYSIS (Diagnostics & Suggestions)")
    print("-" * 50)

    if not patterns:
        print(
            "   βœ… No performance issues detected. Code is structurally efficient.\n"
        )
    else:
        for i, p in enumerate(patterns, 1):
            func_name = p.get("function", "global")
            line_no = p.get("line", "??")
            issue = p.get("issue", p.get("pattern_id"))

            print(
                f"{i}. ⚠️ ISSUE: {issue} (Function: {func_name} | Line: {line_no})"
            )

            if "explanation" in p:
                print(f"   πŸ“– Explanation: {p['explanation']}")

                dsa = p.get("dsa_meta")
                if dsa:
                    complexity = dsa.get("complexity", "O(N)")
                    module_path = dsa.get("module", "builtin")
                    tier = dsa.get("tier", "OSS")

                    print(f"   πŸ’Š RECOMMENDED OPTIMIZATION: {complexity}")
                    print(f"   πŸ—οΈ IMPLEMENTATION: {module_path}")
                    print(f"   πŸ” ACCESS TIER: {tier}")

                costs = p.get("cost_estimate", {})
                if "time" in costs:
                    print(f"   ⏱️ Complexity Shift: {costs['time']}")

                snip = p.get("snippets")
                if snip:
                    print("\n   πŸ’‘ SUGGESTED FIX:")
                    print("   --- BEFORE ---")
                    for line in snip["before"].split("\n"):
                        print(f"   |  {line}")
                    print("   --- AFTER ---")
                    for line in snip["after"].split("\n"):
                        print(f"   |  {line}")

            print()

    # ---------------------------------------------------------
    # 2. DYNAMIC PROFILING
    # ---------------------------------------------------------
    print("⏱️ DYNAMIC PROFILING (Top Execution Bottlenecks)")
    print("-" * 50)

    if not profile_data:
        print("   βœ… No performance bottlenecks detected in execution.\n")
    else:
        for i, func in enumerate(profile_data, 1):
            print(
                f"   {i}. {func['function']}() -> {func['time']}s ({func['percent']}%)"
            )
        print()

    # ---------------------------------------------------------
    # 3. EMPIRICAL SCALING
    # ---------------------------------------------------------
    if empirical_data:
        print(f"πŸ“ˆ EMPIRICAL SCALING ANALYSIS: {target_name}()")
        print("-" * 50)
        print(
            f"{'N':<10} | {'Time (s)':<12} | {'Ratio':<8} | {'Est. Complexity':<15}"
        )
        print("-" * 50)

        final_complexity = "O(1)"
        for row in empirical_data:
            r_str = f"{row['Ratio']:.2f}" if row["Ratio"] > 0 else "-"
            print(
                f"{row['N']:<10} | {row['Time']:<12.5f} | {r_str:<8} | {row['Complexity']:<15}"
            )
            final_complexity = row["Complexity"]

        # ---------------------------------------------------------
        # 4. VERDICT
        # ---------------------------------------------------------
        print("\nβš–οΈ VERDICT:")

        safe_tiers = ["O(1)", "O(log N)", "O(N)", "O(N log N)"]

        if final_complexity in safe_tiers:
            print(
                f"βœ… PASSED: Function operates at {final_complexity}. Safe for scaling."
            )
        elif final_complexity == "O(N^2)":
            print(
                f"⚠️ WARNING: Function operates at {final_complexity}. May not scale efficiently."
            )
        else:
            print(
                f"❌ RISK: Function operates at {final_complexity}. Review recommended."
            )

        # ---------------------------------------------------------
        # 5. CONTEXT
        # ---------------------------------------------------------
        print("\nπŸ“Œ CONTEXT")
        print("-" * 50)
        print(
            "   Empirical scaling validates asymptotic behavior under increasing load.\n"
        )

        # ---------------------------------------------------------
        # 6. IMPACT ESTIMATION
        # ---------------------------------------------------------
        print("πŸš€ EXPECTED IMPACT")
        print("-" * 50)
        print("   For N = 10,000:")
        print("     β€’ O(NΒ²) β†’ ~100,000,000 operations")
        print("     β€’ O(N)  β†’ ~10,000 operations")
        print("   Estimated improvement: 100×–1000Γ— depending on workload.\n")

        # ---------------------------------------------------------
        # 7. CONFIDENCE
        # ---------------------------------------------------------
        print("πŸ€– CONFIDENCE")
        print("-" * 50)

        if final_complexity == "O(N^2)":
            print("   High β€” static and empirical signals agree.\n")
        else:
            print("   Medium β€” mixed signals between analysis methods.\n")

        # ---------------------------------------------------------
        # 8. SIMULATED FIX
        # ---------------------------------------------------------
        print("πŸ” AFTER OPTIMIZATION (SIMULATED)")
        print("-" * 50)
        print("   Expected Complexity: O(N)")
        print("   Behavior: Linear scaling with stable performance.")
        print("   Suggested Implementation:")
        print("       s = set(arr)")
        print("       for x in arr:")
        print("           if x in s:")
        print("               total += x\n")

    else:
        print("ℹ️ EMPIRICAL ANALYSIS SKIPPED\n")

    print("==================================================\n")