Dependency Graph Resolver in JavaScript
Dependency graphs are fundamental in software development, representing relationships between modules, packages, or components. This challenge asks you to build a JavaScript function that resolves a dependency graph, determining the correct order in which to load or execute dependencies to avoid circular dependencies and ensure everything functions as expected. This is useful for build tools, module loaders, and task runners.
Problem Description
You are tasked with creating a function resolveDependencies that takes a dependency graph as input and returns a linear array representing the resolved order of dependencies. The dependency graph is represented as an object where keys are module names (strings) and values are arrays of strings representing the modules that the key module depends on.
The function should:
- Resolve the graph: Determine a valid order to load/execute the modules such that all dependencies of a module are loaded before the module itself.
- Handle Circular Dependencies: If a circular dependency is detected (e.g., A depends on B, and B depends on A), the function should throw an error with a descriptive message.
- Handle Modules with No Dependencies: Modules with no dependencies should be included in the resolved order.
- Handle Modules with Multiple Dependencies: Modules can depend on multiple other modules.
- Return an Empty Array if the Graph is Empty: If the input graph is empty, return an empty array.
Expected Behavior:
The function should return an array of module names in the order they should be loaded/executed. The order should be such that for every module A in the output, all its dependencies (as defined in the input graph) appear before A in the output.
Examples
Example 1:
Input: {
'A': ['B', 'C'],
'B': ['D'],
'C': ['D'],
'D': []
}
Output: ['D', 'B', 'C', 'A']
Explanation: 'D' has no dependencies, so it comes first. 'B' and 'C' both depend on 'D', so they come next. 'A' depends on 'B' and 'C', so it comes last.
Example 2:
Input: {
'A': ['B'],
'B': ['C'],
'C': ['A']
}
Output:
Error: Circular dependency detected: A -> B -> C -> A
Explanation: This graph contains a circular dependency (A -> B -> C -> A), which is not resolvable.
Example 3:
Input: {
'A': ['B', 'C'],
'B': [],
'C': ['D'],
'D': []
}
Output: ['B', 'D', 'C', 'A']
Explanation: 'B' and 'D' have no dependencies, so they come first. 'C' depends on 'D', so it comes next. 'A' depends on 'B' and 'C', so it comes last.
Example 4:
Input: {}
Output: []
Explanation: Empty graph, so return an empty array.
Constraints
- The input graph will be an object where keys are strings (module names) and values are arrays of strings (dependencies).
- Module names will be unique strings.
- The graph can contain up to 100 modules.
- The function should have a time complexity of O(V + E), where V is the number of vertices (modules) and E is the number of edges (dependencies).
- The function should not modify the original input graph.
Notes
- Consider using a topological sorting algorithm (e.g., Kahn's algorithm or Depth-First Search) to solve this problem.
- Keep track of visited nodes to detect circular dependencies.
- A queue can be helpful for managing modules that are ready to be processed.
- Think about how to handle modules that appear as dependencies but are not keys in the graph (e.g., they are unused dependencies). For this challenge, you can assume all dependencies listed in the graph are keys in the graph.