diff --git a/.github/workflows/hashing.yml b/.github/workflows/hashing.yml
new file mode 100644
index 000000000..1461e243f
--- /dev/null
+++ b/.github/workflows/hashing.yml
@@ -0,0 +1,26 @@
+name: Hashing Algorithms CI
+
+on:
+ push:
+ branches: [2024_sem2]
+ paths-ignore:
+ - 'doc/**' # Ignore all files and subdirectories under "doc/"
+ - 'docs/**' # Ignore all files and subdirectories under "docs/"
+ pull_request:
+ branches: [2024_sem2]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout 🛎️
+ uses: actions/checkout@v2.3.1
+ with:
+ persist-credentials: false
+
+ - name: Install and Test 🔧
+ run: |
+ npm install
+ npm run test-hashinsert
+ npm run test-hashsearch
+ npm run test-hashdelete
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 000000000..94a25f7f4
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 000000000..8e9e168d9
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1725873310585
+
+
+ 1725873310585
+
+
+
+
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 9053a771b..2f3fd9b39 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,6 +14,7 @@
"@mui/lab": "latest",
"@mui/material": "^5.14.4",
"@mui/styles": "^5.14.4",
+ "@popperjs/core": "^2.11.8",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^12.1.2",
"@testing-library/react-hooks": "^8.0.1",
@@ -4520,6 +4521,7 @@
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+ "license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
diff --git a/package.json b/package.json
index a892d28a0..f5ead245b 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,10 @@
"test-msdrs": "npm test -- ./src/algorithms/controllers/MSDRadixSort.test.js",
"test-uf": "npm test -- ./src/algorithms/controllers/unionFind.test.js",
"test-234t": "npm test -- ./src/algorithms/controllers/TTFTree.test.js",
+ "test-hashinsert": "npm test -- ./src/algorithms/controllers/tests/HashingInsertion.test.js",
+ "test-hashsearch": "npm test -- ./src/algorithms/controllers/tests/HashingSearch.test.js",
+ "test-hashdelete": "npm test -- ./src/algorithms/controllers/tests/HashingDeletion.test.js",
+ "test-234t": "npm test -- ./src/algorithms/controllers/TTFTree.test.js",
"test-avl": "npm test -- ./src/algorithms/controllers/AVLTree.test.js",
"test-url": "npm test -- ./src/algorithms/parameters/helpers/urlHelpers.test.js"
},
@@ -63,6 +67,7 @@
"@mui/lab": "latest",
"@mui/material": "^5.14.4",
"@mui/styles": "^5.14.4",
+ "@popperjs/core": "^2.11.8",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^12.1.2",
"@testing-library/react-hooks": "^8.0.1",
diff --git a/src/algorithms/controllers/AStar.js b/src/algorithms/controllers/AStar.js
index 0b33b7e6f..95aaa46c3 100644
--- a/src/algorithms/controllers/AStar.js
+++ b/src/algorithms/controllers/AStar.js
@@ -166,7 +166,7 @@ export default {
5,
(vis, v) => {
vis.array.set(v, algNameStr);
- vis.array.getRendererClass().zoom = 8;
+ vis.array.setZoom(0.8);
},
[[nodes, heuristics, minCosts, parents, finalCosts], 0]
);
diff --git a/src/algorithms/controllers/HashingChainingInsertion.js b/src/algorithms/controllers/HashingChainingInsertion.js
new file mode 100644
index 000000000..05fda6da8
--- /dev/null
+++ b/src/algorithms/controllers/HashingChainingInsertion.js
@@ -0,0 +1,394 @@
+import Array2DTracer from '../../components/DataStructures/Array/Array2DTracer';
+import GraphTracer from '../../components/DataStructures/Graph/GraphTracer';
+import { HashingExp } from '../explanations';
+import {
+ hash1,
+ HASH_GRAPH,
+ EMPTY_CHAR,
+ Colors,
+ INDEX,
+ POINTER,
+ POINTER_VALUE,
+ SMALL_SIZE,
+ VALUE,
+ LARGE_SIZE,
+ SPLIT_SIZE,
+ HASH_TYPE,
+ PRIMES,
+ POINTER_CUT_OFF,
+ newCycle
+} from './HashingCommon';
+import { translateInput } from '../parameters/helpers/ParamHelper';
+import HashingDelete from './HashingDelete';
+import { createPopper } from '@popperjs/core';
+
+// Bookmarks to link chunker with pseudocode
+const IBookmarks = {
+ Init: 1,
+ EmptyArray: 2,
+ InitInsertion: 3,
+ NewInsertion: 4,
+ Hash1: 5,
+ Pending: 7,
+ PutIn: 9,
+ Done: 10,
+ BulkInsert: 1,
+}
+
+export default {
+ explanation: HashingExp,
+
+ // Initialize visualizers
+ initVisualisers() {
+ return {
+ array: {
+ instance: new Array2DTracer('array', null, 'Hash Table'),
+ order: 0,
+ },
+ graph: {
+ instance: new GraphTracer('graph', null, 'Hashing Functions'),
+ order: 1,
+ },
+ };
+ },
+
+ /**
+ * Run function for insertion, using the user input to display the illustration through chunker
+ * @param {*} chunker the chunker for the illustrations
+ * @param {*} params different parameters of the algorithm insertion mode e.g. name, array size,...
+ * @returns a table of concluding array to serve testing purposes
+ */
+ run(chunker, params) {
+ // Storing algorithms parameters as local variables
+ const ALGORITHM_NAME = params.name;
+ let inputs = params.values;
+ const SIZE = params.hashSize;
+
+ // Initialize arrays
+ let indexArr = Array.from({ length: SIZE }, (_, i) => i);
+ let valueArr = Array(SIZE).fill(EMPTY_CHAR);
+ let nullArr = Array(SIZE).fill('');
+
+ // For return
+ let table_result;
+
+ // Variable to keep track of insertions done and total inputs hashed into the table
+ let insertions = 0;
+ let total = 0;
+
+ /**
+ * Insertion function for each key
+ * @param {*} table the table to keep track of the internal and illustrated array
+ * @param {*} key the key to insert
+ * @returns the index the key is assigned
+ */
+ function hashInsert(table, key) {
+
+ chunker.add(
+ IBookmarks.NewInsertion,
+ (vis, total) => {
+ vis.array.showKth({
+ key: key,
+ type: HASH_TYPE.Insert
+ })
+ newCycle(vis, table.length, key, ALGORITHM_NAME); // New insert cycle
+ },
+ [total]
+ )
+
+ insertions++; // Increment insertions
+ total++; // Increment total
+
+ // Get initial hash index for current key
+ let i = hash1(chunker, IBookmarks.Hash1, key, table.length, true);
+
+ // Chunker for first pending slot
+ chunker.add(
+ IBookmarks.Pending,
+ (vis, idx) => {
+
+ // Pointer only appear for small table
+ if (table.length <= PRIMES[POINTER_CUT_OFF]) {
+ vis.array.assignVariable(POINTER_VALUE, POINTER, idx);
+ }
+
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Pending); // Color pending slot
+
+ // Uncolor the hashing graph
+ vis.graph.deselect(HASH_GRAPH.Key);
+ vis.graph.deselect(HASH_GRAPH.Value);
+ vis.graph.removeEdgeColor(HASH_GRAPH.Key, HASH_GRAPH.Value);
+ if (ALGORITHM_NAME == "HashingDH") {
+ vis.graph.deselect(HASH_GRAPH.Key2);
+ vis.graph.deselect(HASH_GRAPH.Value2);
+ vis.graph.removeEdgeColor(HASH_GRAPH.Key2, HASH_GRAPH.Value2);
+ }
+ },
+ [i]
+ )
+
+ // Internally assign the key to the index
+ table[i].push(key);
+
+ // Chunker for placing the key
+ chunker.add(
+ IBookmarks.PutIn,
+ (vis, val, idx, insertions, table) => {
+ if (table[idx].length > 1) {
+ const popper = document.getElementById('float_box_' + idx);
+ if (table[idx].length == 2) {
+ const slot = document.getElementById('chain_' + idx);
+ floatingBoxes[idx] = createPopper(slot, popper, {
+ placement: "right-start",
+ strategy: "fixed",
+ modifiers: [
+ {
+ name: 'preventOverflow',
+ options: {
+ boundary: document.getElementById('popper_boundary'),
+ },
+ },
+ ]
+ });
+ }
+ popper.innerHTML = table[idx];
+ }
+
+ let slotCurValue = vis.array.getValueAt(VALUE, idx);
+ if (slotCurValue === EMPTY_CHAR) vis.array.updateValueAt(VALUE, idx, val); // Update value of that index when the slot is empty
+ else vis.array.updateValueAt(VALUE, idx, slotCurValue + (table[idx].length == 2 ? ".." : "")); // Update value of that index when the slot is not empty
+ vis.array.showKth({key: vis.array.getKth().key, type: HASH_TYPE.BulkInsert, insertions: insertions});
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Insert); // Fill it green, indicating successful insertion
+ },
+ [key, i, insertions, table]
+ )
+
+ // Return the insertion index
+ return i;
+ }
+
+
+ /**
+ * Function for bulk insertion
+ * @param {*} table the table to keep track of the internal and illustrated array
+ * @param {*} keys the keys to insert
+ * @returns the index the last key is assigned
+ */
+ function hashBulkInsert(table, keys) {
+ let lastHash;
+ let inserts = {};
+ let bulkInsertions = 0;
+ for (const key of keys) {
+
+ bulkInsertions++;
+
+ // hashed value
+ let i = hash1(null, null, key, table.length, false);
+
+ table[i].push(key);
+ inserts[key] = i;
+ lastHash = i;
+ }
+
+ insertions += bulkInsertions;
+ total += bulkInsertions;
+ chunker.add(
+ IBookmarks.PutIn,
+ (vis, keys, inserts, insertions, table) => {
+ for (const key of keys) {
+ if (table[inserts[key]].length > 1) {
+ const popper = document.getElementById('float_box_' + inserts[key]);
+ if (table[inserts[key]][2] !== null) {
+ const slot = document.getElementById('chain_' + inserts[key]);
+ floatingBoxes[inserts[key]] = createPopper(slot, popper, {
+ placement: "right-start",
+ strategy: "fixed",
+ modifiers: [
+ {
+ name: 'preventOverflow',
+ options: {
+ boundary: document.getElementById('popper_boundary'),
+ },
+ },
+ ]
+ });
+ }
+ popper.innerHTML = table[inserts[key]];
+ }
+
+ let slotCurValue = vis.array.getValueAt(VALUE, inserts[key]);
+ console.log(typeof(slotCurValue));
+ if (slotCurValue === EMPTY_CHAR) vis.array.updateValueAt(VALUE, inserts[key], key); // Update value of that index when the slot is empty
+ else vis.array.updateValueAt(VALUE, inserts[key], slotCurValue + ((table[inserts[key]].length >= 2 && typeof(slotCurValue) == 'number') ? ".." : "")); // Update value of that index when the slot is not empty
+ vis.array.fill(INDEX, inserts[key], undefined, undefined, Colors.Insert);
+ }
+ vis.array.showKth({key: vis.array.getKth().key, type: HASH_TYPE.BulkInsert, insertions: insertions});
+ },
+ [keys, inserts, insertions, table]
+ )
+
+ return lastHash;
+ }
+
+
+ // Inserting inputs
+ let prevIdx;
+
+ let floatingBoxes = new Array(SIZE); // List of all popper instances
+
+ // Init hash table with dynamic array in each slot
+ let table = new Array(SIZE);
+ for (var i = 0; i < SIZE; i++) {
+ table[i] = [];
+ }
+
+ prevIdx = null;
+
+ // Chunker step for the inital loading state
+ chunker.add(
+ IBookmarks.Init,
+ (vis, size, array) => {
+ // Increase Array2D visualizer render space
+ if (SIZE === LARGE_SIZE) {
+ vis.array.setSize(3);
+ vis.array.setZoom(0.7);
+ vis.graph.setZoom(1.5);
+ } else {
+ vis.array.setZoom(1);
+ vis.graph.setZoom(1);
+ }
+
+ // Initialize the array
+ vis.array.set(array,
+ params.name,
+ '',
+ INDEX,
+ {
+ rowLength: size > SMALL_SIZE ? SPLIT_SIZE : SMALL_SIZE,
+ rowHeader: ['Index', 'Value', '']
+ }
+ );
+
+ vis.array.hideArrayAtIndex([VALUE, POINTER]); // Hide value and pointer row intially
+
+ vis.graph.weighted(true);
+
+ // Intialize the graphs
+ switch (ALGORITHM_NAME) {
+ case "HashingLP" :
+ vis.graph.set([[0, 'Hash'], [0, 0]], [' ', ' '], [[-5, 0], [5, 0]]);
+ break;
+ case "HashingCH" :
+ vis.graph.set([[0, 'Hash'], [0, 0]], [' ', ' '], [[-5, 0], [5, 0]]);
+ break;
+ case "HashingDH" :
+ vis.graph.set([
+ [0, 'Hash1', 0, 0], [0, 0, 0, 0], [0, 0, 0, 'Hash2'], [0, 0, 0, 0]], // Node edges
+ [' ', ' ', ' ', ' '], // Node values
+ [[-5, 2], [5, 2], [-5, -2], [5, -2]]); // Node positions
+ break;
+ }
+ },
+ [table.length, table.length <= PRIMES[POINTER_CUT_OFF] ?
+ [indexArr, valueArr, nullArr] :
+ [indexArr, valueArr]
+ ]
+ );
+
+ // Chunker to initialize empty array visually
+ chunker.add(
+ IBookmarks.EmptyArray,
+ (vis) => {
+ // Show the value row
+ vis.array.hideArrayAtIndex(POINTER);
+ },
+ );
+
+ // Chunker for intializing insertion stat
+ chunker.add(
+ IBookmarks.InitInsertion,
+ (vis, insertions) => {
+ vis.array.showKth({
+ key: "",
+ type: EMPTY_CHAR,
+ insertions: insertions,
+ increment: "",
+ }
+ );
+ },
+ [insertions]
+ )
+
+ // Magic numbers for length of splitting a postive integer string by "-", the index of "", and the number to delete when a negative integer is split by "-"
+ const POS_INTEGER_SPLIT_LENGTH = 1;
+ const EMPTY_DELETE_SPLIT_INDEX = 0;
+ const NUMBER_DELETE_SPLIT_INDEX = 1;
+
+ for (const item of inputs) {
+
+ // Different cases of insertion and deletion
+ let split_arr = item.split("-");
+ if (split_arr.length == POS_INTEGER_SPLIT_LENGTH) { // When the input is a positive integer -> normal insert
+ for (const key of translateInput(item, "Array")) {
+ prevIdx = hashInsert(table, key, false);
+ }
+ }
+ else {
+ if (split_arr[EMPTY_DELETE_SPLIT_INDEX] === "") { // When the input is a negative integer -> delete
+ let key = Number(split_arr[NUMBER_DELETE_SPLIT_INDEX]);
+ total = HashingDelete(chunker, params, key, table, total);
+ }
+ else { // When the input is a range -> bulk insert
+ // Preparation for bulk insertion
+ chunker.add(
+ IBookmarks.BulkInsert,
+ (vis, insertions, prevIdx) => {
+ vis.array.unfill(INDEX, 0, undefined, table.length - 1); // Reset any coloring of slots
+ vis.array.showKth({key: item, type: HASH_TYPE.BulkInsert, insertions: insertions, increment: ""});
+ if (table.length <= PRIMES[POINTER_CUT_OFF])
+ vis.array.assignVariable("", POINTER, prevIdx, POINTER_VALUE); // Hide pointer
+
+ vis.graph.updateNode(HASH_GRAPH.Key, ' ');
+ vis.graph.updateNode(HASH_GRAPH.Value, ' ');
+ if (ALGORITHM_NAME === "HashingDH") {
+ vis.graph.updateNode(HASH_GRAPH.Key2, ' ');
+ vis.graph.updateNode(HASH_GRAPH.Value2, ' ');
+ }
+ },
+ [insertions, prevIdx]
+ )
+ prevIdx = hashBulkInsert(table, translateInput(item, "Array"));
+ }
+ }
+ }
+
+ // Chunker for resetting visualizers in case of new insertion cycle
+ chunker.add(
+ IBookmarks.Done,
+ (vis) => {
+
+ vis.array.showKth({key: "", type: EMPTY_CHAR, insertions: insertions, increment: ""}) // Nullify some stats, for better UI
+
+ // Hide pointer
+ if (table.length <= PRIMES[POINTER_CUT_OFF]) {
+ vis.array.assignVariable(POINTER_VALUE, POINTER, undefined);
+ }
+
+ vis.array.unfill(INDEX, 0, undefined, table.length - 1); // Unfill all boxes
+
+ // Reset graphs and uncolor the graph if needed
+ vis.graph.updateNode(HASH_GRAPH.Key, ' ');
+ vis.graph.updateNode(HASH_GRAPH.Value, ' ');
+ if (ALGORITHM_NAME === 'HashingDH') {
+ vis.graph.updateNode(HASH_GRAPH.Key2, ' ');
+ vis.graph.updateNode(HASH_GRAPH.Value2, ' ');
+ }
+
+ // Extract resulting array for testing
+ table_result = vis.array.extractArray([1], EMPTY_CHAR)
+ },
+ )
+
+ return table_result; // Return resulting array for testing
+ },
+};
diff --git a/src/algorithms/controllers/HashingCommon.js b/src/algorithms/controllers/HashingCommon.js
new file mode 100644
index 000000000..43a3a4701
--- /dev/null
+++ b/src/algorithms/controllers/HashingCommon.js
@@ -0,0 +1,203 @@
+// Magic numbers used between all 3 files
+export const SMALL_SIZE = 11;
+export const LARGE_SIZE = 97;
+const PRIME = 3457;
+const PRIME2 = 1429;
+const H2_SMALL_HASH_VALUE = 3;
+const H2_LARGE_HASH_VALUE = 23
+export const INDEX = 0;
+export const VALUE = 1;
+export const POINTER = 2;
+export const SPLIT_SIZE = 17;
+export const FULL_SIGNAL = -1;
+
+// Magic character used between all 3 files
+export const POINTER_VALUE = 'i'
+export const EMPTY_CHAR = '-';
+export const DELETE_CHAR = 'X';
+
+// Color indexes
+export const Colors = {
+ Insert: 1,
+ Pending: 2,
+ Collision: 3,
+ Found: 1,
+ NotFound: 3,
+};
+
+// Graph indexes
+export const HASH_GRAPH = {
+ Key: 0,
+ Value: 1,
+ Key2: 2,
+ Value2: 3
+}
+
+export const HASH_TYPE = {
+ Insert: 'I',
+ Search: 'S',
+ Delete: 'D',
+ BulkInsert: 'BI'
+}
+
+// list of primes each roughly two times larger than previous
+export const PRIMES = [
+ 11, 23, 47, 97
+];
+
+// find the table size from the table extracted from the visualizer
+export function findTableSize(table) {
+ for (let i = 0; i < PRIMES.length; i++) {
+ if (table.length - PRIMES[i] < SPLIT_SIZE) {
+ return PRIMES[i];
+ }
+ }
+ return 0;
+}
+
+// which table size in PRIMES array to allow pointer
+export const POINTER_CUT_OFF = 1;
+
+/**
+ * First hash function
+ * @param {*} chunker the chunker to step the visualized along with the calculation
+ * @param {*} bookmark the bookmark for chunker step
+ * @param {*} key the key to hash
+ * @param {*} tableSize the size of the table
+ * @param {*} toggleAnimate whether animation is carried out or not
+ * @returns the hashed value
+ */
+export function hash1(chunker, bookmark, key, tableSize, toggleAnimate) {
+ let hashed = (key * PRIME) % tableSize; // Hash the key
+
+ if (toggleAnimate) {
+ // Update the graph
+ chunker.add(
+ bookmark,
+ (vis, val) => {
+ vis.graph.updateNode(HASH_GRAPH.Value, val);
+ vis.graph.select(HASH_GRAPH.Value);
+ },
+ [hashed]
+ )
+ }
+
+ return hashed; // Return hashed value
+}
+
+/**
+ * Second hash function
+ * @param {*} chunker the chunker to step the visualized along with the calculation
+ * @param {*} bookmark the bookmark for chunker step
+ * @param {*} key the key to hash
+ * @param {*} tableSize the size of the table
+ * @param {*} toggleAnimate whether animation is carried out or not
+ * @returns the hashed value
+ */
+export function hash2(chunker, bookmark, key, tableSize, toggleAnimate) {
+ let smallishPrime = tableSize == SMALL_SIZE ? H2_SMALL_HASH_VALUE : H2_LARGE_HASH_VALUE; // This variable is to limit the increment to 3 for small table and 23 for large
+ let hashed = (key * PRIME2) % smallishPrime + 1; // Hash the key
+
+ if (toggleAnimate) {
+ // Update the graph
+ chunker.add(
+ bookmark,
+ (vis, val) => {
+ vis.graph.updateNode(HASH_GRAPH.Value2, val);
+ vis.graph.select(HASH_GRAPH.Value2);
+ },
+ [hashed]
+ )
+ }
+
+ return hashed; // Return hashed value
+}
+
+/**
+ * Calculate the increment for the key
+ * @param {*} chunker chunker to step the visualizer along with the calculations
+ * @param {*} bookmark bookmark to step chunker
+ * @param {*} key key to calculate the increment
+ * @param {*} tableSize size of the table
+ * @param {*} collisionHandling name of the algorithm, representing how collision is handled
+ * @param {*} type either search or insert because they have different stat updates
+ * @param {*} toggleAnimate whether animation is carried out or not
+ * @returns the calculated increment value
+ */
+export function setIncrement(
+ chunker, bookmark, key, tableSize, collisionHandling, type, toggleAnimate
+) {
+
+ // Increment = 1 if the algo is Linear Probing, and hashed value of second hash function if its Double Hashing
+ let increment;
+ switch (collisionHandling) {
+ case 'HashingLP':
+ increment = 1;
+ break;
+ case 'HashingDH':
+ increment = hash2(chunker, bookmark, key, tableSize, toggleAnimate);
+ break;
+ }
+
+ if (toggleAnimate) {
+ // Show key, insertions and increment if the type is Insertion
+ if (type == HASH_TYPE.Insert || type == HASH_TYPE.Delete) {
+ chunker.add(
+ bookmark,
+ (vis, increment) => {
+ let kth = vis.array.getKth();
+ vis.array.showKth({
+ key: key,
+ type,
+ insertions: kth.insertions,
+ increment: increment
+ });
+ },
+ [increment]
+ )
+ }
+
+ // Show key\ and increment if the type is Search
+ else if (type == HASH_TYPE.Search) {
+ chunker.add(
+ bookmark,
+ (vis, increment) => {
+ vis.array.showKth({
+ key: key,
+ type,
+ increment: increment
+ });
+ },
+ [increment]
+ )
+ }
+ }
+ return increment; // Return calculated increment
+}
+
+/**
+ * Reset the visualizations for a new cycle of either insertion, deletion or search
+ * @param {*} vis the visualzisers to reset
+ * @param {*} size table size
+ * @param {*} key key to insert/search/delete
+ * @param {*} name name of the algorithm/collision handling
+ */
+export function newCycle(vis, size, key, name) {
+ vis.array.unfill(INDEX, 0, undefined, size - 1); // Reset any coloring of slots
+
+ if (size <= PRIMES[POINTER_CUT_OFF]) {
+ vis.array.resetVariable(POINTER); // Reset pointer
+ }
+
+ // Update key value for the hashing graph and color them to emphasize hashing initialization
+ vis.graph.updateNode(HASH_GRAPH.Key, key);
+ vis.graph.updateNode(HASH_GRAPH.Value, ' ');
+ vis.graph.select(HASH_GRAPH.Key);
+ vis.graph.colorEdge(HASH_GRAPH.Key, HASH_GRAPH.Value, Colors.Pending)
+ if (name === "HashingDH") {
+ vis.graph.updateNode(HASH_GRAPH.Key2, key);
+ vis.graph.updateNode(HASH_GRAPH.Value2, ' ');
+ vis.graph.select(HASH_GRAPH.Key2);
+ vis.graph.colorEdge(HASH_GRAPH.Key2, HASH_GRAPH.Value2, Colors.Pending)
+ }
+}
diff --git a/src/algorithms/controllers/HashingDelete.js b/src/algorithms/controllers/HashingDelete.js
new file mode 100644
index 000000000..7db125843
--- /dev/null
+++ b/src/algorithms/controllers/HashingDelete.js
@@ -0,0 +1,202 @@
+import {
+ hash1,
+ setIncrement,
+ HASH_GRAPH,
+ Colors,
+ INDEX,
+ POINTER,
+ POINTER_VALUE,
+ SMALL_SIZE,
+ VALUE,
+ DELETE_CHAR,
+ HASH_TYPE,
+ newCycle,
+ EMPTY_CHAR
+} from './HashingCommon';
+
+// Bookmarks to link chunker with pseudocode
+const IBookmarks = {
+ ApplyHash: 16,
+ ChooseIncrement: 17,
+ InitDelete: 11,
+ WhileNot: 12,
+ MoveThrough: 13,
+ Found: 14,
+ Delete: 15,
+ NotFound: 18,
+ Pending: 19,
+}
+
+/**
+ * Running function for chunker of delete, using the key provided
+ * @param {*} chunker the chunker for deletion
+ * @param {*} params parameters for deletion algorithm, e.g. name, key, insertion visualizer instances,...
+ * @returns whether the key is found or not
+ */
+export default function HashingDelete(
+ chunker, params, key, table, total
+) {
+
+ // Assigning parameter values to local variables
+ const ALGORITHM_NAME = params.name;
+ const SIZE = table.length; // Hash Modulo being used in the table
+
+ // Chunker for intial state of visualizers
+ chunker.add(
+ IBookmarks.InitDelete,
+ (vis, target) => {
+
+ vis.array.showKth({key: target, insertions: vis.array.getKth().insertions, type: HASH_TYPE.Delete}); // Show stats
+
+ newCycle(vis, SIZE, key, ALGORITHM_NAME); // New delete cycle
+ },
+ [key]
+ );
+
+ // Hashing the key
+ let i = hash1(chunker, IBookmarks.ApplyHash, key, SIZE, true); // Target value after being hashed
+
+ /** This part is for Linear Probing and Double Hashing */
+ if (ALGORITHM_NAME !== 'HashingCH') {
+ // Calculate increment for key
+ let increment = setIncrement(chunker, IBookmarks.ChooseIncrement, key, SIZE, params.name, HASH_TYPE.Delete, true);
+
+ // Chunker for initial slot
+ chunker.add(
+ IBookmarks.WhileNot,
+ (vis, idx) => {
+ if (SIZE === SMALL_SIZE) {
+ vis.array.assignVariable(POINTER_VALUE, POINTER, idx); // Pointer only shows for small tables
+ }
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Pending); // Highlight initial search position
+
+ // Uncoloring the graphs
+ vis.graph.deselect(HASH_GRAPH.Key);
+ vis.graph.deselect(HASH_GRAPH.Value);
+ vis.graph.removeEdgeColor(HASH_GRAPH.Key, HASH_GRAPH.Value);
+ if (ALGORITHM_NAME == "HashingDH") {
+ vis.graph.deselect(HASH_GRAPH.Key2);
+ vis.graph.deselect(HASH_GRAPH.Value2);
+ vis.graph.removeEdgeColor(HASH_GRAPH.Key2, HASH_GRAPH.Value2);
+ }
+ },
+ [i]
+ );
+
+ let explored = 0;
+ // Search for the target key, checking each probed position
+ while (table[i] !== key && table[i] !== undefined && explored < SIZE) {
+ // Chunker for not matching
+ explored += 1;
+ chunker.add(
+ IBookmarks.WhileNot,
+ (vis, idx) => {
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.NotFound); // Fill the slot with red if the slot does not match key
+ },
+ [i]
+ );
+
+ // Move to the next index based on collision handling
+ i = (i + increment) % SIZE;
+
+ // Chunker for probing
+ chunker.add(
+ IBookmarks.MoveThrough,
+ (vis, idx) => {
+ if (SIZE === SMALL_SIZE) {
+ vis.array.assignVariable(POINTER_VALUE, POINTER, idx); // Pointer is only shown for small tables
+ }
+ },
+ [i]
+ );
+
+ // Chunker for searching the slots based on increment
+ chunker.add(
+ IBookmarks.WhileNot,
+ (vis, idx) => {
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Pending); // Fill pending slots with yellow
+ },
+ [i]
+ );
+ }
+
+ // Chunker for found
+ if (table[i] === key) {
+ chunker.add(
+ IBookmarks.Found,
+ (vis, idx) => {
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Found); // Fill the slot with green, indicating that the key is found
+ },
+ [i]
+ );
+ // Replace found element with x
+ table[i] = DELETE_CHAR;
+ chunker.add(
+ IBookmarks.Delete,
+ (vis, val, idx) => {
+ vis.array.updateValueAt(VALUE, idx, val);
+ },
+ [DELETE_CHAR, i]
+ )
+
+ return total - 1; // Decrement total
+ }
+ else {
+ chunker.add(
+ IBookmarks.NotFound,
+ (vis, idx) => {
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.NotFound); // Fill the slot with green, indicating that the key is found
+ },
+ [i]
+ )
+
+ return total; // Since the deletion key is not found, nothing is deleted
+ }
+ }
+
+ /** This part is for Chaining */
+ else {
+
+ chunker.add(
+ IBookmarks.Pending,
+ (vis, idx) => {
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Pending); // Fill pending slots with yellow
+ },
+ [i]
+ );
+
+ if (table[i].includes(key)) {
+ const index = table[i].indexOf(key);
+ if (index > -1) table[i].splice(index, 1); // 2nd parameter means remove one item only
+
+ chunker.add(
+ IBookmarks.Found,
+ (vis, idx, table) => {
+ // Modify the floating array
+ const popper = document.getElementById('float_box_' + idx);
+ popper.innerHTML = table[idx];
+
+ let firstItemOfChain = table[idx][0];
+ if (firstItemOfChain != undefined) vis.array.updateValueAt(VALUE, idx, firstItemOfChain + '..');
+ else vis.array.updateValueAt(VALUE, idx, EMPTY_CHAR);
+
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Found); // Fill the slot with green, indicating that the key is found
+ },
+ [i, table]
+ );
+
+ return total - 1; // Decrement total
+ }
+ else {
+ chunker.add(
+ IBookmarks.NotFound,
+ (vis, idx) => {
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.NotFound); // Fill the slot with green, indicating that the key is found
+ },
+ [i]
+ )
+
+ return total; // Since the deletion key is not found, nothing is deleted
+ }
+ }
+}
diff --git a/src/algorithms/controllers/HashingInsertion.js b/src/algorithms/controllers/HashingInsertion.js
new file mode 100644
index 000000000..bdcf2e864
--- /dev/null
+++ b/src/algorithms/controllers/HashingInsertion.js
@@ -0,0 +1,585 @@
+import Array2DTracer from '../../components/DataStructures/Array/Array2DTracer';
+import GraphTracer from '../../components/DataStructures/Graph/GraphTracer';
+import { HashingExp } from '../explanations';
+import {
+ hash1,
+ setIncrement,
+ HASH_GRAPH,
+ EMPTY_CHAR,
+ Colors,
+ INDEX,
+ POINTER,
+ POINTER_VALUE,
+ SMALL_SIZE,
+ VALUE,
+ LARGE_SIZE,
+ SPLIT_SIZE,
+ DELETE_CHAR,
+ HASH_TYPE,
+ FULL_SIGNAL,
+ PRIMES,
+ POINTER_CUT_OFF,
+ newCycle
+} from './HashingCommon';
+import { translateInput } from '../parameters/helpers/ParamHelper';
+import HashingDelete from './HashingDelete';
+
+// Bookmarks to link chunker with pseudocode
+const IBookmarks = {
+ Init: 1,
+ EmptyArray: 2,
+ InitInsertion: 3,
+ // IncrementInsertions: 4,
+ Hash1: 5,
+ ChooseIncrement: 6,
+ Probing: 7,
+ Collision: 8,
+ PutIn: 9,
+ Done: 10,
+ BulkInsert: 1,
+ CheckTableFull: 19,
+}
+
+/**
+ * Create new arrays for expanded table
+ * @param {*} table the table to keep track of the internal and illustrated array
+ * @returns the new table, and the index, value, variable arrays for the visualiser
+ */
+function expandTable(table) {
+ let currSize = table.length;
+ let nextSize = PRIMES[PRIMES.indexOf(currSize) + 1];
+ if (nextSize === undefined) return [null, null, null, null];
+
+ return [
+ new Array(nextSize),
+ Array.from({ length: nextSize }, (_, i) => i),
+ Array(nextSize).fill(EMPTY_CHAR),
+ Array(nextSize).fill('')
+ ]
+}
+
+export default {
+ explanation: HashingExp,
+
+ // Initialize visualizers
+ initVisualisers() {
+ return {
+ array: {
+ instance: new Array2DTracer('array', null, 'Hash Table'),
+ order: 0,
+ },
+ graph: {
+ instance: new GraphTracer('graph', null, 'Hashing Functions'),
+ order: 1,
+ },
+ };
+ },
+
+ /**
+ * Run function for insertion, using the user input to display the illustration through chunker
+ * @param {*} chunker the chunker for the illustrations
+ * @param {*} params different parameters of the algorithm insertion mode e.g. name, array size,...
+ * @returns a table of concluding array to serve testing purposes
+ */
+ run(chunker, params) {
+ // Storing algorithms parameters as local variables
+ const ALGORITHM_NAME = params.name;
+ let inputs = params.values;
+ const SIZE = params.hashSize;
+
+ // Initialize arrays
+ let indexArr = Array.from({ length: SIZE }, (_, i) => i);
+ let valueArr = Array(SIZE).fill(EMPTY_CHAR);
+ let nullArr = Array(SIZE).fill('');
+
+ // Variable to keep track of insertions done and total inputs hashed into the table
+ let insertions = 0;
+ let total = 0;
+
+ /**
+ * Insertion function for each key
+ * @param {*} table the table to keep track of the internal and illustrated array
+ * @param {*} key the key to insert
+ * @param {*} prevIdx previous index of the previous key
+ * @param {*} isBulkInsert whether it is bulk insert or not
+ * @returns the index the key is assigned
+ */
+ function hashInsert(table, key) {
+ // Chunker for when table is full
+ const limit = () => {
+ if (params.expand && table.length < LARGE_SIZE) return total + 1 === Math.round(table.length * 0.8);
+ return total === table.length - 1;
+ }
+ if (limit()) {
+ chunker.add(
+ IBookmarks.CheckTableFull,
+ (vis, total) => {
+ vis.array.showKth({fullCheck: "Table is filled " + total + "/" + table.length + " -> Table is full, "
+ + ((params.expand) ? "expanding table..." : "stopping...")});
+ },
+ [total]
+ )
+ return FULL_SIGNAL;
+ }
+
+ // Chunker for when the table is not full
+ else {
+ chunker.add(
+ IBookmarks.CheckTableFull,
+ (vis, total) => {
+ newCycle(vis, table.length, key, ALGORITHM_NAME); // New insert cycle
+ vis.array.showKth({fullCheck: "Table is filled " + total + "/" + table.length + " -> Table is not full, continuing..."});
+ },
+ [total]
+ )
+ }
+
+ insertions++; // Increment insertions
+ total++; // Increment total
+
+ // Get initial hash index for current key
+ let i = hash1(chunker, IBookmarks.Hash1, key, table.length, true);
+
+ // Calculate increment for current key
+ let increment = setIncrement(
+ chunker,
+ IBookmarks.ChooseIncrement,
+ key,
+ table.length,
+ ALGORITHM_NAME,
+ HASH_TYPE.Insert,
+ true
+ );
+
+ // Chunker for first pending slot
+ chunker.add(
+ IBookmarks.Probing,
+ (vis, idx) => {
+
+ // Pointer only appear for small table
+ if (table.length < PRIMES[POINTER_CUT_OFF]) {
+ vis.array.assignVariable(POINTER_VALUE, POINTER, idx);
+ }
+
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Pending); // Color pending slot
+
+ // Uncolor the hashing graph
+ vis.graph.deselect(HASH_GRAPH.Key);
+ vis.graph.deselect(HASH_GRAPH.Value);
+ vis.graph.removeEdgeColor(HASH_GRAPH.Key, HASH_GRAPH.Value);
+ if (ALGORITHM_NAME == "HashingDH") {
+ vis.graph.deselect(HASH_GRAPH.Key2);
+ vis.graph.deselect(HASH_GRAPH.Value2);
+ vis.graph.removeEdgeColor(HASH_GRAPH.Key2, HASH_GRAPH.Value2);
+ }
+ },
+ [i]
+ )
+
+ // Internal code for probing, while loop indicates finding an empty slot for insertion
+ while (table[i] !== undefined && table[i] !== key && table[i] !== DELETE_CHAR) {
+ let prevI = i;
+ i = (i + increment) % table.length; // This is to ensure the index never goes over table size
+
+ // Chunker for collision
+ chunker.add(
+ IBookmarks.Collision,
+ (vis, idx) => {
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Collision); // Fill the slot with red, indicating collision
+ },
+ [prevI]
+ )
+
+ // Chunker for Probing
+ chunker.add(
+ IBookmarks.Probing,
+ (vis, idx) => {
+
+ // Pointer only appears for small tables
+ if (table.length < PRIMES[POINTER_CUT_OFF]) {
+ vis.array.assignVariable(POINTER_VALUE, POINTER, idx);
+ }
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Pending); // Filling the pending slot with yellow
+ },
+ [i]
+ )
+ }
+
+ // Internally assign the key to the index
+ table[i] = key;
+
+ // Chunker for placing the key
+ chunker.add(
+ IBookmarks.PutIn,
+ (vis, val, idx) => {
+ vis.array.updateValueAt(VALUE, idx, val); // Update value of that index
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Insert); // Fill it green, indicating successful insertion
+ },
+ [key, i, insertions]
+ )
+
+ // Return the insertion index
+ return i;
+ }
+
+
+ /**
+ * Function for bulk insertion
+ * @param {*} table the table to keep track of the internal and illustrated array
+ * @param {*} keys the keys to insert
+ * @returns the index the last key is assigned
+ */
+ function hashBulkInsert(table, keys) {
+ let lastHash;
+ let inserts = {};
+ let bulkInsertions = 0;
+ let prevTable = [...table];
+ const limit = () => {
+ if (params.expand && table.length < LARGE_SIZE) return total + 1 === Math.round(table.length * 0.8);
+ return total === table.length - 1;
+ }
+ for (const key of keys) {
+ if (limit()) {
+ inserts[key] = FULL_SIGNAL;
+ lastHash = FULL_SIGNAL;
+ break;
+ }
+
+ bulkInsertions++;
+
+ // hashed value
+ let i = hash1(null, null, key, table.length, false);
+
+ // increment for probing
+ let increment = setIncrement(
+ null,
+ null,
+ key,
+ table.length,
+ ALGORITHM_NAME,
+ HASH_TYPE.Insert,
+ false
+ );
+
+ while (table[i] !== undefined) {
+ i = (i + increment) % table.length; // This is to ensure the index never goes over table size
+ }
+
+ table[i] = key;
+ inserts[key] = i;
+ lastHash = i;
+ }
+
+ if (params.expand && (lastHash === FULL_SIGNAL)) {
+ table = [...prevTable];
+ return lastHash;
+ }
+
+ insertions += bulkInsertions;
+ total += bulkInsertions;
+ chunker.add(
+ IBookmarks.PutIn,
+ (vis, keys, inserts, insertions) => {
+ for (const key of keys) {
+ vis.array.updateValueAt(VALUE, inserts[key], key); // Update value of that index
+ vis.array.fill(INDEX, inserts[key], undefined, undefined, Colors.Insert);
+ }
+ vis.array.showKth({key: vis.array.getKth().key, type: HASH_TYPE.BulkInsert, insertions: insertions});
+ },
+ [keys, inserts, insertions]
+ )
+
+ return lastHash;
+ }
+
+
+ const REINSERT_CAPTION_LEN = 5;
+
+ /**
+ * ReInsertion function for inserted key to new table
+ * @param {*} table the table to keep track of the internal and illustrated array
+ * @param {*} key the key to reinsert
+ * @param {*} prevTable rrray of emaining keys from old table to be inserted
+ * @returns the index the key is assigned
+ */
+ function hashReinsert(table, key, prevTable) {
+ chunker.add(
+ IBookmarks.CheckTableFull,
+ (vis, prevTable) => {
+ newCycle(vis, table.length, key, ALGORITHM_NAME); // New insert cycle
+ vis.array.showKth({
+ reinserting: key,
+ toReinsert: `${prevTable.slice(0, REINSERT_CAPTION_LEN)}` +
+ ((prevTable.length > REINSERT_CAPTION_LEN) ? `,...` : ``)
+ });
+ },
+ [prevTable]
+ )
+
+
+ // Get initial hash index for current key
+ let i = hash1(
+ chunker,
+ IBookmarks.CheckTableFull,
+ key,
+ table.length,
+ false
+ );
+
+ // Calculate increment for current key
+ let increment = setIncrement(
+ chunker,
+ IBookmarks.CheckTableFull,
+ key,
+ table.length,
+ ALGORITHM_NAME,
+ HASH_TYPE.Insert,
+ false
+ );
+
+ // Chunker for first pending slot
+ chunker.add(
+ IBookmarks.CheckTableFull,
+ (vis, idx) => {
+
+ // Pointer only appear for small table
+ if (table.length <= PRIMES[POINTER_CUT_OFF]) {
+ vis.array.assignVariable(POINTER_VALUE, POINTER, idx);
+ }
+
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Pending); // Color pending slot
+
+ // Uncolor the hashing graph
+ vis.graph.deselect(HASH_GRAPH.Key);
+ vis.graph.deselect(HASH_GRAPH.Value);
+ vis.graph.removeEdgeColor(HASH_GRAPH.Key, HASH_GRAPH.Value);
+ if (ALGORITHM_NAME == "HashingDH") {
+ vis.graph.deselect(HASH_GRAPH.Key2);
+ vis.graph.deselect(HASH_GRAPH.Value2);
+ vis.graph.removeEdgeColor(HASH_GRAPH.Key2, HASH_GRAPH.Value2);
+ }
+ },
+ [i]
+ )
+
+ // Internal code for probing, while loop indicates finding an empty slot for insertion
+ while (table[i] !== undefined && table[i] !== key && table[i] !== DELETE_CHAR) {
+ let prevI = i;
+ i = (i + increment) % table.length; // This is to ensure the index never goes over table size
+
+ // Chunker for collision
+ chunker.add(
+ IBookmarks.CheckTableFull,
+ (vis, idx) => {
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Collision); // Fill the slot with red, indicating collision
+ },
+ [prevI]
+ )
+
+ // Chunker for Probing
+ chunker.add(
+ IBookmarks.CheckTableFull,
+ (vis, idx) => {
+
+ // Pointer only appears for small tables
+ if (table.length <= PRIMES[POINTER_CUT_OFF]) {
+ vis.array.assignVariable(POINTER_VALUE, POINTER, idx);
+ }
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Pending); // Filling the pending slot with yellow
+ },
+ [i]
+ )
+ }
+
+ // Internally assign the key to the index
+ table[i] = key;
+
+ // Chunker for placing the key
+ chunker.add(
+ IBookmarks.CheckTableFull,
+ (vis, val, idx) => {
+ vis.array.updateValueAt(VALUE, idx, val); // Update value of that index
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Insert); // Fill it green, indicating successful insertion
+ },
+ [key, i, insertions]
+ )
+
+ // Return the insertion index
+ return i;
+ }
+
+
+ // Inserting inputs
+ let prevIdx;
+ // Init hash table
+ let table = new Array(SIZE);
+ let prevTable;
+ // Last input index
+ let lastInput = 0;
+
+ // main loop allowing table extension
+ do {
+ prevIdx = null;
+
+ chunker.add(
+ IBookmarks.Init,
+ (vis, size, array) => {
+ // Increase Array2D visualizer render space
+ if (SIZE === LARGE_SIZE) {
+ vis.array.setSize(3);
+ vis.array.setZoom(0.7);
+ vis.graph.setZoom(1.5);
+ } else {
+ vis.array.setZoom(1);
+ vis.graph.setZoom(1);
+ }
+
+ // Initialize the array
+ vis.array.set(array,
+ params.name,
+ '',
+ INDEX,
+ {
+ rowLength: size > SMALL_SIZE ? SPLIT_SIZE : SMALL_SIZE,
+ rowHeader: ['Index', 'Value', '']
+ }
+ );
+
+ vis.array.hideArrayAtIndex([VALUE, POINTER]); // Hide value and pointer row intially
+
+ vis.graph.weighted(true);
+
+ // Intialize the graphs
+ switch (ALGORITHM_NAME) {
+ case "HashingLP" :
+ vis.graph.set([[0, 'Hash'], [0, 0]], [' ', ' '], [[-5, 0], [5, 0]]);
+ break;
+ case "HashingDH" :
+ vis.graph.set([
+ [0, 'Hash1', 0, 0], [0, 0, 0, 0], [0, 0, 0, 'Hash2'], [0, 0, 0, 0]], // Node edges
+ [' ', ' ', ' ', ' '], // Node values
+ [[-5, 2], [5, 2], [-5, -2], [5, -2]]); // Node positions
+ break;
+ }
+ },
+ [table.length, table.length <= PRIMES[POINTER_CUT_OFF] ?
+ [indexArr, valueArr, nullArr] :
+ [indexArr, valueArr]
+ ]
+ );
+
+ // Chunker to initialize empty array visually
+ chunker.add(
+ IBookmarks.EmptyArray,
+ (vis) => {
+ // Show the value row
+ vis.array.hideArrayAtIndex(POINTER);
+ },
+ );
+
+ // Chunker for intializing insertion stat
+ chunker.add(
+ IBookmarks.InitInsertion,
+ (vis, insertions) => {
+ vis.array.showKth(
+ (params.expand && (lastInput !== 0)) ? {
+ fullCheck: "Expanding Table"
+ } : {
+ key: "",
+ type: EMPTY_CHAR,
+ insertions: insertions,
+ increment: "",
+ }
+ );
+ },
+ [insertions]
+ )
+
+ // Magic numbers for length of splitting a postive integer string by "-", the index of "", and the number to delete when a negative integer is split by "-"
+ const POS_INTEGER_SPLIT_LENGTH = 1;
+ const EMPTY_DELETE_SPLIT_INDEX = 0;
+ const NUMBER_DELETE_SPLIT_INDEX = 1;
+
+ if (params.expand && (lastInput !== 0)) {
+ while (prevTable.length > 0) {
+ let key = prevTable[0];
+ prevTable.shift();
+ hashReinsert(table, key, prevTable);
+ }
+ }
+
+ for (let i = lastInput; i < inputs.length; i++) {
+ let item = inputs[i];
+
+ // Different cases of insertion and deletion
+ let split_arr = item.split("-");
+ if (split_arr.length == POS_INTEGER_SPLIT_LENGTH) { // When the input is a positive integer -> normal insert
+ for (const key of translateInput(item, "Array")) {
+ prevIdx = hashInsert(table, key, false);
+ }
+ }
+ else {
+ if (split_arr[EMPTY_DELETE_SPLIT_INDEX] === "") { // When the input is a negative integer -> delete
+ let key = Number(split_arr[NUMBER_DELETE_SPLIT_INDEX]);
+ total = HashingDelete(chunker, params, key, table, total);
+ }
+ else { // When the input is a range -> bulk insert
+ // Preparation for bulk insertion
+ chunker.add(
+ IBookmarks.BulkInsert,
+ (vis, insertions, prevIdx) => {
+ vis.array.unfill(INDEX, 0, undefined, table.length - 1); // Reset any coloring of slots
+ vis.array.showKth({key: item, type: HASH_TYPE.BulkInsert, insertions: insertions, increment: ""});
+ if (table.length <= PRIMES[POINTER_CUT_OFF])
+ vis.array.assignVariable("", POINTER, prevIdx, POINTER_VALUE); // Hide pointer
+
+ vis.graph.updateNode(HASH_GRAPH.Key, ' ');
+ vis.graph.updateNode(HASH_GRAPH.Value, ' ');
+ if (ALGORITHM_NAME === "HashingDH") {
+ vis.graph.updateNode(HASH_GRAPH.Key2, ' ');
+ vis.graph.updateNode(HASH_GRAPH.Value2, ' ');
+ }
+ },
+ [insertions, prevIdx]
+ )
+ prevIdx = hashBulkInsert(table, translateInput(item, "Array"));
+ }
+ }
+
+ // when table is full or almost full
+ if (prevIdx === FULL_SIGNAL) {
+ lastInput = i;
+ prevTable = table.filter(n => n !== undefined);
+ if (params.expand && (table.length < LARGE_SIZE)) [table, indexArr, valueArr, nullArr] = expandTable(table);
+ break;
+ }
+ }
+ } while (params.expand && (prevIdx === FULL_SIGNAL) && (table.length < LARGE_SIZE));
+
+ // Chunker for resetting visualizers in case of new insertion cycle
+ chunker.add(
+ IBookmarks.Done,
+ (vis) => {
+
+ vis.array.showKth({key: "", type: EMPTY_CHAR, insertions: insertions, increment: ""}) // Nullify some stats, for better UI
+
+ // Hide pointer
+ if (table.length <= PRIMES[POINTER_CUT_OFF]) {
+ vis.array.assignVariable(POINTER_VALUE, POINTER, undefined);
+ }
+
+ vis.array.unfill(INDEX, 0, undefined, table.length - 1); // Unfill all boxes
+
+ // Reset graphs and uncolor the graph if needed
+ vis.graph.updateNode(HASH_GRAPH.Key, ' ');
+ vis.graph.updateNode(HASH_GRAPH.Value, ' ');
+ if (ALGORITHM_NAME === 'HashingDH') {
+ vis.graph.updateNode(HASH_GRAPH.Key2, ' ');
+ vis.graph.updateNode(HASH_GRAPH.Value2, ' ');
+ }
+ },
+ )
+
+ return table; // Return resulting array for testing
+ },
+};
diff --git a/src/algorithms/controllers/HashingSearch.js b/src/algorithms/controllers/HashingSearch.js
new file mode 100644
index 000000000..ffa5deb80
--- /dev/null
+++ b/src/algorithms/controllers/HashingSearch.js
@@ -0,0 +1,223 @@
+import {
+ hash1,
+ setIncrement,
+ HASH_GRAPH,
+ EMPTY_CHAR,
+ Colors,
+ INDEX,
+ POINTER,
+ POINTER_VALUE,
+ SMALL_SIZE,
+ DELETE_CHAR,
+ HASH_TYPE,
+ PRIMES,
+ POINTER_CUT_OFF,
+ newCycle,
+ findTableSize
+} from './HashingCommon';
+
+// Bookmarks to link chunker with pseudocode
+const IBookmarks = {
+ Init: 1,
+ ApplyHash: 5,
+ ChooseIncrement: 6,
+ WhileNot: 2,
+ Probing: 3,
+ CheckValue: 4,
+ Found: 7,
+ NotFound: 8,
+ Pending: 9
+}
+
+export default {
+
+ // Initialize visualizers
+ initVisualisers({ visualisers }) {
+ return {
+ array: {
+ instance: visualisers.array.instance,
+ order: 0,
+ },
+ graph: {
+ instance: visualisers.graph.instance,
+ order: 1,
+ },
+ };
+ },
+
+ /**
+ * Running function for chunker of search, using the key provided
+ * @param {*} chunker the chunker for searching
+ * @param {*} params parameters for searching algorithm, e.g. name, key, insertion visualizer instances,...
+ * @returns whether the key is found or not
+ */
+ run(chunker, params) {
+
+ // Assigning parameter values to local variables
+ const ALGORITHM_NAME = params.name;
+ const TARGET = params.target; // Target value we are searching for
+ let table = params.visualisers.array.instance.extractArray(1, EMPTY_CHAR); // The table with inserted values
+ const SIZE = findTableSize(table); // Hash Modulo being used in the table
+
+ // Variable for testing
+ let found = true;
+
+ // Chunker for intial state of visualizers
+ chunker.add(
+ IBookmarks.Init,
+ (vis, target) => {
+
+ vis.array.showKth({key: target, type: HASH_TYPE.Search}); // Show stats
+
+ newCycle(vis, SIZE, target, ALGORITHM_NAME);
+ },
+ [TARGET]
+ );
+
+ // Hashing the key
+ let i = hash1(chunker, IBookmarks.ApplyHash, TARGET, SIZE, true); // Target value after being hashed
+
+ /** This part is for Linear Probing and Double Hashing */
+ if (ALGORITHM_NAME !== 'HashingCH') {
+ // Calculate increment for key
+ let increment = setIncrement(chunker, IBookmarks.ChooseIncrement, TARGET, SIZE, params.name, HASH_TYPE.Search, true);
+
+ // Chunker for initial slot
+ chunker.add(
+ IBookmarks.WhileNot,
+ (vis, idx) => {
+ if (SIZE <= PRIMES[POINTER_CUT_OFF]) {
+ vis.array.assignVariable(POINTER_VALUE, POINTER, idx); // Pointer only shows for small tables
+ }
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Pending); // Highlight initial search position
+
+ // Uncoloring the graphs
+ vis.graph.deselect(HASH_GRAPH.Key);
+ vis.graph.deselect(HASH_GRAPH.Value);
+ vis.graph.removeEdgeColor(HASH_GRAPH.Key, HASH_GRAPH.Value);
+ if (ALGORITHM_NAME == "HashingDH") {
+ vis.graph.deselect(HASH_GRAPH.Key2);
+ vis.graph.deselect(HASH_GRAPH.Value2);
+ vis.graph.removeEdgeColor(HASH_GRAPH.Key2, HASH_GRAPH.Value2);
+ }
+ },
+ [i]
+ );
+
+ let explored = 0;
+ // Search for the target key, checking each probed position
+ while (table[i] !== TARGET && table[i] !== undefined && explored < SIZE) {
+ explored += 1;
+
+ // Chunker for not matching
+ chunker.add(
+ IBookmarks.WhileNot,
+ (vis, idx) => {
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Collision); // Fill the slot with red if the slot does not match key
+ },
+ [i]
+ );
+
+ // Move to the next index based on collision handling
+ i = (i + increment) % SIZE;
+
+ // Chunker for probing
+ chunker.add(
+ IBookmarks.Probing,
+ (vis, idx) => {
+ if (SIZE <= PRIMES[POINTER_CUT_OFF]) {
+ vis.array.assignVariable(POINTER_VALUE, POINTER, idx); // Pointer is only shown for small tables
+ }
+ },
+ [i]
+ );
+
+ // Chunker for searching the slots based on increment
+ chunker.add(
+ IBookmarks.WhileNot,
+ (vis, idx) => {
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Pending); // Fill pending slots with yellow
+ },
+ [i]
+ );
+ }
+
+ // Chunker for found
+ if (table[i] === TARGET) {
+ chunker.add(
+ IBookmarks.Found,
+ (vis, idx) => {
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Insert); // Fill the slot with green, indicating that the key is found
+ },
+ [i]
+ );
+ found = true; // Set testing variable
+ }
+
+ // Chunker for not found
+ else {
+ chunker.add(
+ IBookmarks.NotFound,
+ (vis, idx) => {
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Collision); // Fill last slot with red
+ },
+ [i]
+ );
+ found = false; // Set testing variable
+ }
+ return found; // Return found or not for testing
+ }
+
+ /** This part is for Chaining */
+ else {
+
+ chunker.add(
+ IBookmarks.Pending,
+ (vis, idx) => {
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Pending); // Fill pending slots with yellow
+ },
+ [i]
+ );
+
+ // Chunker for found
+ if (table[i] != undefined) {
+ if (Array.isArray(table[i])) {
+ if (table[i].includes(TARGET)) {
+ chunker.add(
+ IBookmarks.Found,
+ (vis, idx) => {
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Insert); // Fill the slot with green, indicating that the key is found
+ },
+ [i]
+ );
+ found = true; // Set testing variable
+ }
+ if (table[i] === TARGET) {
+ chunker.add(
+ IBookmarks.Found,
+ (vis, idx) => {
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Insert); // Fill the slot with green, indicating that the key is found
+ },
+ [i]
+ );
+ found = true; // Set testing variable
+ }
+ }
+ }
+
+ // Chunker for not found
+ else {
+ chunker.add(
+ IBookmarks.NotFound,
+ (vis, idx) => {
+ vis.array.fill(INDEX, idx, undefined, undefined, Colors.Collision); // Fill last slot with red
+ found = false; // Set testing variable
+ },
+ [i]
+ );
+ found = false; // Set testing variable
+ }
+ return found; // Return found or not for testing
+ }
+ },
+};
diff --git a/src/algorithms/controllers/index.js b/src/algorithms/controllers/index.js
index 0032e15a8..3d126dad7 100644
--- a/src/algorithms/controllers/index.js
+++ b/src/algorithms/controllers/index.js
@@ -23,3 +23,7 @@ export { default as DFSrec } from './DFSrec';
export { default as prim_old } from './prim_old';
export { default as prim } from './prim';
export { default as kruskal } from './kruskal';
+export { default as HashingInsertion } from './HashingInsertion';
+export { default as HashingSearch } from './HashingSearch';
+export { default as HashingDelete } from './HashingDelete';
+export { default as HashingChainingInsertion} from './HashingChainingInsertion'
diff --git a/src/algorithms/controllers/tests/HashingDeletion.test.js b/src/algorithms/controllers/tests/HashingDeletion.test.js
new file mode 100644
index 000000000..9822ad389
--- /dev/null
+++ b/src/algorithms/controllers/tests/HashingDeletion.test.js
@@ -0,0 +1,80 @@
+/* The purpose of the test here is to detect whether the correct result is generated
+ under the legal input, not to test its robustness, because this is not considered
+ in the implementation process of the algorithm.
+*/
+
+/* eslint-disable no-undef */
+
+import { LARGE_SIZE, SMALL_SIZE } from '../HashingCommon';
+import HashingInsertion from '../HashingInsertion';
+
+// Simple stub for the chunker
+const chunker = {
+ add: () => {},
+};
+
+describe('HashingDeletion', () => {
+ // Test cases for Linear Probing
+ it('LP insert small table, delete 1 element after all has been inserted', () => {
+ const input = ["12", "10", "18", "6", "21", "48", "47", "49", "24", "26", "-18"];
+ const result = [47, 48, 26, 12, 49, undefined, 24, 6, 10, 21, "X"];
+ expect(HashingInsertion.run(chunker, { values: input, hashSize: SMALL_SIZE, name: "HashingLP" })).toEqual(result);
+ });
+ it('LP delete element in the middle of insertion', () => {
+ const input = ["3", "2", "10", "18", "-10", "28", "36", "25", "17","22","44"];
+ const result = [36, 25, 22, 44, undefined, undefined, 2, 28, 17, 3, 18];
+ expect(HashingInsertion.run(chunker, { values: input, hashSize: SMALL_SIZE, name: "HashingLP" })).toEqual(result);
+ });
+ it('LP delete element, table contains no empty indices', () => {
+ const input = ["29","40","15","14","43","10","16","48","12","18","-12","46"];
+ const result = [40, 15, 10, 48, 16, "X", 18, 46, 43, 14, 29];
+ expect(HashingInsertion.run(chunker, { values: input, hashSize: SMALL_SIZE, name: "HashingLP" })).toEqual(result);
+ });
+ it('LP attempt to delete non-existent element from non-empty table', () => {
+ const input = ["29","40","15","14","43","10","16","48","12","18","-12","46","-99"];
+ const result = [40, 15, 10, 48, 16, "X", 18, 46, 43, 14, 29];
+ expect(HashingInsertion.run(chunker, { values: input, hashSize: SMALL_SIZE, name: "HashingLP" })).toEqual(result);
+ });
+ it('LP delete everything', () => {
+ const input = ["23", "11", "38", "22", "19", "3", "26", "35", "14", "37","-23", "-11", "-38", "-22", "-19", "-3", "-26", "-35", "-14", "-37"];
+ const result = ["X", "X", "X", "X", "X", "X", "X", "X", undefined, "X", "X"];
+ expect(HashingInsertion.run(chunker, { values: input, hashSize: SMALL_SIZE, name: "HashingDH" })).toEqual(result);
+ });
+ it('LP delete multiple elements during insertion in large table', () => {
+ const input = ["34","57","55","43","42","100","71","62","-55","78","97","-43","45","59","87","-34","70","73","67","68","20","77","26","4","46","32","95","49","56","51","58","2","89","-77","66","18","98","27","48","13","36","84","10","74","-67","63","28","39","54","65","61","35","7","82","72","14","93","50","79","8","37","1","80","76","44","94","11","52","86","33","31","96","81","64","40","85","21","17","90","83","38","22","23","53","30","60","41","29","16","9","15","19","47","99","69","25","12","5","75","6","91"];
+ const result = [97, 36, 72, 11, 47, 83, 22, 58, 94, 33, 69, 8, 44, 80, 19, "X", 91, 30, 66, 5, 41, "X", 16 , 52, undefined, 27, 63, 2, 38, 74, 13, 49, 85, 99, 60, 96, 35, 71, 10, 46, 82, 21, 57, 93, 32, 68, 7, "X", 79, 18, 54, 90, 29, 65, 4, 40, 76, 15, 51, 87, 26, 62, 98, 37, 73, 1, 48, 84, 23, 59, 95, 12, 70, 9, 45, 81, 20, 56, undefined, 31, "X", 6, 42, 78, 17, 53, 89, 28, 64, 100, 39, 75, 14, 50, 86, 25, 61];
+ expect(HashingInsertion.run(chunker, { values: input, hashSize: LARGE_SIZE, name: "HashingLP" })).toEqual(result);
+ });
+
+ // Test cases for Double Hashing
+ it('DH insert small table, delete 1 element after all has been inserted', () => {
+ const input = ["31","13","9","17","6","43","2","15","3","50","-9"];
+ const result = [3, 43, 15, 50, undefined, 31, 13, "X", 6, 2, 17];
+ expect(HashingInsertion.run(chunker, { values: input, hashSize: SMALL_SIZE, name: "HashingDH" })).toEqual(result);
+ });
+ it('DH delete element in the middle of insertion', () => {
+ const input = ["9", "29", "12", "46", "33", "2", "-46", "15", "37", "6", "18"];
+ const result = [33, 15, 18, 12, undefined, 9, "X", 37, 6, 2, 29];
+ expect(HashingInsertion.run(chunker, { values: input, hashSize: SMALL_SIZE, name: "HashingDH" })).toEqual(result);
+ });
+ it('DH delete element, table contains no empty indices and deletion probe passes through a deleted index', () => {
+ const input = ["30", "11", "24", "27", "14", "46", "44", "41", "50", "12", "-14", "92", "-12"];
+ const result = [11, 44, 30, 41, 27, "X", 24, 46, 50, 92, undefined];
+ expect(HashingInsertion.run(chunker, { values: input, hashSize: SMALL_SIZE, name: "HashingLP" })).toEqual(result);
+ });
+ it('DH attempt to delete non-existent element from non-empty table', () => {
+ const input = ["14", "16", "25", "42", "32", "10", "18", "20", "5", "29", "-20", "97", "-53"];
+ const result = [25, 18, 29, "X", 16, 42, 97, 5, 32, 14, 10];
+ expect(HashingInsertion.run(chunker, { values: input, hashSize: SMALL_SIZE, name: "HashingDH" })).toEqual(result);
+ });
+ it('DH delete everything', () => {
+ const input = ["48","50","36","27","49","19","41","24","45","32","-48","-50","-36","-27","-49","-19","-41","-24","-45","-32"];
+ const result = ["X", "X", "X", "X", "X", "X", "X", "X", "X", "X", undefined];
+ expect(HashingInsertion.run(chunker, { values: input, hashSize: SMALL_SIZE, name: "HashingDH" })).toEqual(result);
+ });
+ it('DH delete multiple elements during insertion in large table', () => {
+ const input = ["44", "26", "83", "49", "95", "22", "1", "84", "-83", "4", "31", "63", "33", "51", "-95", "-22", "47", "53", "68", "81", "72", "7", "23", "34", "32", "69", "6", "-34", "-81", "40", "5", "99", "52", "79", "14", "-40", "50", "15", "73", "86", "98", "70", "19", "39","61", "38", "96", "30", "54", "-86", "67", "12", "17", "62", "-73", "75", "80", "94", "82", "13", "85", "78", "45", "-67", "88", "24", "42","89", "28", "56", "21", "74", "92", "97", "27", "25", "3", "100", "43", "18", "91", "37", "93", "11", "87", "29", "8", "20", "16", "58", "9", "65", "10", "36", "60", "2", "66", "59", "64", "77", "57", "48", "76", "46", "55"];
+ const result = [97, 36, 72, 98, 47, "X", 100, 58, 94, 33, 69, 8, 44, 80, 19, 55, 91, 30, 66, 5, undefined, 77, 16, 52, 88, 27, 63, 99, 38, 74, 13, 49, 85, 24, 60, 96, 11, undefined, 10, 46, 82, 21, 57, 93, 32, 68, 7, 43, 79, 18, 54, undefined, 29, 65, 4, 2, 76, 15, 51, 87, 26, 62, 1, 37, "X", 12, 48, 84, 23, 59, "X", "X", 70, 9, 45, "X", 20, 56, 92, 31, "X", 6, 42, 78, 17, 53, 89, 28, 64, 3, 39, 75, 14, 50, "X", 25, 61];
+ expect(HashingInsertion.run(chunker, { values: input, hashSize: LARGE_SIZE, name: "HashingDH" })).toEqual(result);
+ });
+});
diff --git a/src/algorithms/controllers/tests/HashingInsertion.test.js b/src/algorithms/controllers/tests/HashingInsertion.test.js
new file mode 100644
index 000000000..a932418ea
--- /dev/null
+++ b/src/algorithms/controllers/tests/HashingInsertion.test.js
@@ -0,0 +1,60 @@
+/* The purpose of the test here is to detect whether the correct result is generated
+ under the legal input, not to test its robustness, because this is not considered
+ in the implementation process of the algorithm.
+*/
+
+/* eslint-disable no-undef */
+
+import { LARGE_SIZE, SMALL_SIZE } from '../HashingCommon';
+import HashingInsertion from '../HashingInsertion';
+
+// Simple stub for the chunker
+const chunker = {
+ add: () => {},
+};
+
+describe('HashingInsertion', () => {
+ // Test cases for Linear Probing
+ it('LP insert small table', () => {
+ const input = ["42", "87", "16", "59", "23", "74", "31", "5", "68", "90"];
+ const result = [undefined, 59, 74, 23, 16, 42, 31, 5, 87, 68, 90];
+ expect(HashingInsertion.run(chunker, { values: input, hashSize: SMALL_SIZE, name: "HashingLP" })).toEqual(result);
+ });
+ it('LP insert with duplicates', () => {
+ const input = ["14", "62", "14", "33", "57", "62", "85", "33"];
+ const result = [33, undefined, 85, undefined, undefined, undefined, 57, undefined, undefined, 14, 62];
+ expect(HashingInsertion.run(chunker, { values: input, hashSize: SMALL_SIZE, name: "HashingLP" })).toEqual(result);
+ });
+ it('LP insert with bulk insert', () => {
+ const input = ["14", "2-6-2", "8-10", "3"];
+ const result = [undefined, 4, 8, undefined, undefined, 9, 2, 6, 10, 14, 3];
+ expect(HashingInsertion.run(chunker, { values: input, hashSize: SMALL_SIZE, name: "HashingLP" })).toEqual(result);
+ });
+ it('LP insert large table', () => {
+ const input = ["1", "57", "84", "39", "12", "93", "66", "2", "48", "76", "35", "26", "49", "19", "87", "73", "62", "28", "17", "8", "94", "33", "70", "30", "11", "45", "38", "81", "15", "5", "60", "46", "32", "88", "27", "86", "69", "3", "54", "24", "77", "22", "72", "91", "41", "78", "25", "90", "34", "44", "52", "130", "196", "14", "23", "31", "42", "125"];
+ const result = [undefined, undefined, 72, 11, undefined, undefined, 22, undefined, 94, 33, 69, 8, 44, 130, 19, undefined, 91, 30, 66, 5, 41, 77, undefined, 52, 88, 27, undefined, 2, 38, 196, undefined, 49, undefined, 24, 60, undefined, 35, undefined, undefined, 46, undefined, undefined, 57, 93, 32, undefined, undefined, undefined, undefined, undefined, 54, 90, undefined, undefined, undefined, undefined, 76, 15, undefined, 87, 26, 62, 1, undefined, 73, 12, 48, 84, 23, undefined, undefined, 34, 70, undefined, 45, 81, undefined, undefined, undefined, 31, undefined, undefined, 42, 78, 17, undefined, undefined, 28, 125, 3, 39, undefined, 14, undefined, 86, 25, undefined];
+ expect(HashingInsertion.run(chunker, { values: input, hashSize: LARGE_SIZE, name: "HashingLP" })).toEqual(result);
+ });
+
+ // Test cases for Double Hashing
+ it('DH insert small table', () => {
+ const input = ["42", "87", "16", "59", "23", "74", "31", "5", "68", "90"];
+ const result = [undefined, 59, 74, 23, 16, 42, 68, 31, 87, 90, 5];
+ expect(HashingInsertion.run(chunker, { values: input, hashSize: SMALL_SIZE, name: "HashingDH" })).toEqual(result);
+ });
+ it('DH insert with duplicates', () => {
+ const input = ["14", "62", "14", "33", "57", "62", "85", "33"];
+ const result = [33, undefined, 85, undefined, undefined, undefined, 57, undefined, undefined, 14, 62];
+ expect(HashingInsertion.run(chunker, { values: input, hashSize: SMALL_SIZE, name: "HashingDH" })).toEqual(result);
+ });
+ it('DH insert with bulk insert', () => {
+ const input = ["14", "2-6-2", "8-10", "3"];
+ const result = [undefined, 4, 8, undefined, undefined, 9, 2, 6, 10, 14, 3];
+ expect(HashingInsertion.run(chunker, { values: input, hashSize: SMALL_SIZE, name: "HashingDH" })).toEqual(result);
+ });
+ it('DH insert large table', () => {
+ const input = ["1", "57", "84", "39", "12", "93", "66", "2", "48", "76", "35", "26", "49", "19", "87", "73", "62", "28", "17", "8", "94", "33", "70", "30", "11", "45", "38", "81", "15", "5", "60", "46", "32", "88", "27", "86", "69", "3", "54", "24", "77", "22", "72", "91", "41", "78", "25", "90", "34", "44", "52", "130", "196", "14", "23", "31", "42", "125"];
+ const result = [undefined, undefined, 72, 11, undefined, undefined, 22, undefined, 94, 33, 69, 8, 44, undefined, 19, undefined, 91, 30, 66, 5, 41, 77, 125, 52, 88, 27, undefined, 2, 38, undefined, undefined, 49, 130, 24, 60, undefined, 35, undefined, undefined, 46, undefined, 196, 57, 93, 32, undefined, undefined, undefined, undefined, undefined, 54, 90, undefined, undefined, undefined, undefined, 76, 15, undefined, 87, 26, 62, 1, undefined, 73, 12, 48, 84, 23, undefined, undefined, 34, 70, undefined, 45, 81, undefined, undefined, undefined, 31, undefined, undefined, 42, 78, 17, undefined, undefined, 28, undefined, 3, 39, undefined, 14, undefined, 86, 25, undefined];
+ expect(HashingInsertion.run(chunker, { values: input, hashSize: LARGE_SIZE, name: "HashingDH" })).toEqual(result);
+ });
+});
diff --git a/src/algorithms/controllers/tests/HashingSearch.test.js b/src/algorithms/controllers/tests/HashingSearch.test.js
new file mode 100644
index 000000000..bb36acd23
--- /dev/null
+++ b/src/algorithms/controllers/tests/HashingSearch.test.js
@@ -0,0 +1,148 @@
+/*
+The purpose of the test here is to detect whether the correct result is generated
+*/
+
+/* eslint-disable no-undef */
+
+import { LARGE_SIZE, SMALL_SIZE } from '../HashingCommon';
+import HashingInsertion from '../HashingInsertion';
+import HashingSearch from '../HashingSearch';
+
+// Simple stub for the chunker
+const chunker = {
+ add: () => {},
+};
+
+describe('HashingSearch', () => {
+ // Search tests for Linear Probing
+ it('Search small table found LP', () => {
+ const arr = ["x", 59, 74, 23, 16, 42, 31, 5, 87, 68, 90];
+ const target = 16;
+ const found = true;
+ const hashSize = SMALL_SIZE;
+ const visualisers = {
+ array: {
+ instance: {
+ extractArray(row = [1], empty = "x") {
+ return arr;
+ }
+ },
+ },
+ };
+ expect(HashingSearch.run(chunker, { name: "HashingLP", visualisers, target, hashSize })).toEqual(found);
+ });
+ it('Search small table not found LP', () => {
+ const arr = ["x", 59, 74, 23, 16, 42, 31, 5, 87, 68, 90];
+ const target = 20;
+ const found = false;
+ const hashSize = SMALL_SIZE;
+ const visualisers = {
+ array: {
+ instance: {
+ extractArray(row = [1], empty = "x") {
+ return arr;
+ }
+ },
+ },
+ };
+ expect(HashingSearch.run(chunker, { name: "HashingLP", visualisers, target, hashSize })).toEqual(found);
+ });
+ it('Search with duplicates LP', () => {
+ const arr = [14, 33, 62, 85, 33, "x", 57, "x", "x", 14, 62];
+ const target = 33;
+ const found = true;
+ const hashSize = SMALL_SIZE;
+ const visualisers = {
+ array: {
+ instance: {
+ extractArray(row = [1], empty = "x") {
+ return arr;
+ }
+ },
+ },
+ };
+ expect(HashingSearch.run(chunker, { name: "HashingLP", visualisers, target, hashSize })).toEqual(found);
+ });
+ it('Search large table LP', () => {
+ const arr = ["x", "x", 72, 11, "x", "x", 22, "x", 94, 33, 69, 8, 44, 130, 19, "x", 91, 30, 66, 5, 41, 77, "x", 52, 88, 27, "x", 2, 38, 196, "x", 49, "x", 24, 60, "x", 35, "x", "x", 46, "x", "x", 57, 93, 32, "x", "x", "x", "x", "x", 54, 90, "x", "x", "x", "x", 76, 15, "x", 87, 26, 62, 1, "x", 73, 12, 48, 84, 23, "x", "x", 34, 70, "x", 45, 81, "x", "x", "x", 31, "x", "x", 42, 78, 17, "x", "x", 28, 125, 3, 39, "x", 14, "x", 86, 25, "x"];
+ const target = 66;
+ const found = true;
+ const hashSize = LARGE_SIZE;
+ const visualisers = {
+ array: {
+ instance: {
+ extractArray(row = [1], empty = "x") {
+ return arr;
+ }
+ },
+ },
+ };
+ expect(HashingSearch.run(chunker, { name: "HashingLP", visualisers, target, hashSize })).toEqual(found);
+ });
+
+ // Search tests for Double Hashing
+ it('Search small table found DH', () => {
+ const arr = ["x", 59, 74, 23, 16, 42, 68, 31, 87, 90, 5];
+ const target = 16;
+ const found = true;
+ const hashSize = SMALL_SIZE;
+ const visualisers = {
+ array: {
+ instance: {
+ extractArray(row = [1], empty = "x") {
+ return arr;
+ }
+ },
+ },
+ };
+ expect(HashingSearch.run(chunker, { name: "HashingDH", visualisers, target, hashSize })).toEqual(found);
+ });
+ it('Search small table not found DH', () => {
+ const arr = ["x", 59, 74, 23, 16, 42, 68, 31, 87, 90, 5];
+ const target = 20;
+ const found = false;
+ const hashSize = SMALL_SIZE;
+ const visualisers = {
+ array: {
+ instance: {
+ extractArray(row = [1], empty = "x") {
+ return arr;
+ }
+ },
+ },
+ };
+ expect(HashingSearch.run(chunker, { name: "HashingDH", visualisers, target, hashSize })).toEqual(found);
+ });
+ it('Search with duplicates DH', () => {
+ const arr = [33, 14, 62, 33, 85, "x", 57, "x", "x", 14, 62];
+ const target = 33;
+ const found = true;
+ const hashSize = SMALL_SIZE;
+ const visualisers = {
+ array: {
+ instance: {
+ extractArray(row = [1], empty = "x") {
+ return arr;
+ }
+ },
+ },
+ };
+ expect(HashingSearch.run(chunker, { name: "HashingDH", visualisers, target, hashSize })).toEqual(found);
+ });
+ it('Search large table DH', () => {
+ const arr = [97, 36, "x", 11, "x", "x", 22, 58, 94, 33, "x", 8, "x", "x", 19, "x", 128, "x", 66, 5, "x", 77, 113, 59, 88, 27, "x", 2, 16, 171, "x", 24, "x", "x", "x", 96, 35, "x", 174, 143, 179, "x", "x", 93, "x", 74, 10, "x", "x", 18, 54, "x", "x", 65, 101, 137, 173, "x", 51, "x", 123, "x", 1, "x", "x", 12, 145, 84, 120, 156, 95, 34, 167, 106, 142, 178, "x", "x", "x", 31, 4, 200, 181, "x", "x", "x", 89, "x", "x", "x", 39, 75, "x", 186, "x", "x", 61];
+ const target = 66;
+ const found = true;
+ const hashSize = LARGE_SIZE;
+ const visualisers = {
+ array: {
+ instance: {
+ extractArray(row = [1], empty = "x") {
+ return arr;
+ }
+ },
+ },
+ };
+ expect(HashingSearch.run(chunker, { name: "HashingDH", visualisers, target, hashSize })).toEqual(found);
+ });
+});
diff --git a/src/algorithms/controllers/TTFTree.test.js b/src/algorithms/controllers/tests/TTFTree.test.js
similarity index 96%
rename from src/algorithms/controllers/TTFTree.test.js
rename to src/algorithms/controllers/tests/TTFTree.test.js
index 34f769a11..4b169113e 100644
--- a/src/algorithms/controllers/TTFTree.test.js
+++ b/src/algorithms/controllers/tests/TTFTree.test.js
@@ -1,7 +1,7 @@
/* eslint-disable no-undef */
-import TTFTreeInsertion from './TTFTreeInsertion';
-import TTFTreeSearch from './TTFTreeSearch';
-import VariableTreeNode from '../../components/DataStructures/Graph/NAryTreeTracer/NAryTreeVariable';
+import TTFTreeInsertion from '../TTFTreeInsertion';
+import TTFTreeSearch from '../TTFTreeSearch';
+import VariableTreeNode from '../../../components/DataStructures/Graph/NAryTreeTracer/NAryTreeVariable';
// simple stub for the chunker
const chunker = {
diff --git a/src/algorithms/controllers/binaryTreeInsertion.test.js b/src/algorithms/controllers/tests/binaryTreeInsertion.test.js
similarity index 96%
rename from src/algorithms/controllers/binaryTreeInsertion.test.js
rename to src/algorithms/controllers/tests/binaryTreeInsertion.test.js
index a53ba213d..c4914b913 100644
--- a/src/algorithms/controllers/binaryTreeInsertion.test.js
+++ b/src/algorithms/controllers/tests/binaryTreeInsertion.test.js
@@ -5,7 +5,7 @@
/* eslint-disable no-undef */
-import binaryTreeInsertion from './binaryTreeInsertion';
+import binaryTreeInsertion from '../binaryTreeInsertion';
// Simple stub for the chunker
const chunker = {
diff --git a/src/algorithms/controllers/binaryTreeSearch.test.js b/src/algorithms/controllers/tests/binaryTreeSearch.test.js
similarity index 96%
rename from src/algorithms/controllers/binaryTreeSearch.test.js
rename to src/algorithms/controllers/tests/binaryTreeSearch.test.js
index b55edebfb..27524bec5 100644
--- a/src/algorithms/controllers/binaryTreeSearch.test.js
+++ b/src/algorithms/controllers/tests/binaryTreeSearch.test.js
@@ -4,7 +4,7 @@
*/
/* eslint-disable no-undef */
-import binaryTreeSearch from './binaryTreeSearch';
+import binaryTreeSearch from '../binaryTreeSearch';
// Simple stub for the chunker
const chunker = {
add: () => {},
diff --git a/src/algorithms/controllers/heapSort.test.js b/src/algorithms/controllers/tests/heapSort.test.js
similarity index 97%
rename from src/algorithms/controllers/heapSort.test.js
rename to src/algorithms/controllers/tests/heapSort.test.js
index 3e7539555..c12f70e0f 100644
--- a/src/algorithms/controllers/heapSort.test.js
+++ b/src/algorithms/controllers/tests/heapSort.test.js
@@ -5,7 +5,7 @@
/* eslint-disable no-undef */
-import heapSort from './heapSort';
+import heapSort from '../heapSort';
// Simple stub for the chunker
const chunker = {
diff --git a/src/algorithms/controllers/prim.test.js b/src/algorithms/controllers/tests/prim.test.js
similarity index 98%
rename from src/algorithms/controllers/prim.test.js
rename to src/algorithms/controllers/tests/prim.test.js
index 8514c272f..afdf4afac 100644
--- a/src/algorithms/controllers/prim.test.js
+++ b/src/algorithms/controllers/tests/prim.test.js
@@ -5,7 +5,7 @@
/* eslint-disable no-undef */
-import prim from './prim';
+import prim from '../prim';
// Simple stub for the chunker
const chunker = {
diff --git a/src/algorithms/controllers/prim_old.test.js b/src/algorithms/controllers/tests/prim_old.test.js
similarity index 98%
rename from src/algorithms/controllers/prim_old.test.js
rename to src/algorithms/controllers/tests/prim_old.test.js
index 8514c272f..afdf4afac 100644
--- a/src/algorithms/controllers/prim_old.test.js
+++ b/src/algorithms/controllers/tests/prim_old.test.js
@@ -5,7 +5,7 @@
/* eslint-disable no-undef */
-import prim from './prim';
+import prim from '../prim';
// Simple stub for the chunker
const chunker = {
diff --git a/src/algorithms/controllers/quickSort.test.js b/src/algorithms/controllers/tests/quickSort.test.js
similarity index 97%
rename from src/algorithms/controllers/quickSort.test.js
rename to src/algorithms/controllers/tests/quickSort.test.js
index ed4ca5322..871a09380 100644
--- a/src/algorithms/controllers/quickSort.test.js
+++ b/src/algorithms/controllers/tests/quickSort.test.js
@@ -5,7 +5,7 @@
/* eslint-disable no-undef */
-import quickSort from './quickSort';
+import quickSort from '../quickSort';
// Simple stub for the chunker
const chunker = {
diff --git a/src/algorithms/controllers/transitiveClosure.test.js b/src/algorithms/controllers/tests/transitiveClosure.test.js
similarity index 87%
rename from src/algorithms/controllers/transitiveClosure.test.js
rename to src/algorithms/controllers/tests/transitiveClosure.test.js
index b7b02557f..7bbff06b1 100644
--- a/src/algorithms/controllers/transitiveClosure.test.js
+++ b/src/algorithms/controllers/tests/transitiveClosure.test.js
@@ -5,10 +5,10 @@
/* eslint-disable no-undef */
-import Array2DTracer from '../../components/DataStructures/Array/Array2DTracer';
-import GraphTracer from '../../components/DataStructures/Graph/GraphTracer';
-import Chunker from '../../context/chunker';
-import transitiveClosure from './transitiveClosure';
+import Array2DTracer from '../../../components/DataStructures/Array/Array2DTracer';
+import GraphTracer from '../../../components/DataStructures/Graph/GraphTracer';
+import Chunker from '../../../context/chunker';
+import transitiveClosure from '../transitiveClosure';
// Simple stub for the chunker
diff --git a/src/algorithms/controllers/unionFind.test.js b/src/algorithms/controllers/tests/unionFind.test.js
similarity index 96%
rename from src/algorithms/controllers/unionFind.test.js
rename to src/algorithms/controllers/tests/unionFind.test.js
index 30016a0a5..f9f9f2e02 100644
--- a/src/algorithms/controllers/unionFind.test.js
+++ b/src/algorithms/controllers/tests/unionFind.test.js
@@ -5,8 +5,8 @@
/* eslint-disable no-undef */
-import unionFindUnion from './unionFindUnion';
-import unionFindFind from './unionFindFind';
+import unionFindUnion from '../unionFindUnion';
+import unionFindFind from '../unionFindFind';
// simple stub for the chunker
const chunker = {
diff --git a/src/algorithms/explanations/HashingExp.md b/src/algorithms/explanations/HashingExp.md
new file mode 100644
index 000000000..2865677a1
--- /dev/null
+++ b/src/algorithms/explanations/HashingExp.md
@@ -0,0 +1,44 @@
+# Hashing
+
+---
+
+### Hashing Introduction
+
+Hashing is a popular method for storing and looking up records. This
+module visualises a data structure called a hash table, which utilises
+hashing to enable quick insertions, searches and deletion.
+
+Hashing is based on the transformation of the record key via a
+'Hashing Function' into a table address.
+
+For hashing to be efficient, the hashed keys should spread out over the
+table evenly and avoid clusters from forming. To achieve this, the
+hash function should use as much of the key as possible, and the hashed
+key and the table size should be relatively prime.
+* For the small table we have chosen 11
+* For the larger table we have chosen 97
+
+### Collision
+
+Even in sparse tables, sometimes two keys will hash to the same value.
+Provisions must be taken to resolve these collisions. Two commonly used
+methods for collision resolution are depicted in this module:
+* Linear Probing
+ * Checks the next available slot sequentially using step size 1
+* Double Hashing
+ * Uses a secondary hashing function to determine the sequential step size
+
+### Time complexity and Rehashing
+
+Hashing allows for very fast data retrieval, often achieving an O(1)
+time complexity in the average cases for insertion, searches and
+deletions. However this performance degrades quite dramatically as
+the table starts to get full, particularly for unsuccessful searches
+which can effectively search the whole table.
+
+Due to this issue it is necessary to keep track of the number of records
+in the table, and to make sure this is well below the table size. One
+tactic used is to increase the table size every time the number of
+records gets above the capacity. This strategy is called rehashing, and
+it involves transforming existing keys in the table using an updated
+hash function and have their new keys relocated to a larger table.
\ No newline at end of file
diff --git a/src/algorithms/explanations/index.js b/src/algorithms/explanations/index.js
index 6345de4de..9b79e343b 100644
--- a/src/algorithms/explanations/index.js
+++ b/src/algorithms/explanations/index.js
@@ -19,3 +19,4 @@ export { default as ASTARExp } from './ASTExp.md';
export { default as BFSExp } from './BFSExp.md';
export { default as DFSExp } from './DFSExp.md';
export { default as DFSrecExp } from './DFSrecExp.md';
+export { default as HashingExp } from './HashingExp.md';
diff --git a/src/algorithms/extra-info/HashingInfo.md b/src/algorithms/extra-info/HashingInfo.md
new file mode 100644
index 000000000..bdbb8895c
--- /dev/null
+++ b/src/algorithms/extra-info/HashingInfo.md
@@ -0,0 +1,44 @@
+
+
+## Extra Info
+
+-----
+
+### For a comprehensive explanation of Hashing algorithm:
+
+Geeks for Geeks Hashing introductory video:
+
+VIDEO
+
+### Extra Reading:
+
+Geeks for Geeks Hashing Links:
+
+* [**Hashing**][G4GHashing]
+* [**Linear Probing**][G4GLP]
+* [**Double Hashing**][G4GDH]
+
+### Real-life appliation of Hashing
+
+Geeks for Geeks: [**Hashing Applications**][G4GApplication]
+
+[G4GHashing]: "https://www.geeksforgeeks.org/hashing-data-structure/"
+[G4GLP]: "https://www.geeksforgeeks.org/implementing-hash-table-open-addressing-linear-probing-cpp/"
+[G4GDH]: "https://www.geeksforgeeks.org/double-hashing/"
+[G4GApplication]: "https://www.geeksforgeeks.org/applications-of-hashing/"
+
diff --git a/src/algorithms/extra-info/index.js b/src/algorithms/extra-info/index.js
index 2bbee1d41..f69fee4a7 100644
--- a/src/algorithms/extra-info/index.js
+++ b/src/algorithms/extra-info/index.js
@@ -19,3 +19,5 @@ export { default as ASTARInfo } from './ASTInfo.md';
export { default as BFSInfo } from './BFSInfo.md';
export { default as DFSInfo } from './DFSInfo.md';
export { default as DFSrecInfo } from './DFSrecInfo.md';
+export { default as HashingInfo } from './HashingInfo.md';
+
diff --git a/src/algorithms/index.js b/src/algorithms/index.js
index b1170ee47..1ef7fb742 100644
--- a/src/algorithms/index.js
+++ b/src/algorithms/index.js
@@ -31,7 +31,7 @@ import * as Instructions from './instructions';
// Also: the key for the algorithm MUST be the same as the "name"
// of the top level Param block returned by the parameter function.
// Eg, parameters/msort_arr_td.js has
-//
+//
// function MergesortParam() {
// ...
// return (
@@ -163,6 +163,58 @@ const allalgs = {
search: Controller.TTFTreeSearch,
},
},
+
+ 'HashingLP': {
+ name: 'Hashing (Linear Probing)',
+ category: 'Insert/Search',
+ param: ,
+ instructions: Instructions.HashingLPDHInstruction,
+ explanation: Explanation.HashingExp,
+ extraInfo: ExtraInfo.HashingInfo,
+ pseudocode: {
+ insertion: Pseudocode.linearProbing,
+ search: Pseudocode.linearSearch,
+ },
+ controller: {
+ insertion: Controller.HashingInsertion,
+ search: Controller.HashingSearch,
+ },
+ },
+
+ 'HashingDH': {
+ name: 'Hashing (Double Hashing)',
+ category: 'Insert/Search',
+ param: ,
+ instructions: Instructions.HashingLPDHInstruction,
+ explanation: Explanation.HashingExp,
+ extraInfo: ExtraInfo.HashingInfo,
+ pseudocode: {
+ insertion: Pseudocode.doubleHashing,
+ search: Pseudocode.doubleSearch,
+ },
+ controller: {
+ insertion: Controller.HashingInsertion,
+ search: Controller.HashingSearch,
+ },
+ },
+
+ 'HashingCH': {
+ name: 'Hashing (Chaining)',
+ category: 'Insert/Search',
+ param: ,
+ instructions: Instructions.HashingCHInstruction,
+ explanation: Explanation.HashingExp,
+ extraInfo: ExtraInfo.HashingInfo,
+ pseudocode: {
+ insertion: Pseudocode.chaining,
+ search: Pseudocode.chainingSearch,
+ },
+ controller: {
+ insertion: Controller.HashingChainingInsertion,
+ search: Controller.HashingSearch,
+ },
+ },
+
'AVLTree': {
name: 'AVL Tree',
category: 'Insert/Search',
@@ -235,7 +287,7 @@ const allalgs = {
find: Controller.dijkstra,
},
- },
+ },
'aStar': {
name: 'A* (heuristic search)',
category: 'Graph',
@@ -250,7 +302,7 @@ const allalgs = {
find: Controller.AStar,
},
- },
+ },
'prim': {
noDeploy: false,
name: 'Prim\'s (min. spanning tree)',
diff --git a/src/algorithms/instructions/index.js b/src/algorithms/instructions/index.js
index 3f8747272..8460afdfd 100644
--- a/src/algorithms/instructions/index.js
+++ b/src/algorithms/instructions/index.js
@@ -13,9 +13,10 @@ const KEY_UF_UNION = 'UNION';
const KEY_UF_FIND = 'FIND';
const KEY_UF_PC_ON = 'ON';
const KEY_UF_PC_OFF = 'OFF';
+const KEY_INSDEL = 'INSERT/DELETE';
export const KEY_WORDS = [
- KEY_CODE, KEY_INSERT, KEY_PLAY, KEY_SEARCH, KEY_SORT, KEY_LOAD,
+ KEY_CODE, KEY_INSERT, KEY_PLAY, KEY_SEARCH, KEY_SORT, KEY_LOAD, KEY_INSDEL
];
const bstInstructions = [
@@ -51,6 +52,75 @@ const stringInstructions = [{
],
}];
+const hashingInstructions1 = [
+ {
+ title: 'Insert/Delete Mode',
+ content: [
+ `Click on ${KEY_CODE} on the right panel.`,
+ `Select small or larger table via the radio buttons.`,
+ `Enter a comma separated list of integers into the Insert parameter.
+ There should be less than 11 integers if it is a small table, and less than 97 if it is a large table.
+
+ **Valid inputs**:
+
+ - x : Insert x into table.
+ - x - y: Bulk insert from integers x to y.
+ - x - y - z: Bulk insert from integers x to y in steps of z.
+ - -x: Delete x from table.
+
+ Only for small table, if you wish to input more integers, select the Expand radio button.
+ The table will now expand after reaching 80% capacity until it reaches 97 slots, after which it will
+ stop at one slot left`,
+
+ `Click on ${KEY_INSERT} to enter Insert mode and load the algorithm.`,
+ `Click on ${KEY_PLAY} to watch the algorithm run. The speed may be adjusted using the speed slider.`,
+ ],
+ },
+ {
+ title: 'Search Mode',
+ content: [
+ 'Make sure table has inserted values before searching.',
+ `Click on ${KEY_CODE} on the right panel.`,
+ 'Enter an Integer in the Search parameter.',
+ `Click on ${KEY_SEARCH} to enter Search mode and load the algorithm.`,
+ `Click on ${KEY_PLAY} to watch the algorithm run. The speed may be adjusted using the speed slider.`,
+ ],
+ },
+];
+
+const hashingInstructions2 = [
+ {
+ title: 'Insert/Delete Mode',
+ content: [
+ `Click on ${KEY_CODE} on the right panel.`,
+ `Select small or larger table via the radio buttons.`,
+ `Enter a comma separated list of integers into the Insert parameter.
+
+ **Valid inputs**:
+
+ - x : Insert x into table.
+ - x - y: Bulk insert from integers x to y.
+ - x - y - z: Bulk insert from integers x to y in steps of z.
+ - -x: Delete x from table.
+
+ You can hover over a slot to see the chain when you see a .. in the slot`,
+
+ `Click on ${KEY_INSERT} to enter Insert mode and load the algorithm.`,
+ `Click on ${KEY_PLAY} to watch the algorithm run. The speed may be adjusted using the speed slider.`,
+ ],
+ },
+ {
+ title: 'Search Mode',
+ content: [
+ 'Make sure table has inserted values before searching.',
+ `Click on ${KEY_CODE} on the right panel.`,
+ 'Enter an Integer in the Search parameter.',
+ `Click on ${KEY_SEARCH} to enter Search mode and load the algorithm.`,
+ `Click on ${KEY_PLAY} to watch the algorithm run. The speed may be adjusted using the speed slider.`,
+ ],
+ },
+];
+
const sortInstructions = [{
title: 'Sorting Numbers',
content: [
@@ -129,3 +199,5 @@ export const ASTARInstruction = graphInstructions;
export const BFSInstruction = graphInstructions;
export const DFSInstruction = graphInstructions;
export const DFSrecInstruction = graphInstructions;
+export const HashingLPDHInstruction = hashingInstructions1;
+export const HashingCHInstruction = hashingInstructions2;
diff --git a/src/algorithms/parameters/HashingCHParam.js b/src/algorithms/parameters/HashingCHParam.js
new file mode 100644
index 000000000..03c1b37ef
--- /dev/null
+++ b/src/algorithms/parameters/HashingCHParam.js
@@ -0,0 +1,223 @@
+import React, { useState, useContext, useEffect } from 'react';
+import { GlobalContext } from '../../context/GlobalState';
+import { GlobalActions } from '../../context/actions';
+import FormControlLabel from '@mui/material/FormControlLabel';
+import Radio from '@mui/material/Radio';
+import { withStyles } from '@mui/styles';
+import ListParam from './helpers/ListParam';
+import SingleValueParam from './helpers/SingleValueParam';
+import '../../styles/Param.scss';
+import {
+ genUniqueRandNumList,
+ singleNumberValidCheck,
+ successParamMsg,
+ errorParamMsg,
+ commaSeparatedPairTripleCheck,
+ checkAllRangesValid,
+} from './helpers/ParamHelper';
+import { SMALL_SIZE, LARGE_SIZE } from '../controllers/HashingCommon';
+
+// Algotiyhm information and magic phrases
+const ALGORITHM_NAME = 'Hashing (linear probing)';
+const HASHING_INSERT = 'Hashing Insertion';
+const HASHING_SEARCH = 'Hashing Search';
+const HASHING_EXAMPLE = 'PLACE HOLDER ERROR MESSAGE';
+
+// Default inputs
+const DEFAULT_ARRAY = genUniqueRandNumList(10, 1, 50);
+const DEFAULT_SEARCH = 2
+
+const UNCHECKED = {
+ smallTable: false,
+ largeTable: false
+};
+
+// Styling of radio buttons
+const BlueRadio = withStyles({
+ root: {
+ color: '#2289ff',
+ '&$checked': {
+ color: '#027aff',
+ },
+ },
+ checked: {},
+ // eslint-disable-next-line react/jsx-props-no-spreading
+})((props) => )
+
+// Error messages
+const ERROR_INVALID_INPUT_INSERT = 'Please enter a list containing positive integers, pairs or triples';
+const ERROR_INVALID_INPUT_SEARCH = 'Please enter a positive integer';
+const ERROR_TOO_LARGE = `Please enter the right amount of inputs`;
+const ERROR_INVALID_RANGES = 'If you had entered ranges, please input valid ranges'
+
+/**
+ * Chaining input component
+ * @returns the component
+ */
+function HashingCHParam() {
+ const [message, setMessage] = useState(null);
+ const { algorithm, dispatch } = useContext(GlobalContext);
+ const [array, setArray] = useState(DEFAULT_ARRAY);
+ const [search, setSearch] = useState(DEFAULT_SEARCH);
+ const [HASHSize, setHashSize] = useState({
+ smallTable: true,
+ largeTable: false,
+ });
+
+ /**
+ * Handle changes to input
+ * @param {*} e the input box component
+ */
+ const handleChange = (e) => {
+ setHashSize({ ...UNCHECKED, [e.target.name]: true })
+ }
+
+ /**
+ * Handle insert box inputs
+ * @param {*} e the insert box component
+ */
+ const handleInsertion = (e) => {
+ e.preventDefault();
+ const inputs = e.target[0].value; // Get the value of the input
+
+ let removeSpace = inputs.split(' ').join('');
+
+ // Check if the inputs are either positive integers, pairs or triples
+ if (commaSeparatedPairTripleCheck(true, true, removeSpace)) {
+ let values = removeSpace.split(","); // Converts input to array
+ if (checkAllRangesValid(values)) {
+ let hashSize = HASHSize.smallTable ? SMALL_SIZE : LARGE_SIZE; // Table size
+
+ // Dispatch algo
+ dispatch(GlobalActions.RUN_ALGORITHM, {
+ name: 'HashingCH',
+ mode: 'insertion',
+ hashSize: hashSize,
+ values,
+ expand: false
+ });
+ setMessage(successParamMsg(ALGORITHM_NAME));
+ }
+ else {
+ setMessage(errorParamMsg(ALGORITHM_NAME, ERROR_INVALID_RANGES));
+ }
+ } else {
+ setMessage(errorParamMsg(ALGORITHM_NAME, ERROR_INVALID_INPUT_INSERT));
+ }
+ }
+
+ /**
+ * Handle search box input
+ * @param {*} e search box component
+ */
+ const handleSearch = (e) => {
+ e.preventDefault();
+ const inputValue = e.target[0].value;
+ let hashSize = HASHSize.smallTable ? SMALL_SIZE : LARGE_SIZE; // Table size
+
+ const visualisers = algorithm.chunker.visualisers; // Visualizers from insertion
+ if (singleNumberValidCheck(inputValue)) { // Check if input is a single positive number
+ const target = parseInt(inputValue);
+
+ // Dispatch algorithm
+ dispatch(GlobalActions.RUN_ALGORITHM, {
+ name: 'HashingCH',
+ mode: 'search',
+ hashSize: hashSize,
+ visualisers,
+ target
+ });
+ setMessage(successParamMsg(ALGORITHM_NAME));
+ } else {
+ setMessage(errorParamMsg(ALGORITHM_NAME, ERROR_INVALID_INPUT_SEARCH));
+ }
+ }
+
+ // Use effect to detect changes in radio box choice
+ useEffect(
+ () => {
+ document.getElementById('startBtnGrp').click();
+ },
+ [HASHSize],
+ );
+
+
+ return (
+ <>
+
+ {
+ if (HASHSize.smallTable) {
+ return () => genUniqueRandNumList(SMALL_SIZE-1, 1, 50);
+ }
+ else if(HASHSize.largeTable) {
+ return () => genUniqueRandNumList(LARGE_SIZE-1, 1, 100);
+ }
+ })()
+ }
+ ALGORITHM_NAME = {HASHING_INSERT}
+ EXAMPLE={HASHING_EXAMPLE}
+ handleSubmit={handleInsertion}
+ setMessage={setMessage}
+ />
+
+
+ { }
+
+
+
+
+
+ }
+ label="Small Table"
+ className="checkbox"
+ />
+
+ }
+ label="Larger Table"
+ className="checkbox"
+ />
+
+
+
+ {/* render success/error message */}
+ {message}
+ >
+ );
+}
+
+export default HashingCHParam;
diff --git a/src/algorithms/parameters/HashingDHParam.js b/src/algorithms/parameters/HashingDHParam.js
new file mode 100644
index 000000000..30418a9be
--- /dev/null
+++ b/src/algorithms/parameters/HashingDHParam.js
@@ -0,0 +1,279 @@
+import PropTypes from 'prop-types';
+import { withAlgorithmParams } from './helpers/urlHelpers'
+
+import { URLContext } from '../../context/urlState.js';
+
+import React, { useState, useContext, useEffect } from 'react';
+import { GlobalContext } from '../../context/GlobalState';
+import { GlobalActions } from '../../context/actions';
+import FormControlLabel from '@mui/material/FormControlLabel';
+import Radio from '@mui/material/Radio';
+import { withStyles } from '@mui/styles';
+import ListParam from './helpers/ListParam';
+import SingleValueParam from './helpers/SingleValueParam';
+import '../../styles/Param.scss';
+import {
+ genUniqueRandNumList,
+ singleNumberValidCheck,
+ successParamMsg,
+ errorParamMsg,
+ commaSeparatedPairTripleCheck,
+ checkAllRangesValid
+} from './helpers/ParamHelper';
+import { SMALL_SIZE, LARGE_SIZE } from '../controllers/HashingCommon';
+
+// Algotiyhm information and magic phrases
+const ALGORITHM_NAME = 'Hashing (double hashing)';
+const HASHING_INSERT = 'Hashing Insertion';
+const HASHING_SEARCH = 'Hashing Search';
+const HASHING_EXAMPLE = 'PLACE HOLDER ERROR MESSAGE';
+
+// Default inputs
+const DEFAULT_ARRAY = genUniqueRandNumList(10, 1, 50);
+const DEFAULT_SEARCH = 2
+
+const UNCHECKED = {
+ smallTable: false,
+ largeTable: false
+};
+
+const DEFAULT_EXPAND = false;
+
+// Styling of radio buttons
+const BlueRadio = withStyles({
+ root: {
+ color: '#2289ff',
+ '&$checked': {
+ color: '#027aff',
+ },
+ },
+ checked: {},
+ // eslint-disable-next-line react/jsx-props-no-spreading
+})((props) => )
+
+// Error messages
+const ERROR_INVALID_INPUT_INSERT = 'Please enter a list containing positive integers, pairs or triples';
+const ERROR_INVALID_INPUT_SEARCH = 'Please enter a positive integer';
+const ERROR_TOO_LARGE = `Please enter the right amount of inputs`;
+const ERROR_INVALID_RANGES = 'If you had entered ranges, please input valid ranges'
+
+/**
+ * Double Hashing input component
+ * @returns the component
+ */
+function HashingDHParam({ mode, list, value }) {
+ const [message, setMessage] = useState(null);
+ const { algorithm, dispatch } = useContext(GlobalContext);
+ const [array, setLocalArray] = useState(list || DEFAULT_ARRAY);
+ const [search, setLocalSearch] = useState(DEFAULT_SEARCH);
+ const [HASHSize, setHashSize] = useState({
+ smallTable: true,
+ largeTable: false,
+ });
+
+ const [expand, setExpand] = useState(DEFAULT_EXPAND);
+ const { setNodes, setSearchValue } = useContext(URLContext);
+
+ useEffect(() => {
+ setNodes(array);
+ setSearchValue(search);
+ }, [array, search])
+
+ /**
+ * Handle changes to input
+ * @param {*} e the input box component
+ */
+ const handleChange = (e) => {
+ setHashSize({ ...UNCHECKED, [e.target.name]: true })
+ }
+
+ /**
+ * Handle changes to input
+ * @param {*} e the input box component
+ */
+ const handleExpand = (e) => {
+ setExpand(!expand)
+ }
+
+ /**
+ * Handle insert box inputs
+ * @param {*} e the insert box component
+ */
+ const handleInsertion = (e) => {
+ e.preventDefault();
+ const inputs = e.target[0].value; // Get the value of the input
+
+ let removeSpace = inputs.split(' ').join('');
+
+
+ // Check if the inputs are either positive integers, pairs or triples
+ if (commaSeparatedPairTripleCheck(true, true, removeSpace)) {
+ let values = removeSpace.split(","); // Converts input to array
+ if (checkAllRangesValid(values)) {
+ let hashSize = HASHSize.smallTable ? SMALL_SIZE : LARGE_SIZE; // Table size
+
+ // Dispatch algo
+ dispatch(GlobalActions.RUN_ALGORITHM, {
+ name: 'HashingDH',
+ mode: 'insertion',
+ hashSize: hashSize,
+ values,
+ expand: expand
+ });
+ setMessage(successParamMsg(ALGORITHM_NAME));
+ }
+ else {
+ setMessage(errorParamMsg(ALGORITHM_NAME, ERROR_INVALID_RANGES));
+ }
+ } else {
+ setMessage(errorParamMsg(ALGORITHM_NAME, ERROR_INVALID_INPUT_INSERT));
+ }
+ }
+
+ /**
+ * Handle search box input
+ * @param {*} e search box component
+ */
+ const handleSearch = (e) => {
+ e.preventDefault();
+ const inputValue = e.target[0].value;
+ let hashSize = HASHSize.smallTable ? SMALL_SIZE : LARGE_SIZE; // Table size
+
+ const visualisers = algorithm.chunker.visualisers; // Visualizers from insertion
+ if (singleNumberValidCheck(inputValue)) { // Check if input is a single positive number
+ const target = parseInt(inputValue);
+
+ // Dispatch algorithm
+ dispatch(GlobalActions.RUN_ALGORITHM, {
+ name: 'HashingDH',
+ mode: 'search',
+ hashSize: hashSize,
+ visualisers,
+ target
+ });
+ setMessage(successParamMsg(ALGORITHM_NAME));
+ } else {
+ setMessage(errorParamMsg(ALGORITHM_NAME, ERROR_INVALID_INPUT_SEARCH));
+ }
+ }
+
+ // Use effect to detect changes in radio box choice
+ useEffect(
+ () => {
+ document.getElementById('startBtnGrp').click();
+ },
+ [HASHSize],
+ );
+
+ // Use effect to detect changes in expand radio box choice
+ useEffect(
+ () => {
+ document.getElementById('startBtnGrp').click();
+ },
+ [expand],
+ );
+
+
+ return (
+ <>
+
+ {
+ if (HASHSize.smallTable) {
+ return () => genUniqueRandNumList(SMALL_SIZE-1, 1, 50);
+ }
+ else if(HASHSize.largeTable) {
+ return () => genUniqueRandNumList(LARGE_SIZE-1, 1, 100);
+ }
+ })()
+ }
+ ALGORITHM_NAME = {HASHING_INSERT}
+ EXAMPLE={HASHING_EXAMPLE}
+ handleSubmit={handleInsertion}
+ setMessage={setMessage}
+ />
+
+
+ { }
+
+
+
+
+
+ }
+ label="Small Table"
+ className="checkbox"
+ />
+
+ }
+ label="Larger Table"
+ className="checkbox"
+ />
+
+
+
+
+ {HASHSize.smallTable && (
+
+ }
+ label="Expand"
+ className="checkbox"
+ />
+ )}
+
+
+
+ {/* render success/error message */}
+ {message}
+ >
+ );
+}
+
+// Define the prop types for URL Params
+HashingDHParam.propTypes = {
+ alg: PropTypes.string.isRequired, // keep alg for all algorithms
+ mode: PropTypes.string.isRequired, //keep mode for all algorithms
+ list: PropTypes.string.isRequired,
+ value: PropTypes.string.isRequired
+ };
+export default withAlgorithmParams(HashingDHParam);
+
diff --git a/src/algorithms/parameters/HashingLPParam.js b/src/algorithms/parameters/HashingLPParam.js
new file mode 100644
index 000000000..35ff04250
--- /dev/null
+++ b/src/algorithms/parameters/HashingLPParam.js
@@ -0,0 +1,277 @@
+import PropTypes from 'prop-types';
+import { withAlgorithmParams } from './helpers/urlHelpers'
+
+import { URLContext } from '../../context/urlState.js';
+
+import React, { useState, useContext, useEffect } from 'react';
+import { GlobalContext } from '../../context/GlobalState';
+import { GlobalActions } from '../../context/actions';
+import FormControlLabel from '@mui/material/FormControlLabel';
+import Radio from '@mui/material/Radio';
+import { withStyles } from '@mui/styles';
+import ListParam from './helpers/ListParam';
+import SingleValueParam from './helpers/SingleValueParam';
+import '../../styles/Param.scss';
+import {
+ genUniqueRandNumList,
+ singleNumberValidCheck,
+ successParamMsg,
+ errorParamMsg,
+ commaSeparatedPairTripleCheck,
+ checkAllRangesValid,
+} from './helpers/ParamHelper';
+import { SMALL_SIZE, LARGE_SIZE } from '../controllers/HashingCommon';
+
+// Algotiyhm information and magic phrases
+const ALGORITHM_NAME = 'Hashing (linear probing)';
+const HASHING_INSERT = 'Hashing Insertion';
+const HASHING_SEARCH = 'Hashing Search';
+const HASHING_EXAMPLE = 'PLACE HOLDER ERROR MESSAGE';
+
+// Default inputs
+const DEFAULT_ARRAY = genUniqueRandNumList(10, 1, 50);
+const DEFAULT_SEARCH = 2
+
+const UNCHECKED = {
+ smallTable: false,
+ largeTable: false
+};
+
+const DEFAULT_EXPAND = false;
+
+// Styling of radio buttons
+const BlueRadio = withStyles({
+ root: {
+ color: '#2289ff',
+ '&$checked': {
+ color: '#027aff',
+ },
+ },
+ checked: {},
+ // eslint-disable-next-line react/jsx-props-no-spreading
+})((props) => )
+
+// Error messages
+const ERROR_INVALID_INPUT_INSERT = 'Please enter a list containing positive integers, pairs or triples';
+const ERROR_INVALID_INPUT_SEARCH = 'Please enter a positive integer';
+const ERROR_TOO_LARGE = `Please enter the right amount of inputs`;
+const ERROR_INVALID_RANGES = 'If you had entered ranges, please input valid ranges'
+
+/**
+ * Linear probing input component
+ * @returns the component
+ */
+function HashingLPParam({ mode, list, value }) {
+ const [message, setMessage] = useState(null);
+ const { algorithm, dispatch } = useContext(GlobalContext);
+ const [array, setLocalArray] = useState(list || DEFAULT_ARRAY);
+ const [search, setLocalSearch] = useState(DEFAULT_SEARCH);
+ const [HASHSize, setHashSize] = useState({
+ smallTable: true,
+ largeTable: false,
+ });
+ const [expand, setExpand] = useState(DEFAULT_EXPAND);
+ const { setNodes, setSearchValue } = useContext(URLContext);
+
+ useEffect(() => {
+ setNodes(array);
+ setSearchValue(search);
+ }, [array, search])
+
+
+ /**
+ * Handle changes to input
+ * @param {*} e the input box component
+ */
+ const handleChange = (e) => {
+ setHashSize({ ...UNCHECKED, [e.target.name]: true })
+ }
+
+ /**
+ * Handle expand
+ * @param {*} e the input box component
+ */
+ const handleExpand = (e) => {
+ setExpand(!expand)
+ }
+
+ /**
+ * Handle insert box inputs
+ * @param {*} e the insert box component
+ */
+ const handleInsertion = (e) => {
+ e.preventDefault();
+ const inputs = e.target[0].value; // Get the value of the input
+
+ let removeSpace = inputs.split(' ').join('');
+
+ // Check if the inputs are either positive integers, pairs or triples
+ if (commaSeparatedPairTripleCheck(true, true, removeSpace)) {
+ let values = removeSpace.split(","); // Converts input to array
+ if (checkAllRangesValid(values)) {
+ let hashSize = HASHSize.smallTable ? SMALL_SIZE : LARGE_SIZE; // Table size
+
+ // Dispatch algo
+ dispatch(GlobalActions.RUN_ALGORITHM, {
+ name: 'HashingLP',
+ mode: 'insertion',
+ hashSize: hashSize,
+ values,
+ expand: expand
+ });
+ setMessage(successParamMsg(ALGORITHM_NAME));
+ }
+ else {
+ setMessage(errorParamMsg(ALGORITHM_NAME, ERROR_INVALID_RANGES));
+ }
+ } else {
+ setMessage(errorParamMsg(ALGORITHM_NAME, ERROR_INVALID_INPUT_INSERT));
+ }
+ }
+
+ /**
+ * Handle search box input
+ * @param {*} e search box component
+ */
+ const handleSearch = (e) => {
+ e.preventDefault();
+ const inputValue = e.target[0].value;
+ let hashSize = HASHSize.smallTable ? SMALL_SIZE : LARGE_SIZE; // Table size
+
+ const visualisers = algorithm.chunker.visualisers; // Visualizers from insertion
+ if (singleNumberValidCheck(inputValue)) { // Check if input is a single positive number
+ const target = parseInt(inputValue);
+
+ // Dispatch algorithm
+ dispatch(GlobalActions.RUN_ALGORITHM, {
+ name: 'HashingLP',
+ mode: 'search',
+ hashSize: hashSize,
+ visualisers,
+ target
+ });
+ setMessage(successParamMsg(ALGORITHM_NAME));
+ } else {
+ setMessage(errorParamMsg(ALGORITHM_NAME, ERROR_INVALID_INPUT_SEARCH));
+ }
+ }
+
+ // Use effect to detect changes in radio box choice
+ useEffect(
+ () => {
+ document.getElementById('startBtnGrp').click();
+ },
+ [HASHSize],
+ );
+
+ // Use effect to detect changes in expand radio box choice
+ useEffect(
+ () => {
+ document.getElementById('startBtnGrp').click();
+ },
+ [expand],
+ );
+
+
+ return (
+ <>
+
+ {
+ if (HASHSize.smallTable) {
+ return () => genUniqueRandNumList(SMALL_SIZE-1, 1, 50);
+ }
+ else if(HASHSize.largeTable) {
+ return () => genUniqueRandNumList(LARGE_SIZE-1, 1, 100);
+ }
+ })()
+ }
+ ALGORITHM_NAME = {HASHING_INSERT}
+ EXAMPLE={HASHING_EXAMPLE}
+ handleSubmit={handleInsertion}
+ setMessage={setMessage}
+ />
+
+
+ { }
+
+
+
+
+
+ }
+ label="Small Table"
+ className="checkbox"
+ />
+
+ }
+ label="Larger Table"
+ className="checkbox"
+ />
+
+
+
+
+ {HASHSize.smallTable && (
+
+ }
+ label="Expand"
+ className="checkbox"
+ />
+ )}
+
+
+
+ {/* render success/error message */}
+ {message}
+ >
+ );
+}
+
+// Define the prop types for URL Params
+HashingLPParam.propTypes = {
+ alg: PropTypes.string.isRequired, // keep alg for all algorithms
+ mode: PropTypes.string.isRequired, //keep mode for all algorithms
+ list: PropTypes.string.isRequired,
+ value: PropTypes.string.isRequired
+ };
+export default withAlgorithmParams(HashingLPParam);
diff --git a/src/algorithms/parameters/helpers/ParamHelper.js b/src/algorithms/parameters/helpers/ParamHelper.js
index 2b827c2ae..c0c7c9d1f 100644
--- a/src/algorithms/parameters/helpers/ParamHelper.js
+++ b/src/algorithms/parameters/helpers/ParamHelper.js
@@ -441,3 +441,77 @@ export const shuffleArray = (array) => {
}
return array;
};
+
+/**
+ * Check if the input string are comma-separated numbers, pairs and triples
+ * @param {*} allowPosInteger is a toggle, if true it allows positive integers
+ * @param {*} allowNegInteger is a toggle, if true it allows negative integers
+ * @param {*} input the input string
+ * @returns whether the check is true
+ */
+export const commaSeparatedPairTripleCheck = (allowPosInteger, allowNegInteger, input) => {
+ const regex_pos_num = /^[0-9]+(-[0-9]+){0,2}$/g;
+ const regex_all_num = /^[0-9]+(-[0-9]+){0,2}$|^-[0-9]+$/g;
+ const regex_no_num = /^[0-9]+(-[0-9]+){1,2}$/g;
+ let array = input.split(",");
+ for (let item of array) {
+ if (!item.match(allowPosInteger ? (allowNegInteger ? regex_all_num : regex_pos_num) : regex_no_num)) return false;
+ }
+ return true;
+}
+
+/**
+ * return an array of number according to the range specified
+ * @param {*} str the string of range, e.g."2-7-4", "2-5"
+ * @param {*} mode "Array" or "Count", return array of inputs or count of inputs, respectively (delete "Count" returns -1 and "Array return array of that negative number")
+ * @returns the array of number in that range
+ */
+export const translateInput = (str, mode) => {
+ let arr = str.split("-");
+ switch (mode) {
+ case "Count":
+ if (arr.length == 1) return 1;
+ else if (arr.length == 2) {
+ if (arr[0] === "") return 0;
+ else return arrayRange(Number(arr[0]), Number(arr[1]), 1).length;
+ }
+ else if (arr.length == 3) return arrayRange(Number(arr[0]), Number(arr[1]), Number(arr[2])).length;
+ break;
+ case "Array":
+ if (arr.length == 1) return arr.map(Number);
+ else if (arr.length == 2) {
+ if (arr[0] === "") return [str].map(Number);
+ else return arrayRange(Number(arr[0]), Number(arr[1]), 1);
+ }
+ else if (arr.length == 3) return arrayRange(Number(arr[0]), Number(arr[1]), Number(arr[2]));
+ break;
+ }
+}
+
+/**
+ * return an array of number according to the range specified
+ * @param {*} start start point(inclusive)
+ * @param {*} stop end point(inclusive)
+ * @param {*} step the step
+ * @returns an array of number
+ */
+const arrayRange = (start, stop, step) =>
+ Array.from(
+ { length: (stop - start) / step + 1 },
+ (value, index) => start + index * step
+ );
+
+/**
+ * Check if all ranges in array of inputs are valid (e.g for a-b, a must < b)
+ * @param {*} values the array of inputs
+ * @returns whether the check is true or not
+ */
+export const checkAllRangesValid = (values) => {
+ for (let item of values) {
+ let rangesItems = item.split("-").map(Number);
+ if ((rangesItems.length == 2 || rangesItems.length == 3) && rangesItems[0] > rangesItems[1]) {
+ return false;
+ }
+ }
+ return true;
+}
diff --git a/src/algorithms/parameters/index.js b/src/algorithms/parameters/index.js
index b5e3fffa2..b6dbd493d 100644
--- a/src/algorithms/parameters/index.js
+++ b/src/algorithms/parameters/index.js
@@ -20,3 +20,6 @@ export { default as ASTARParam } from './ASTParam';
export { default as BFSParam } from './BFSParam';
export { default as DFSParam } from './DFSParam';
export { default as DFSrecParam } from './DFSrecParam';
+export { default as HashingLPParam } from './HashingLPParam';
+export { default as HashingDHParam } from './HashingDHParam';
+export { default as HashingCHParam } from './HashingCHParam';
diff --git a/src/algorithms/pseudocode/HashingInsertion.js b/src/algorithms/pseudocode/HashingInsertion.js
index 06b087790..dab59287b 100644
--- a/src/algorithms/pseudocode/HashingInsertion.js
+++ b/src/algorithms/pseudocode/HashingInsertion.js
@@ -8,6 +8,7 @@ import parse from '../../pseudocode/parse';
// NOTE: code now no longer explicitly keeps track of number of
// insertions and CheckTableFullness is modified so some bookmarks (eg,
// 4, 19, 20) no longer exist and controller code will have to change
+
const main = `
\\Code{
@@ -53,7 +54,7 @@ prevent infinite loops when searching.
T[i] <- k // unoccupied slot found so we put k in it \\B 9
// Done \\B 10
\\In}
-
+
//=======================================================
HashDelete(T, k) // Delete key k in table T \\B 11
@@ -82,7 +83,7 @@ prevent infinite loops when searching.
\\In{
// Do nothing
\\In}
- \\In}
+ \\In}
\\Code}
\\Code{
@@ -181,5 +182,86 @@ export const doubleHashingIncrement = `
\\Expl}
\\Code}
`
+
+let chainingInsert = `
+ \\Code{
+ NullTable
+ i <- 0
+ while i {
+ const elRef = useRef(null);
+ const executeScroll = () => elRef.current.scrollIntoView({
+ behavior: 'smooth',
+ block: 'start',
+ });
+
+ return [executeScroll, elRef];
+};
+
+const ScrollToHighlight = ({col, j, toString}) => {
+ const [executeScroll, elRef] = useScroll();
+ useEffect(executeScroll, []);
+
+ return (
+
+
+ {toString(col.value)}
+
+
+ );
+}
+
+ScrollToHighlight.propTypes = {
+ col: PropTypes.object.isRequired,
+ j: PropTypes.number.isRequired,
+}
+
+const handleMouseEnter = (id) => {
+ console.log(id);
+}
+
class Array2DRenderer extends Renderer {
constructor(props) {
super(props);
@@ -53,9 +102,29 @@ class Array2DRenderer extends Renderer {
renderData() {
// For DFSrec+msort_arr_td,... listOfNumbers is actually a list of
// pairs of numbers, or strings such as '(2,5)'
- const { data, algo, kth, listOfNumbers, motionOn, hideArrayAtIdx } =
- this.props.data;
+ const {
+ data,
+ algo,
+ kth,
+ listOfNumbers,
+ motionOn,
+ hideArrayAtIdx,
+ splitArray,
+ highlightRow,
+ // newZoom,
+ } = this.props.data;
+ let centerX = this.centerX;
+ let centerY = this.centerY;
+ let zoom = this.zoom;
+
+ // // Change Renderer's zoom on newZoom change
+ // if (newZoom != this.zoom && newZoom !== undefined) {
+ // this.zoom = newZoom;
+ // this.refresh();
+ // }
+
const isArray1D = true;
+ let render = [];
// eslint-disable-next-line camelcase
let data_T;
if (algo === 'tc') {
@@ -63,269 +132,426 @@ class Array2DRenderer extends Renderer {
data_T = data[0].map((col, i) => data.map((row) => row[i]));
}
// const isArray1D = this instanceof Array1DRenderer;
+
+ // These are for setting up the floating boxes
+ const firstColIsHeaders = ALGOS_WITH_FIRST_COL_AS_HEADERS.includes(algo);
+
// XXX sometimes caption (listOfNumbers) is longer than any row...
- let longestRow = data.reduce(
- (longestRow, row) => (longestRow.length < row.length ? row : longestRow),
- []
- );
- return (
-
+ function createArray(data, toString, longestRow, currentSub, subArrayNum) {
+ return (
- {algo === 'unionFind' && ( // adding the array indicies for union find
-
-
- {data[0].map((col, idx) => (
-
-
+
+ {data[0].map((col, idx) => (
+
+
+ {col.variables.map((v) => (
+
- {col.variables.map((v) => (
-
- {v}
-
- ))}
-
-
- ))}
-
-
- )}
+ {v}
+
+ ))}
+
+
+ ))}
+
+
+ )}
-
- {!isArray1D && }
- {algo === 'tc' && ( // Leave a blank cell at the header row
-
- )}
- { /* XXX really should have a displayIndex flag for this */
- algo !== 'BFS' &&
- algo !== 'DFSrec' &&
- algo !== 'DFS' &&
- algo !== 'kruskal' &&
- algo !== 'dijkstra' &&
- algo !== 'aStar' &&
- algo !== 'aStar' &&
- algo !== 'msort_lista_td' &&
- longestRow.map((_, i) => {
- if (algo === 'tc') {
- i += 1;
- }
- if (algo === 'prim' || algo == 'unionFind') {
- return ;
- }
- return (
-
- {i}
-
- );
- })}
-
- {data.map((row, i) => {
- let pointer = false;
+ 1 &&
+ (algo === 'HashingLP' || algo === 'HashingDH' || algo === 'HashingCH')
+ ) ? 5 : styles.row.height
+ }}
+ >
+ {!isArray1D && }
+ {algo === 'tc' && ( // Leave a blank cell at the header row
+
+ )}
+ { /* XXX really should have a displayIndex flag for */
+ algo !== 'BFS' &&
+ algo !== 'DFSrec' &&
+ algo !== 'DFS' &&
+ algo !== 'kruskal' &&
+ algo !== 'dijkstra' &&
+ algo !== 'aStar' &&
+ algo !== 'aStar' &&
+ algo !== 'msort_lista_td' &&
+ algo !== 'HashingLP' &&
+ algo !== 'HashingDH' &&
+ algo !== 'HashingCH' &&
+ longestRow.map((_, i) => {
+ if (algo === 'tc') {
+ i += 1;
+ }
+ if (algo === 'prim' || algo == 'unionFind') {
+ return ;
+ }
+ return (
+
+ {i}
+
+ );
+ })
+ }
+
+ {data.map((row, i) => {
+ let pointer = false;
- if (i === hideArrayAtIdx) return null;
+ if (
+ (Array.isArray(hideArrayAtIdx) && hideArrayAtIdx.includes(i))
+ || (i === hideArrayAtIdx)
+ ) return null;
- // eslint-disable-next-line no-plusplus
- for (let j = 0; j < row.length; j++) {
- if (row[j].selected) {
- pointer = true;
- }
+ // eslint-disable-next-line no-plusplus
+ for (let j = 0; j < row.length; j++) {
+ if (row[j].selected) {
+ pointer = true;
}
- return (
-
- {algo === 'tc' && ( // generate vertical index, which starts from 1
-
- {i + 1}
-
- )}
- {!isArray1D && algo !== 'tc' && (
-
- {i}
-
- )}
- {row.map((col, j) => {
- const varGreen = col.fill === 1; // for simple fill
- const varOrange = col.fill === 2;
- const varRed = col.fill === 3;
+ }
+ return (
+
+ {algo === 'tc' && ( // generate vertical index, which starts from 1
+
+ {i + 1}
+
+ )}
+ {!isArray1D && algo !== 'tc' && (
+
+ {i}
+
+ )}
+ {row.map((col, j) => {
+ const varGreen = col.fill === 1; // for simple fill
+ const varOrange = col.fill === 2;
+ const varRed = col.fill === 3;
+ if (varOrange) {
return (
-
-
- {this.toString(col.value)}
-
-
+
);
- })}
- {
- (pointer && algo === 'tc' && (
-
- i
-
- ))
- ||
- (algo === 'aStar' && i === 1 && (
-
- )Priority
-
- ))
- ||
- (algo === 'aStar' && i === 2 && (
-
- )Queue
-
- ))
- ||
- (((algo === 'prim' && i === 2) ||
- (algo === 'dijkstra' && i === 2)
- ) && (
-
- Priority Queue
-
- ))
- || }
-
- );
- })}
- {algo === 'tc' && (
- // Don't remove "j-tag='transitive_closure'"
-
-
- {data_T.map((row) => {
- let pointer = false;
- // eslint-disable-next-line no-plusplus
- for (let j = 0; j < row.length; j++) {
- if (row[j].selected1) {
- pointer = true;
- }
}
+
return (
- (pointer && (
-
- j
-
- )) ||
+ {
+ let element = e.target.children[1];
+ if (element && element.innerHTML !== "") {
+ element.style.display = 'block'
+ }
+ }}
+ onMouseLeave={(e) => {
+ let element = e.target.children[1];
+ if (element && element.innerHTML !== "") {
+ element.style.display = 'none'
+ }
+ }}
+ >
+
+
+ {toString(col.value)}
+
+
+ {
+ (i == 1 && ALGOS_USING_FLOAT_BOX.includes(algo) && (!(firstColIsHeaders && j == 0)) && (
+
+
+ ))
+ }
+
);
- })}
+ })
+ }
+ {
+ (pointer && algo === 'tc' && (
+
+ i
+
+ ))
+ ||
+ (algo === 'aStar' && i === 1 && (
+
+ )Priority
+
+ ))
+ ||
+ (algo === 'aStar' && i === 2 && (
+
+ )Queue
+
+ ))
+ ||
+ (((algo === 'prim' && i === 2) ||
+ (algo === 'dijkstra' && i === 2)
+ ) && (
+
+ Priority Queue
+
+ ))
+ || }
- )}
- {(algo === 'prim' ||
- algo === 'kruskal' ||
- algo === 'dijkstra' ||
- algo === 'aStar' ||
- algo === 'DFS' ||
- algo === 'DFSrec' ||
- algo === 'msort_lista_td' ||
- algo === 'BFS') &&
- data.map(
- (row, i) =>
- i === 2 && (
-
-
- {row.map((col, j) => (
-
+
+ {data_T.map((row) => {
+ let pointer = false;
+ // eslint-disable-next-line no-plusplus
+ for (let j = 0; j < row.length; j++) {
+ if (row[j].selected1) {
+ pointer = true;
+ }
+ }
+ return (
+ (pointer && (
+
+ j
+
+ )) ||
+ );
+ })}
+
+ )}
+ {(algo === 'prim' ||
+ algo === 'kruskal' ||
+ algo === 'dijkstra' ||
+ algo === 'aStar' ||
+ algo === 'DFS' ||
+ algo === 'DFSrec' ||
+ algo === 'msort_lista_td' ||
+ algo === 'BFS' ||
+ algo === 'HashingLP' ||
+ algo === 'HashingDH' ||
+ algo === 'HashingCH') &&
+ data.map(
+ (row, i) =>
+ i === 2 && (
+
+
+ {row.map((col, j) => (
+
+ {col.variables.map((v) => (
+
- {col.variables.map((v) => (
-
- {v}
-
- ))}
-
+ {v}
+
))}
-
-
- )
- )}
+
+ ))}
+
+
+ )
+ )}
- {algo === 'tc' && (
- k = {kth}
- )}
- {algo == 'unionFind' && ( // bottom centre caption for union find
-
- Union ({kth})
-
- )}
- {algo === 'DFS' && (
-
- Nodes (stack): {listOfNumbers}
-
- )}
- {algo === 'DFSrec' && (
-
- Call stack (n,p): {listOfNumbers}
-
- )}
- {algo === 'msort_arr_td' && (
-
- Call stack (n,p): {listOfNumbers}
-
- )}
- {algo === 'msort_lista_td' && listOfNumbers && (
-
- Call stack (L, len): {listOfNumbers}
-
- )}
- {algo === 'BFS' && (
-
+ {render}
+ {algo === 'tc' && (
+ k = {kth}
+ )}
+ {algo == 'unionFind' && ( // bottom centre caption for union find
+
+ Union ({kth})
+
+ )}
+ {algo === 'DFS' && (
+
+ Nodes (stack): {listOfNumbers}
+
+ )}
+ {algo === 'DFSrec' && (
+
+ Call stack (n,p): {listOfNumbers}
+
+ )}
+ {algo === 'msort_arr_td' && (
+
+ Call stack (n,p): {listOfNumbers}
+
+ )}
+ {algo === 'msort_lista_td' && listOfNumbers && (
+
+ Call stack (L, len): {listOfNumbers}
+
+ )}
+ {algo === 'BFS' && (
+
+ Nodes (queue): {listOfNumbers}
+
+ )}
+
+ )
+ }
+
+ if (!splitArray.doSplit) {
+ let longestRow = data.reduce(
+ (longestRow, row) => (longestRow.length < row.length ? row : longestRow),
+ []
+ );
+ render.push(createArray(data, this.toString, longestRow));
+ return createRender(render);
+
+ } else {
+ for (let i = 0; i < data.length; i++) {
+ let longestRow = data[i].reduce(
+ (longestRow, row) => (longestRow.length < row.length ? row : longestRow),
+ []
+ );
+ render.push(createArray(
+ data[i],
+ this.toString,
+ longestRow,
+ i,
+ data.length
+ ));
+ }
+
+ return (
+
+
- Nodes (queue): {listOfNumbers}
-
- )}
-
- );
+ {createRender(render)}
+
+
+ {(algo === 'HashingLP' ||
+ algo === 'HashingDH' ||
+ algo === 'HashingCH') &&
+ kth !== '' &&
+ ((kth.fullCheck === undefined) && (kth.reinserting === undefined) ?
+ (
+
+ {(kth.type == 'I' || kth.type == 'BI') ? 'Inserting' : (kth.type == 'S' ? 'Searching' : (kth.type == 'D' ? 'Deleting' : '')) } Key{kth.type == 'BI' ? 's' : ''}: {kth.key}
+ {kth.insertions !== undefined && (
+
+
+ Insertions: {kth.insertions}
+
+ )}
+ {algo !== 'HashingCH' && (
+
+
+ Increment: {kth.increment}
+
+ )}
+
+ ) : ((kth.fullCheck !== undefined) ? (
+
+ {kth.fullCheck}
+
+ ) : (
+
+ Reinserting: {kth.reinserting}
+
+ To reinsert: {kth.toReinsert}
+
+ )
+ )
+ )
+ }
+
+
+ );
+ }
+
}
}
diff --git a/src/components/DataStructures/Array/Array2DTracer.js b/src/components/DataStructures/Array/Array2DTracer.js
index db48d4488..b116e1bcb 100644
--- a/src/components/DataStructures/Array/Array2DTracer.js
+++ b/src/components/DataStructures/Array/Array2DTracer.js
@@ -56,16 +56,73 @@ class Array2DTracer extends Tracer {
/**
* @param {array} array2d
* @param {string} algo used to mark if it is a specific algorithm
+ * @param {any} kth used to display kth
+ * @param {number} highlightRow used mark the row to highlight
+ * @param {Object} splitArray determine how to split the array
+ * @param {number} splitArray.rowLength determine the length of a split array
+ * @param {string[]} splitArray.rowHeader determine the header of each row of a split array
*/
- set(array2d = [], algo, kth = 1) {
- this.data = array2d.map((array1d) =>
- [...array1d].map((value, i) => new Element(value, i))
- );
+ set(array2d = [], algo, kth = 1, highlightRow, splitArray) {
+ // set the array2d based of the splitArray values
+ if (splitArray === undefined || splitArray.rowLength < 1) {
+ this.splitArray = {doSplit: false};
+
+ // set the value of array cells
+ this.data = array2d.map((array1d) =>
+ [...array1d].map((value, i) => new Element(value, i))
+ );
+ } else {
+ this.data = [];
+ this.splitArray = splitArray;
+ this.splitArray.doSplit = true;
+
+ // check if the rows have headers
+ if (Array.isArray(splitArray.rowHeader) && splitArray.rowHeader.length) {
+ this.splitArray.hasHeader = true;
+ } else {
+ this.splitArray.hasHeader = false;
+ }
+ let split = [];
+
+ // splitting the array into multiple arrays of length rowLength
+ let step = 0;
+ while (step < array2d[0].length) {
+ // one smaller array
+ let arr2d = [];
+ for (let i = 0; i < array2d.length; i++ ) {
+ arr2d.push([
+ splitArray.rowHeader[i],
+ ...array2d[i].slice(step, step + splitArray.rowLength),
+ ...(
+ (
+ (array2d[0].length - step) > 0 &&
+ (array2d[0].length - step) < splitArray.rowLength
+ )
+ ? Array(step + splitArray.rowLength - array2d[0].length)
+ : Array(0)
+ )
+ ]);
+ }
+
+ step += splitArray.rowLength;
+
+ // push to a main array of multiple split arrays
+ split.push(arr2d);
+ }
+
+ // set the value of array cells
+ for (const item of split) {
+ this.data.push(item.map((array1d) =>
+ [...array1d].map((value, i) => new Element(value, i))
+ ));
+ }
+ }
this.algo = algo;
this.kth = kth;
this.motionOn = true; // whether to use animation
this.hideArrayAtIdx = null; // to hide array at given index
this.listOfNumbers = '';
+ this.highlightRow = highlightRow;
super.set();
}
@@ -118,21 +175,145 @@ class Array2DTracer extends Tracer {
}
}
- // a simple fill function based on aia themes
- // where green=1, yellow=2, and red=3
+ /**
+ * a simple fill function based on aia themes
+ * @param {number} sx the starting row to fill
+ * @param {number} sy the starting row to fill
+ * @param {number} ex the ending row to fill, defaults to sx
+ * @param {number} ey the ending row to fill, defaults to sy
+ * @param {number} c the color value, where green=1, yellow=2, and red=3
+ */
fill(sx, sy, ex = sx, ey = sy, c = 0) {
- for (let x = sx; x <= ex; x++) {
- for (let y = sy; y <= ey; y++) {
- this.data[x][y].fill = c === 1 || c === 2 || c === 3 ? c : 0;
+ if (!this.splitArray.doSplit) {
+ for (let x = sx; x <= ex; x++) {
+ for (let y = sy; y <= ey; y++) {
+ this.data[x][y].fill = c === 1 || c === 2 || c === 3 ? c : 0;
+ }
+ }
+ } else {
+ for (let i = 0; i < this.data.length; i++) {
+ // when it is just one cell for each row
+ if (sy === ey) {
+ let relativeY = sy + (this.splitArray.hasHeader ? 1 : 0);
+
+ // if the relative start position is over the split array length, wrap to next split array
+ if (relativeY > this.splitArray.rowLength) {
+ sy -= this.splitArray.rowLength;
+ ey -= this.splitArray.rowLength;
+ continue;
+ }
+
+ for (let x = sx; x <= ex; x++) {
+ this.data[i][x][relativeY].fill =
+ c === 1 ||
+ c === 2 ||
+ c === 3 ?
+ c : 0;
+ }
+
+ break;
+ }
+
+
+ // when there are multiple columns
+ // if the relative start position is over the split array length, wrap to next split array
+ let relativeSY = sy + (this.splitArray.hasHeader ? 1 : 0);
+ if (relativeSY > this.splitArray.rowLength) {
+ sy -= this.splitArray.rowLength;
+ ey -= this.splitArray.rowLength;
+ continue;
+ }
+
+
+ // if the relative start position is over the split array length, limit
+ let relativeEY = ey + (this.splitArray.hasHeader ? 1 : 0);
+ if (relativeEY > this.splitArray.rowLength) {
+ relativeEY = this.splitArray.rowLength;
+ }
+
+ // out of range, stop
+ if (relativeEY < 0) {
+ break;
+ }
+
+ // start at the first index of subarray
+ if (relativeSY < 0) {
+ relativeSY = 0;
+ }
+
+ for (let x = sx; x <= ex; x++) {
+ for (let y = relativeSY; y <= relativeEY; y++) {
+ this.data[i][x][y].fill = c === 1 || c === 2 || c === 3 ? c : 0;
+ }
+ }
+
+ sy -= this.splitArray.rowLength;
+ ey -= this.splitArray.rowLength;
}
}
}
- // unfills the given element (used with fill)
+ /**
+ * unfills the given element (used with fill)
+ * @param {number} sx the starting row to unfill
+ * @param {number} sy the starting row to unfill
+ * @param {number} ex the ending row to unfill, defaults to sx
+ * @param {number} ey the ending row to unfill, defaults to sy
+ */
unfill(sx, sy, ex = sx, ey = sy) {
- for (let x = sx; x <= ex; x++) {
- for (let y = sy; y <= ey; y++) {
- this.data[x][y].fill = 0;
+ if (!this.splitArray.doSplit) {
+ for (let x = sx; x <= ex; x++) {
+ for (let y = sy; y <= ey; y++) {
+ this.data[x][y].fill = 0;
+ }
+ }
+ } else {
+ for (let i = 0; i < this.data.length; i++) {
+ if (sy === ey) {
+ let relativeSY = sy + (this.splitArray.hasHeader ? 1 : 0);
+ if (relativeSY > this.splitArray.rowLength) {
+ sy -= this.splitArray.rowLength;
+ continue;
+ }
+
+ for (let x = sx; x <= ex; x++) {
+ this.data[i][x][relativeSY].fill = 0;
+ }
+
+ break;
+ }
+
+
+ let relativeSY = sy + (this.splitArray.hasHeader ? 1 : 0);
+ if (relativeSY > this.splitArray.rowLength) {
+ sy -= this.splitArray.rowLength;
+ ey -= this.splitArray.rowLength;
+ continue;
+ }
+
+ let relativeEY = ey + (this.splitArray.hasHeader ? 1 : 0);
+ if (relativeEY > this.splitArray.rowLength) {
+ relativeEY = this.splitArray.rowLength;
+ }
+
+ // out of range
+ if (relativeEY < 0) {
+ break;
+ }
+
+ // start at the first index of subarray
+ if (relativeSY < 0) {
+ relativeSY = 0;
+ }
+
+ for (let x = sx; x <= ex; x++) {
+ for (let y = relativeSY; y <= relativeEY; y++) {
+ this.data[i][x][y].fill = 0;
+ }
+ }
+
+ sy -= this.splitArray.rowLength;
+ ey -= this.splitArray.rowLength;
}
}
}
@@ -158,7 +339,7 @@ class Array2DTracer extends Tracer {
// XXX for some reason, variables only seem to be displayed if
// row==2, and if you don't have enough rows in the table you are
// stuck unless you add an extra dummy row and hide it using hideArrayAtIndex
- assignVariable(v, row, idx) {
+ assignVariable(v, row, idx, changeFrom) {
// deep clone data so that changes to this.data are all made at the same time which will allow for tweening
// eslint-disable-next-line consistent-return
function customizer(val) {
@@ -168,24 +349,98 @@ class Array2DTracer extends Tracer {
if (val.selected) newEl.selected = true;
if (val.sorted) newEl.sorted = true;
newEl.variables = val.variables;
+ newEl.fill = val.fill;
return newEl;
}
}
- const newData = cloneDeepWith(this.data, customizer);
- // remove all current occurences of the variable
- for (let y = 0; y < newData[row].length; y++) {
- newData[row][y].variables = newData[row][y].variables.filter(
- (val) => val !== v
- );
+ if (!this.splitArray.doSplit) {
+ const newData = cloneDeepWith(this.data, customizer);
+
+ // remove all current occurences of the variable
+ for (let y = 0; y < newData[row].length; y++) {
+ newData[row][y].variables = newData[row][y].variables.filter(
+ (val) => val !== v
+ );
+ }
+
+ // add variable to item if not undefined or null
+ if (idx !== null && idx !== undefined)
+ newData[row][idx].variables.push(v);
+
+ // update this.data
+ this.data = newData;
+
+ } else {
+ let newData = [];
+ for (let i = 0; i < this.data.length; i++) {
+ let _newData = cloneDeepWith(this.data[i], customizer);
+
+ // remove all current occurences of the variable
+ for (let y = 0; y < _newData[row].length; y++) {
+ _newData[row][y].variables = _newData[row][y].variables.filter(
+ (val) => val !== ((changeFrom !== undefined) ? changeFrom : v)
+ );
+ }
+
+ // add variable to item if not undefined or null
+ if (idx !== null && idx !== undefined) {
+ // check if idx is in subarray
+ // account for header offset
+ let relativeIdx = idx + (this.splitArray.hasHeader ? 1 : 0);
+ if (relativeIdx > 0 && relativeIdx <= this.splitArray.rowLength)
+ _newData[row][relativeIdx].variables.push(v);
+ }
+
+ newData.push(_newData);
+ idx -= this.splitArray.rowLength;
+ }
+
+ // update this.data
+ this.data = newData;
}
+ }
- // add variable to item if not undefined or null
- if (idx !== null && idx !== undefined)
- newData[row][idx].variables.push(v);
+ resetVariable(row) {
+ // deep clone data so that changes to this.data are all made at the same time which will allow for tweening
+ // eslint-disable-next-line consistent-return
+ function customizer(val) {
+ if (val instanceof Element) {
+ const newEl = new Element(val.value, val.key);
+ if (val.patched) newEl.patched = true;
+ if (val.selected) newEl.selected = true;
+ if (val.sorted) newEl.sorted = true;
+ newEl.variables = val.variables;
+ newEl.fill = val.fill;
+ return newEl;
+ }
+ }
- // update this.data
- this.data = newData;
+ if (!this.splitArray.doSplit) {
+ const newData = cloneDeepWith(this.data, customizer);
+
+ // remove all current occurences of the variable
+ for (let y = 0; y < newData[row].length; y++) {
+ newData[row][y].variables = []
+ }
+
+ this.data = newData;
+ } else {
+ let newData = [];
+ for (let i = 0; i < this.data.length; i++) {
+ let _newData = cloneDeepWith(this.data[i], customizer);
+
+ // remove all current occurences of the variable
+ for (let y = 0; y < _newData[row].length; y++) {
+ _newData[row][y].variables = [];
+ }
+
+ newData.push(_newData);
+ }
+
+ // update this.data
+ this.data = newData;
+ }
}
/**
@@ -269,10 +524,134 @@ class Array2DTracer extends Tracer {
* @param {*} newValue the new value.
*/
updateValueAt(x, y, newValue) {
- if (!this.data[x] || !this.data[x][y]) {
- return;
+ if (!this.splitArray.doSplit) {
+ if (!this.data[x] || !this.data[x][y]) {
+ return;
+ }
+ this.data[x][y].value = newValue;
+ } else {
+ for (let i = 0; i < this.data.length; i++) {
+ if (y !== null || y !== undefined || y >= 0) {
+ // check if y is in subarray
+ // add 1 to account for header offset
+ let relativeY = y + (this.splitArray.hasHeader ? 1 : 0);
+ if (relativeY > 0 && relativeY <= this.splitArray.rowLength) {
+ if (!this.data[i][x] || !this.data[i][x][relativeY]) continue;
+ this.data[i][x][relativeY].value = newValue;
+ }
+ y -= this.splitArray.rowLength;
+ }
+ }
+ }
+ }
+
+ /**
+ * Get the value at the given position of the array.
+ * @param {*} x the row index.
+ * @param {*} y the column index.
+ */
+ getValueAt(x, y) {
+ if (!this.splitArray.doSplit) {
+ if (!this.data[x] || !this.data[x][y]) {
+ return;
+ }
+
+ return this.data[x][y].value;
+ } else {
+ for (let i = 0; i < this.data.length; i++) {
+ if (y !== null || y !== undefined || y >= 0) {
+ // check if y is in subarray
+ // add 1 to account for header offset
+ let relativeY = y + (this.splitArray.hasHeader ? 1 : 0);
+ if (relativeY > 0 && relativeY <= this.splitArray.rowLength) {
+ if (!this.data[i][x] || !this.data[i][x][relativeY]) continue;
+ return this.data[i][x][relativeY].value;
+ }
+ y -= this.splitArray.rowLength;
+ }
+ }
}
- this.data[x][y].value = newValue;
+ }
+
+ /**
+ * Extract the array at the given row(s) of the array.
+ * @param {*} row the row index(es).
+ * @param {*} empty the character to change to empty.
+ */
+ extractArray(row, empty) {
+ let extract = [];
+ // currently does not support empty character replacement
+ // to implement later
+ if (!this.splitArray.doSplit) {
+ if (Array.isArray(row) && row.length) {
+ for (const i of row) {
+ extract.push(this.data[i].map((e) => e.value));
+ }
+ } else {
+ extract = this.data[row].map((e) => e.value);
+ }
+
+ } else {
+ // combine the split array and remove the headers if exist
+ let combined = [];
+ if (this.splitArray.hasHeader) {
+ for (const array of this.data) {
+ // get the first subarray
+ if (!combined.length) {
+ combined = array.map((arr) => arr.slice(1));
+ continue;
+ }
+
+ // append the next subarray
+ for (let i = 0; i < combined.length; i++) {
+ combined[i] = [...combined[i], ...array[i].slice(1)];
+ }
+ }
+ } else {
+ for (const array of this.data) {
+ // get the first subarray
+ if (!combined.length) {
+ combined = array;
+ continue;
+ }
+
+ // append the next subarray
+ for (let i = 0; i < combined.length; i++) {
+ combined[i] = [...combined[i], ...array[i]];
+ }
+ }
+ }
+
+ // extract the value array
+ if (Array.isArray(row) && row.length) {
+ // extracting multiple rows
+ for (const i of row) {
+ // get the value
+ extract.push(combined[i].map((e) => e.value));
+ }
+ } else {
+ // get the value
+ extract = combined[row].map((e) => e.value);
+ }
+ }
+
+ // change an empty character to undefined
+ // also extract a chaining array for hash chaining
+ for (let i = 0; i < extract.length; i++) {
+ extract[i] = (extract[i] === empty) ? undefined : extract[i];
+ if (typeof extract[i] === 'string') {
+ if (extract[i].includes("..")) {
+ let popper = document.getElementById('float_box_' + i);
+ let array = popper.innerHTML.split(',').map(Number);
+ extract[i] = array;
+ }
+ }
+ }
+ return extract;
+ }
+
+ setHighlightRow(row) {
+ this.highlightRow = row;
}
}
diff --git a/src/components/DataStructures/Graph/GraphRenderer/index.js b/src/components/DataStructures/Graph/GraphRenderer/index.js
index dcf142aa9..5c25f99ce 100644
--- a/src/components/DataStructures/Graph/GraphRenderer/index.js
+++ b/src/components/DataStructures/Graph/GraphRenderer/index.js
@@ -449,7 +449,7 @@ class GraphRenderer extends Renderer {
}
renderData() {
- const { nodes, edges, isDirected, isWeighted, dimensions, text, functionInsertText, functionNode, functionBalance, rectangle, radius, tagInfo } =
+ const { nodes, edges, isDirected, isWeighted, dimensions, text, functionInsertText, functionNode, functionBalance, rectangle, radius, tagInfo, newZoom } =
this.props.data;
const {
baseWidth,
@@ -472,6 +472,12 @@ class GraphRenderer extends Renderer {
rootX = root.x;
rootY = root.y;
}
+ //
+ // // Change Renderer's zoom on newZoom change
+ // if (newZoom != this.zoom && newZoom !== undefined) {
+ // this.zoom = newZoom;
+ // this.refresh();
+ // }
return (
{title}
{ this.renderData() }
diff --git a/src/components/DataStructures/common/Tracer.jsx b/src/components/DataStructures/common/Tracer.jsx
index 08dac1800..186a88784 100644
--- a/src/components/DataStructures/common/Tracer.jsx
+++ b/src/components/DataStructures/common/Tracer.jsx
@@ -10,6 +10,7 @@ class Tracer {
if (options !== undefined) {
this.arrayItemMagnitudes = options.arrayItemMagnitudes;
this.largestValue = options.largestValue;
+ this.size = options.size;
}
this.init();
this.reset();
@@ -25,13 +26,35 @@ class Tracer {
render() {
const RendererClass = this.getRendererClass();
return (
-
+
);
}
set() {
}
+ /**
+ * Set visualiser size (flex value for renderer)
+ * @param {*} size
+ */
+ setSize(size) {
+ this.size = size;
+ }
+
+ /**
+ * Change the zoom of the visualizer
+ * @param {*} zoom the new zoom
+ */
+ setZoom(zoom) {
+ this.newZoom = zoom;
+ window.setTimeout(() => {this.newZoom = undefined}, 200)
+ }
+
reset() {
this.set();
}
diff --git a/src/components/mainmenu/InsertSearchAlgorithms.js b/src/components/mainmenu/InsertSearchAlgorithms.js
index 9b26e37cb..2275c55b1 100644
--- a/src/components/mainmenu/InsertSearchAlgorithms.js
+++ b/src/components/mainmenu/InsertSearchAlgorithms.js
@@ -1,5 +1,5 @@
import React from 'react';
-import '../../styles/InsertSearchAlgorithms.scss';
+import '../../styles/InsertSearchAlgorithms.scss';
// Get the base URL dynamically
const baseUrl = window.location.origin;
@@ -7,6 +7,9 @@ const baseUrl = window.location.origin;
const insertSearchAlgorithms = [
{ name: 'Binary Search Tree', url: `${baseUrl}/?alg=binarySearchTree&mode=search` },
{ name: '2-3-4 Tree', url: `${baseUrl}/?alg=TTFTree&mode=search` },
+ { name: 'Hashing (Linear Probing)', url: `${baseUrl}/?alg=HashingLP&mode=insertion` },
+ { name: 'Hashing (Double Hashing)', url: `${baseUrl}/?alg=HashingDH&mode=insertion` },
+ { name: 'Hashing (Chaining)', url: `${baseUrl}/?alg=HashingCH&mode=insertion` },
];
const InsertSearchAlgorithms = () => {
@@ -22,4 +25,4 @@ const InsertSearchAlgorithms = () => {
);
};
-export default InsertSearchAlgorithms;
\ No newline at end of file
+export default InsertSearchAlgorithms;