Hone logo
Hone
Problems

Build a Declarative Constraint Solver in Jest

This challenge asks you to implement a simple declarative constraint solver. You'll create a system that allows users to define variables with possible values and a set of constraints that these variables must satisfy. Your solver will then find all possible assignments of values to variables that meet all defined constraints. This is a foundational concept in areas like artificial intelligence, logic programming, and optimization.

Problem Description

You need to build a TypeScript class, let's call it ConstraintSolver, that can:

  1. Define Variables: Allow users to declare variables and specify the domain (set of possible values) for each variable.
  2. Define Constraints: Allow users to define functions that act as constraints. These functions will take an assignment of values to variables and return true if the assignment satisfies the constraint, and false otherwise.
  3. Solve: Given a set of variables and constraints, find all possible assignments of values to variables that satisfy all defined constraints.

Key Requirements:

  • The solver should be able to handle variables with various data types (numbers, strings, booleans) within their domains.
  • Constraints should be arbitrary functions that can express complex relationships between variables.
  • The solver should return an array of all valid assignments. Each assignment should be an object mapping variable names to their assigned values.
  • The implementation should be tested using Jest.

Expected Behavior:

When the solve method is called, it should explore all possible combinations of variable assignments and filter them based on the provided constraints.

Edge Cases:

  • No Solutions: The solver should return an empty array if no assignment satisfies all constraints.
  • Empty Domains: Variables with empty domains should be handled gracefully (likely leading to no solutions if such a variable is required).
  • No Constraints: If no constraints are provided, all possible assignments should be returned.
  • Circular Dependencies (Implicit): While not explicitly modeling dependencies, constraints might implicitly create complex interdependencies. The solver should handle these by exhaustively searching.

Examples

Example 1:

Imagine a simple scenario where we have two variables, A and B, each with a domain of numbers from 1 to 3. We want to find assignments where A is not equal to B.

// Conceptual input
const variables = {
  A: [1, 2, 3],
  B: [1, 2, 3],
};

const constraints = [
  (assignment: Record<string, number>) => assignment.A !== assignment.B,
];

// Expected output
[
  { A: 1, B: 2 },
  { A: 1, B: 3 },
  { A: 2, B: 1 },
  { A: 2, B: 3 },
  { A: 3, B: 1 },
  { A: 3, B: 2 },
]

Explanation: The solver considers all 3x3 = 9 possible assignments. It then filters these assignments, keeping only those where the value of A is different from the value of B.

Example 2:

A classic "map coloring" problem. We have three regions: North, South, West. Each can be colored with one of three colors: Red, Green, Blue. Adjacent regions must have different colors.

// Conceptual input
const variables = {
  North: ['Red', 'Green', 'Blue'],
  South: ['Red', 'Green', 'Blue'],
  West: ['Red', 'Green', 'Blue'],
};

const constraints = [
  (assignment: Record<string, string>) => assignment.North !== assignment.South,
  (assignment: Record<string, string>) => assignment.North !== assignment.West,
  (assignment: Record<string, string>) => assignment.South !== assignment.West,
];

// Expected output (one of several valid solutions)
[
  { North: 'Red', South: 'Green', West: 'Blue' },
  { North: 'Red', South: 'Blue', West: 'Green' },
  { North: 'Green', South: 'Red', West: 'Blue' },
  { North: 'Green', South: 'Blue', West: 'Red' },
  { North: 'Blue', South: 'Red', West: 'Green' },
  { North: 'Blue', South: 'Green', West: 'Red' },
]

Explanation: With 3 regions and 3 colors, there are 3^3 = 27 total possible assignments. The constraints ensure that no two adjacent regions share the same color. The output shows a subset of valid solutions.

Example 3 (No Solution):

Consider a scenario with two variables, X and Y, both with domain [1, 2]. We have two contradictory constraints.

// Conceptual input
const variables = {
  X: [1, 2],
  Y: [1, 2],
};

const constraints = [
  (assignment: Record<string, number>) => assignment.X === 1,
  (assignment: Record<string, number>) => assignment.X === 2,
];

// Expected output
[]

Explanation: It's impossible for X to be both 1 and 2 simultaneously. Therefore, no assignment can satisfy both constraints, and the solver returns an empty array.

Constraints

  • The number of variables will be between 0 and 10.
  • The size of any variable's domain will be between 0 and 10.
  • The total number of possible assignments (product of domain sizes) will not exceed 1,000,000. This is to keep the brute-force approach feasible within reasonable test execution times.
  • Variable names will be strings.
  • Domain values can be of any primitive JavaScript type (string, number, boolean, null, undefined).
  • Constraints will be functions that accept a Record<string, any> (representing a partial or full assignment) and return a boolean.

Notes

  • You will need to implement the ConstraintSolver class.
  • The core of the problem lies in generating all possible assignments and efficiently checking them against the constraints.
  • Consider how to represent the variables and their domains. A Record<string, any[]> where keys are variable names and values are arrays of possible values is a good starting point.
  • You will need to write Jest tests for your ConstraintSolver class to verify its correctness across various scenarios, including the examples provided. Think about testing edge cases like empty inputs, no solutions, and multiple solutions.
  • A brute-force approach (generating all combinations) is acceptable given the stated constraints. More advanced constraint satisfaction techniques (like backtracking with propagation) are beyond the scope of this challenge.
Loading editor...
typescript