@qudtlib/core
Version:
Data model for QUDTLib
156 lines (155 loc) • 4.92 kB
JavaScript
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