UNPKG

@qudtlib/core

Version:

Data model for QUDTLib

156 lines (155 loc) 4.92 kB
import { arrayContains } from "./utils.js"; export class AssignmentProblem { static instance(weights) { const rows = weights.length; if (!rows) { throw "Cannot create instance with 0x0 weights matrix"; } const cols = weights[0].length; if (rows > cols) { throw "The weights matrix may not have more rows than columns"; } return new NaiveAlgorithmInstance(AssignmentProblem.copy(weights)); } static copy(weights) { const ret = []; weights.forEach((row) => ret.push(row)); return ret; } } export class Instance { constructor(weights) { if (!weights?.length || !weights[0]?.length) { throw "Not a valid weights matrix: " + weights; } this.weights = weights; this.rows = weights.length; this.cols = weights[0].length; } weightOfAssignment(assignment) { if (!assignment) { throw "Not a valid assignment: " + assignment; } if (assignment?.length === 0) { return undefined; } const sum = assignment .map((col, row) => this.weights[row][col]) .reduce((a, b) => a + b); return sum; } } export class Solution { constructor(instance, assignment) { this.instance = instance; this.assignment = assignment || []; this.weight = instance.weightOfAssignment(this.assignment); } isComplete() { return this.assignment.length >= this.instance.rows; } isEmpty() { return this.assignment.length === 0; } assignColumnInNextRow(col) { if (this.isComplete()) { throw "Solution is already complete"; } return new Solution(this.instance, [...this.assignment, col]); } isBetterSolutionThan(other) { if (!(this.isComplete() && other.isComplete())) { throw "Cannot compare incomplete solutions"; } if (typeof this.weight === "undefined" || typeof other.weight === "undefined") { throw "Cannot compare empty solutions"; } return this.weight < other.weight; } } export class ValueWithIndex { constructor(value, index) { this.value = value; this.index = index; } } export class NaiveAlgorithmInstance extends Instance { constructor(weights) { super(weights); } isLowerThanBestWeight(weightToTest) { if (!this.currentBestSolution) { return true; } if (!this.currentBestSolution.isComplete()) { return true; } if (!this.currentBestSolution.weight) { return true; } return this.currentBestSolution.weight > weightToTest; } updateBestSolutionIfPossible(candidate) { if (!this.currentBestSolution || candidate.isBetterSolutionThan(this.currentBestSolution)) { this.currentBestSolution = candidate; } } solve() { this.doSolve(0, new Solution(this)); if (!this.currentBestSolution) { return new Solution(this); } return this.currentBestSolution; } doSolve(row, solution) { if (row >= this.rows) { this.updateBestSolutionIfPossible(solution); return; } if (solution.weight) { const bestAttainableScore = this.sum(this.minPerRow(row, solution.assignment)); if (!this.isLowerThanBestWeight(solution.weight + bestAttainableScore)) { return; } } const nMin = this.rowSortedAscending(row, solution.assignment); for (let i = 0; i < nMin.length; i++) { if (!solution.weight || this.isLowerThanBestWeight(solution.weight + nMin[i].value)) { this.doSolve(row + 1, solution.assignColumnInNextRow(nMin[i].index)); } } } minPerRow(startRow, skipCols) { const ret = []; for (let r = startRow; r < this.rows; r++) { let min = Number.MAX_VALUE; for (let c = 0; c < this.cols; c++) { if (!arrayContains(skipCols, c)) { const val = this.weights[r][c]; if (min > val) { min = val; } } } ret.push(min); } return ret; } sum(arr) { return arr.reduce((a, b) => a + b); } rowSortedAscending(row, skipCols) { const sorted = []; for (let i = 0; i < this.cols; i++) { if (!arrayContains(skipCols, i)) { sorted.push(new ValueWithIndex(this.weights[row][i], i)); } } sorted.sort((l, r) => l.value - r.value); return sorted; } } //# sourceMappingURL=assignmentProblem.js.map