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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy