Files
skillhub-117-code-mentor/scripts/complexity_analyzer.py
T
2026-07-13 21:36:32 +08:00

292 lines
9.7 KiB
Python

#!/usr/bin/env python3
"""
Complexity Analyzer - Analyze time and space complexity of algorithms
Features:
- Parse code using AST
- Detect loops (nested, sequential)
- Identify recursion
- Analyze data structure operations
- Estimate Big-O complexity
- Suggest optimizations
Usage:
python complexity_analyzer.py <file_path> [--function <function_name>]
"""
import argparse
import ast
import json
import os
import sys
from typing import Dict, List, Tuple
class ComplexityAnalyzer(ast.NodeVisitor):
"""Analyze time and space complexity of Python code."""
def __init__(self, function_name=None):
self.function_name = function_name
self.results = {}
self.current_function = None
def visit_FunctionDef(self, node):
"""Analyze a function definition."""
# Only analyze specific function if requested
if self.function_name and node.name != self.function_name:
return
self.current_function = node.name
analysis = {
'name': node.name,
'line': node.lineno,
'time_complexity': 'O(1)',
'space_complexity': 'O(1)',
'loops': [],
'recursion': False,
'operations': [],
'suggestions': []
}
# Analyze the function body
loop_depth = self._analyze_loops(node)
has_recursion = self._check_recursion(node)
data_structure_ops = self._analyze_data_structures(node)
# Determine time complexity
if has_recursion:
analysis['recursion'] = True
recursion_type = self._classify_recursion(node)
analysis['time_complexity'] = recursion_type
analysis['suggestions'].append(
"Recursive function - consider memoization or iterative approach"
)
elif loop_depth >= 3:
analysis['time_complexity'] = f'O(n^{loop_depth})'
analysis['suggestions'].append(
f"Deep nesting ({loop_depth} levels) - consider optimization"
)
elif loop_depth == 2:
analysis['time_complexity'] = 'O(n²)'
analysis['suggestions'].append(
"Nested loop detected - can this be optimized with hash map?"
)
elif loop_depth == 1:
analysis['time_complexity'] = 'O(n)'
# Adjust for data structure operations
for op in data_structure_ops:
if op['type'] == 'sort':
if 'n²' not in analysis['time_complexity']:
analysis['time_complexity'] = 'O(n log n)'
elif op['type'] == 'dict_lookup':
analysis['operations'].append(op)
elif op['type'] == 'list_search':
if loop_depth == 0:
analysis['time_complexity'] = 'O(n)'
# Analyze space complexity
space = self._analyze_space_complexity(node)
analysis['space_complexity'] = space
self.results[node.name] = analysis
self.generic_visit(node)
def _analyze_loops(self, node, depth=0) -> int:
"""Calculate maximum loop nesting depth."""
max_depth = depth
for child in ast.walk(node):
if isinstance(child, (ast.For, ast.While)):
# Check if this is a direct child, not in a nested function
if self._is_direct_child(node, child):
child_depth = self._analyze_loops(child, depth + 1)
max_depth = max(max_depth, child_depth)
return max_depth
def _is_direct_child(self, parent, child):
"""Check if child is a direct descendant (not in nested function)."""
for node in ast.walk(parent):
if node == child:
return True
if isinstance(node, ast.FunctionDef) and node != parent:
# Stop if we hit another function definition
return False
return False
def _check_recursion(self, node) -> bool:
"""Check if function is recursive."""
function_name = node.name
for child in ast.walk(node):
if isinstance(child, ast.Call):
if isinstance(child.func, ast.Name) and child.func.id == function_name:
return True
# Check for indirect recursion via attribute
if isinstance(child.func, ast.Attribute):
if child.func.attr == function_name:
return True
return False
def _classify_recursion(self, node) -> str:
"""Classify type of recursion for complexity estimation."""
# Count recursive calls
recursive_calls = 0
function_name = node.name
for child in ast.walk(node):
if isinstance(child, ast.Call):
if isinstance(child.func, ast.Name) and child.func.id == function_name:
recursive_calls += 1
if recursive_calls == 1:
# Linear recursion (e.g., factorial)
return 'O(n)'
elif recursive_calls == 2:
# Binary recursion (e.g., fibonacci)
return 'O(2^n)'
else:
return 'O(recursive)'
def _analyze_data_structures(self, node) -> List[Dict]:
"""Analyze data structure operations."""
operations = []
for child in ast.walk(node):
# Sorting
if isinstance(child, ast.Call):
if isinstance(child.func, ast.Attribute):
if child.func.attr == 'sort':
operations.append({'type': 'sort', 'line': child.lineno})
elif isinstance(child.func, ast.Name):
if child.func.id == 'sorted':
operations.append({'type': 'sort', 'line': child.lineno})
# Dictionary/set operations (O(1) average)
if isinstance(child, ast.Subscript):
if isinstance(child.value, (ast.Dict, ast.Set)):
operations.append({'type': 'dict_lookup', 'line': child.lineno})
# List search operations (O(n))
if isinstance(child, ast.Compare):
if any(isinstance(op, ast.In) for op in child.ops):
operations.append({'type': 'list_search', 'line': child.lineno})
return operations
def _analyze_space_complexity(self, node) -> str:
"""Estimate space complexity."""
# Check for list comprehensions, array creation
has_array_creation = False
has_recursion = self._check_recursion(node)
for child in ast.walk(node):
# List comprehension or list creation
if isinstance(child, (ast.ListComp, ast.List)):
has_array_creation = True
# Dictionary comprehension
if isinstance(child, (ast.DictComp, ast.Dict)):
has_array_creation = True
if has_recursion:
# Recursion uses call stack
return 'O(n) - call stack'
elif has_array_creation:
return 'O(n) - auxiliary space'
else:
return 'O(1)'
def format_output(results, output_format='text'):
"""Format analysis results."""
if output_format == 'json':
print(json.dumps(results, indent=2))
else:
print("\n" + "=" * 60)
print("COMPLEXITY ANALYSIS")
print("=" * 60 + "\n")
for func_name, analysis in results.items():
print(f"Function: {func_name} (line {analysis['line']})")
print(f" Time Complexity: {analysis['time_complexity']}")
print(f" Space Complexity: {analysis['space_complexity']}")
if analysis['recursion']:
print(f" Recursion: Yes")
if analysis['operations']:
print(f" Operations:")
for op in analysis['operations']:
print(f" - {op['type']} at line {op['line']}")
if analysis['suggestions']:
print(f" Suggestions:")
for suggestion in analysis['suggestions']:
print(f" → {suggestion}")
print()
def analyze_file(filepath, function_name=None, output_format='text'):
"""Analyze a Python file."""
if not os.path.exists(filepath):
print(f"Error: File '{filepath}' not found", file=sys.stderr)
sys.exit(1)
with open(filepath, 'r', encoding='utf-8') as f:
code = f.read()
try:
tree = ast.parse(code)
except SyntaxError as e:
print(f"Syntax error in file: {e}", file=sys.stderr)
sys.exit(1)
analyzer = ComplexityAnalyzer(function_name)
analyzer.visit(tree)
if not analyzer.results:
if function_name:
print(f"Error: Function '{function_name}' not found", file=sys.stderr)
else:
print("No functions found in file", file=sys.stderr)
sys.exit(1)
format_output(analyzer.results, output_format)
def analyze_code_snippet(code, output_format='text'):
"""Analyze a code snippet."""
try:
tree = ast.parse(code)
except SyntaxError as e:
print(f"Syntax error: {e}", file=sys.stderr)
sys.exit(1)
analyzer = ComplexityAnalyzer()
analyzer.visit(tree)
format_output(analyzer.results, output_format)
def main():
parser = argparse.ArgumentParser(
description='Analyze time and space complexity of code'
)
parser.add_argument('file', help='Python file to analyze')
parser.add_argument('--function', help='Specific function to analyze')
parser.add_argument('--format', choices=['text', 'json'], default='text',
help='Output format (default: text)')
args = parser.parse_args()
analyze_file(args.file, args.function, args.format)
if __name__ == '__main__':
main()