Compare commits

...

3 Commits

Author SHA1 Message Date
TiiJay
a1ac8e1b81 v.2.0.2 doBacktracking return Matrix-String 2024-11-30 12:08:15 +01:00
TiiJay
0e588651bf v.2.0.1 doBacktracking @Query('starting-position'...) 2024-11-30 10:06:51 +01:00
TiiJay
63626ce9d0 v.2.0.0 NDameBacktracker implementation 2024-11-29 20:35:48 +01:00
10 changed files with 224 additions and 109 deletions

View File

@@ -0,0 +1,24 @@
import { ABacktracker } from '@app/backtracking-lib/abstract-backtracker';
import { NDameModel } from './n-dame-model';
export class NDameBacktracker extends ABacktracker {
private _model: NDameModel;
constructor(
private readonly startingPosition,
private readonly modelSize: number,
) {
super();
this._model = new NDameModel(startingPosition, modelSize);
}
get model() {
return this._model;
}
doBacktrack = (): boolean[][] => {
const bSolution = this.findSolution(this._model);
return this.model.matrix;
};
}

View File

@@ -1,22 +0,0 @@
import { NDameModel } from './n-dame-model';
export class NDameBacktracking {
private model: NDameModel;
constructor(private readonly modelSize: number) {
this.model = new NDameModel(modelSize);
}
options = (): Array<number> => {
// const optionsArray = Array.from(
// { length: this.modelSize },
// (value, index) => index,
// );
// wir können nur 1 Dame pro Feld platzieren !!!
return [1];
};
solved = (): boolean => {
return this.model.solved();
};
}

View File

@@ -1,4 +1,4 @@
import { IBacktracking } from '@app/backtracking-lib/backtracking.interface'; import { IBacktrackingModel } from '@app/backtracking-lib/backtracking-model.interface';
import { MatrixPosition } from '@app/backtracking-lib/matrix-position'; import { MatrixPosition } from '@app/backtracking-lib/matrix-position';
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
@@ -7,29 +7,46 @@ enum EDiagonal {
NO_SW = -1, NO_SW = -1,
} }
export class NDameModel implements IBacktracking { export class NDameModel implements IBacktrackingModel {
private readonly logger = new Logger(this.constructor.name); private readonly logger = new Logger(this.constructor.name);
private nDamesMatrix: boolean[][] = []; private nDamesMatrix: boolean[][] = [];
// Wert initial setzen, damit nextPosition beim ersten Schritt richtig platziert wird // Wert initial setzen, damit nextPosition beim ersten Schritt richtig platziert wird
private modelPosition: MatrixPosition = new MatrixPosition( private modelPosition: MatrixPosition = new MatrixPosition(
-1, -1,
this.modelSize - 1, this._modelSize - 1,
); );
constructor(private readonly modelSize: number) { constructor(
private readonly _startingPosition,
private readonly _modelSize: number,
) {
this.initNDameField(); this.initNDameField();
} }
private initNDameField() { private initNDameField() {
for (var zeile = 0; zeile < this.modelSize; zeile++) { // haben wir eine StartPosition, das muss die modelPosition expilzit gesetzt werden
// StartPosition darf nicht größer als modelSize sein !
if (
this._startingPosition > 0 &&
this._startingPosition <= this.modelSize
) {
this.modelPosition.col = this._startingPosition - 2;
this.modelPosition.row = 0;
}
for (var zeile = 0; zeile < this._modelSize; zeile++) {
this.nDamesMatrix[zeile] = []; this.nDamesMatrix[zeile] = [];
for (var spalte = 0; spalte < this.modelSize; spalte++) { for (var spalte = 0; spalte < this._modelSize; spalte++) {
this.nDamesMatrix[zeile].push(false); this.nDamesMatrix[zeile].push(false);
} }
} }
} }
public get modelSize() {
return this._modelSize;
}
public get matrix(): boolean[][] { public get matrix(): boolean[][] {
return this.nDamesMatrix; return this.nDamesMatrix;
} }
@@ -43,43 +60,58 @@ export class NDameModel implements IBacktracking {
// sind alle N Damen auf dem Feld platziert ? // sind alle N Damen auf dem Feld platziert ?
public solved = (): boolean => { public solved = (): boolean => {
return this.dames == this.modelSize; return this.dames == this._modelSize;
}; };
options = (): Array<number> => { hasNext = (): boolean => {
// wir können nur 1 Dame pro Feld platzieren !!! if (
return [1]; // wenn das Maximum erreicht haben ist Schluss
this.modelPosition.row === this.modelSize - 1 &&
this.modelPosition.col === this.modelSize - 1
)
return false;
// const optionsArray = Array.from( return true;
// { length: this.modelSize },
// (value, index) => index,
// );
}; };
nextPosition = () => { nextPosition = () => {
this.modelPosition.col++; this.modelPosition.col++;
if (this.modelPosition.col == this.modelSize) { if (this.modelPosition.col == this._modelSize) {
this.modelPosition.row++; this.modelPosition.row++;
this.modelPosition.col = 0; this.modelPosition.col = 0;
} }
if (this.modelPosition.row == this.modelSize) if (this.modelPosition.row == this._modelSize)
throw new Error(`model bounds crashed...\n${this.modelPosition}`); throw new Error(`model bounds crashed...\n${this.modelPosition}`);
return this.modelPosition; return this.modelPosition;
}; };
nextPossiblePosition = (): boolean => {
try {
var np = this.nextPosition();
var isPossible = false;
while (!isPossible) {
isPossible = this.positionPossible();
if (isPossible) return isPossible;
this.nextPosition();
}
} catch (error) {
return null;
}
};
private rowProhibited = (): boolean => { private rowProhibited = (): boolean => {
const damesRow = this.nDamesMatrix[this.modelPosition.row]; const damesRow = this.nDamesMatrix[this.modelPosition.row];
// steht schon eine Dame in der selben Zeile ? // steht schon eine Dame in der selben Zeile ?
const damesInRow = damesRow.reduce((acc, act) => (act ? ++acc : acc), 0); const damesInRow = damesRow.reduce((acc, act) => (act ? ++acc : acc), 0);
if (damesInRow > 1) if (damesInRow > 0) this.logger.warn(`row occupied: ${this.modelPosition}`);
this.logger.error(`row occupied: ${this.modelPosition}`);
return damesInRow > 1; return damesInRow > 0;
}; };
private colProhibited = (): boolean => { private colProhibited = (): boolean => {
@@ -91,24 +123,19 @@ export class NDameModel implements IBacktracking {
// steht schon eine Dame in der selben Spalte ? // steht schon eine Dame in der selben Spalte ?
const damesInCol = damesCol.reduce((acc, act) => (act ? ++acc : acc), 0); const damesInCol = damesCol.reduce((acc, act) => (act ? ++acc : acc), 0);
if (damesInCol > 1) if (damesInCol > 0) this.logger.warn(`col occupied: ${this.modelPosition}`);
this.logger.error(`col occupied: ${this.modelPosition}`);
return damesInCol > 1; return damesInCol > 0;
}; };
private damesInDiagonal = (diagDirection: EDiagonal) => { private damesInDiagonal = (diagDirection: EDiagonal) => {
this.logger.log(
`diaginal ${diagDirection == EDiagonal.NW_SO ? 'NW_SO' : 'NO_SW'}`,
);
// const startPos = { ...this.modelPosition }; // const startPos = { ...this.modelPosition };
const startPos = new MatrixPosition( const startPos = new MatrixPosition(
this.modelPosition.row, this.modelPosition.row,
this.modelPosition.col, this.modelPosition.col,
); );
const colSearchEndPos = const colSearchEndPos =
diagDirection == EDiagonal.NW_SO ? 0 : this.modelSize - 1; diagDirection == EDiagonal.NW_SO ? 0 : this._modelSize - 1;
var found = false; var found = false;
@@ -124,9 +151,9 @@ export class NDameModel implements IBacktracking {
var diagPos = startPos; var diagPos = startPos;
const diagCoords = []; const diagCoords = [];
const damesDiag = []; const damesDiag = [];
const colExitPos = diagDirection == EDiagonal.NW_SO ? this.modelSize : -1; const colExitPos = diagDirection == EDiagonal.NW_SO ? this._modelSize : -1;
while (!diagReady) { while (!diagReady) {
if (diagPos.row == this.modelSize || diagPos.col == colExitPos) if (diagPos.row == this._modelSize || diagPos.col == colExitPos)
diagReady = true; diagReady = true;
else { else {
diagCoords.push(new MatrixPosition(diagPos.row, diagPos.col)); diagCoords.push(new MatrixPosition(diagPos.row, diagPos.col));
@@ -136,7 +163,6 @@ export class NDameModel implements IBacktracking {
} }
} }
this.logger.verbose(diagCoords.join());
return damesDiag; return damesDiag;
}; };
@@ -156,12 +182,12 @@ export class NDameModel implements IBacktracking {
0, 0,
); );
if (damesInDiagonalNW_SO > 1) if (damesInDiagonalNW_SO > 0)
this.logger.error(`diagNW_SO occupied: ${this.modelPosition}`); this.logger.warn(`diagNW_SO occupied: ${this.modelPosition}`);
if (damesInDiagonalNO_SW > 1) if (damesInDiagonalNO_SW > 0)
this.logger.error(`diagNO_SW occupied: ${this.modelPosition}`); this.logger.warn(`diagNO_SW occupied: ${this.modelPosition}`);
return damesInDiagonalNW_SO > 1 || damesInDiagonalNO_SW > 1; return damesInDiagonalNW_SO > 0 || damesInDiagonalNO_SW > 0;
}; };
// darf hier eine Dame stehen ? // darf hier eine Dame stehen ?
@@ -179,14 +205,33 @@ export class NDameModel implements IBacktracking {
// Dame auf das Feld platzieren // Dame auf das Feld platzieren
applyPosition = (): MatrixPosition => { applyPosition = (): MatrixPosition => {
this.logger.warn(`apply: ${JSON.stringify(this.modelPosition)}`); this.logger.verbose(`apply: ${JSON.stringify(this.modelPosition)}`);
this.nDamesMatrix[this.modelPosition.row][this.modelPosition.col] = true; this.nDamesMatrix[this.modelPosition.row][this.modelPosition.col] = true;
return this.modelPosition; return this.modelPosition;
}; };
// letzte Dame vom Feld nehmen // letzte Dame vom Feld nehmen
discardPosition = (): MatrixPosition => { discardPosition = (): MatrixPosition => {
this.nDamesMatrix[this.modelPosition.row][this.modelPosition.col] = false; // 1. letzte Dame suchen
const lastDameFlatPos = this.nDamesMatrix.flat().lastIndexOf(true);
const lastDameRow = Math.floor(lastDameFlatPos / this.modelSize);
const lastDameCol = lastDameFlatPos % this.modelSize;
// 2. alle Damen kicken
// const resettedNDamesMatrix = this.nDamesMatrix.map((row) => {
// const resettedRow = row.map(() => false);
// return resettedRow;
// });
// 3. assign cleared Matrix
// this.nDamesMatrix = resettedNDamesMatrix;
// 3. Position auf Feld letzten Dame
this.modelPosition.row = lastDameRow;
this.modelPosition.col = lastDameCol;
// 4. Wert resetten
this.nDamesMatrix[lastDameRow][lastDameCol] = false;
return this.modelPosition; return this.modelPosition;
}; };
} }

View File

@@ -19,12 +19,39 @@ export class NDameProblemController {
@Get('/model') @Get('/model')
getModel( getModel(
@Query('field', new DefaultValuePipe(-1), ParseIntPipe) field: number, @Query('field', new DefaultValuePipe(-1), ParseIntPipe) field: number,
@Query('model-size', new DefaultValuePipe(5), ParseIntPipe)
modelSize: number,
): boolean[][] { ): boolean[][] {
return this.nDameProblemService.getModel(field); // wenn field gesetzt, dann hat das Prio
return this.nDameProblemService.getModel(field, modelSize);
} }
@Get('/backtracking') @Get('/backtracking')
getBacktracking() { getBacktracking() {
return this.nDameProblemService.getBacktracking(); return this.nDameProblemService.getBacktracking();
} }
@Get('/do-backtracking')
doBacktracking(
@Query('model-size', new DefaultValuePipe(5), ParseIntPipe)
modelSize: number,
@Query('starting-position', new DefaultValuePipe(-1), ParseIntPipe)
startingPosition: number,
) {
const backtrackMatrix: boolean[][] =
this.nDameProblemService.doBacktracking(startingPosition, modelSize);
const resultMatrix = backtrackMatrix.map((row: boolean[]) =>
row.map((b: boolean) => (b ? ' X' : ' .')),
);
const verticalLine = ' '.concat(''.padStart(modelSize * 2, '_'), ' ');
const resultMatrixStr = '\n|'.concat(
resultMatrix.map((row) => row.join('').concat('|\n')).join('|'),
);
return verticalLine.concat(
resultMatrixStr,
verticalLine.replaceAll('_', '~'),
);
}
} }

View File

@@ -1,30 +1,48 @@
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { NDameModel } from './n-dame-model'; import { NDameModel } from './n-dame-model';
import { NDameBacktracking } from './n-dame-backtracking'; import { NDameBacktracker } from './n-dame-backtracker';
@Injectable() @Injectable()
export class NDameProblemService { export class NDameProblemService {
private readonly logger = new Logger(this.constructor.name); private readonly logger = new Logger(this.constructor.name);
private readonly MODEL_SIZE = 5;
getBacktracking(): NDameBacktracking { doBacktracking(startingPosition: number, modelSize: number): boolean[][] {
const backTracking = new NDameBacktracking(4); const nDameBacktracker = new NDameBacktracker(startingPosition, modelSize);
this.logger.log(`${backTracking.options()}`); return nDameBacktracker.doBacktrack();
backTracking.solved();
return backTracking;
} }
getModel(field: number): boolean[][] { getBacktracking(): NDameBacktracker {
const model = new NDameModel(this.MODEL_SIZE); const backTracker = new NDameBacktracker(0, 5);
const modelFieldCount = this.MODEL_SIZE * this.MODEL_SIZE;
backTracker.model.solved();
for (var i = 0; i < 5; i++) {
backTracker.model.nextPosition();
backTracker.model.applyPosition();
}
backTracker.model.discardPosition();
backTracker.model.nextPosition();
backTracker.model.applyPosition();
return backTracker;
}
getModel(field: number, modelSize: number): boolean[][] {
const model = new NDameModel(-1, modelSize);
const modelFieldCount = model.modelSize * model.modelSize;
const rndFields = []; const rndFields = [];
// geben wir keine FeldNr. an, dann die Zufallsreihe // geben wir keine FeldNr. an, dann die Zufallsreihe
if (field === -1) if (field === -1)
for (var i = 0; i < this.MODEL_SIZE; i++) for (var i = 0; i < model.modelSize; i++) {
rndFields.push(Math.floor(Math.random() * modelFieldCount)); var rndNr = Math.floor(Math.random() * modelFieldCount);
while (rndFields.includes(rndNr))
rndNr = Math.floor(Math.random() * modelFieldCount);
rndFields.push(rndNr);
}
else rndFields.push(field); else rndFields.push(field);
this.logger.fatal(`[${rndFields.toString()}]`); this.logger.fatal(`[${rndFields.toString()}]`);

View File

@@ -0,0 +1,55 @@
import { Logger } from '@nestjs/common';
import { IBacktrackingModel } from './backtracking-model.interface';
export abstract class ABacktracker {
protected readonly logger = new Logger(this.constructor.name);
abstract doBacktrack: () => boolean[][];
protected findSolution = (dataModel: IBacktrackingModel): boolean => {
if (dataModel.solved()) return true;
// nächstes freie Position suchen
// const nxtPossPos = dataModel.nextPossiblePosition();
while (dataModel.nextPossiblePosition()) {
dataModel.applyPosition();
// Lösung mit dem neu konfigurierten dataObject finden
const solved: boolean = this.findSolution(dataModel);
if (solved) return true;
const afterDiscardedPos = dataModel.discardPosition();
this.logger.debug(`afterDiscardedPos: ${afterDiscardedPos}`);
}
return false;
};
protected ___findSolution___ = (dataModel: IBacktrackingModel): boolean => {
if (dataModel.solved()) return true;
// dataModel.options.forEach((option) => {
while (dataModel.hasNext()) {
// nächstes freie Position suchen
const nxtPos = dataModel.nextPosition();
this.logger.debug(`nxtPos: ${nxtPos}`);
// darf die Option hier eingestellt werden ?
if (dataModel.positionPossible()) {
dataModel.applyPosition();
// Lösung mit dem neu konfigurierten dataObject finden
const solved: boolean = this.findSolution(dataModel);
if (solved) return true;
const afterDiscardedPos = dataModel.discardPosition();
this.logger.debug(`afterDiscardedPos: ${afterDiscardedPos}`);
}
}
return false;
};
}

View File

@@ -1,7 +0,0 @@
import { Backtracker } from './backtracker';
describe('Backtracker', () => {
it('should be defined', () => {
expect(new Backtracker()).toBeDefined();
});
});

View File

@@ -1,26 +0,0 @@
import { IBacktracking } from './backtracking.interface';
export class Backtracker {
findSolution = (dataObject: IBacktracking): boolean => {
if (dataObject.solved()) return true;
// nächstes freie Position suchen
dataObject.nextPosition();
dataObject.options().forEach((option) => {
// darf die Option hier eingestellt werden ?
if (dataObject.positionPossible()) {
dataObject.applyPosition();
// Lösung mit dem neu konfigurierten dataObject finden
const solved: boolean = this.findSolution(dataObject);
if (solved) return true;
dataObject.discardPosition();
}
});
return false;
};
}

View File

@@ -1,9 +1,10 @@
import { MatrixPosition } from './matrix-position'; import { MatrixPosition } from './matrix-position';
export interface IBacktracking { export interface IBacktrackingModel {
options: () => Array<any>;
solved: () => boolean; solved: () => boolean;
hasNext: () => boolean;
nextPosition: () => MatrixPosition; nextPosition: () => MatrixPosition;
nextPossiblePosition: () => boolean;
positionPossible: () => boolean; positionPossible: () => boolean;
applyPosition: () => MatrixPosition; applyPosition: () => MatrixPosition;
discardPosition: () => MatrixPosition; discardPosition: () => MatrixPosition;

View File

@@ -1,6 +1,6 @@
{ {
"name": "backtracking", "name": "backtracking",
"version": "1.0.0", "version": "2.0.2",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,