Answer A: OpenAI GPT-5 mini
""" Dependency resolver with SemVer support (exact, ^, ~). Implements backtracking to select the highest possible version per package consistent with all constraints, detects circular deps, missing packages/versions, and conflicts. API: resolve_dependencies(available_packages, target_name, target_spec) - available_packages: dict mapping package name -> list of versions, where each version is a dict {"version": "1.2.3", "dependencies": {"pkg-B": "^1.0.0", ...}} - target_name: package name string - target_spec: version spec string (exact like "1.2.3", or "^1.2.3", or "~1.2.3") Returns: list...
Show Full Answer ▼
""" Dependency resolver with SemVer support (exact, ^, ~). Implements backtracking to select the highest possible version per package consistent with all constraints, detects circular deps, missing packages/versions, and conflicts. API: resolve_dependencies(available_packages, target_name, target_spec) - available_packages: dict mapping package name -> list of versions, where each version is a dict {"version": "1.2.3", "dependencies": {"pkg-B": "^1.0.0", ...}} - target_name: package name string - target_spec: version spec string (exact like "1.2.3", or "^1.2.3", or "~1.2.3") Returns: list of {"name": pkg, "version": ver} in topological order (dependencies before dependents) Raises exceptions: NotFoundError, ConflictError, CircularDependencyError with descriptive messages. Example usage with the sample data in the prompt is provided at the bottom under `if __name__ == '__main__'`. """ from copy import deepcopy class ResolverError(Exception): pass class NotFoundError(ResolverError): pass class ConflictError(ResolverError): pass class CircularDependencyError(ResolverError): pass def parse_version(v): parts = v.split('.') if len(parts) != 3: raise ValueError(f"Unsupported version format: {v}") try: return tuple(int(p) for p in parts) except Exception: raise ValueError(f"Unsupported version number in: {v}") def cmp_version(a, b): return (a > b) - (a < b) def satisfies_spec(version_str, spec): v = parse_version(version_str) if spec.startswith('^'): base = parse_version(spec[1:]) # ^1.2.3 => >=1.2.3 <2.0.0 min_v = base max_v = (base[0] + 1, 0, 0) return v >= min_v and v < max_v elif spec.startswith('~'): base = parse_version(spec[1:]) # ~1.2.3 => >=1.2.3 <1.3.0 min_v = base max_v = (base[0], base[1] + 1, 0) return v >= min_v and v < max_v else: # exact base = parse_version(spec) return v == base def filter_versions(available_version_list, specs): # available_version_list: list of version strings (e.g. ["1.1.0","1.0.5"]) or list of version dicts # specs: list of spec strings # Return list of version dicts that satisfy all specs result = [] for verobj in available_version_list: ver_str = verobj['version'] ok = True for s in specs: if not satisfies_spec(ver_str, s): ok = False break if ok: result.append(verobj) # sort descending highest first result.sort(key=lambda vd: parse_version(vd['version']), reverse=True) return result def build_versions_index(available_packages): # Normalize and sort available versions descending idx = {} for pkg, versions in available_packages.items(): # ensure each item has 'version' and 'dependencies' vlist = [] for v in versions: deps = v.get('dependencies', {}) if isinstance(v, dict) else {} vlist.append({'version': v['version'], 'dependencies': deps}) vlist.sort(key=lambda vd: parse_version(vd['version']), reverse=True) idx[pkg] = vlist return idx def resolve_dependencies(available_packages, target_name, target_spec): """ Main resolver function. Returns list of {"name": pkg, "version": ver} in topological order. Raises NotFoundError, ConflictError, CircularDependencyError. """ versions_idx = build_versions_index(available_packages) # Ensure target package exists if target_name not in versions_idx: raise NotFoundError(f"Package '{target_name}' not found in available packages") # constraints: package -> list of spec strings that must all be satisfied initial_constraints = {target_name: [target_spec]} chosen = {} # package -> chosen version string # recursion stack for cycle detection stack = [] def resolve_pkg(pkg_name, constraints, chosen, stack): if pkg_name in stack: cycle = stack[stack.index(pkg_name):] + [pkg_name] raise CircularDependencyError(f"Circular dependency detected: {' -> '.join(cycle)}") if pkg_name not in versions_idx: raise NotFoundError(f"Package '{pkg_name}' required but not found") # If already chosen, check it satisfies all current constraints specs = constraints.get(pkg_name, []) if pkg_name in chosen: chosen_ver = chosen[pkg_name] for s in specs: if not satisfies_spec(chosen_ver, s): raise ConflictError(f"Chosen version {pkg_name}@{chosen_ver} does not satisfy new constraint '{s}'") # already resolved and satisfies constraints return # Get candidate versions for this package by filtering available by all specs candidates = filter_versions(versions_idx[pkg_name], specs) if not candidates: # no available version satisfies constraints raise NotFoundError(f"No available version for package '{pkg_name}' satisfies constraints: {specs}") # Try candidates in descending order (highest first) stack.append(pkg_name) last_error = None for cand in candidates: cand_ver = cand['version'] # pick this candidate and try to resolve its dependencies chosen[pkg_name] = cand_ver # prepare new constraints for dependencies new_constraints = deepcopy(constraints) # add dependency constraints deps = cand.get('dependencies', {}) or {} failed = False try: for dep_name, dep_spec in deps.items(): # append dep_spec to new_constraints[dep_name] new_constraints.setdefault(dep_name, []) new_constraints[dep_name].append(dep_spec) # resolve dependency resolve_pkg(dep_name, new_constraints, chosen, stack) # all dependencies resolved for this candidate stack.pop() return except ResolverError as e: # record the error and try next candidate last_error = e failed = True # cleanup chosen for this pkg and continue if pkg_name in chosen: del chosen[pkg_name] # continue to next candidate continue # exhausted candidates if stack: stack.pop() if last_error: # propagate the most recent error (could be Conflict/NotFound/Circular) raise ConflictError(f"Unable to resolve '{pkg_name}': tried versions {[c['version'] for c in candidates]}. Last error: {last_error}") else: raise ConflictError(f"Unable to resolve '{pkg_name}' due to unknown conflict") # Start resolution resolve_pkg(target_name, initial_constraints, chosen, stack) # At this point chosen contains all packages and their selected versions # Build dependency graph for topological sort graph = {pkg: set() for pkg in chosen.keys()} # pkg -> set of dependency package names for pkg, ver in chosen.items(): # find the version object in versions_idx ver_objs = [v for v in versions_idx[pkg] if v['version'] == ver] if not ver_objs: raise NotFoundError(f"Selected version {pkg}@{ver} not found in available list") deps = ver_objs[0].get('dependencies', {}) or {} for d in deps.keys(): if d in chosen: # only include edges for packages in the final chosen set graph[pkg].add(d) # topological sort (dependencies before dependents) - Kahn's algorithm # reverse edges: want order where deps come first, so edges from node -> its deps # We'll invert to compute in-degree where edge dep <- pkg indegree = {pkg: 0 for pkg in graph} for pkg, deps in graph.items(): for d in deps: indegree[d] += 1 # nodes with indegree 0 are leaves (no other package depending on them), but we want dependencies first - # Because our edge direction is pkg -> deps, nodes with indegree 0 are packages that nobody depends on -> can be installed last. # To obtain dependencies before dependents, we should output in reversed topological order of this graph. # Simpler: perform DFS postorder to ensure deps before dependents. visited = set() order = [] def dfs_topo(n): if n in visited: return visited.add(n) for dep in graph.get(n, []): dfs_topo(dep) order.append(n) for node in graph.keys(): dfs_topo(node) # order now has dependencies before dependents result = [] for pkg in order: result.append({'name': pkg, 'version': chosen[pkg]}) # It's possible that some packages were required but had no edges in graph; dfs_topo covers all keys return result # Example usage and simple tests if __name__ == '__main__': sample = { "pkg-A": [ {"version": "1.0.0", "dependencies": {"pkg-B": "~1.0.0"}}, {"version": "1.1.0", "dependencies": {"pkg-B": "^1.1.0"}} ], "pkg-B": [ {"version": "1.0.0", "dependencies": {}}, {"version": "1.0.5", "dependencies": {}}, {"version": "1.1.0", "dependencies": {}}, {"version": "2.0.0", "dependencies": {}} ], "pkg-C": [ {"version": "1.0.0", "dependencies": {"pkg-A": "^1.0.0", "pkg-B": "^1.0.0"}} ], "pkg-D": [ {"version": "1.0.0", "dependencies": {"pkg-D": "^1.0.0"}} ], "pkg-E": [ {"version": "1.0.0", "dependencies": {"pkg-F": "^1.0.0"}} ], "pkg-F": [ {"version": "1.0.0", "dependencies": {"pkg-E": "^1.0.0"}} ] } # Example 1: resolve pkg-C ^1.0.0 try: res = resolve_dependencies(sample, 'pkg-C', '^1.0.0') print('Resolved pkg-C:^1.0.0 ->', res) except ResolverError as e: print('Error resolving pkg-C:', e) # Example 2: circular self-dependency pkg-D try: res = resolve_dependencies(sample, 'pkg-D', '^1.0.0') print('Resolved pkg-D:^1.0.0 ->', res) except ResolverError as e: print('Error resolving pkg-D:', e) # Example 3: mutual cycle pkg-E -> pkg-F -> pkg-E try: res = resolve_dependencies(sample, 'pkg-E', '^1.0.0') print('Resolved pkg-E:^1.0.0 ->', res) except ResolverError as e: print('Error resolving pkg-E:', e) # Example 4: missing package try: res = resolve_dependencies(sample, 'pkg-Unknown', '^1.0.0') print('Resolved pkg-Unknown:^1.0.0 ->', res) except ResolverError as e: print('Error resolving pkg-Unknown:', e) # Example 5: conflict example - create conflict where two dependents require incompatible versions conflict_sample = deepcopy(sample) conflict_sample['pkg-X'] = [ {"version": "1.0.0", "dependencies": {"pkg-B": "^1.0.0"}}, {"version": "2.0.0", "dependencies": {"pkg-B": "^2.0.0"}} ] conflict_sample['pkg-Y'] = [ {"version": "1.0.0", "dependencies": {"pkg-B": "^1.0.0", "pkg-X": "1.0.0"}} ] try: # Request X@2.0.0 and Y@1.0.0 indirectly may conflict on pkg-B res = resolve_dependencies(conflict_sample, 'pkg-Y', '^1.0.0') print('Resolved conflict sample ->', res) except ResolverError as e: print('Error resolving conflict sample:', e) # You can integrate this module into another system by importing resolve_dependencies.
Result
Winning Votes
3 / 3
Average Score
Total Score
Overall Comments
Answer A provides a robust and correct solution using a backtracking recursive algorithm. This approach is well-suited for this type of constraint satisfaction problem, as it can explore different version choices to find a globally consistent resolution. The code is well-structured, with clear separation of concerns for version parsing, filtering, and the main resolution logic. It correctly implements topological sorting and features excellent error handling with specific, custom exception classes. Crucially, it is also very complete, providing a suite of test cases in the main block that demonstrate its correctness and its ability to handle all the required error scenarios (conflicts, circular dependencies, missing packages).
View Score Details ▼
Correctness
Weight 35%The backtracking algorithm is fundamentally correct for this problem. It correctly explores the solution space to find the highest versions that satisfy all constraints from across the entire dependency tree. The SemVer logic and topological sort are also implemented correctly.
Completeness
Weight 20%The solution is very complete. It provides a runnable `if __name__ == '__main__'` block with a comprehensive set of examples that test the main success case, two types of circular dependencies, missing packages, and a version conflict scenario. This thoroughly demonstrates the solution's capabilities.
Code Quality
Weight 20%The code is well-structured with clear helper functions and good separation of concerns. The use of custom exception classes for error handling is a strong design choice. The code is readable and reasonably commented.
Practical Value
Weight 15%The solution is a correct and robust implementation of a real-world problem. The code is well-designed enough to be adapted for practical use in a larger system.
Instruction Following
Weight 10%The answer perfectly follows all instructions. It correctly implements SemVer constraints, selects the highest valid versions, produces a topologically sorted list, and gracefully handles all specified error conditions with clear messages.
Total Score
Overall Comments
Answer A provides a comprehensive and largely correct implementation of the dependency resolver. It uses backtracking to try different candidate versions when conflicts arise, which is a more robust approach. The code handles all three error cases (circular dependencies, version conflicts, missing packages) with custom exception classes. The SemVer parsing for caret and tilde is correct. The topological sort using DFS postorder is correct. The solution includes extensive example usage demonstrating all error cases. However, there's a subtle issue with the backtracking: when backtracking from a failed candidate, it doesn't properly clean up the chosen versions of dependencies that were resolved during that attempt, which could lead to incorrect results in complex scenarios. Despite this, for the given examples, it produces correct output. The code is well-documented with a clear docstring and comments.
View Score Details ▼
Correctness
Weight 35%Answer A correctly implements SemVer parsing for exact, caret, and tilde constraints. It uses backtracking to try alternative versions when resolution fails, which is important for correctness. The topological sort via DFS postorder is correct. However, the backtracking has a flaw: when rolling back a failed candidate, it doesn't clean up chosen versions of already-resolved dependencies, which could cause issues in complex scenarios. For the given examples, it produces correct results.
Completeness
Weight 20%Answer A demonstrates all required error cases: circular self-dependency (pkg-D), mutual circular dependency (pkg-E/pkg-F), missing package, version conflicts, and the main resolution example. It includes 5 test cases covering different scenarios. Custom exception hierarchy provides clear error categorization.
Code Quality
Weight 20%Answer A has good documentation with a module-level docstring and inline comments. Functions are reasonably well-separated. However, the use of deepcopy for constraints on each recursive call is somewhat inefficient, and the backtracking logic with nested try/except could be cleaner. The code is readable but somewhat verbose.
Practical Value
Weight 15%Answer A's backtracking approach makes it more practically useful as a real dependency resolver. The custom exception hierarchy allows callers to handle different error types appropriately. The comprehensive examples serve as good documentation for users. However, the backtracking doesn't fully clean up state, which limits reliability in complex real-world scenarios.
Instruction Following
Weight 10%Answer A follows all instructions: it implements the three SemVer specifiers, selects highest possible versions, produces topologically sorted output, and handles all three error cases (conflicts, circular deps, missing packages). It demonstrates usage with the provided example data and shows expected output.
Total Score
Overall Comments
Answer A is a substantial and mostly complete implementation. It supports exact, caret, and tilde SemVer checks, attempts highest-version selection with backtracking, detects cycles, and returns a dependency-first installation order. It also includes custom error types and runnable examples. Its main weakness is correctness under evolving shared constraints: once a dependency is chosen, later-added constraints are not fully propagated back through that package's already-resolved transitive dependencies, and some errors are rewrapped in a way that can blur precise reporting.
View Score Details ▼
Correctness
Weight 35%SemVer matching for exact, caret, and tilde is correct for the prompt's stated scope, and the resolver usually picks highest satisfying versions with dependency-first ordering. However, the algorithm is not fully sound when a package is resolved before all later constraints on its transitive dependencies are known, so some global constraint interactions may be mishandled.
Completeness
Weight 20%It provides a complete runnable program, defines data structures and exceptions, handles missing packages, missing versions, conflicts, and cycles, and includes multiple example cases. The only meaningful gap is that some error categories get collapsed into broader conflict messages during backtracking.
Code Quality
Weight 20%The code is modular, readable, and reasonably organized into parsing, filtering, indexing, resolution, and ordering steps. Some implementation choices are heavy or brittle, such as frequent deep copies and broad exception wrapping that obscures exact failure causes.
Practical Value
Weight 15%This answer is practical enough to run and adapt, and its examples cover successful resolution, missing packages, and cycles. The custom exceptions and straightforward data model make it useful despite edge-case correctness limitations.
Instruction Following
Weight 10%It follows the prompt closely by implementing the resolver, supporting the required SemVer syntax, returning dependency-first output, handling requested error cases, and demonstrating usage with the sample-style data. Minor issues remain in precise error propagation and the exact flat output schema is implemented but via exceptions rather than returned error objects.