Skip to content

Commit 4822457

Browse files
committed
Added hard/quick_knight
1 parent 8fb8b71 commit 4822457

File tree

2 files changed

+193
-0
lines changed

2 files changed

+193
-0
lines changed

hard/quick_knight.js

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/**
2+
* Using the JavaScript language, have the function quickKnight(str) read str
3+
* which will be a string consisting of the location of a knight on a standard
4+
* 8x8 chess board with no other pieces on the board and another space on the
5+
* chess board. The structure of str will be the following: "(x y)(a b)" where
6+
* (x y) represents the position of the knight with x and y ranging from 1 to 8
7+
* and (a b) represents some other space on the chess board with a and b also
8+
* ranging from 1 to 8. Your program should determine the least amount of moves
9+
* it would take the knight to go from its position to position (a b). For
10+
* example if str is "(2 3)(7 5)" then your program should output 3 because that
11+
* is the least amount of moves it would take for the knight to get from (2 3)
12+
* to (7 5) on the chess board.
13+
*
14+
* https://www.coderbyte.com/results/bhanson:Quick%20Knight:JavaScript
15+
*
16+
* @param {string} str
17+
* @return {number}
18+
*/
19+
function quickKnight(str) {
20+
// Parse input and check validity
21+
const digits = str.match(/[1-8]+/g).map(Number);
22+
if (digits.length !== 4) {
23+
return -1;
24+
}
25+
26+
const knight = new Coords(digits[0], digits[1]);
27+
const destination = new Coords(digits[2], digits[3]);
28+
29+
const path = knightPathTo(knight, destination);
30+
31+
/*
32+
Example knightPathTo() result for '(1 1)(8 8)':
33+
[ Coords { x: 1, y: 1 },
34+
Coords { x: 3, y: 2 },
35+
Coords { x: 5, y: 3 },
36+
Coords { x: 7, y: 4 },
37+
Coords { x: 8, y: 6 },
38+
Coords { x: 6, y: 7 },
39+
Coords { x: 8, y: 8 } ]
40+
*/
41+
// console.log(path);
42+
43+
return path.length - 1;
44+
}
45+
46+
function Coords(x = 0, y = 0) {
47+
this.x = x;
48+
this.y = y;
49+
}
50+
51+
Coords.prototype.toString = function() {
52+
return `${this.x},${this.y}`;
53+
};
54+
55+
Coords.prototype.isEqual = function(loc) {
56+
if (!(loc instanceof Coords)) {
57+
return false;
58+
}
59+
return this.x === loc.x && this.y === loc.y;
60+
};
61+
62+
Coords.arrayIncludes = function(arr, loc) {
63+
if (!Array.isArray(arr) || !(loc instanceof Coords)) {
64+
return false;
65+
}
66+
67+
for (let i = 0; i < arr.length; i++) {
68+
if (arr[i].isEqual(loc)) {
69+
return true;
70+
}
71+
}
72+
return false;
73+
};
74+
75+
function knightPathTo(knight, destination) {
76+
if (!(knight instanceof Coords) || !(destination instanceof Coords)) {
77+
return [];
78+
}
79+
80+
// Iterative breadth-first search
81+
//
82+
// We only care about finding _a_ shortest path, although many might exist.
83+
// To reduce the search space we will combine similar iterations together.
84+
//
85+
// A -> B -> D
86+
// A -> C -> D
87+
//
88+
// Will be combined into either A -> B -> D or A -> C -> D. The exact
89+
// choice of combination is undefined, but they both represent valid paths
90+
// of the same length.
91+
//
92+
// The last node, in this case "D" will be stored as the key in the `queue`
93+
// Map object.
94+
//
95+
// "D" for us is a Coords object, but because Map() will happily accept
96+
// object references we need to convert it into a string such that two
97+
// different Coords objects with the same actual coordinates will be the
98+
// same.
99+
//
100+
// As a breadth-first search, each iteration will represent one additional
101+
// path length. We will return the first successful path found as any
102+
// additional paths found in that level will be of the same length. It is
103+
// guaranteed to be the shortest length;
104+
//
105+
// A "path" is represented as an array of Coords
106+
let queue = new Map();
107+
queue.set(knight.toString(), [knight]);
108+
109+
while (queue.size !== 0) {
110+
const nextQueue = new Map();
111+
112+
for (const path of queue) {
113+
const [pathKey, pathArr] = path;
114+
115+
const lastLocation = pathArr[pathArr.length - 1];
116+
for (const move of knightLocations(lastLocation)) {
117+
if (move.isEqual(destination)) {
118+
// Found destination!
119+
const finalPath = pathArr.slice();
120+
finalPath.push(move);
121+
return finalPath;
122+
}
123+
124+
if (!Coords.arrayIncludes(pathArr, move)) {
125+
// Only visit Coords we haven't been to yet
126+
const pathCopy = pathArr.slice();
127+
pathCopy.push(move);
128+
nextQueue.set(move.toString(), pathCopy);
129+
}
130+
}
131+
}
132+
queue = nextQueue;
133+
}
134+
135+
// This should never happen because a knight can reach any other spot on an
136+
// empty board, but this is where code goes if a destination is unreachable.
137+
return [];
138+
}
139+
140+
function* knightLocations(loc) {
141+
if (!(loc instanceof Coords)) {
142+
return undefined;
143+
}
144+
145+
const offsets = [
146+
new Coords(1, 2),
147+
new Coords(1, -2),
148+
new Coords(-1, 2),
149+
new Coords(-1, -2),
150+
new Coords(2, 1),
151+
new Coords(2, -1),
152+
new Coords(-2, 1),
153+
new Coords(-2, -1)
154+
];
155+
156+
for (let i = 0; i < offsets.length; i++) {
157+
const offset = offsets[i];
158+
159+
const tryX = loc.x + offset.x;
160+
const tryY = loc.y + offset.y;
161+
162+
if (tryX >= 1 && tryX <= 8 && tryY >= 1 && tryY <= 8) {
163+
yield new Coords(tryX, tryY);
164+
}
165+
}
166+
}
167+
168+
module.exports = quickKnight;

hard/quick_knight.test.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const quickKnight = require('./quick_knight');
2+
3+
describe('quickKnight()', () => {
4+
test('passes Coderbyte.com tests', () => {
5+
expect(quickKnight('(1 1)(8 8)')).toBe(6);
6+
7+
expect(quickKnight('(2 2)(3 3)')).toBe(2);
8+
9+
expect(quickKnight('(2 3)(7 5)')).toBe(3);
10+
11+
expect(quickKnight('(1 1)(2 2)')).toBe(4);
12+
13+
expect(quickKnight('(1 1)(3 3)')).toBe(4);
14+
15+
expect(quickKnight('(1 1)(8 1)')).toBe(5);
16+
17+
expect(quickKnight('(7 8)(5 6)')).toBe(4);
18+
19+
expect(quickKnight('(7 8)(8 6)')).toBe(1);
20+
21+
expect(quickKnight('(6 3)(7 2)')).toBe(2);
22+
23+
expect(quickKnight('(1 1)(1 2)')).toBe(3);
24+
});
25+
});

0 commit comments

Comments
 (0)