Hone logo
Hone
Problems

Generic Constraint Solver in TypeScript

This challenge asks you to build a flexible constraint solver in TypeScript that can handle various types of constraints between variables. Constraint solvers are useful in many domains, such as scheduling, resource allocation, and puzzle solving, where you need to find values for variables that satisfy a set of rules. This solver should be generic, allowing you to define constraints on different data types.

Problem Description

You are tasked with creating a generic constraint solver in TypeScript. The solver should be able to:

  1. Define Variables: Allow the creation of variables with associated types.
  2. Define Constraints: Allow the definition of constraints between variables. Constraints can be of different types (e.g., equality, inequality, range).
  3. Solve Constraints: Find a set of values for the variables that satisfy all defined constraints.
  4. Handle Unsatisfiable Constraints: Gracefully handle cases where no solution exists.

Key Requirements:

  • Genericity: The solver should be generic, allowing constraints on variables of any type.
  • Constraint Types: Implement at least three constraint types:
    • Equality: variable1 must equal variable2.
    • Inequality: variable1 must not equal variable2.
    • Range: variable must be within a specified range (inclusive).
  • Error Handling: The solver should throw an error if a constraint is invalid (e.g., trying to apply a range constraint to a non-numeric variable).
  • Solution Representation: The solution should be a map where keys are variable names and values are the assigned values.

Expected Behavior:

  • When a solution is found, the solver should return a map containing the variable names and their corresponding values.
  • If no solution is found, the solver should return null.
  • The solver should not modify the original variables.

Edge Cases to Consider:

  • Circular dependencies between variables.
  • Conflicting constraints (e.g., x = y and y = z and x != z).
  • Empty constraint sets (should return a solution with all variables unassigned).
  • Variables with no constraints.

Examples

Example 1:

Input:
variables: {
  x: 10,
  y: 20,
  z: 30
}
constraints: [
  { type: 'equality', variable1: 'x', variable2: 'y' },
  { type: 'range', variable: 'z', min: 20, max: 40 }
]
Output: null
Explanation: The equality constraint `x = y` is violated because x is 10 and y is 20.  Therefore, no solution exists.

Example 2:

Input:
variables: {
  x: 10,
  y: 10,
  z: 30
}
constraints: [
  { type: 'equality', variable1: 'x', variable2: 'y' },
  { type: 'range', variable: 'z', min: 20, max: 40 }
]
Output: { x: 10, y: 10, z: 30 }
Explanation: All constraints are satisfied. x and y are equal, and z is within the range [20, 40].

Example 3: (Edge Case - Circular Dependency)

Input:
variables: {
  x: 5,
  y: 5
}
constraints: [
  { type: 'equality', variable1: 'x', variable2: 'y' },
  { type: 'equality', variable1: 'y', variable2: 'x' }
]
Output: { x: 5, y: 5 }
Explanation:  The constraints are consistent and solvable.

Constraints

  • Variable Types: The solver should support number, string, and boolean types for variables.
  • Constraint Complexity: The solver does not need to handle complex constraint types (e.g., greater than, less than). Focus on the three specified types.
  • Performance: For the scope of this challenge, efficiency is not the primary concern. A simple, understandable solution is preferred over a highly optimized one. Assume a maximum of 10 variables and 20 constraints.
  • Error Messages: Error messages should be descriptive and helpful in identifying the cause of the problem.

Notes

  • Consider using a data structure like a graph to represent the relationships between variables and constraints.
  • A backtracking algorithm or a simple constraint propagation technique can be used to solve the constraints.
  • Start by implementing the core functionality (variable definition, constraint definition, and solving) and then add error handling and edge case handling.
  • Think about how to make your code reusable and extensible for future constraint types.
  • The order of constraints does not matter.
  • The initial values of the variables are not part of the solution; the solver should determine the correct values based on the constraints.
Loading editor...
typescript