diff --git a/apps/n-dame-problem/src/n-dame-backtracking.ts b/apps/n-dame-problem/src/n-dame-backtracking.ts new file mode 100644 index 0000000..afc8146 --- /dev/null +++ b/apps/n-dame-problem/src/n-dame-backtracking.ts @@ -0,0 +1,22 @@ +import { NDameModel } from './n-dame-model'; + +export class NDameBacktracking { + private model: NDameModel; + + constructor(private readonly modelSize: number) { + this.model = new NDameModel(modelSize); + } + + options = (): Array => { + // 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(); + }; +} diff --git a/apps/n-dame-problem/src/n-dame-model.ts b/apps/n-dame-problem/src/n-dame-model.ts new file mode 100644 index 0000000..f3d3073 --- /dev/null +++ b/apps/n-dame-problem/src/n-dame-model.ts @@ -0,0 +1,192 @@ +import { IBacktracking } from '@app/backtracking-lib/backtracking.interface'; +import { MatrixPosition } from '@app/backtracking-lib/matrix-position'; +import { Logger } from '@nestjs/common'; + +enum EDiagonal { + NW_SO = 1, + NO_SW = -1, +} + +export class NDameModel implements IBacktracking { + private readonly logger = new Logger(this.constructor.name); + + private nDamesMatrix: boolean[][] = []; + // Wert initial setzen, damit nextPosition beim ersten Schritt richtig platziert wird + private modelPosition: MatrixPosition = new MatrixPosition( + -1, + this.modelSize - 1, + ); + + constructor(private readonly modelSize: number) { + this.initNDameField(); + } + + private initNDameField() { + for (var zeile = 0; zeile < this.modelSize; zeile++) { + this.nDamesMatrix[zeile] = []; + for (var spalte = 0; spalte < this.modelSize; spalte++) { + this.nDamesMatrix[zeile].push(false); + } + } + } + + public get matrix(): boolean[][] { + return this.nDamesMatrix; + } + + // getter: Anzahl der gesetzten Damen + private get dames(): number { + const arrOfMatrix = this.nDamesMatrix.flat(); + const dames = arrOfMatrix.reduce((a, b: boolean) => (b ? a + 1 : a), 0); + return dames; + } + + // sind alle N Damen auf dem Feld platziert ? + public solved = (): boolean => { + return this.dames == this.modelSize; + }; + + options = (): Array => { + // wir können nur 1 Dame pro Feld platzieren !!! + return [1]; + + // const optionsArray = Array.from( + // { length: this.modelSize }, + // (value, index) => index, + // ); + }; + + nextPosition = () => { + this.modelPosition.col++; + + if (this.modelPosition.col == this.modelSize) { + this.modelPosition.row++; + this.modelPosition.col = 0; + } + + if (this.modelPosition.row == this.modelSize) + throw new Error(`model bounds crashed...\n${this.modelPosition}`); + + return this.modelPosition; + }; + + private rowProhibited = (): boolean => { + const damesRow = this.nDamesMatrix[this.modelPosition.row]; + + // steht schon eine Dame in der selben Zeile ? + const damesInRow = damesRow.reduce((acc, act) => (act ? ++acc : acc), 0); + + if (damesInRow > 1) + this.logger.error(`row occupied: ${this.modelPosition}`); + + return damesInRow > 1; + }; + + private colProhibited = (): boolean => { + const damesCol = this.nDamesMatrix.reduce((acc, act) => { + acc.push(act[this.modelPosition.col]); + return acc; + }, []); + + // steht schon eine Dame in der selben Spalte ? + const damesInCol = damesCol.reduce((acc, act) => (act ? ++acc : acc), 0); + + if (damesInCol > 1) + this.logger.error(`col occupied: ${this.modelPosition}`); + + return damesInCol > 1; + }; + + private damesInDiagonal = (diagDirection: EDiagonal) => { + this.logger.log( + `diaginal ${diagDirection == EDiagonal.NW_SO ? 'NW_SO' : 'NO_SW'}`, + ); + + // const startPos = { ...this.modelPosition }; + const startPos = new MatrixPosition( + this.modelPosition.row, + this.modelPosition.col, + ); + const colSearchEndPos = + diagDirection == EDiagonal.NW_SO ? 0 : this.modelSize - 1; + + var found = false; + + while (!found) { + if (startPos.row == 0 || startPos.col == colSearchEndPos) found = true; + else { + --startPos.row; + startPos.col += -diagDirection; + } + } + + var diagReady = false; + var diagPos = startPos; + const diagCoords = []; + const damesDiag = []; + const colExitPos = diagDirection == EDiagonal.NW_SO ? this.modelSize : -1; + while (!diagReady) { + if (diagPos.row == this.modelSize || diagPos.col == colExitPos) + diagReady = true; + else { + diagCoords.push(new MatrixPosition(diagPos.row, diagPos.col)); + damesDiag.push(this.nDamesMatrix[diagPos.row][diagPos.col]); + ++diagPos.row; + diagPos.col += diagDirection; + } + } + + this.logger.verbose(diagCoords.join()); + return damesDiag; + }; + + private diagonalsProhibited = (): boolean => { + const diagNW_SO = this.damesInDiagonal(EDiagonal.NW_SO); + const diagNO_SW = this.damesInDiagonal(EDiagonal.NO_SW); + + // steht schon eine Dame in der Diagonale NW_SO ? + const damesInDiagonalNW_SO = diagNW_SO.reduce( + (acc, act) => (act ? ++acc : acc), + 0, + ); + + // steht schon eine Dame in der Diagonale NW_SO ? + const damesInDiagonalNO_SW = diagNO_SW.reduce( + (acc, act) => (act ? ++acc : acc), + 0, + ); + + if (damesInDiagonalNW_SO > 1) + this.logger.error(`diagNW_SO occupied: ${this.modelPosition}`); + if (damesInDiagonalNO_SW > 1) + this.logger.error(`diagNO_SW occupied: ${this.modelPosition}`); + + return damesInDiagonalNW_SO > 1 || damesInDiagonalNO_SW > 1; + }; + + // darf hier eine Dame stehen ? + positionPossible = (): boolean => { + const rp = this.rowProhibited(); + const cp = this.colProhibited(); + const dp = this.diagonalsProhibited(); + const positionOk = !rp && !cp && !dp; + + if (positionOk) + this.logger.log(`free: ${JSON.stringify(this.modelPosition)}`); + + return positionOk; + }; + + // Dame auf das Feld platzieren + applyPosition = (): MatrixPosition => { + this.logger.warn(`apply: ${JSON.stringify(this.modelPosition)}`); + this.nDamesMatrix[this.modelPosition.row][this.modelPosition.col] = true; + return this.modelPosition; + }; + + // letzte Dame vom Feld nehmen + discardPosition = (): MatrixPosition => { + this.nDamesMatrix[this.modelPosition.row][this.modelPosition.col] = false; + return this.modelPosition; + }; +} diff --git a/apps/n-dame-problem/src/n-dame-problem.controller.ts b/apps/n-dame-problem/src/n-dame-problem.controller.ts index fe0f814..4005c36 100644 --- a/apps/n-dame-problem/src/n-dame-problem.controller.ts +++ b/apps/n-dame-problem/src/n-dame-problem.controller.ts @@ -1,12 +1,30 @@ -import { Controller, Get } from '@nestjs/common'; +import { + Controller, + DefaultValuePipe, + Get, + ParseIntPipe, + Query, +} from '@nestjs/common'; import { NDameProblemService } from './n-dame-problem.service'; @Controller() export class NDameProblemController { constructor(private readonly nDameProblemService: NDameProblemService) {} - @Get() + @Get('/hello') getHello(): string { return this.nDameProblemService.getHello(); } + + @Get('/model') + getModel( + @Query('field', new DefaultValuePipe(-1), ParseIntPipe) field: number, + ): boolean[][] { + return this.nDameProblemService.getModel(field); + } + + @Get('/backtracking') + getBacktracking() { + return this.nDameProblemService.getBacktracking(); + } } diff --git a/apps/n-dame-problem/src/n-dame-problem.service.ts b/apps/n-dame-problem/src/n-dame-problem.service.ts index 5d4bc4b..1ccd78e 100644 --- a/apps/n-dame-problem/src/n-dame-problem.service.ts +++ b/apps/n-dame-problem/src/n-dame-problem.service.ts @@ -1,7 +1,48 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; +import { NDameModel } from './n-dame-model'; +import { NDameBacktracking } from './n-dame-backtracking'; @Injectable() export class NDameProblemService { + private readonly logger = new Logger(this.constructor.name); + private readonly MODEL_SIZE = 5; + + getBacktracking(): NDameBacktracking { + const backTracking = new NDameBacktracking(4); + + this.logger.log(`${backTracking.options()}`); + backTracking.solved(); + + return backTracking; + } + + getModel(field: number): boolean[][] { + const model = new NDameModel(this.MODEL_SIZE); + const modelFieldCount = this.MODEL_SIZE * this.MODEL_SIZE; + const rndFields = []; + + // geben wir keine FeldNr. an, dann die Zufallsreihe + if (field === -1) + for (var i = 0; i < this.MODEL_SIZE; i++) + rndFields.push(Math.floor(Math.random() * modelFieldCount)); + else rndFields.push(field); + + this.logger.fatal(`[${rndFields.toString()}]`); + + for (var i = 0; i < modelFieldCount; i++) { + const nxtPos = model.nextPosition(); + + if (rndFields.includes(i)) { + model.applyPosition(); + model.positionPossible(); + } + + // this.logger.verbose(`${i + 1} - ${JSON.stringify(nxtPos)}`); + } + + return model.matrix; + } + getHello(): string { return 'Hello World!'; } diff --git a/libs/backtracking-lib/src/backtracker.spec.ts b/libs/backtracking-lib/src/backtracker.spec.ts new file mode 100644 index 0000000..fc3ee1e --- /dev/null +++ b/libs/backtracking-lib/src/backtracker.spec.ts @@ -0,0 +1,7 @@ +import { Backtracker } from './backtracker'; + +describe('Backtracker', () => { + it('should be defined', () => { + expect(new Backtracker()).toBeDefined(); + }); +}); diff --git a/libs/backtracking-lib/src/backtracker.ts b/libs/backtracking-lib/src/backtracker.ts new file mode 100644 index 0000000..0c7eb46 --- /dev/null +++ b/libs/backtracking-lib/src/backtracker.ts @@ -0,0 +1,26 @@ +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; + }; +} diff --git a/libs/backtracking-lib/src/backtracking-lib.service.ts b/libs/backtracking-lib/src/backtracking-lib.service.ts index de5f68d..b4a48f3 100644 --- a/libs/backtracking-lib/src/backtracking-lib.service.ts +++ b/libs/backtracking-lib/src/backtracking-lib.service.ts @@ -1,4 +1,27 @@ import { Injectable } from '@nestjs/common'; +/** + * PSEUDO-CODE: + * +void FIND_SOLUTIONS (Parameter): + + wenn (gültige Lösung ): + Bewahren Sie die Lösung auf + Zurückkehren (gelöst) + + für (alle Auswahlmöglichkeiten ): + wenn (gültige Auswahl ): + ANWENDEN ( Auswahl ) + + FIND_SOLUTIONS (Parameter) + wenn (gültige Lösung ): + Bewahren Sie die Lösung auf + Zurückkehren (gelöst) + + BACKTRACK ( Auswahl entfernen ) + + Zurückkehren (nicht gelöst) + */ + @Injectable() export class BacktrackingLibService {} diff --git a/libs/backtracking-lib/src/backtracking.interface.ts b/libs/backtracking-lib/src/backtracking.interface.ts new file mode 100644 index 0000000..873a52c --- /dev/null +++ b/libs/backtracking-lib/src/backtracking.interface.ts @@ -0,0 +1,10 @@ +import { MatrixPosition } from './matrix-position'; + +export interface IBacktracking { + options: () => Array; + solved: () => boolean; + nextPosition: () => MatrixPosition; + positionPossible: () => boolean; + applyPosition: () => MatrixPosition; + discardPosition: () => MatrixPosition; +} diff --git a/libs/backtracking-lib/src/matrix-position.ts b/libs/backtracking-lib/src/matrix-position.ts new file mode 100644 index 0000000..f323952 --- /dev/null +++ b/libs/backtracking-lib/src/matrix-position.ts @@ -0,0 +1,25 @@ +export class MatrixPosition { + constructor( + private _row: number, + private _col: number, + ) {} + + public get row() { + return this._row; + } + public set row(r: number) { + this._row = r; + } + + public get col() { + return this._col; + } + + public set col(c: number) { + this._col = c; + } + + public toString() { + return `[${this._row},${this._col}]`; + } +} diff --git a/package.json b/package.json index b66b3fc..3b59341 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "backtracking", - "version": "0.9.0", + "version": "1.0.0", "description": "", "author": "", "private": true,