Skip to content

Instantly share code, notes, and snippets.

@raganwald
Last active August 7, 2018 23:22
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save raganwald/e8896be8f032ba80019fd6a20fc6bb7d to your computer and use it in GitHub Desktop.
Save raganwald/e8896be8f032ba80019fd6a20fc6bb7d to your computer and use it in GitHub Desktop.
The Eight Queens Problem
// Computes the twelve "fundamental" solutions to the eight queens problem
// by filtering the results of the "half-inductive" algorithm.
//
// see http://raganwald.com/2018/08/03/eight-queens.html
//
// search space: 2,750 candidate positions
function testDiagonals (queens) {
const nesw = [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "."];
const nwse = [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "."];
if (queens.length < 2) return true;
for (const [i, j] of queens) {
if (nwse[i + j] !== '.' || nesw[i + 7 - j] !== '.') return false;
nwse[i + j] = 'x';
nesw[i + 7 - j] = 'x';
}
return true;
}
const without = (array, element) =>
array.filter(x => x !== element);
function * inductive (
queens = [],
candidateColumns = [0, 1, 2, 3, 4, 5, 6, 7]
) {
if (queens.length === 8) {
yield queens;
} else {
for (const chosenColumn of candidateColumns) {
const candidateQueens = queens.concat([[queens.length, chosenColumn]]);
const remainingColumns = without(candidateColumns, chosenColumn);
if (testDiagonals(candidateQueens)) {
yield * inductive(candidateQueens, remainingColumns);
}
}
}
}
const allColumns = [0, 1, 2, 3, 4, 5, 6, 7];
function * halfInductive () {
for (const column of [0, 1, 2, 3]) {
const candidateQueens = [[0, column]];
const remainingColumns = without(allColumns, column);
yield * inductive(candidateQueens, remainingColumns);
}
}
function verticalReflection (queens) {
return queens.map(
([row, col]) => [row, 7 - col]
);
}
const sortQueens = queens =>
queens.reduce(
(acc, [row, col]) => (acc[row] = [row, col], acc),
[null, null, null, null, null, null, null, null]
);
const rotateRight = queens =>
sortQueens( queens.map(([row, col]) => [col, 7 - row]) );
const rotations = solution => {
const rotations = [null, null, null];
let temp = rotateRight(solution);
rotations[0] = temp;
temp = rotateRight(temp);
rotations[1] = temp;
temp = rotateRight(temp);
rotations[2] = temp;
return rotations;
}
const indexQueens = queens => queens.map(([row, col]) => `${row},${col}`).join(' ');
function * fundamentals (solutions) {
const solutionsSoFar = new Set();
for (const solution of solutions) {
const iSolution = indexQueens(solution);
if (solutionsSoFar.has(iSolution)) continue;
solutionsSoFar.add(iSolution);
const rSolutions = rotations(solution);
const irSolutions = rSolutions.map(indexQueens);
for (let irSolution of irSolutions) {
solutionsSoFar.add(irSolution);
}
const vSolution = verticalReflection(solution);
const rvSolutions = rotations(vSolution);
const irvSolutions = rvSolutions.map(indexQueens);
for (let irvSolution of irvSolutions) {
solutionsSoFar.add(irvSolution);
}
yield solution;
}
}
function * mapWith (mapFunction, iterable) {
for (const element of iterable) {
yield mapFunction(element);
}
}
function niceDiagramOf (queens) {
const board = [
["⬜️", "⬛️", "⬜️", "⬛️", "⬜️", "⬛️", "⬜️", "⬛️"],
["⬛️", "⬜️", "⬛️", "⬜️", "⬛️", "⬜️", "⬛️", "⬜️"],
["⬜️", "⬛️", "⬜️", "⬛️", "⬜️", "⬛️", "⬜️", "⬛️"],
["⬛️", "⬜️", "⬛️", "⬜️", "⬛️", "⬜️", "⬛️", "⬜️"],
["⬜️", "⬛️", "⬜️", "⬛️", "⬜️", "⬛️", "⬜️", "⬛️"],
["⬛️", "⬜️", "⬛️", "⬜️", "⬛️", "⬜️", "⬛️", "⬜️"],
["⬜️", "⬛️", "⬜️", "⬛️", "⬜️", "⬛️", "⬜️", "⬛️"],
["⬛️", "⬜️", "⬛️", "⬜️", "⬛️", "⬜️", "⬛️", "⬜️"]
];
for (const [row, col] of queens) {
board[7 - row][col] = "👸🏾";
}
return board.map(row => row.join('')).join("\n");
}
mapWith(niceDiagramOf, fundamentals(halfInductive()))
// Computes the all 92 solutions to the eight queens problem
// by computing half of the results of the inductive solution
// and then adding their vertical reflections.
//
// see http://raganwald.com/2018/08/03/eight-queens.html
//
// search space: 2,750 candidate positions
function testDiagonals (queens) {
const nesw = [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "."];
const nwse = [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "."];
if (queens.length < 2) return true;
for (const [i, j] of queens) {
if (nwse[i + j] !== '.' || nesw[i + 7 - j] !== '.') return false;
nwse[i + j] = 'x';
nesw[i + 7 - j] = 'x';
}
return true;
}
const without = (array, element) =>
array.filter(x => x !== element);
function * inductive (
queens = [],
candidateColumns = [0, 1, 2, 3, 4, 5, 6, 7]
) {
if (queens.length === 8) {
yield queens;
} else {
for (const chosenColumn of candidateColumns) {
const candidateQueens = queens.concat([[queens.length, chosenColumn]]);
const remainingColumns = without(candidateColumns, chosenColumn);
if (testDiagonals(candidateQueens)) {
yield * inductive(candidateQueens, remainingColumns);
}
}
}
}
const allColumns = [0, 1, 2, 3, 4, 5, 6, 7];
function * halfInductive () {
for (const column of [0, 1, 2, 3]) {
const candidateQueens = [[0, column]];
const remainingColumns = without(allColumns, column);
yield * inductive(candidateQueens, remainingColumns);
}
}
function verticalReflection (queens) {
return queens.map(
([row, col]) => [row, 7 - col]
);
}
function * flatMapWith (fn, iterable) {
for (const element of iterable) {
yield * fn(element);
}
}
const withReflections = flatMapWith(
queens => [queens, verticalReflection(queens)], halfInductive());
Array.from(withReflections).length
//=> 92
// Computes the all 92 solutions to the eight queens problem
// by testing partial solutions to the "rooks" algorithm
// as they are created, thus pruning subtrees when possible.
//
// see http://raganwald.com/2018/08/03/eight-queens.html
//
// search space: 5,508 candidate positions
function testDiagonals (queens) {
const nesw = [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "."];
const nwse = [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "."];
if (queens.length < 2) return true;
for (const [i, j] of queens) {
if (nwse[i + j] !== '.' || nesw[i + 7 - j] !== '.') return false;
nwse[i + j] = 'x';
nesw[i + 7 - j] = 'x';
}
return true;
}
const without = (array, element) =>
array.filter(x => x !== element);
function * inductive (
queens = [],
candidateColumns = [0, 1, 2, 3, 4, 5, 6, 7]
) {
if (queens.length === 8) {
yield queens;
} else {
for (const chosenColumn of candidateColumns) {
const candidateQueens = queens.concat([[queens.length, chosenColumn]]);
const remainingColumns = without(candidateColumns, chosenColumn);
if (testDiagonals(candidateQueens)) {
yield * inductive(candidateQueens, remainingColumns);
}
}
}
}
Array.from(inductive()).length
//=> 92
// Computes the all 92 solutions to the eight queens problem
// by generating all of the solutions to the eight rooks
// problem using permutations, and then filtering them
// by testing for diagonal attacks.
//
// see http://raganwald.com/2018/08/03/eight-queens.html
//
// search space: 40,320 candidate positions
function diagramOf (queens) {
const board = [
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."]
];
for (const [i, j] of queens) {
board[i][j] = "Q";
}
return board.map(row => row.join('')).join("\n");
}
function test (queens) {
const board = [
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."]
];
for (const [i, j] of queens) {
if (board[i][j] != '.') {
// square is occupied or threatened
return false;
}
for (let k = 0; k <= 7; ++k) {
// fill row and column
board[i][k] = board[k][j] = "x";
const vOffset = k-i;
const hDiagonal1 = j - vOffset;
const hDiagonal2 = j + vOffset;
// fill diagonals
if (hDiagonal1 >= 0 && hDiagonal1 <= 7) {
board[k][hDiagonal1] = "x";
}
if (hDiagonal2 >= 0 && hDiagonal2 <= 7) {
board[k][hDiagonal2] = "x";
}
board[i][j] = "Q";
}
}
return true;
}
function * filterWith (predicateFunction, iterable) {
for (const element of iterable) {
if (predicateFunction(element)) {
yield element;
}
}
}
function first (iterable) {
const [value] = iterable;
return value;
}
function * mapWith (mapFunction, iterable) {
for (const element of iterable) {
yield mapFunction(element);
}
}
function * permutations (arr, prefix = []) {
if (arr.length === 1) {
yield prefix.concat(arr);
} else if (arr.length > 1) {
for (let i = 0; i < arr.length; ++i) {
const chosen = arr[i];
const remainder = arr.slice(0, i).concat(arr.slice(i+1, arr.length))
yield * permutations(remainder, prefix.concat([chosen]));
}
}
}
const solutionsToEightRooks = mapWith(
ii => ii.map((i, j) => [i, j]),
permutations([0, 1, 2, 3, 4, 5, 6, 7])
);
const solutionsToEightQueens = filterWith(test, solutionsToEightRooks);
diagramOf(first(solutionsToEightQueens))
// Computes the all 92 solutions to the eight queens problem
// by computing all of the ways eight queens can be arranged
// on the board using 64 choose 8, then filtering them
// for horizontal, vertical, and diagonal attacks.
//
// see http://raganwald.com/2018/08/03/eight-queens.html
//
// search space: 4,426,165,368 candidate positions
function diagramOf (queens) {
const board = [
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."]
];
for (const [i, j] of queens) {
board[i][j] = "Q";
}
return board.map(row => row.join('')).join("\n");
}
function test (queens) {
const board = [
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."]
];
for (const [i, j] of queens) {
if (board[i][j] != '.') {
// square is occupied or threatened
return false;
}
for (let k = 0; k <= 7; ++k) {
// fill row and column
board[i][k] = board[k][j] = "x";
const vOffset = k-i;
const hDiagonal1 = j - vOffset;
const hDiagonal2 = j + vOffset;
// fill diagonals
if (hDiagonal1 >= 0 && hDiagonal1 <= 7) {
board[k][hDiagonal1] = "x";
}
if (hDiagonal2 >= 0 && hDiagonal2 <= 7) {
board[k][hDiagonal2] = "x";
}
board[i][j] = "Q";
}
}
return true;
}
function * filterWith (predicateFunction, iterable) {
for (const element of iterable) {
if (predicateFunction(element)) {
yield element;
}
}
}
function first (iterable) {
const [value] = iterable;
return value;
}
function * mapWith (mapFunction, iterable) {
for (const element of iterable) {
yield mapFunction(element);
}
}
function * choose (n, k, offset = 0) {
if (k === 1) {
for (let i = 0; i <= (n - k); ++i) {
yield [i + offset];
}
} else if (k > 1) {
for (let i = 0; i <= (n - k); ++i) {
const remaining = n - i - 1;
const otherChoices = choose(remaining, k - 1, i + offset + 1);
yield * mapWith(x => [i + offset].concat(x), otherChoices);
}
}
}
const numberToPosition = n => [Math.floor(n/8), n % 8];
const numbersToPositions = queenNumbers => queenNumbers.map(numberToPosition);
const combinationCandidates = mapWith(numbersToPositions, choose(64, 8));
const solutionsToEightQueens = filterWith(test, combinationCandidates);
diagramOf(first(solutionsToEightQueens))
// Computes the all 92 solutions to the eight queens problem
// by computing all of the possible arrangements of eight
// chess squares (64^8), then filtering them
// for horizontal, vertical, and diagonal attacks.
//
// see http://raganwald.com/2018/08/03/eight-queens.html
//
// search space: 281,474,976,710,656 candidate positions
function diagramOf (queens) {
const board = [
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."]
];
for (const [i, j] of queens) {
board[i][j] = "Q";
}
return board.map(row => row.join('')).join("\n");
}
function * mostPessimumGenerator () {
for (let i0 = 0; i0 <= 7; ++i0) {
for (let j0 = 0; j0 <= 7; ++j0) {
for (let i1 = 0; i1 <= 7; ++i1) {
for (let j1 = 0; j1 <= 7; ++j1) {
for (let i2 = 0; i2 <= 7; ++i2) {
for (let j2 = 0; j2 <= 7; ++j2) {
for (let i3 = 0; i3 <= 7; ++i3) {
for (let j3 = 0; j3 <= 7; ++j3) {
for (let i4 = 0; i4 <= 7; ++i4) {
for (let j4 = 0; j4 <= 7; ++j4) {
for (let i5 = 0; i5 <= 7; ++i5) {
for (let j5 = 0; j5 <= 7; ++j5) {
for (let i6 = 0; i6 <= 7; ++i6) {
for (let j6 = 0; j6 <= 7; ++j6) {
for (let i7 = 0; i7 <= 7; ++i7) {
for (let j7 = 0; j7 <= 7; ++j7) {
const queens = [
[i0, j0],
[i1, j1],
[i2, j2],
[i3, j3],
[i4, j4],
[i5, j5],
[i6, j6],
[i7, j7]
];
yield queens;
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
function test (queens) {
const board = [
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."],
[".", ".", ".", ".", ".", ".", ".", "."]
];
for (const [i, j] of queens) {
if (board[i][j] != '.') {
// square is occupied or threatened
return false;
}
for (let k = 0; k <= 7; ++k) {
// fill row and column
board[i][k] = board[k][j] = "x";
const vOffset = k-i;
const hDiagonal1 = j - vOffset;
const hDiagonal2 = j + vOffset;
// fill diagonals
if (hDiagonal1 >= 0 && hDiagonal1 <= 7) {
board[k][hDiagonal1] = "x";
}
if (hDiagonal2 >= 0 && hDiagonal2 <= 7) {
board[k][hDiagonal2] = "x";
}
board[i][j] = "Q";
}
}
return true;
}
function * filterWith (predicateFunction, iterable) {
for (const element of iterable) {
if (predicateFunction(element)) {
yield element;
}
}
}
function first (iterable) {
const [value] = iterable;
return value;
}
const solutionsToEightQueens = filterWith(test, mostPessimumGenerator());
diagramOf(first(solutionsToEightQueens))
const OCCUPATION_HELPER = Symbol("occupationHelper");
class Board {
constructor () {
this.threats = [
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
];
this.queenIndices = [];
}
isValid (index) {
return index >= 0 && index <= 63;
}
isAvailable (index) {
return this.threats[index] === 0;
}
isEmpty () {
return this.queenIndices.length === 0;
}
isOccupiable (index) {
if (this.isEmpty()) {
return this.isValid(index);
} else {
return this.isValid(index) && index > this.lastQueen() && this.isAvailable(index);
}
}
numberOfQueens () {
return this.queenIndices.length
}
hasQueens () {
return this.numberOfQueens() > 0;
}
queens () {
return this.queenIndices.map(index => [Math.floor(index / 8), index % 8]);
}
lastQueen () {
if (this.queenIndices.length > 0) {
return this.queenIndices[this.queenIndices.length - 1];
}
}
* availableIndices () {
for (let index = (this.isEmpty() ? 0 : this.lastQueen() + 1); index <= 63; ++index) {
if (this.isAvailable(index)) {
yield index;
}
}
}
[OCCUPATION_HELPER] (index, action) {
const [row, col] = [Math.floor(index / 8), index % 8];
// the rest of the row
const endOfTheRow = row * 8 + 7;
for (let iThreatened = index + 1; iThreatened <= endOfTheRow; ++iThreatened) {
action(iThreatened);
}
// the rest of the column
const endOfTheColumn = 56 + col;
for (let iThreatened = index + 8; iThreatened <= endOfTheColumn; iThreatened += 8) {
action(iThreatened);
}
// diagonals to the left
const lengthOfLeftDiagonal = Math.min(col, 7 - row);
for (let i = 1; i <= lengthOfLeftDiagonal; ++i) {
const [rowThreatened, colThreatened] = [row + i, col - i];
const iThreatened = rowThreatened * 8 + colThreatened;
action(iThreatened);
}
// diagonals to the right
const lengthOfRightDiagonal = Math.min(7 - col, 7 - row);
for (let i = 1; i <= lengthOfRightDiagonal; ++i) {
const [rowThreatened, colThreatened] = [row + i, col + i];
const iThreatened = rowThreatened * 8 + colThreatened;
action(iThreatened);
}
return this;
}
occupy (index) {
const occupyAction = index => {
++this.threats[index];
};
if (this.isOccupiable(index)) {
this.queenIndices.push(index);
return this[OCCUPATION_HELPER](index, occupyAction);
}
}
unoccupy () {
const unoccupyAction = index => {
--this.threats[index];
};
if (this.hasQueens()) {
const index = this.queenIndices.pop();
return this[OCCUPATION_HELPER](index, unoccupyAction);
}
}
}
function * inductive (board = new Board()) {
if (board.numberOfQueens() === 8) {
yield board.queens();
} else {
for (const index of board.availableIndices()) {
board.occupy(index);
yield * inductive(board);
board.unoccupy();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment