Algorithm: Generate path for a Knight move to all Chess Board Squares - javascript

Problem description
I am trying to get an algorithm that will find the path of a possible sequence of moves that a Knight can move in a chessboard and visit all squares without repeating any of the squares. This is possible as illustrated by the diagram below
My Approach
To try and achieve this I have followed the below steps
Created an array with allSquares ['a1', 'a2', 'a3', ..., 'h7', 'h8']
Created another array of visitedSquares. initialy this is empty []
Created a function an array of paths for each square. This represents squares to which a Knight can move from other squares
{
"a8": ["c7", "b6"],
"a7": ["c6", "b5","c8"],
"a6": ["c5","b4","c7","b8"],
...
"h3": ["f2","g1","f4","g5"],
"h2": ["f1","f3","g4"],
"h1": ["f2","g3"]
}
Created a getNextNode() function to return the maximum of the cost of nodes visited
Finally I try to solve for the longest path with the below steps
while (this.squaresNotVisited.length > 0) {
const nextTerm = this.getNextNode();
const currentCost = this.pathCosts[nextTerm[0]];
const nextPaths: string[] = this.paths[nextTerm[0]];
nextPaths.forEach(square => {
if (this.pathCosts[square] < currentCost + 1) {
this.pathCosts[square] = currentCost + 1;
}
});
this.squaresVisited = [...this.squaresVisited, nextTerm[0]];
this.squaresNotVisited = this.allSquares.filter(
x => !this.squaresVisited.includes(x)
);
}
Below is the complete Javascript code
class AppComponent {
board = {
columns: ["a", "b", "c", "d", "e", "f", "g", "h"],
rows: [8, 7, 6, 5, 4, 3, 2, 1]
};
allSquares = this.board.columns.reduce(
(prev, next) => [...prev, ...this.board.rows.map(x => next + x)],
[]
);
currentKnightPosition = "h5";
squaresVisited = [];
squaresNotVisited = [...this.allSquares];
nextPossibleKnightPosition = currentKnightPosition => {
const row = this.board.columns.indexOf(currentKnightPosition[0]);
const column = Number(currentKnightPosition[1]) - 1;
return [
[column - 1, row - 2],
[column - 1, row + 2],
[column - 2, row - 1],
[column - 2, row + 1],
[column + 1, row - 2],
[column + 1, row + 2],
[column + 2, row - 1],
[column + 2, row + 1]
]
.filter(
([row, column]) => column >= 0 && column < 8 && row >= 0 && row < 8
)
.map(
([row, column]) =>
this.board.columns[column] + this.board.rows[8 - row - 1]
);
};
paths = this.allSquares.reduce(
(prev, next) => ({
...prev,
[next]: this.nextPossibleKnightPosition(next)
}),
{}
);
isNextSquare = square =>
this.nextPossibleKnightPosition(this.currentKnightPosition).includes(
square
);
costs = { [this.currentKnightPosition]: 0 };
pathCosts = {
...this.allSquares.reduce(
(prev, next) => ({ ...prev, [next]: -Infinity }),
{}
),
[this.currentKnightPosition]: 0
};
getNextTerm = () => {
let nonVisted = Object.entries(this.pathCosts).filter(
([x, y]) => !this.squaresVisited.includes(x)
);
const maxPath = Math.max(...Object.values(nonVisted.map(([, x]) => x)));
return nonVisted.find(([, x]) => x === maxPath);
};
costsCalc = () => {
while (this.squaresNotVisited.length > 0) {
const nextTerm = this.getNextTerm();
const currentCost = this.pathCosts[nextTerm[0]];
const nextPaths = this.paths[nextTerm[0]];
nextPaths.forEach(square => {
if (this.pathCosts[square] < currentCost + 1) {
this.pathCosts[square] = currentCost + 1;
}
});
this.squaresVisited = [...this.squaresVisited, nextTerm[0]];
this.squaresNotVisited = this.allSquares.filter(
x => !this.squaresVisited.includes(x)
);
}
};
ngOnInit() {
this.costsCalc();
console.log(Math.max(...Object.values(this.pathCosts)))
}
}
const app = new AppComponent();
app.ngOnInit()
The Problem
The Approach returns that the longest path is 51... which is incorrect as the number of squares are 64. I am stuck at whether the error is in my code or the error is in the approach I am using. Below is also a demo on stackblitz

You're function gets stuck
In each step your method just takes the longest path and updates all its neighbors. It's not undoing decisions, that's why it gets stuck at a path length of 52.
It is essential that you go back, when you find out your current solution does not work, i.e. Backtracking.
Possible implementation
...using Warnsdorff's rule.
const findPath = (knightPosition) => {
if (squaresVisited.size === allSquares.length - 1) return [knightPosition]
squaresVisited.add(knightPosition)
const neighbors = paths[knightPosition]
.filter(neighbor => !squaresVisited.has(neighbor))
.map(neighbor => {
const neighborCount = paths[neighbor]
.filter(square => !squaresVisited.has(square))
.length
return {
position: neighbor,
count: neighborCount
}
})
const minNeighborsCount = Math.min(...neighbors.map(({ count }) => count))
const minNeighbors = neighbors.filter(neighbor => neighbor.count === minNeighborsCount)
for (const minNeighbor of minNeighbors) {
const { position, count } = minNeighbor
const path = findPath(position)
if (path) return [knightPosition, ...path]
}
squaresVisited.delete(knightPosition)
}
Full working example
const board = {
columns: ["a", "b", "c", "d", "e", "f", "g", "h"],
rows: [8, 7, 6, 5, 4, 3, 2, 1]
};
const allSquares = board.columns.reduce(
(prev, next) => [...prev, ...board.rows.map(x => next + x)],
[]
);
const nextPossibleKnightPositions = currentKnightPosition => {
const row = board.columns.indexOf(currentKnightPosition[0]);
const column = Number(currentKnightPosition[1]) - 1;
return [
[column - 1, row - 2],
[column - 1, row + 2],
[column - 2, row - 1],
[column - 2, row + 1],
[column + 1, row - 2],
[column + 1, row + 2],
[column + 2, row - 1],
[column + 2, row + 1]
]
.filter(
([row, column]) => column >= 0 && column < 8 && row >= 0 && row < 8
)
.map(
([row, column]) =>
board.columns[column] + board.rows[8 - row - 1]
);
};
const paths = allSquares.reduce(
(prev, next) => ({
...prev,
[next]: nextPossibleKnightPositions(next)
}),
{}
);
const squaresVisited = new Set();
const findPath = (knightPosition) => {
if (squaresVisited.size === allSquares.length - 1) return [knightPosition]
squaresVisited.add(knightPosition)
const neighbors = paths[knightPosition]
.filter(neighbor => !squaresVisited.has(neighbor))
.map(neighbor => {
const neighborCount = paths[neighbor]
.filter(square => !squaresVisited.has(square))
.length
return {
position: neighbor,
count: neighborCount
}
})
const minNeighborsCount = Math.min(...neighbors.map(({ count }) => count))
const minNeighbors = neighbors.filter(neighbor => neighbor.count === minNeighborsCount)
for (const minNeighbor of minNeighbors) {
const { position, count } = minNeighbor
const path = findPath(position)
if (path) return [knightPosition, ...path]
}
squaresVisited.delete(knightPosition)
}
const path = findPath("h5");
console.log(path)
allSquares.forEach(square => {
squaresVisited.clear()
const path = findPath(square)
if(path.length !== 64) throw new Error(sqaure)
})
console.log("Works for all squares")

Related

Feeding Array to a Classifier

I have an issue looping through an array using a bayesian classifier function.
Here is my array:
var data = ['good', {
dry: 1,
wet: 0,
moist:0
}, 'bad', {
dry: 0,
wet: 1,
moist: 1
}, 'neutral', {
dry: 1,
wet: 1,
moist:1
}, 'good', {
dry: 1,
wet: 0,
moist: 1
}];
Here's my classifier function:
class Bayes{
constructor(...categories) {
this.categories = {};
this.categoryCounts = {};
categories.forEach(category => {
this.categories[category] = {};
this.categoryCounts[category] = 0;
});
}
train(category, dataset) {
this.categoryCounts[category]++;
Object.keys(dataset).forEach(key => {
this.categories[category][key] = (this.categories[category][key] || '') + dataset[key];
});
};
classify(dataset) {
let scores = {};
let trainingCount = Object.values(this.categoryCounts).reduce((a, b) => a + b );
Object.keys(this.categories).forEach(category => {
scores[category] = 0;
let categoryWords = this.categories[category];
let total = Object.values(categoryWords).reduce((a, b) => a + b );
Object.keys(dataset).forEach(function (key) {
let value = dataset[key];
let s = categoryWords[key] || 0.1;
let i = 0;
while(i<value){
scores[category] += Math.log(s / parseFloat(total));
i++;
}
});
let s = this.categoryCounts[category] || 0.1;
scores[category] = (s / trainingCount);
});
return scores;
};
};
Normally, to classify the data; I'll do:
var b = new Bayes('good', 'bad', 'neutral');
b.train('good', { dry: 1, wet: 0, moist:0});
b.train('bad', {dry: 0,wet: 1,moist: 1});
b.train('neutral', {dry: 1,wet: 1,moist:1});
b.train('good', {dry: 1,wet: 0,moist: 1});
console.log(b.classify({ dry: 0, wet: 1, moist: 1}));
// good: 0.5, bad: 0.25, neutral: 0.25
But when I can't figure out how to train the data by iterating through data.
I need help to feed the array dynamically as a javascript object.
if you can guarantee the data structure consistency, such as
let data = [key, value, key, value, key, value......]
const data = ['good', { dry: 1, wet: 0, moist:0}, 'neutral', {dry: 1,wet: 1,moist:1}, 'good', {dry: 1,wet: 0,moist: 1}];
// 1: chunk it
const size = 2;
const chunks = [];
while (data.length) {
chunks.push(data.splice(0, size));
}
console.log(chunks);
// 2: loop through your train
let keys = chunks.map(val=>val[0])
let deDupeKeys = [...new Set(keys)]
console.log(keys)
console.log(deDupeKeys)
// var b = new Bayes(deDupeKeys)
chunks.forEach(chunk => {
console.log(chunk[0])
console.log(chunk[1])
// b.train(chunk[0],chunk[1]);
})
Assuming the data array will have the format: data = [category, dataset, category, dataset...], a simple solution would be to loop the data array as follows and train the classifier.
for (let i = 0; i < data.length; i = i + 2) {
console.log("category : ", data[i], "dataset : ", data[i + 1]);
b.train(data[i], data[i + 1]);
}

How to get the three key/value pairs with the lowest value from an object?

I have an object as below:
var countryobj = {
"Canada": 10,
"Peru": 1,
"Argentina": 5,
"Colombia": 2,
"Mexico": 8
};
I want to get the first 3 smallest key value pair so that my output would be:
Peru: 1
Colombia: 2
Argentina: 5
Just get the Object.entries and sort them based on their value in ascending order, then print the first three items formatted how you like.
var countryobj = {
"Canada": 10,
"Peru": 1,
"Argentina": 5,
"Colombia": 2,
"Mexico": 8
};
const entries = Object.entries(countryobj).sort(([, a], [, b]) => a - b);
for (let i = 0; i < 3; i++) {
console.log(entries[i][0] + ": " + entries[i][1]);
}
function getRange(obj, count) {
return Object.entries(obj)
.sort(([, prev], [, next]) => a - b)
.slice(0, count)
.reduce(
(result, [key]) => ({
...result,
[key]: obj[key],
}),
{},
);
}

Javascript - Find most frequent number in array, even if there are two

If I have an array of numbers and I want to get the one that occurs the most frequent, yet there are two possible answers, Im having trouble sorting that part out. For example, below should return 1 and 7 but I only get 7. Any help is appreciated.
let arr = [1, 1, 2, 3, 4, 5, 6, 7, 7];
function findMode(numbers) {
let counted = numbers.reduce((acc, curr) => {
if (curr in acc) {
acc[curr]++;
} else {
acc[curr] = 1;
}
return acc;
}, {});
let mode = Object.keys(counted).reduce((a, b) => counted[a] > counted[b] ? a : b);
return mode;
}
console.log(findMode(arr));
You could group equal items in sub-arrays, then sort by sub-array length and retrieve the first values with the same array length like this:
const arr = [1, 1, 2, 3, 4, 5, 6, 7, 7],
output = arr
.sort((a, b) => a - b)
.reduce(
(acc, cur, i, { [i - 1]: last }) =>
(cur === last ? acc[acc.length - 1].push(cur) : acc.push([cur])) && acc,
[]
)
.sort((a, b) => b.length - a.length)
.reduce(
(a, b, _, { 0: first }) => (first.length === b.length ? [...a, b[0]] : a),
[]
);
console.log(output);
You can use an array as the accumulator.
let arr = [1, 1, 2, 3, 4, 5, 6, 7, 7];
function findMode(numbers) {
let counted = numbers.reduce((acc, curr) => {
if (curr in acc) {
acc[curr]++;
} else {
acc[curr] = 1;
}
return acc;
}, {});
let mode = Object.keys(counted).reduce((acc, curr) => {
if(!acc.length || counted[curr] > counted[acc[0]]) return [curr];
if(counted[curr] === counted[acc[0]]) acc.push(curr);
return acc;
}, []);
return mode;
}
console.log(findMode(arr));
Alternatively, you can find the highest frequency and then use filter to find numbers with that frequency.
let arr = [1, 1, 2, 3, 4, 5, 6, 7, 7];
function findMode(numbers) {
let counted = numbers.reduce((acc, curr) => {
if (curr in acc) {
acc[curr]++;
} else {
acc[curr] = 1;
}
return acc;
}, {});
let mode = Math.max(...Object.values(counted));
return Object.keys(counted).filter(x => counted[x] === mode);
}
console.log(findMode(arr));
You can keep track of the max number of occurrences in the first loop, and then use Array#filter to get the keys with such value:
function findMode(numbers) {
let max = 0;
const counted = numbers.reduce((acc, curr) => {
if (curr in acc) acc[curr]++;
else acc[curr] = 1;
if(acc[curr] > max) max = acc[curr];
return acc;
}, {});
const mode = Object.keys(counted)
.filter(key => counted[key] === max)
.map(Number);
return mode;
}
console.log( findMode([1, 1, 2, 3, 4, 5, 6, 7, 7]) );
As the whole action is happening within a function scope we can also do it with two .forEach() loops: in the first one we collect the counts and in the second one we then assemble a results array with the "winners".
By using a map for collecting the counts we avoid the type conversion to string that would have occurred had we used a plain object.
let arr = [1, 1, 2, "1", 3, 4, "1", 5, 6, 7, 7];
function findMode(nums) {
let cn=new Map(),mx=0,res;
nums.forEach(n=>cn.set(n,(cn.get(n)||0)+1));
[...cn.entries()].forEach(([v,c])=>{
if(c>mx) {res=[v];mx=c}
else if (c===mx) res.push(v) });
return res;
}
console.log(findMode(arr));

Extract duplicate values with number of occurrences for JS Array

For example we have an array
const nums = [1,1,8,12,2,3,3,3,7];
If I want to map number of occurrences of each array member I could use something like
function extractDupes(arr) {
return arr.reduce(function (acc, item) {
if (item in acc) {
acc[item]++
}
else {
acc[item] = 1
}
return acc
}, {})
}
This would return object like
{ '1': 2, '2': 1, '3': 3, '7': 1, '8': 1, '12': 1 }
Is there an optimal way to filter out numbers which are showing up more than once just with using reduce (in a single pass) and have just
{ '1': 2, '3': 3 }
const nums = [1,1,8,12,2,3,3,3,7];
const dups = {};
nums.forEach((v, i, s) => {
if (s.indexOf(v) != i)
if (dups[v])
dups[v]++;
else
dups[v] = 2;
});
console.log(dups);
If you also want the array of unique values at the end:
const nums = [1,1,8,12,2,3,3,3,7];
const dups = {};
const uniques = nums.filter((v, i, s) => {
if (s.indexOf(v) != i)
if (dups[v])
dups[v]++;
else
dups[v] = 2;
else
return true;
});
console.log(dups);
console.log(uniques);
have a staging variable for values equaling 1 and promote them to the main result when they're hit the second time?
const nums = [1,1,8,12,2,3,3,3,7];
function extractDupes(arr) {
const staging = {}
return arr.reduce(function (acc, item) {
if (item in acc) {
acc[item]++
} else if(item in staging) {
acc[item] = 2
delete staging[item]
} else {
staging[item] = 1
}
return acc
}, {})
}
document.getElementById('hi').innerHTML = JSON.stringify(extractDupes(nums))
<div id="hi"></div>
You could take a nested property for the final object.
function extractDupes(array) {
return array
.reduce(function(acc, item) {
if (acc[item]) acc.dupes[item] = (acc.dupes[item] || 1) + 1;
else acc[item] = true;
return acc;
}, { dupes: {} })
.dupes;
}
const nums = [1, 1, 8, 12, 2, 3, 3, 3, 7];
console.log(extractDupes(nums))
You could use Object.entries to transform the object to key-value pairs then filter the pair that have value greater than 1, and then transform the pairs back to object by Object.fromEntries
const nums = [1, 1, 8, 12, 2, 3, 3, 3, 7]
function extractDupes(arr) {
return Object.fromEntries(
Object.entries(
arr.reduce(function (acc, item) {
if (item in acc) {
acc[item]++
} else {
acc[item] = 1
}
return acc
}, {})
).filter(([key, value]) => value > 1)
)
}
console.log(extractDupes(nums))
let arrAssist = [];
array.sort();
arrAssist.push(inventory[0]);
for(var i = 1; i < array.length; i++){
if(array[i] != array[i - 1]){
arrAssist.push(array[i]);
}
}
in this example, arrAssist contains the array with no duplicates
It has been answered and accepted but anyhow here is how i would do.
const nums = [1, 1, 8, 12, 2, 3, 3, 3, 7];
const map = nums.reduce((acc, num) => {
acc.frequency[num] = (acc.frequency[num] || 0) + 1;
if (acc.frequency[num] > 1) {
acc.replicates[num] = acc.frequency[num];
}
return acc;
}, { frequency: {}, replicates: {} });
console.log(map);

Javascript check the closest different from two array of numbers

I have two arrays of numbers a and b, I want to find closest pair of numbers from this array. But I am stuck inside the reducer, with given match to.
Expected output is
[
{
"dif": 1,
"val": 3,
"to": 4
},
{
"dif": 2,
"val": 3,
"to": 5
},
{
"dif": 2,
"val": 8,
"to": 6
}
]
const a = [1,2,3,8]
, b = [4,5,6]
const result = b.map(to => {
return a
.map(v => {return {val:v}})
.reduce((prev, curr) => {
return Math.abs(curr.val - to) < Math.abs(prev.val - to) ? {dif:Math.abs(prev.val - to), val:curr.val, to} : {dif: Math.abs(prev.val - to), val:prev.val, to}
});
})
console.log(result)
There is one correction in your code. {dif:Math.abs(prev - to), val:curr.val, to} should be {dif:Math.abs(curr.val - to), val:curr.val, to}
const a = [1,2,3,8]
, b = [4,5,6]
const result = b.map(to => {
return a
.map(v => {return {val:v}})
.reduce((prev, curr) => {
return Math.abs(curr.val - to) < Math.abs(prev.val - to) ? {dif:Math.abs(curr.val - to), val:curr.val, to} : {dif: Math.abs(prev.val - to), val:prev.val, to}
});
})
console.log(result)
another choice:
const a = [1, 2, 3, 8],
b = [4, 5, 6];
const result = b.map(v => {
return a.reduce((re, value) => {
let updateRule = re.val === undefined || (re.val !== undefined && Math.abs(value - re.to)) < re.dif;
return updateRule ? { val: value, dif: Math.abs(value - re.to), to: v } : re;
}, { to: v });
});
console.log(result);
You could generate the cartesian product and sort by difference.
var a = [1, 2, 3, 8],
b = [4, 5, 6],
result = a
.reduce((r, c) => r.concat(b.map(d => ({ dif: Math.abs(c - d), val: c, to: d }))), [])
.sort((a, b) => a.dif - b.dif);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories