cspSolver = (constraints) => {
const constrain = (variables, key, value) => {
const cloneVar = ([id, domain]) => [id, domain.slice()];
const localVars = new Map(_(variables).map(cloneVar));
localVars.set(key, [value]);
const queue = constraints.slice();
while (queue.length) {
const [head, tail, constraint] = queue.shift();
const [hv, tv] = [localVars.get(head), localVars.get(tail)];
const validValues = tv.filter((t) => hv.some((h) => constraint(h, t)));
if (validValues.length < tv.length) {
localVars.set(tail, validValues);
for (const constraint of constraints)
if (constraint.from == tail) queue.push(constraint);
}
}
return localVars;
};
return function* (variables, options = {}) {
const log = options.logger || (() => {});
const recSolve = function* (variables) {
yield variables;
log(variables);
let [bestKey, min] = [null, Number.POSITIVE_INFINITY];
for (const [key, { length }] of variables) {
if (length < min && length > 1) (bestKey = key), (min = length);
if (!options.nobestkey && bestKey) break;
}
log(bestKey);
// If no variable needs to be resolved, problem is solved
if (bestKey == null) return;
const domain = variables.get(bestKey);
// Reorder values for this key (Least Constraining Values heuristic):
// Perform arc consistency on each possible value,
// and order values according to how many values were eliminated
// from all the domains (fewest eliminated in the front).
// This helps makes success more likely by keeping future options open.
if (!options.noreorder) {
const sum = (sum, { length }) => sum + length;
const val2vars = (val) => constrain(variables, bestKey, val);
const val2sum = (val) => _(val2vars(val).values()).reduce(sum, 0);
const lut = new Map(domain.map((val) => [val, val2sum(val)]));
domain.sort((a, b) => lut.get(b) - lut.get(a));
}
// for each of these values:
for (const value of domain) {
// create local state of variables,
// assign value to variable previously chosen,
// and propagate constraints
const localVars = constrain(variables, bestKey, value);
// if one domain is empty, discard value
if (_(localVars).find(([, { length }]) => !length)) continue;
// recursively solve narrowed problem
let final;
for (const value of recSolve(localVars)) yield (final = value);
// if recursive solving found a solution, return
if (final) return;
}
// No solution has been found, return failure
yield;
};
yield* recSolve(variables);
};
}