diff --git a/specs/bloom-filters/bloom-filters.test.js b/specs/bloom-filters/bloom-filters.test.js index d70651cb..a0172cbd 100644 --- a/specs/bloom-filters/bloom-filters.test.js +++ b/specs/bloom-filters/bloom-filters.test.js @@ -2,46 +2,60 @@ // a library called xxhashjs is being loaded (as XXH) and we're using three different // instances of that as your hashing functions const XXH = require("xxhashjs"); + const h1 = (string) => Math.abs(XXH.h32(0xabcd).update(string).digest().toNumber() % 100); + const h2 = (string) => Math.abs(XXH.h32(0x1234).update(string).digest().toNumber() % 100); + const h3 = (string) => Math.abs(XXH.h32(0x6789).update(string).digest().toNumber() % 100); +const getHashes = value => [h1(value), h2(value), h3(value)]; + // fill out these two methods // `add` adds a string to the bloom filter and returns void (nothing, undefined) // `contains` takes a string and tells you if a string is maybe in the bloom filter class BloomFilter { - // you'll probably need some instance variables + constructor() { + // this.seen = []; + this.seen = new Array(100).fill(false); + } + add(string) { - // code here + getHashes(string).forEach(hash => this.seen[hash] = true) } + contains(string) { - // code here + return getHashes(string).every(hash => this.seen[hash] === true); } } // unit tests // do not modify the below code -describe.skip("BloomFilter", function () { +describe("BloomFilter", function () { let bf; + beforeEach(() => { bf = new BloomFilter(); }); - test.skip("returns false when empty", () => { + + test("returns false when empty", () => { expect(bf.contains("Brian")).toBe(false); expect(bf.contains("Sarah")).toBe(false); expect(bf.contains("Simona")).toBe(false); }); - test.skip("handles one item", () => { + + test("handles one item", () => { expect(bf.contains("Brian")).toBe(false); bf.add("Brian"); expect(bf.contains("Brian")).toBe(true); expect(bf.contains("Sarah")).toBe(false); expect(bf.contains("Simona")).toBe(false); }); - test.skip("handles many items", () => { + + test("handles many items", () => { const names = [ "Brian", "Simona", diff --git a/specs/bubble-sort/bubble-sort.solution.test.js b/specs/bubble-sort/bubble-sort.solution.test.js index 445aa000..542626f9 100644 --- a/specs/bubble-sort/bubble-sort.solution.test.js +++ b/specs/bubble-sort/bubble-sort.solution.test.js @@ -14,7 +14,7 @@ function bubbleSort(nums) { let swapped = false; do { swapped = false; - for (let i = 0; i < nums.length; i++) { + for (let i = 0; i < nums.length - 1; i++) { // snapshot(nums); if (nums[i] > nums[i + 1]) { const temp = nums[i]; diff --git a/specs/pathfinding/pathfinding.test.js b/specs/pathfinding/pathfinding.test.js index 8bbe50cc..be5a6d98 100644 --- a/specs/pathfinding/pathfinding.test.js +++ b/specs/pathfinding/pathfinding.test.js @@ -16,8 +16,120 @@ // the way I did. however feel free to use it if you'd like const logMaze = require("./logger"); +const NO_ONE = 0; +const BY_A = 1; +const BY_B = 2; + +function generateVisited(maze) { + const visited = []; + + for (let y = 0; y < maze.length; y++) { + const yAxis = []; + + for (let x = 0; x < maze.length; x++) { + const coordinate = { + closed: maze[y][x] === 1, + length: 0, + openedBy: NO_ONE, + x, + y + } + yAxis.push(coordinate); + } + + visited.push(yAxis); + } + + return visited; +} + +function getNeighbors(visited, x, y) { + const neighbors = []; + + // left? + if (y - 1 >= 0 && !visited[y - 1][x].closed) { + neighbors.push(visited[y - 1][x]); + } + + // right? + if (y + 1 < visited[0].length && !visited[y + 1][x].closed) { + neighbors.push(visited[y + 1][x]); + } + + // top + if (x - 1 >= 0 && !visited[y][x - 1].closed) { + neighbors.push(visited[y][x - 1]); + } + + // bottom + if (x + 1 < visited.length && !visited[y][x + 1].closed) { + neighbors.push(visited[y][x + 1]); + } + + return neighbors; +} + function findShortestPathLength(maze, [xA, yA], [xB, yB]) { - // code goes here + const visited = generateVisited(maze); + + const a = visited[yA][xA]; + const b = visited[yB][xB]; + + a.openedBy = BY_A; + b.openedBy = BY_B; + + let aQueue = [a]; + let bQueue = [b]; + let iteration = 0; + + // go until we find they meet, or don't meet + while (aQueue.length && bQueue.length) { + iteration++; + + // enqueue all valid A neighbors + let aNeighbors = []; + while (aQueue.length) { + const { x, y } = aQueue.shift(); + aNeighbors = aNeighbors.concat(getNeighbors(visited, x, y)); + } + + // process A neighbors + for (let i = 0; i < aNeighbors.length; i++) { + const neighbor = aNeighbors[i]; + if (neighbor.openedBy === BY_B) { + // found path! Add my current path length plus other's distance + return neighbor.length + iteration; + } else if (neighbor.openedBy === NO_ONE) { + neighbor.openedBy = BY_A; + neighbor.length = iteration; + // prepare to process this in next iteration + aQueue.push(neighbor); + } + } + + // enqueue all valid B neighbors + let bNeighbors = []; + while (bQueue.length) { + const { x, y } = bQueue.shift(); + bNeighbors = bNeighbors.concat(getNeighbors(visited, x, y)); + } + + // process B neighbors + for (let i = 0; i < bNeighbors.length; i++) { + const neighbor = bNeighbors[i]; + if (neighbor.openedBy === BY_A) { + // found path! Add my current path length plus other's distance + return neighbor.length + iteration; + } else if (neighbor.openedBy === NO_ONE) { + neighbor.openedBy = BY_B; + neighbor.length = iteration; + // prepare to process this in next iteration + bQueue.push(neighbor); + } + } + } + + return -1; } // there is a visualization tool in the completed exercise @@ -26,7 +138,7 @@ function findShortestPathLength(maze, [xA, yA], [xB, yB]) { // unit tests // do not modify the below code -describe.skip("pathfinding – happy path", function () { +describe("pathfinding – happy path", function () { const fourByFour = [ [2, 0, 0, 0], [0, 0, 0, 0], @@ -90,7 +202,7 @@ describe.skip("pathfinding – happy path", function () { // I care far less if you solve these // nonetheless, if you're having fun, solve some of the edge cases too! // just remove the .skip from describe.skip -describe.skip("pathfinding – edge cases", function () { +describe("pathfinding – edge cases", function () { const byEachOther = [ [0, 0, 0, 0, 0], [0, 2, 2, 0, 0], diff --git a/specs/radix-sort/radix-sort.solution.test.js b/specs/radix-sort/radix-sort.solution.test.js index f3bb6322..fe5b6b95 100644 --- a/specs/radix-sort/radix-sort.solution.test.js +++ b/specs/radix-sort/radix-sort.solution.test.js @@ -49,7 +49,7 @@ function radixSort(array) { // unit tests // do not modify the below code -describe.skip("radix sort", function () { +describe("radix sort", function () { it("should sort correctly", () => { const nums = [ 20, @@ -104,7 +104,7 @@ describe.skip("radix sort", function () { const nums = new Array(fill) .fill() .map(() => Math.floor(Math.random() * 500000)); - const ans = radixSort(nums); - expect(ans).toEqual(nums.sort()); + const ans = radixSort([...nums]); + expect(ans).toEqual(nums.sort((a, b) => a - b)); }); }); diff --git a/specs/tries/tries.test.js b/specs/tries/tries.test.js index 14043666..19247335 100644 --- a/specs/tries/tries.test.js +++ b/specs/tries/tries.test.js @@ -13,28 +13,84 @@ const { CITY_NAMES } = require("./cities.js"); const _ = require("lodash"); // needed for unit tests class Node { - // you don't have to use this data structure, this is just how I did it - // you'll almost definitely need more methods than this and a constructor - // and instance variables + constructor(string) { + this.children = []; + this.terminus = false; + + const first = string.slice(0, 1); + const rest = string.slice(1); + + this.value = first; + if(rest.length > 0) { + this.children.push(new Node(rest)); + } else { + this.terminus = true; + } + } + + add(string) { + const first = string.slice(0, 1); + const rest = string.slice(1); + + for (let i = 0; i < this.children.length; i++) { + const child = this.children[i]; + if (child.value === first) { + if (rest) { + child.add(rest); + } else { + child.terminus = true; + } + return; + } + } + + this.children.push(new Node(string)); + } + complete(string) { - return []; + let completions = []; + + for (let i = 0; i < this.children.length; i++) { + const child = this.children[i]; + completions = completions.concat(child._complete(string, "", [])); + } + + return completions; + } + + _complete(search, built, completions) { + if (completions.length >= 3 || (search && search[0] !== this.value)) { + // wrong branch or we have enough suggestions + return completions; + } + + if (this.terminus) { + completions.push(`${built}${this.value}`); + } + + for (let i = 0; i < this.children.length; i++) { + const child = this.children[i]; + child._complete(search.substr(1), built + this.value, completions) + } + + return completions; } } const createTrie = (words) => { - // you do not have to do it this way; this is just how I did it const root = new Node(""); - // more code should go here + words.forEach(word => root.add(word.toLowerCase())); return root; }; // unit tests // do not modify the below code -describe.skip("tries", function () { +describe("tries", function () { test("dataset of 10 – san", () => { const root = createTrie(CITY_NAMES.slice(0, 10)); + console.log(root); const completions = root.complete("san"); expect(completions.length).toBe(3); expect( @@ -133,7 +189,7 @@ describe.skip("tries", function () { }); }); -describe.skip("edge cases", () => { +describe("edge cases", () => { test("handle whole words – seattle", () => { const root = createTrie(CITY_NAMES.slice(0, 30)); const completions = root.complete("seattle");