diff --git a/CHANGELOG.md b/CHANGELOG.md index 910bdb1..447ad92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,69 @@ +# Changelog + +## 1.6.0 (Oct 15, 2024) + +- **Floyd Warshall Algorithm:** Implemented the Floyd warshall algorithm for finding the shortest path in a weighted graph. +- **Loading State Management:** Added loading state management during the execution of the algorithm. +- **Error Handling:** Implemented error handling to log errors in development mode. + +## 1.5.5 (Oct 10, 2024) + +- **Bellman-Ford Algorithm:** Implemented the Bellman-Ford algorithm for finding the shortest path in a weighted graph. +- **Loading State Management:** Added loading state management during the execution of the algorithm. +- **Error Handling:** Implemented error handling to log errors in development mode. + +## 1.4.4 (Oct 09, 2024) + +Handle error on sudoku solver component. + +## 1.4.3 (Oct 07, 2024) + +Sudoku solver stop visualization functionality is integrated. + +## 1.4.2 (Oct 07, 2024) + +Improved some UI in footer component & Home component. + +## 1.4.0 (Oct 07, 2024) + +### Added + +- Basic functionality for users to input Sudoku puzzles. +- Visualization of the solving process with step-by-step highlights of cell updates. +- Reset and submit buttons for user interaction. + +### Changed + +- Updated styles to improve the visual appearance of the Sudoku grid. +- Optimized performance for larger grids to enhance rendering speed. + +### Fixed + +- Corrected errors in the random number generation function to ensure valid placements. +- Fixed accessibility issues for keyboard navigation in the grid. + +## 1.3.0 (Oct 03, 2024) + +- **Detect cycle in linked list :** Implemented a new feature for linked lists to visualize if a list contains a cycle. + +## 1.2.0 (Sep 26, 2024) + +- Home component hero section is integrated. + +## 1.1.0 (Sep 26, 2024) + +### New Features: + +- **Dijkstra's Algorithm Visualization:** Added a new visualization for Dijkstra’s shortest path algorithm with interactive graph support. +- **Graph Coupling:** Implemented a feature allowing users to create coupled graphs, enhancing visualization of connected nodes and paths. + +### Linked List Features: + +- **Create Linked List:** Introduced functionality to create linked lists and interact with nodes dynamically. +- **CRUD Operations for Linked List:** Implemented Create, Read, Update, and Delete (CRUD) operations for managing linked list nodes. +- **Reverse Linked List:** Added a feature to reverse the linked list, showcasing the process step-by-step. +- **Merge Two Sorted Linked Lists:** Developed a feature to merge two sorted linked lists into one while maintaining sorted order. + ## 1.0.0 (Sep 09, 2024) - Initial release with visualizations for unique path finding, N-Queens problem, tree and graph traversals, and sorting algorithms. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 500dec6..0000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,36 +0,0 @@ -# Code of Conduct - -We are committed to providing a friendly, safe, and welcoming environment for all participants in the Algorithm Visualizations project. - -## Our Pledge - -We pledge to make participation in this project a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment include: - -- Being respectful and considerate of others. -- Using welcoming and inclusive language. -- Encouraging others to participate and share their ideas. -- Constructive criticism is welcome, but please be respectful. - -Examples of unacceptable behavior include: - -- Harassment, intimidation, or abuse. -- Discriminatory jokes or comments. -- Personal attacks or trolling. - -## Enforcement - -Instances of unacceptable behavior may be reported to the project maintainers. Project maintainers will review the reported behavior and take appropriate action, which may include issuing warnings or banning individuals from participating in the project. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/) version 1.4. - -Thank you for helping to create a positive and inclusive community! - ---- - -**Together, we make a difference!** 🌟 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index aa2d84a..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,32 +0,0 @@ -# Contributing to Algorithm Visualizations - -Thank you for considering contributing to the Algorithm Visualizations project! Here are a few guidelines to help you get started: - -## How to Contribute - -1. **Report Bugs**: If you find a bug, please open an issue describing the problem and how to reproduce it. - -2. **Request Features**: If you have a feature request, please open an issue with a detailed description of the feature. - -3. **Submit Pull Requests**: - - Fork the repository and create a new branch for your changes. - - Make your changes and ensure that they are well-tested. - - Submit a pull request with a clear description of your changes and why they are needed. - -## Code Style - -- Follow the existing coding style and conventions used in the project. -- Use [Prettier](https://prettier.io/) and [ESLint](https://eslint.org/) to maintain code quality. - -## Testing - -- Write tests for your changes and ensure that all existing tests pass. -- Use [Jest](https://jestjs.io/) for writing and running tests. - -## Documentation - -- Update the documentation if your changes affect any usage or configuration aspects. - -Thank you for helping to improve the project! - -**Happy Contributing!** 🎉 diff --git a/FAQ.md b/FAQ.md index 6211271..f471445 100644 --- a/FAQ.md +++ b/FAQ.md @@ -5,7 +5,3 @@ Follow the installation instructions in the [README.md](README.md). ## How do I run tests? Use `npm test` or `yarn test` to run the unit tests. - -## How do I contribute? - -See the [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines on how to contribute. diff --git a/LICENSE b/LICENSE index 44ffdc6..6a408b7 100644 --- a/LICENSE +++ b/LICENSE @@ -2,20 +2,8 @@ MIT License Copyright (c) 2024, Md. Al Amin. -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 78675ee..5a9627d 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,24 @@ A Next.js 14 project for visualizing famous algorithms using React, TypeScript, ## Features -- **Unique Path Finding**: Visualize algorithms to find the number of unique paths in a grid and solve the number of islands problem. +- **Path Finding Visualization**: Visualize algorithms for finding paths in a grid, including: + + 1. **Unique Path Finding**: Solve the unique paths problem in a grid. + 2. **Number of Islands Problem**: Visualize the algorithm to count the number of islands. + 3. **Shortest Path Finding**: Implementations of `Bellman-Ford`, `Floyd Warshall` and `Dijkstra's` algorithms for shortest path calculations. + - **N-Queens Problem**: Interactive visualization of the N-Queens problem, demonstrating various solutions and techniques. - **Tree and Graph Traversals**: Visualize traversal algorithms including depth-first search (DFS) with in-order, pre-order, and post-order techniques, as well as breadth-first search (BFS). - **Sorting Algorithms**: Visualize five sorting algorithms, including merge sort, quick sort, heap sort (with a tree graph visualization), selection sort, and bubble sort. - **Interactive UI**: Built with React and styled using Tailwind CSS, providing a user-friendly interface for exploring algorithms. +- **Linked List Operations** : + + 1. **Basic Operations** : Create, find, and delete nodes in a linked list. + 2. **Reverse Linked List** : Visualize the reversal of a linked list + 3. **Merge Two Sorted Lists** : Visualize the merging of two sorted linked lists + 4. **Cycle Detection** : Implement and visualize a cycle detection algorithm in linked lists to see if a list contains a cycle. + +- **Sudoku Solver**: Introduced an interactive visualization for the `Sudoku Solver problem`, allowing users to create and customize their own Sudoku boards. ## Getting Started @@ -41,52 +54,18 @@ npm install ``` 4. Start the development server: - `bash + +```sh {"id":"01JA7DBZ9NEQ04ND8EJW745P39"} npm run dev -` - The application will be available at http://localhost:3000. +``` + +The application will be available at http://localhost:3000. ### Usage - Navigate to the specific visualization from the home page. - Use the provided controls to interact with the algorithms and view their visualizations. -## Contributing - -We welcome contributions from the community! To contribute to this project, please follow these steps: - -1. Fork the repository on GitHub. - -2. Create a new branch for your feature or bug fix: - -```bash {"id":"01J7AT1ZH6GZWCDTK6XX8T3ACR"} -git checkout -b my-feature-branch - -``` - -3. Make your changes and commit them with a descriptive message: - -```bash {"id":"01J7AT1ZH6GZWCDTK6XZSSNJPM"} -git add . -git commit -m "Add new feature or fix bug" - -``` - -4. Push your changes to your fork: - -```bash {"id":"01J7AT1ZH7V01BT5M99R0V29BF"} -git push origin my-feature-branch - -``` - -5. Open a pull request on GitHub, describing your changes and any relevant details. - -Please review our [CONTRIBUTING.md](CONTRIBUTING.md) for more details on contributing. - -## Code of Conduct - -We are committed to creating a welcoming and inclusive community. Please adhere to our Code of [Conduct](CODE_OF_CONDUCT.md) while participating in this project. - ## License This project is licensed under the [MIT License](https://choosealicense.com/licenses/mit/). See the [LICENSE](LICENSE) file for details. diff --git a/package-lock.json b/package-lock.json index 6d97d42..b864301 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "algorithm-visualizer", - "version": "1.0.0", + "version": "1.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "algorithm-visualizer", - "version": "1.0.0", + "version": "1.3.0", "dependencies": { "next": "14.2.5", "react": "^18", diff --git a/package.json b/package.json index d2a6b0b..94e4198 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "algorithm-visualizer", - "version": "1.0.0", + "version": "1.6.0", "private": true, "scripts": { "dev": "next dev", diff --git a/src/app/algorithm/linked-list/detectCycle.ts b/src/app/algorithm/linked-list/detectCycle.ts new file mode 100644 index 0000000..030a19c --- /dev/null +++ b/src/app/algorithm/linked-list/detectCycle.ts @@ -0,0 +1,226 @@ +import { CycleFlags, ITreeNode, PointerFlags } from '@/app/types/TreeTypeProps'; + +/** + * Marks the nodes in a linked list that are part of a cycle. + * + * @param {ITreeNode} head - The head of the linked list. + * @param {ITreeNode | null | undefined} startNode - The node where the cycle starts. + * @param {ITreeNode | null | undefined} endNode - The node where the cycle ends. + * @param {CycleFlags} flags - The flags to mark the cycle properties on the nodes. + * @returns {ITreeNode} The updated cloned linked list with marked cycle nodes. + */ +export const markCycleInList = ( + head: ITreeNode, + startNode: ITreeNode | null | undefined, + endNode: ITreeNode | null | undefined, + flags: CycleFlags +) => { + const clonedList = { ...head }; + let node = clonedList; + let flag = false; + + while (node) { + if (node === startNode) { + flag = true; + node.isCycleStartPoint = flags.isCycleStartPoint ?? true; // Mark the start of the cycle + } + + if (node === endNode) { + node.isCycle = flags.isCycle ?? true; + node.isCycleEndPoint = flags.isCycleEndPoint ?? true; // Mark the end of the cycle + } + + if (flag) { + node.isCycle = flags.isCycle ?? true; // Mark all nodes in the cycle + } + + // Move to the next node + if (node === endNode) break; + + node = node.next as ITreeNode; + } + + return clonedList; // Return the updated cloned list +}; + +/** + * Updates the pointer flags for the slow and fast pointers in a linked list. + * + * This function traverses the linked list, marking the nodes that correspond + * to the given slow and fast pointers. It also prevents marking nodes + * multiple times in case of cycles. + * + * @param {ITreeNode} head - The head of the linked list. + * @param {ITreeNode | null | undefined} slow - The current slow pointer node. + * @param {ITreeNode | null | undefined} fast - The current fast pointer node. + * @param {PointerFlags} pointerFlags - Flags to indicate whether to mark the pointers. + * @returns {ITreeNode} The updated cloned linked list with marked pointer flags. + */ +export const updatePointersInList = ( + head: ITreeNode, + slow: ITreeNode | null | undefined, + fast: ITreeNode | null | undefined, + pointerFlags: PointerFlags = {} +) => { + const clonedList = { ...head }; + let node = clonedList; + const localMap = new Map(); + + // Iterate through clonedList to reset and set pointer flags + while (node) { + // if node is already found, stop the loop (cycle detected) + if (localMap.has(Number(node.id))) { + return clonedList; + } + + localMap.set(Number(node.id), true); + + // Update fast pointer based on condition + node.firstPointer = Number(node.id) === Number(fast?.id) ? (pointerFlags.firstPointer ?? true) : false; + + // Update slow pointer based on condition + node.slowPointer = Number(node.id) === Number(slow?.id) ? (pointerFlags.slowPointer ?? true) : false; + + // Move to the next node + node = node.next as ITreeNode; + } + + return clonedList; +}; + +export const resetNodeFlags = (head: ITreeNode, resetFlags: (node: ITreeNode) => void): ITreeNode => { + const clonedList = { ...head }; + let node = clonedList; + const localMap = new Map(); + + // Iterate through the cloned list + while (node) { + // Stop the loop if a cycle is detected + if (localMap.has(Number(node.id))) { + return clonedList; + } + + localMap.set(Number(node.id), true); + + // Reset node flags using the provided callback + resetFlags(node); + + node = node.next as ITreeNode; + } + + return clonedList; // Return the updated cloned list +}; + +/** + * Resets the flags of each node in a linked list using a provided callback function. + * + * This function traverses the linked list and applies the `resetFlags` callback + * to each node, allowing for flexible flag resetting. It also prevents processing + * nodes multiple times in case of cycles. + * + * @param {ITreeNode} head - The head of the linked list to process. + * @param {(node: ITreeNode) => void} resetFlags - A callback function to reset the flags of a node. + * @returns {ITreeNode} The updated cloned linked list with reset flags. + */ +export const resetNodes = (head: ITreeNode | null): ITreeNode | null => { + let currentNode = head; // Start from the head + + while (currentNode) { + // Reset the property + currentNode.isVisited = false; + currentNode.isCurrent = false; + currentNode.isSwap = false; + currentNode.isSorted = false; + currentNode.isTarget = false; + currentNode.isInvalid = false; + currentNode.isCycle = false; + currentNode.isInsertedPosition = false; + currentNode.slowPointer = false; + currentNode.firstPointer = false; + currentNode.isCycleStartPoint = false; + currentNode.isCycleEndPoint = false; + // Move to the next node + currentNode = currentNode.next as ITreeNode; + } + return head; +}; + +/** + * Creates a cycle in a linked list by connecting the end node to the start node. + * + * This function traverses the linked list to find the nodes with the specified + * values and creates a cycle if both nodes are found. If either node is not found, + * the list remains unchanged. + * + * @param {number} start - The value of the node to be the start of the cycle. + * @param {number} end - The value of the node to be the end of the cycle. + * @param {ITreeNode | null | undefined} head - The head of the linked list. + * @returns {ITreeNode | null} The head of the modified linked list, or null if the head is not defined. + */ +export const createACycleMethod = ( + start: number, + end: number, + head: ITreeNode | null | undefined +): ITreeNode | null => { + if (!head) return null; + + let startNode: ITreeNode | null = null; + let endNode: ITreeNode | null = null; + + // Traverse the linked list to find the start and end nodes + let currentNode: ITreeNode | null = head; // Keep original node references + + while (currentNode) { + if (currentNode.value === start) { + startNode = currentNode; // Set the start node + } + if (currentNode.value === end) { + endNode = currentNode; // Set the end node + } + currentNode = currentNode.next as ITreeNode; // Move to the next node + } + + // If both startNode and endNode are found, create the cycle + if (endNode && startNode) { + endNode.next = startNode; // Create a cycle + } + + return head; // Return the head +}; + +/** + * Finds the starting node of a cycle in a linked list. + * + * This function takes the head of the linked list and a meeting point (where + * two pointers meet) to determine the start of the cycle. It iterates + * through the list until the start node and the meeting point converge. + * + * @param {ITreeNode} head - The head of the linked list. + * @param {ITreeNode | null | undefined} meetingPoint - A node where two pointers meet in the cycle. + * @returns {ITreeNode} The starting node of the cycle in the linked list. + */ +export const findCycleStart = (head: ITreeNode, meetingPoint: ITreeNode | null | undefined): ITreeNode => { + let startNode: ITreeNode | null = head; + while (startNode !== meetingPoint) { + startNode = startNode!.next; + meetingPoint = meetingPoint!.next; + } + return startNode as ITreeNode; +}; + +/** + * Finds the end node of a cycle in a linked list. + * + * This function takes the starting node of the cycle and traverses the linked list + * until it finds the last node that points back to the starting node. + * + * @param {ITreeNode} startNode - The starting node of the cycle. + * @returns {ITreeNode} The end node of the cycle in the linked list. + */ +export const findCycleEnd = (startNode: ITreeNode): ITreeNode => { + let endNode: ITreeNode | null = startNode; + while (endNode!.next !== startNode) { + endNode = endNode!.next; + } + return endNode as ITreeNode; +}; diff --git a/src/app/algorithm/linked-list/mergeTwoSortedList.ts b/src/app/algorithm/linked-list/mergeTwoSortedList.ts new file mode 100644 index 0000000..8021e6f --- /dev/null +++ b/src/app/algorithm/linked-list/mergeTwoSortedList.ts @@ -0,0 +1,104 @@ +import { LINKED_LIST_NODE_START_CY } from '@/app/constant'; +import { TreeNode } from '@/app/data-structure/Tree/Node'; +import { Sleep } from '@/app/lib/sleepMethod'; +import React from 'react'; + +/** + * Visualizes the current node in the given list by setting the isCurrent flag. + * @async + * @param {TreeNode} node - The current node to visualize. + * @param {Function} setListState - The state setter function for the list (setListOne or setListTwo). + * @returns {Promise} + */ +export const visualizeCurrentNode = async ( + node: TreeNode, + setListState: React.Dispatch>, + speedRange: number +): Promise => { + setListState((previousList: TreeNode | null | undefined) => { + const clonedList = JSON.parse(JSON.stringify(previousList)); + let currentNode: TreeNode | null = clonedList; + + while (currentNode) { + currentNode.isCurrent = currentNode.id === node.id; // Highlight the current node + currentNode = currentNode.next; + } + + return clonedList; + }); + + await Sleep(speedRange); // Delay for visualization + + // Reset the isCurrent flag after visualization + setListState((previousList: TreeNode | null | undefined) => { + const clonedList = JSON.parse(JSON.stringify(previousList)); + let currentNode: TreeNode | null = clonedList; + + while (currentNode) { + currentNode.isCurrent = false; // Reset all nodes to not current + currentNode = currentNode.next; + } + + return clonedList; + }); +}; + +/** + * Processes the remaining nodes in a list and merges them into the merged list. + * @async + * @param {TreeNode} remainingList - The remaining nodes to process. + * @param {Function} setListState - The state setter function for the list (setListOne or setListTwo). + * @param {TreeNode | null} mergedListTail - The current tail of the merged list. + * @param {number} currentXPosition - The current X position for placing the nodes. + * @param {TreeNode} dummyHeadNode - The dummy head node for the merged list. + * @returns {Promise} + */ +export const processRemainingNodes = async ( + remainingList: TreeNode | null, + setListState: React.Dispatch>, + mergedListTail: TreeNode | null, + currentXPosition: number, + dummyHeadNode: TreeNode, + speedRange: number, + setReverseNodes: React.Dispatch> +): Promise => { + while (remainingList) { + await visualizeCurrentNode(remainingList, setListState, speedRange); + + remainingList.cx = currentXPosition; + remainingList.cy = LINKED_LIST_NODE_START_CY; + + mergedListTail!.next = { ...remainingList, next: null }; // Safely assign the next node + mergedListTail = mergedListTail!.next; // Advance the merged list tail + remainingList = remainingList.next; // Move to the next node + + currentXPosition += 25; // Increment the position for the next node + + if (mergedListTail) { + mergedListTail.isTarget = true; // Mark as target for visualization + } + + setReverseNodes(() => ({ ...(dummyHeadNode.next as TreeNode) })); // Update the merged list visualization + await Sleep(speedRange); // Add delay for visualization + } +}; + +/** + * Resets the status of all nodes in a list by clearing the isCurrent flag. + * @param {Function} setListState - The state setter function for the list (setListOne or setListTwo). + */ +export const resetNodeStatus = ( + setListState: React.Dispatch> +): void => { + setListState((previousList: TreeNode | null | undefined) => { + const clonedList = JSON.parse(JSON.stringify(previousList)); + let currentNode: TreeNode | null = clonedList; + + while (currentNode) { + currentNode.isCurrent = false; // Reset all nodes to not current + currentNode = currentNode.next; + } + + return clonedList; + }); +}; diff --git a/src/app/algorithm/linked-list/reverseList.ts b/src/app/algorithm/linked-list/reverseList.ts new file mode 100644 index 0000000..bb0e023 --- /dev/null +++ b/src/app/algorithm/linked-list/reverseList.ts @@ -0,0 +1,112 @@ +import { LINKED_LIST_NODE_START_CX } from '@/app/constant'; +import { TreeNode } from '@/app/data-structure/Tree/Node'; +import { Sleep } from '@/app/lib/sleepMethod'; +import { ITreeNode } from '@/app/types/TreeTypeProps'; +import React from 'react'; + +/** + * Recursively traverses a linked list and highlights each node as it is visited. + * + * @param {TreeNode | null} currentNode - The current node to be processed in the list. + * @param {React.Dispatch>} updateRoot - Function to update the root node for re-rendering. + * @param {number} delayDuration - The delay in milliseconds to visualize the node traversal. + * + * @returns {Promise} - A promise that resolves after all nodes have been processed. + */ +export const traverseLinkedListRecursively = async ( + currentNode: TreeNode | null, + updateRoot: React.Dispatch>, + delayDuration: number +): Promise => { + // Return early if the node is null + if (!currentNode) return; + + // Highlight the current node + updateRoot((previousRoot) => { + const clonedRoot = JSON.parse(JSON.stringify(previousRoot)); + let nodeIterator: TreeNode | null = clonedRoot as TreeNode; + + while (nodeIterator) { + // Set the isCurrent property of the current node to true, others to false + nodeIterator.isCurrent = nodeIterator.id === currentNode.id; + nodeIterator = nodeIterator.next; + } + return clonedRoot; + }); + + // Delay for visualizing the current node + await Sleep(delayDuration); + + // Reset the current node highlight + currentNode.isCurrent = false; + updateRoot((previousRoot) => { + const clonedRoot = JSON.parse(JSON.stringify(previousRoot)); + let nodeIterator: TreeNode | null = clonedRoot as TreeNode; + + while (nodeIterator) { + // Reset isCurrent property to false + if (nodeIterator.id === currentNode.id) { + nodeIterator.isCurrent = false; + } + nodeIterator = nodeIterator.next; + } + return clonedRoot; + }); + + // Recursively process the next node + await traverseLinkedListRecursively(currentNode.next, updateRoot, delayDuration); +}; + +/** + * Reverses a linked list and updates the visual position of each node. + * + * @param {TreeNode | null} currentNode - The current node to start reversing from. + * @param {number} totalNodes - The total number of nodes in the list. + * @param {React.Dispatch>} setReversedList - Function to update the state of the reversed list for re-rendering. + * @param {number} delayDuration - The delay in milliseconds to visualize each step of reversing. + * + * @returns {Promise} - The new head of the reversed list. + */ +export const reverseLinkedListWithPositions = async ( + currentNode: TreeNode | null, + totalNodes: number, + setReversedList: React.Dispatch>, + delayDuration: number +): Promise => { + let previousNode: TreeNode | null = null; + let positionIndex = 0; // Track the position index of nodes + const nodeSpacing = 25; // Horizontal spacing between each node + const startXPosition = LINKED_LIST_NODE_START_CX; // Initial x-coordinate for the first node + + // Reverse the linked list and update the position of each node + while (currentNode) { + const nextNode = currentNode.next; // Temporarily store the next node + + // Reverse the current node's next pointer + currentNode.next = previousNode; + + // Update the x-coordinate (cx) of the node based on its new position in the reversed list + currentNode.cx = startXPosition + (totalNodes - positionIndex - 1) * nodeSpacing; + + // Mark the current node as the target node and update the list + previousNode = currentNode; + currentNode.isTarget = true; + setReversedList(JSON.parse(JSON.stringify(previousNode))); + + // Delay to visualize the node reversal process + await Sleep(delayDuration); + + // Un-mark the current node and update the list again + currentNode.isTarget = false; + setReversedList(JSON.parse(JSON.stringify(previousNode))); + + // Move to the next node in the original list + currentNode = nextNode; + + // Increment the position index for the next node + positionIndex++; + } + + // Return the new head of the reversed linked list + return previousNode; +}; diff --git a/src/app/algorithm/linked-list/singlyLinkedListBasics.ts b/src/app/algorithm/linked-list/singlyLinkedListBasics.ts new file mode 100644 index 0000000..915507f --- /dev/null +++ b/src/app/algorithm/linked-list/singlyLinkedListBasics.ts @@ -0,0 +1,261 @@ +import { LINKED_LIST_NODE_START_CX, LINKED_LIST_NODE_START_CY } from '@/app/constant'; +import { TreeNode } from '@/app/data-structure/Tree/Node'; +import { deleteKey } from '@/app/lib/mapUtils'; +import { Sleep } from '@/app/lib/sleepMethod'; +import { ITreeNode } from '@/app/types/TreeTypeProps'; +import React from 'react'; + +/** + * Asynchronously inserts a new node into a tree structure at the specified position. + * The tree structure is updated visually with an animation using a delay based on the speedRange. + * Also, updates a Map with new values and adjusts the cx and cy (coordinate) values of nodes. + * + * @param {TreeNode} root - The root of the tree structure. + * @param {number} value - The value of the new node to be inserted. + * @param {number} position - The 1-based position to insert the new node. + * @param {number} speedRange - The speed (in ms) for visualizing the node insertion. + * @param {Map} dataMap - A map storing key-value pairs for the tree nodes. + * @param {React.Dispatch>} setRoot - A function to update the root node state. + * + * @returns {Promise} The updated root node of the tree structure. + */ +export const updateTreeToInsertData = async ( + root: TreeNode, + value: number, + position: number, + speedRange: number, + dataMap: Map, + setRoot: React.Dispatch> +): Promise => { + let currentRoot: TreeNode | null = root; + const newNode = new TreeNode(value); + newNode.cx = LINKED_LIST_NODE_START_CX; + newNode.cy = LINKED_LIST_NODE_START_CY; + + // If the position is 1, insert the node as the new head + if (position === 1) { + currentRoot.isInsertedPosition = true; + setRoot({ ...currentRoot }); + + await Sleep(speedRange); + + currentRoot.isInsertedPosition = false; + setRoot({ ...currentRoot }); + + newNode.next = currentRoot; + currentRoot = newNode; + + // Adjust the cx values of the shifted nodes + let currentNode: TreeNode | null = newNode.next; + while (currentNode) { + currentNode.cx = (currentNode.cx || 0) + 25; + currentNode = currentNode.next; + } + + return currentRoot; + } else { + let previousNode: TreeNode | null = null; + let currentNode: TreeNode | null = currentRoot; + let remainingSteps = position - 1; + + // Traverse to the target position + while (remainingSteps-- > 0 && currentNode) { + currentNode.isCurrent = true; + setRoot({ ...root }); + + await Sleep(speedRange); + + currentNode.isCurrent = false; + setRoot({ ...root }); + + previousNode = currentNode; + currentNode = currentNode.next; + } + + // Ensure the position is valid for insertion + if (previousNode && remainingSteps < 0) { + if (currentNode) { + currentNode.isInsertedPosition = true; + setRoot({ ...root }); + + await Sleep(speedRange); + + currentNode.isInsertedPosition = false; + setRoot({ ...root }); + } + + // Insert the new node between previousNode and currentNode + newNode.next = currentNode; + previousNode.next = newNode; + + // Set the coordinates for the new node + if (currentNode) { + newNode.cx = currentNode.cx || 25; + newNode.cy = currentNode.cy || 20; + } else { + newNode.cx = (previousNode.cx || 0) + 25; + newNode.cy = previousNode.cy || 20; + } + + // Adjust the cx values for all subsequent nodes + let nextNode: TreeNode | null = newNode.next; + while (nextNode) { + nextNode.cx = (nextNode.cx || 0) + 25; + nextNode = nextNode.next; + } + } else if (!currentNode && position > 1) { + // If the position exceeds the list size, append the new node at the end + previousNode!.next = newNode; + newNode.cx = (previousNode!.cx || 0) + 25; + } + + return root; // Return the updated root node + } +}; + +export /** + * Asynchronously deletes a node from the linked list at the specified position. + * The linked list structure is updated visually with an animation using a delay based on the speedRange. + * Also updates a Map with the modified values and adjusts the cx and cy (coordinate) values of nodes. + * + * @param {TreeNode} root - The root of the linked list. + * @param {number} position - The 1-based position of the node to be deleted. + * @param {number} speedRange - The speed (in ms) for visualizing the node deletion. + * @param {Map} dataMap - A map storing key-value pairs for the linked list nodes. + * @param {React.Dispatch>>} setDataMap - A function to update the dataMap state. + * @param {React.Dispatch>} setRoot - A function to update the root node state. + * + * @returns {Promise} The updated root node of the linked list. + */ +const updateTreeToDeleteData = async ( + root: TreeNode, + position: number, + speedRange: number, + setRoot: React.Dispatch>, + dataMap: Map, + setDataMap: React.Dispatch>>, + setInsertedData: React.Dispatch> +): Promise => { + const currentRoot: TreeNode | null = root; + + if (!currentRoot) { + return null; + } + + if (position === 1) { + // Deleting the head node + if (currentRoot) { + currentRoot.isInsertedPosition = true; + setRoot({ ...currentRoot }); + // delete from data-map also + setDataMap(deleteKey(dataMap, Number(currentRoot.value))); + setInsertedData((prv) => prv.filter((i) => i !== Number(currentRoot.value))); + await Sleep(speedRange); + + currentRoot.isInsertedPosition = false; + setRoot({ ...currentRoot }); + + const newRoot: TreeNode | null = currentRoot.next || null; + // Adjust coordinates for remaining nodes + let nextNode: TreeNode | null = newRoot; + while (nextNode) { + nextNode.cx = (nextNode.cx || 0) - 25; + nextNode = nextNode.next; + } + + return newRoot; // Return the new root of the list + } + } else { + let previousNode: TreeNode | null = null; + let currentNode: TreeNode | null = currentRoot; + let remainingSteps = position - 1; + + // Traverse to the node just before the target position + while (remainingSteps-- > 0 && currentNode) { + currentNode.isCurrent = true; + setRoot({ ...root }); + + await Sleep(speedRange); + + currentNode.isCurrent = false; + setRoot({ ...root }); + + previousNode = currentNode; + currentNode = currentNode.next; + } + + // Ensure the position is valid for deletion + if (previousNode && currentNode) { + currentNode.isInsertedPosition = true; + setRoot({ ...root }); + + await Sleep(speedRange); + + currentNode.isInsertedPosition = false; + setRoot({ ...root }); + // delete from data-map also + setDataMap(deleteKey(dataMap, Number(currentNode.value))); + setInsertedData((prv) => prv.filter((i) => i !== Number(currentNode.value))); + + // Remove the node from the list + previousNode.next = currentNode.next; + + // Adjust coordinates for remaining nodes + let nextNode: TreeNode | null = previousNode.next; + while (nextNode) { + nextNode.cx = (nextNode.cx || 0) - 25; + nextNode = nextNode.next; + } + + return root; // Return the updated root of the list + } + } + + return root; // Return the root if the position is invalid or no deletion is performed +}; + +/** + * Asynchronously searches for a node in a linked list and updates its visual state. + * The search highlights each node and marks the found node as the target. + * + * @param {TreeNode} root - The root of the linked list to search within. + * @param {number} searchItem - The value of the node to search for. + * @param {number} speedRange - The speed (in ms) for visualizing the search process. + * @param {React.Dispatch>} setRoot - A function to update the root node state. + * @returns {Promise} - Returns nothing. The state updates and visualization are handled within the function. + */ +export const updateTreeToSearchNodeData = async ( + root: TreeNode, + searchItem: number, + speedRange: number, + setRoot: React.Dispatch> +): Promise => { + let currentNode: TreeNode | null = root; + + if (!currentNode) { + return; + } + + // Traverse the linked list + while (currentNode) { + currentNode.isCurrent = true; + setRoot({ ...root }); // Trigger re-render + await Sleep(speedRange); + + currentNode.isCurrent = false; + setRoot({ ...root }); // Trigger re-render + + if (currentNode.value === searchItem) { + currentNode.isTarget = true; + setRoot({ ...root }); // Trigger re-render + await Sleep(speedRange); + + currentNode.isTarget = false; + setRoot({ ...root }); // Trigger re-render + + return; // Exit the function when the node is found + } + + currentNode = currentNode.next; // Move to the next node + } +}; diff --git a/src/app/algorithm/noOfValidIslands.ts b/src/app/algorithm/path-finding/noOfValidIslands.ts similarity index 87% rename from src/app/algorithm/noOfValidIslands.ts rename to src/app/algorithm/path-finding/noOfValidIslands.ts index a6cef7c..7d03415 100644 --- a/src/app/algorithm/noOfValidIslands.ts +++ b/src/app/algorithm/path-finding/noOfValidIslands.ts @@ -1,8 +1,11 @@ +import { del_col, del_row } from '@/app/constant'; +import { islandColorsPlate } from '@/app/data/mockData'; +import { isValidDirection } from '@/app/lib/helpers'; +import { Sleep } from '@/app/lib/sleepMethod'; +import { GridProps, PathFindingQueueProps } from '@/app/types/uniquePathProps'; import React from 'react'; -import { GridProps, PathFindingQueueProps } from '../types/uniquePathProps'; -import { Sleep } from '../lib/sleepMethod'; -import { del_col, del_row } from '../constant'; -import { isValidDirection } from '../lib/helpers'; + +let counter = 0; /** * Performs a Breadth-First Search (BFS) to find and mark islands in a grid. @@ -37,6 +40,11 @@ export const findNoOfIslands = async ( const tempData = [...data]; const tempQueue = [...queue]; + // Generate a unique color for this island + const islandColor = islandColorsPlate[counter % 20]; + // update counter + counter++; + // Add the starting cell to the queue const newElement = { rowIdx: row, colIdx: col }; tempQueue.unshift(newElement); @@ -44,6 +52,7 @@ export const findNoOfIslands = async ( // Mark the starting cell as visited and as part of a valid path tempData[row][col].isVisit = true; tempData[row][col].isValidPath = true; + tempData[row][col].islandColor = islandColor; // Update the state with the new grid and queue setData([...tempData]); @@ -80,6 +89,8 @@ export const findNoOfIslands = async ( rowIdx: row, colIdx: col, }; + // Assign the same color to the entire island + tempData[new_row][new_col].islandColor = islandColor; // Add the new cell to the queue for further exploration tempQueue.unshift({ rowIdx: new_row, colIdx: new_col }); diff --git a/src/app/algorithm/uniquePath.ts b/src/app/algorithm/path-finding/uniquePath.ts similarity index 95% rename from src/app/algorithm/uniquePath.ts rename to src/app/algorithm/path-finding/uniquePath.ts index 2600ff3..240e526 100644 --- a/src/app/algorithm/uniquePath.ts +++ b/src/app/algorithm/path-finding/uniquePath.ts @@ -1,10 +1,9 @@ -import { toast } from 'react-toastify'; -import { Sleep } from '../lib/sleepMethod'; -import { del_col, del_row, directions } from '../constant'; -import { isValidDirection } from '../lib/helpers'; +import { del_col, del_row, directions } from '@/app/constant'; +import { isValidDirection } from '@/app/lib/helpers'; +import { Sleep } from '@/app/lib/sleepMethod'; +import { GridProps } from '@/app/types/uniquePathProps'; import React from 'react'; -import { GridProps } from '../types/uniquePathProps'; - +import { toast } from 'react-toastify'; /** * Finds unique paths from the top-left to the bottom-right corner of a grid using Depth-First Search (DFS). * diff --git a/src/app/algorithm/binarySearch.ts b/src/app/algorithm/searching/binarySearch.ts similarity index 97% rename from src/app/algorithm/binarySearch.ts rename to src/app/algorithm/searching/binarySearch.ts index 364d11d..7694230 100644 --- a/src/app/algorithm/binarySearch.ts +++ b/src/app/algorithm/searching/binarySearch.ts @@ -1,6 +1,6 @@ +import { Sleep } from '@/app/lib/sleepMethod'; +import { ITreeNode } from '@/app/types/TreeTypeProps'; import React from 'react'; -import { Sleep } from '../lib/sleepMethod'; -import { ITreeNode } from '../types/TreeTypeProps'; /** * Performs a binary search on a binary search tree (BST) and visually updates the state at each step of the search. diff --git a/src/app/algorithm/searching/linearSearch.ts b/src/app/algorithm/searching/linearSearch.ts new file mode 100644 index 0000000..c60c930 --- /dev/null +++ b/src/app/algorithm/searching/linearSearch.ts @@ -0,0 +1,59 @@ +import { Sleep } from '@/app/lib/sleepMethod'; +import { LinearSearchDataProps } from '@/app/types/commonProps'; +import React from 'react'; +import { toast } from 'react-toastify'; + +export const LinearSearchToFindTheTarget = async ( + data: LinearSearchDataProps[], + setSteps: React.Dispatch>, + setData: React.Dispatch>, + setSearchItem: React.Dispatch>, + speedRange: number, + searchItem: number +) => { + // Create a shallow copy of the data for manipulation + const tempData = [...data]; + + // Iterate through the data array to perform the search + for (let i = 0; i < data.length; i++) { + // Increment the step count for each iteration + setSteps((prev) => prev + 1); + + // Check if the current item matches the search item + if (tempData[i].data === searchItem) { + // Highlight the item as the target + setData(() => { + tempData[i].isTarget = true; + return tempData; + }); + + // Wait for the specified speed before proceeding + await Sleep(speedRange); + + // Show success notification + toast.success(`Target is found at index ${i}`); + + // Set a new random search item for the next search iteration + setSearchItem(Math.floor(Math.random() * data.length) % data.length); + return; + } + + // Highlight the current item being compared + setData(() => { + tempData[i].isCurrent = true; + return tempData; + }); + + // Wait for the specified speed to allow visualization + await Sleep(speedRange); + + // Remove the highlight after comparison + setData(() => { + tempData[i].isCurrent = false; + return tempData; + }); + } + + // If the loop completes without finding the target, show error notification + toast.error(`Target is not found`); +}; diff --git a/src/app/algorithm/shortest-path/bellmanFordShortestPath.ts b/src/app/algorithm/shortest-path/bellmanFordShortestPath.ts new file mode 100644 index 0000000..600b246 --- /dev/null +++ b/src/app/algorithm/shortest-path/bellmanFordShortestPath.ts @@ -0,0 +1,237 @@ +import { handleResetDataForShortestPath } from '@/app/lib/graph'; +import { Sleep } from '@/app/lib/sleepMethod'; +import { IGraphEdge } from '@/app/types/shortestPathProps'; +import { GraphNodesProps } from '@/app/types/sortingProps'; +import React from 'react'; +import { toast } from 'react-toastify'; + +/** + * Executes the Bellman-Ford algorithm to find the shortest path from a source node to a target node. + * + * @param {number} source - The starting node ID. + * @param {number} target - The target node ID. + * @param {number} nodeSizes - The total number of nodes. + * @param {GraphNodesProps[]} nodes - Array of node objects representing the graph. + * @param {number} speedRange - Speed range for visualization delay. + * @param {React.Dispatch>} setNodes - Function to update the node states. + * @param {IGraphEdge[]} edges - Array of edges representing the graph. + * @param {React.Dispatch>} setBtnLoading - Function to toggle loading state. + * @param {React.Dispatch>} setShortestPathEdges - Function to track edges in the shortest path. + * + * @returns {Promise} Resolves once the shortest path is found and visualized. + */ +export const findShortestPathUsingBellmanFord = async ( + source = 0, + target: number = 4, + nodeSizes = 10, + nodes: GraphNodesProps[], + speedRange: number, + setNodes: React.Dispatch>, + edges: IGraphEdge[], + setBtnLoading: React.Dispatch>, + setShortestPathEdges: React.Dispatch> +): Promise => { + try { + // Initialize node distances and predecessors + const tempNodes = handleResetDataForShortestPath(nodes); + const predecessors = new Array(nodeSizes).fill(null); + + // Set initial nodes and add visualization delay + setNodes(() => + tempNodes.map((node) => + node.id === source + ? { ...node, isCurrentNode: true, isSource: true, distance: 0 } + : node.id === target + ? { ...node, isDestination: true } + : node + ) + ); + await Sleep(speedRange); + + // Set source node distance to 0 and visualize + tempNodes[source].distance = 0; + await visualizeNode(source, setNodes, speedRange); + + // Relax edges up to (nodes.length - 1) times + for (let i = 1; i <= nodes.length - 1; i++) { + let updated = false; + + for (const { source: u, target: v, weight: w } of edges) { + const cost = tempNodes[u].distance + w; + + // Relax the edge if a shorter path is found + if (tempNodes[u].distance !== Number.MAX_SAFE_INTEGER && cost < tempNodes[v].distance) { + tempNodes[v].distance = cost; + predecessors[v] = u; + + // Visualize the edge relaxation + await visualizeEdge(u, v, cost, setNodes, speedRange); + updated = true; + } + } + + // If no updates happen, terminate early + if (!updated) { + break; + } + } + setNodes([...tempNodes]); + + // Check for negative weight cycles + if (detectNegativeCycle(tempNodes, edges)) return; + + // Backtrack to mark the shortest path + await backtrackShortestPath( + predecessors, + source, + target, + tempNodes, + edges, + setShortestPathEdges, + setNodes, + speedRange + ); + } catch (error) { + if (process.env.NODE_ENV === 'development') { + // eslint-disable-next-line no-console + console.error(error); + } + } finally { + setBtnLoading(false); + } +}; + +/** + * Visualizes a node as the current node by updating its state, + * then resets the node's state after a delay. + * + * @param {number} nodeId - The ID of the node to visualize. + * @param {React.Dispatch>} setNodes - Function to update the state of nodes. + * @param {number} speedRange - The delay in milliseconds to pause between updates. + * @returns {Promise} A promise that resolves after the visualization is complete. + */ +const visualizeNode = async ( + nodeId: number, + setNodes: React.Dispatch>, // Correct type + speedRange: number +) => { + setNodes((prevNodes) => { + const updatedNodes = [...prevNodes]; // Use prevNodes from state + updatedNodes[nodeId].isCurrentNode = true; + return updatedNodes; + }); + await Sleep(speedRange); + + setNodes((prevNodes) => { + const updatedNodes = [...prevNodes]; // Use prevNodes from state + updatedNodes[nodeId].isCurrentNode = false; + updatedNodes[nodeId].isSource = false; + return updatedNodes; + }); +}; + +/** + * Visualizes an edge by highlighting the source and target nodes, updating their states with a distance cost, + * and then resets the states after a delay. + * + * @param {number} u - The ID of the source node. + * @param {number} v - The ID of the target node. + * @param {number} cost - The computed distance or cost from the source node to the target node. + * @param {React.Dispatch>} setNodes - Function to update the state of nodes. + * @param {number} speedRange - The delay in milliseconds to pause between updates. + * @returns {Promise} A promise that resolves after the visualization is complete. + */ +const visualizeEdge = async ( + u: number, + v: number, + cost: number, + setNodes: React.Dispatch>, // Correct type + speedRange: number +) => { + setNodes((prevNodes) => { + const updatedNodes = [...prevNodes]; // Use prevNodes from state + return updatedNodes.map((node) => + node.id === u + ? { ...node, isCurrentNode: true } + : node.id === v + ? { ...node, isCurrentNode: true, isTargetNode: true, distance: cost } + : node + ); + }); + await Sleep(speedRange); + + setNodes((prevNodes) => { + const updatedNodes = [...prevNodes]; // Use prevNodes from state + return updatedNodes.map((node) => + node.id === u || node.id === v ? { ...node, isCurrentNode: false, isTargetNode: false } : node + ); + }); +}; + +/** + * Detects if a negative weight cycle exists in the graph. + * A negative weight cycle occurs when a shorter path is found after all nodes have been processed. + * + * @param {GraphNodesProps[]} tempNodes - The array of graph nodes, where each node contains a `distance` property representing its current shortest distance from the source node. + * @param {IGraphEdge[]} edges - The array of edges in the graph, where each edge has a source node, target node, and weight. + * @returns {boolean} - Returns `true` if a negative weight cycle is detected, otherwise `false`. + */ +const detectNegativeCycle = (tempNodes: GraphNodesProps[], edges: IGraphEdge[]) => { + for (const { source: u, target: v, weight: w } of edges) { + if (tempNodes[u].distance !== Number.MAX_SAFE_INTEGER && tempNodes[u].distance + w < tempNodes[v].distance) { + toast.error(`Graph contains a negative weight cycle`); + return true; + } + } + return false; +}; + +/** + * Backtracks from the target node to the source node using the `predecessors` array to find and mark the shortest path. + * This function highlights nodes and edges that are part of the shortest path and updates the state asynchronously. + * + * @param {number[]} predecessors - Array where each index represents a node, and its value represents the predecessor of that node in the shortest path. + * @param {number} source - The starting node of the path (source node). + * @param {number} target - The ending node of the path (target node). + * @param {GraphNodesProps[]} tempNodes - Array of graph nodes where each node represents a point in the graph with properties like `isShortestPath` and `isDestination`. + * @param {IGraphEdge[]} edges - Array of graph edges that connect nodes and have properties like source, target, and weight. + * @param {React.Dispatch>} setShortestPathEdges - State updater function to set the edges that are part of the shortest path. + * @param {React.Dispatch>} setNodes - State updater function to update node properties in the graph. + * @param {number} speedRange - The delay time between each step of the path visualization. + * + * @returns {Promise} - Returns a promise that resolves after the entire shortest path has been visualized. + */ +const backtrackShortestPath = async ( + predecessors: number[], + source: number, + target: number, + tempNodes: GraphNodesProps[], + edges: IGraphEdge[], + setShortestPathEdges: React.Dispatch>, + setNodes: React.Dispatch>, + speedRange: number +) => { + let currentNode = target; + + while (currentNode !== null && currentNode !== source) { + const prevNode = predecessors[currentNode]; + if (prevNode !== null) { + const edge = edges.find( + (e) => + (e.source === currentNode && e.target === prevNode) || (e.source === prevNode && e.target === currentNode) + ); + if (edge) { + setShortestPathEdges((prev) => [...prev, edge]); + } + } + setNodes((prev) => + prev.map((node) => (node.id === currentNode ? { ...node, isShortestPath: true, isDestination: false } : node)) + ); + await Sleep(speedRange); + currentNode = predecessors[currentNode]; + } + + setNodes((prev) => + prev.map((node) => (node.id === source ? { ...node, isShortestPath: true, isSource: false } : node)) + ); +}; diff --git a/src/app/algorithm/shortest-path/dijkstraShortestPath.ts b/src/app/algorithm/shortest-path/dijkstraShortestPath.ts new file mode 100644 index 0000000..cf4d913 --- /dev/null +++ b/src/app/algorithm/shortest-path/dijkstraShortestPath.ts @@ -0,0 +1,127 @@ +import React from 'react'; +import { MinHeap } from '../../data-structure/minHeap/MinHeap'; +import { Sleep } from '../../lib/sleepMethod'; +import { IGraphEdge } from '../../types/shortestPathProps'; +import { GraphNodesProps } from '@/app/types/sortingProps'; + +/** + * Finds the shortest path between a source and a destination node using Dijkstra's algorithm. + * This function visualizes the algorithm's progress by updating the state of the nodes and edges. + * + * @param {number} source - The ID of the source node. + * @param {number} destination - The ID of the destination node. + * @param {number} nodeSizes - The total number of nodes in the graph. + * @param {number} speedRange - The delay time for animations (in milliseconds). + * @param {React.Dispatch>} setNodes - A state setter function for updating the node states. + * @param {IGraphEdge[]} edges - An array of edges representing the graph. + * @param {React.Dispatch>} setShortestPathEdges - A state setter function for tracking the edges that are part of the shortest path. + * + * @returns {Promise} A promise that resolves when the shortest path has been found and visualized. + */ +export const findShortestPathUsingDijkstra = async ( + source: number, + destination: number, + nodeSizes: number, + speedRange: number, + setNodes: React.Dispatch>, + edges: IGraphEdge[], + setShortestPathEdges: React.Dispatch> +): Promise => { + try { + // initialized a minHeap + const pq = new MinHeap(); + const distances = Array(nodeSizes).fill(Infinity); + const predecessors = Array(nodeSizes).fill(null); // To track the shortest path + + // Put source node into queue + pq.insert(source, 0); + distances[source] = 0; + + // Visualize the current node being processed + setNodes((prev) => + prev.map((node) => + node.id === source + ? { ...node, isCurrentNode: true, isSource: true, distance: 0 } + : node.id === destination + ? { ...node, isDestination: true } + : node + ) + ); + // Animation delay + await Sleep(speedRange); + // Reset isCurrentNode after processing + setNodes((prev) => prev.map((node) => (node.id === source ? { ...node, isCurrentNode: false } : node))); + + while (!pq.isEmpty()) { + const { node: currentNodeId } = pq.extractMin()!; + + // If we reached the destination, break the loop + if (currentNodeId === destination) { + break; + } + + // Iterate through neighbors (edges connected to the current node) + const currentEdges = edges.filter((edge) => edge.source === currentNodeId || edge.target === currentNodeId); + + // iterate over the edges using a for loop to handle async properly + for (const edge of currentEdges) { + const targetNodeId = edge.source === currentNodeId ? edge.target : edge.source; + const newDistance = Math.round(distances[currentNodeId] + edge.weight); + + // If a shorter path to the target node is found + if (newDistance < distances[targetNodeId]) { + // Visualize the current node update + setNodes((prev) => { + return prev.map((node) => + node.id === targetNodeId ? { ...node, isCurrentNode: true, distance: newDistance } : node + ); + }); + + // Wait for some time to simulate animation/visualization delay + await Sleep(speedRange); + + // Optionally reset the node highlight + setNodes((prev) => { + return prev.map((node) => (node.id === targetNodeId ? { ...node, isCurrentNode: false } : node)); + }); + + // Update the distances and insert the new node into the priority queue + distances[targetNodeId] = newDistance; + pq.insert(targetNodeId, newDistance); + // Track the predecessor of the target node + // or keep track of parent node + predecessors[targetNodeId] = currentNodeId; + } + } + } + + // Backtrack to mark the shortest path and edges + let currentNode = destination; + while (currentNode !== null && currentNode !== source) { + const prevNode = predecessors[currentNode]; + if (prevNode !== null) { + // Find the edge that connects currentNode and prevNode + const edge = edges.find( + (e) => + (e.source === currentNode && e.target === prevNode) || (e.source === prevNode && e.target === currentNode) + ); + if (edge) { + setShortestPathEdges((prev) => [...prev, edge]); // Track the edge + } + } + setNodes((prev) => + prev.map((node) => (node.id === currentNode ? { ...node, isShortestPath: true, isDestination: false } : node)) + ); + await Sleep(speedRange); + currentNode = predecessors[currentNode]; + } + setNodes((prev) => + prev.map((node) => (node.id === source ? { ...node, isShortestPath: true, isSource: false } : node)) + ); + } catch (error) { + if (process.env.NODE_ENV === 'development') { + // eslint-disable-next-line no-console + console.log(error); + } + } +}; diff --git a/src/app/algorithm/shortest-path/floydWarshall.ts b/src/app/algorithm/shortest-path/floydWarshall.ts new file mode 100644 index 0000000..63cbee3 --- /dev/null +++ b/src/app/algorithm/shortest-path/floydWarshall.ts @@ -0,0 +1,191 @@ +import { handleResetDataForShortestPath } from '@/app/lib/graph'; +import { Sleep } from '@/app/lib/sleepMethod'; +import { IGraphEdge } from '@/app/types/shortestPathProps'; +import { GraphNodesProps } from '@/app/types/sortingProps'; +import React from 'react'; + +/** + * Finds the shortest path between a source node and a destination node in a graph + * using the Floyd-Warshall algorithm. + * + * @async + * @param {number} [source=0] - The index of the source node (default is 0). + * @param {number} [destination=4] - The index of the destination node (default is 4). + * @param {number} [nodeSizes=10] - The total number of nodes in the graph (default is 10). + * @param {GraphNodesProps[]} nodes - The array of graph nodes to be updated. + * @param {number} speedRange - The speed range for visualization purposes, affecting delays. + * @param {React.Dispatch>} setNodes - State setter for updating the nodes in the graph. + * @param {IGraphEdge[]} edges - The array of edges representing connections between nodes. + * @param {React.Dispatch>} setBtnLoading - State setter for controlling the loading state of a button. + * @param {React.Dispatch>} setShortestPathEdges - State setter for updating the shortest path edges. + * + * @returns {Promise} A promise that resolves when the shortest path calculation is complete. + */ +export const findShortestPathUsingFloydWarshall = async ( + source = 0, + destination: number = 4, + nodeSizes: number = 9, + nodes: GraphNodesProps[], + speedRange: number, + setNodes: React.Dispatch>, + edges: IGraphEdge[], + setBtnLoading: React.Dispatch>, + setShortestPathEdges: React.Dispatch> +): Promise => { + try { + // Make a copy of the current nodes to update + const tempNodes = handleResetDataForShortestPath(nodes); + + // Initialize the distance matrix as a 2D array of numbers + const distance: number[][] = Array.from({ length: nodeSizes }, () => + Array.from({ length: nodeSizes }, () => Number.MAX_SAFE_INTEGER) + ); + + const predecessor: number[][] = Array.from({ length: nodeSizes }, () => + Array.from({ length: nodeSizes }, () => -1) + ); + + // Populate the distance matrix with edge weights and set direct predecessors + edges.forEach((item) => { + const s = item.source; + const t = item.target; + const w = item.weight; + distance[s][t] = w; + distance[t][s] = w; + + predecessor[s][t] = s; // Direct predecessor + predecessor[t][s] = t; // Direct predecessor for undirected graph + }); + + // Set distance from each node to itself as 0 and initialize predecessors + for (let i = 0; i < nodeSizes; i++) { + distance[i][i] = 0; + predecessor[i][i] = i; // Each node is its own predecessor + } + + // Highlight the source and destination nodes in the UI + setNodes(() => + tempNodes.map((node) => + node.id === source + ? { ...node, isCurrentNode: true, isSource: true, distance: 0 } + : node.id === destination + ? { ...node, isDestination: true } + : node + ) + ); + await Sleep(speedRange); // Pause for visualization + + /** + * Implementing the Floyd-Warshall algorithm to find shortest paths + * between all pairs of nodes. + */ + for (let k = 0; k < nodeSizes; k++) { + for (let i = 0; i < nodeSizes; i++) { + for (let j = 0; j < nodeSizes; j++) { + // Skip if both distances are infinite + if (distance[i][k] === Number.MAX_SAFE_INTEGER && distance[k][j] === Number.MAX_SAFE_INTEGER) { + continue; + } + const cost = Number(distance[i][k] + distance[k][j]); + + // Update distance and predecessor if a shorter path is found + if (distance[i][j] > cost) { + distance[i][j] = cost; + predecessor[i][j] = predecessor[k][j]; // Update predecessor + } + } + } + } + + /* Update cost from source to each node */ + for (let i = 0; i < nodeSizes; i++) { + tempNodes[i].distance = distance[source][i]; // Set the computed distance + tempNodes[i].isCurrentNode = true; // Highlight current node + setNodes([...tempNodes]); // Update state to reflect changes + + await Sleep(speedRange); // Pause for visualization + + tempNodes[i].isCurrentNode = false; // Reset current node state + setNodes([...tempNodes]); // Update state again + } + + // Backtrack to determine the shortest path from source to destination + await backtrackShortestPath( + predecessor, + source, + destination, + tempNodes, + edges, + setShortestPathEdges, + setNodes, + speedRange + ); + } catch (error) { + if (process.env.NODE_ENV === 'development') { + // eslint-disable-next-line no-console + console.error(error); + } + } finally { + setBtnLoading(false); // Reset button loading state + } +}; + +/** + * Backtracks to find the shortest path from the source node to the target node + * using the predecessor matrix obtained from the Floyd-Warshall algorithm. + * + * @async + * @param {number[][]} predecessor - A 2D array representing the predecessor of each node + * in the shortest path tree. + * @param {number} source - The index of the source node. + * @param {number} target - The index of the target node. + * @param {GraphNodesProps[]} tempNodes - The array of nodes to update in the UI. + * @param {IGraphEdge[]} edges - The array of edges in the graph. + * @param {React.Dispatch>} setShortestPathEdges - + * State setter for updating the shortest path edges. + * @param {React.Dispatch>} setNodes - + * State setter for updating the nodes in the graph. + * @param {number} speedRange - The speed range for visualization purposes, affecting delays. + * + * @returns {Promise} A promise that resolves when the backtracking process is complete. + */ +const backtrackShortestPath = async ( + predecessor: number[][], + source: number, + target: number, + tempNodes: GraphNodesProps[], + edges: IGraphEdge[], + setShortestPathEdges: React.Dispatch>, + setNodes: React.Dispatch>, + speedRange: number +) => { + let currentNode = target; // Start from the target node + + // Backtrack through the predecessor array to find the path + while (currentNode !== -1 && currentNode !== source) { + const prevNode = currentNode; // Store the current node as the previous node + if (prevNode !== null) { + // Find the edge that connects the current node to its predecessor + const edge = edges.find( + (e) => + (e.source === currentNode && e.target === prevNode) || (e.source === prevNode && e.target === currentNode) + ); + if (edge) { + // Add the found edge to the shortest path edges + setShortestPathEdges((prev) => [...prev, edge]); + } + } + // Update the current node in the UI to indicate it's part of the shortest path + setNodes((prev) => + prev.map((node) => (node.id === currentNode ? { ...node, isShortestPath: true, isDestination: false } : node)) + ); + await Sleep(speedRange); // Pause for visualization + // Move to the predecessor of the current node + currentNode = predecessor[source][currentNode]; + } + + // Mark the source node as part of the shortest path + setNodes((prev) => + prev.map((node) => (node.id === source ? { ...node, isShortestPath: true, isSource: false } : node)) + ); +}; diff --git a/src/app/algorithm/bubbleSort.ts b/src/app/algorithm/sorting/bubbleSort.ts similarity index 94% rename from src/app/algorithm/bubbleSort.ts rename to src/app/algorithm/sorting/bubbleSort.ts index 236f1bc..43bb4f1 100644 --- a/src/app/algorithm/bubbleSort.ts +++ b/src/app/algorithm/sorting/bubbleSort.ts @@ -1,6 +1,6 @@ +import { Sleep } from '@/app/lib/sleepMethod'; +import { SortingDataProps } from '@/app/types/sortingProps'; import React from 'react'; -import { SortingDataProps } from '../types/sortingProps'; -import { Sleep } from '../lib/sleepMethod'; /** * A approach of bubble sort algorithm diff --git a/src/app/algorithm/heapSort.ts b/src/app/algorithm/sorting/heapSort.ts similarity index 96% rename from src/app/algorithm/heapSort.ts rename to src/app/algorithm/sorting/heapSort.ts index 97c3214..e097bb3 100644 --- a/src/app/algorithm/heapSort.ts +++ b/src/app/algorithm/sorting/heapSort.ts @@ -1,7 +1,7 @@ +import { Sleep } from '@/app/lib/sleepMethod'; +import { HeapSortedItemProps } from '@/app/types/sortingProps'; +import { ITreeNode } from '@/app/types/TreeTypeProps'; import React from 'react'; -import { Sleep } from '../lib/sleepMethod'; -import { ITreeNode } from '../types/TreeTypeProps'; -import { HeapSortedItemProps } from '../types/sortingProps'; /** * Performs heap sort on an array of tree nodes. diff --git a/src/app/algorithm/mergeSort.ts b/src/app/algorithm/sorting/mergeSort.ts similarity index 97% rename from src/app/algorithm/mergeSort.ts rename to src/app/algorithm/sorting/mergeSort.ts index c767ff0..e799429 100644 --- a/src/app/algorithm/mergeSort.ts +++ b/src/app/algorithm/sorting/mergeSort.ts @@ -1,7 +1,6 @@ +import { clearAllTimeouts, Sleep } from '@/app/lib/sleepMethod'; +import { currentIndicesProps, SortingDataProps } from '@/app/types/sortingProps'; import React from 'react'; -// import { MERGE_SLEEP_DELAY } from '../constant'; -import { clearAllTimeouts, Sleep } from '../lib/sleepMethod'; -import { currentIndicesProps, SortingDataProps } from '../types/sortingProps'; /** * Initiates the merge sort algorithm. diff --git a/src/app/algorithm/quickSort.ts b/src/app/algorithm/sorting/quickSort.ts similarity index 97% rename from src/app/algorithm/quickSort.ts rename to src/app/algorithm/sorting/quickSort.ts index 7811564..1635107 100644 --- a/src/app/algorithm/quickSort.ts +++ b/src/app/algorithm/sorting/quickSort.ts @@ -1,6 +1,6 @@ +import { Sleep } from '@/app/lib/sleepMethod'; +import { SortingDataProps } from '@/app/types/sortingProps'; import React from 'react'; -import { SortingDataProps } from '../types/sortingProps'; -import { Sleep } from '../lib/sleepMethod'; /** * Performs the QuickSort algorithm on the provided data array, updating the visualization diff --git a/src/app/algorithm/selectionSort.ts b/src/app/algorithm/sorting/selectionSort.ts similarity index 96% rename from src/app/algorithm/selectionSort.ts rename to src/app/algorithm/sorting/selectionSort.ts index 5cc69f3..64719c7 100644 --- a/src/app/algorithm/selectionSort.ts +++ b/src/app/algorithm/sorting/selectionSort.ts @@ -1,6 +1,6 @@ +import { Sleep } from '@/app/lib/sleepMethod'; +import { SortingDataProps } from '@/app/types/sortingProps'; import React from 'react'; -import { SortingDataProps } from '../types/sortingProps'; -import { Sleep } from '../lib/sleepMethod'; /** * A selection algorithm visualization to sort the actual data diff --git a/src/app/algorithm/sudoku-solver/sudokuSolver.ts b/src/app/algorithm/sudoku-solver/sudokuSolver.ts new file mode 100644 index 0000000..ca40dac --- /dev/null +++ b/src/app/algorithm/sudoku-solver/sudokuSolver.ts @@ -0,0 +1,283 @@ +import { Sleep } from '@/app/lib/sleepMethod'; +import { SudoKuBoardProps } from '@/app/types/sudokyProps'; +import React from 'react'; +import { toast } from 'react-toastify'; + +/** + * Checks if the given Sudoku board is valid. + * A valid Sudoku board has no duplicate numbers in each row, column, and 3x3 sub-grid. + * + * @param {SudoKuBoardProps[][]} board - The 9x9 Sudoku board where each cell contains a value. + * @returns {boolean} - Returns true if the Sudoku board is valid, otherwise false. + */ +const isValidSudoku = (board: SudoKuBoardProps[][]): boolean => { + // Sets to track values in rows, columns, and sub-grids + const rows = new Set(); + const cols = new Set(); + const grids = new Set(); + + // Iterate through each cell in the 9x9 grid + for (let i = 0; i < 9; i++) { + for (let j = 0; j < 9; j++) { + const value = board[i][j].value; + + // Skip empty cells + if (value === '#') continue; + + // if any cell is empty, need to validate them + if (value.trim() === '') { + return false; + } + + // Create a unique key for row, column, and sub-grid checks + const rowKey = `row-${i}-${value}`; + const colKey = `col-${j}-${value}`; + const gridIndex = Math.floor(i / 3) * 3 + Math.floor(j / 3); + const gridKey = `grid-${gridIndex}-${value}`; + + // Check if the value already exists in the row, column, or sub-grid + if (rows.has(rowKey) || cols.has(colKey) || grids.has(gridKey)) { + return false; // Duplicate found, Sudoku is invalid + } + + // Add keys to track the current value in the respective row, column, and sub-grid + rows.add(rowKey); + cols.add(colKey); + grids.add(gridKey); + } + } + + // If no duplicates are found, return true (valid Sudoku) + return true; +}; + +/** + * Solves the given Sudoku board by filling in empty cells. + * The function recursively checks each cell, validating the current placement + * and backtracking if necessary. + * + * @param {SudoKuBoardProps[][]} board - The 9x9 Sudoku board to solve. + * @returns {Promise} - Returns a promise resolving to true if solved, otherwise false. + */ +export const Solve = async ( + board: SudoKuBoardProps[][], + setBoard: React.Dispatch>, + setInputsData: React.Dispatch>, + speedRange: number +): Promise => { + // Before attempting to solve, check if the provided board is already valid + if (!isValidSudoku(board)) { + toast.error(`The Sudoku grid is invalid!`); + return false; + } + + const n: number = board.length; + const m: number = board[0].length; + + // Iterate through each cell in the grid + for (let i = 0; i < n; i++) { + for (let j = 0; j < m; j++) { + // Highlight the current cell being checked + setBoard(() => { + const tempData: SudoKuBoardProps[][] = [...board]; + tempData[i][j].isCurrent = true; // Mark the cell as the current one + return tempData; + }); + + await Sleep(speedRange); // Sleep for visualization purposes + + // Deactivate the current cell highlighting after checking + setBoard(() => { + const tempData: SudoKuBoardProps[][] = [...board]; + tempData[i][j].isCurrent = false; // Remove the current cell mark + return tempData; + }); + + // Check for empty cells (represented by '#') + if (board[i][j].value === '#') { + // Try numbers from 1 to 9 in the empty cell + for (let c = 1; c <= 9; c++) { + // Validate the placement of the number in the cell + if (await isValid(board, i, j, String(c), setBoard, setInputsData, speedRange)) { + board[i][j].value = String(c); // Place the number in the cell + + // Mark the cell as valid and update the board + setBoard(() => { + const tempData: SudoKuBoardProps[][] = [...board]; + tempData[i][j].isValid = true; + return tempData; + }); + + // Recursively try to solve the rest of the board + if (await Solve(board, setBoard, setInputsData, speedRange)) { + return true; // If solved, return true + } else { + // If the current placement leads to an invalid solution, backtrack + setBoard(() => { + const tempData: SudoKuBoardProps[][] = [...board]; + tempData[i][j].isValid = false; // Mark the cell as invalid + tempData[i][j].isInvalid = true; // Highlight it as an invalid choice + return tempData; + }); + + await Sleep(speedRange); // Delay for visualization + + // Reset the invalid flag after the visualization + setBoard(() => { + const tempData: SudoKuBoardProps[][] = [...board]; + tempData[i][j].isInvalid = false; + return tempData; + }); + + // Revert the cell back to empty + board[i][j].value = '#'; + } + } + } + + return false; // Return false if no valid number is found for the current cell + } + } + } + + // If all cells are successfully filled, notify success + toast.success(`Sudoku solved successfully!`); + return true; // Return true when the board is solved +}; + +/** + * Checks if placing a number in a specific cell of the Sudoku board is valid. + * It verifies the row, column, and 3x3 subgrid for duplicates of the number being placed. + * + * @param {SudoKuBoardProps[][]} board - The Sudoku board. + * @param {number} row - The row index of the cell being checked. + * @param {number} col - The column index of the cell being checked. + * @param {string} c - The value to place in the cell. + * @returns {Promise} - Returns a promise that resolves to true if valid, otherwise false. + */ +const isValid = async ( + board: SudoKuBoardProps[][], + row: number, + col: number, + c: string, + setBoard: React.Dispatch>, + setInputsData: React.Dispatch>, + speedRange: number +): Promise => { + try { + // Clone the board for immutability purposes when performing updates + const tempUpdates: SudoKuBoardProps[][] = board.map((r) => r.map((cell) => ({ ...cell }))); + + // Set the current cell with the given value for both board and input data + setBoard(() => { + const tempData = tempUpdates.map((r) => r.map((cell) => ({ ...cell }))); + tempData[row][col].value = String(c); + return tempData; + }); + setInputsData(() => { + const tempData = tempUpdates.map((r) => r.map((cell) => ({ ...cell }))); + tempData[row][col].value = String(c); + return tempData; + }); + + await Sleep(speedRange); // Delay for visualization + + // Check the column for duplicates + for (let i = 0; i < 9; i++) { + tempUpdates[i][col].isValidColItem = true; // Highlight the column being checked + setBoard([...tempUpdates]); + + await Sleep(speedRange); // Visualization delay + + // If a duplicate is found in the column, mark it as invalid + if (board[i][col].value === String(c)) { + tempUpdates[i][col].isInvalid = true; // Highlight invalid cell + setBoard([...tempUpdates]); + await Sleep(speedRange); + + tempUpdates[i][col].isInvalid = false; // Reset invalid state + setBoard([...tempUpdates]); + + return false; // Invalid placement + } + } + + // Check the row for duplicates + for (let j = 0; j < 9; j++) { + tempUpdates[row][j].isValidRowItem = true; // Highlight the row being checked + setBoard([...tempUpdates]); + + await Sleep(speedRange); // Visualization delay + + // If a duplicate is found in the row, mark it as invalid + if (board[row][j].value === String(c)) { + tempUpdates[row][j].isInvalid = true; // Highlight invalid cell + setBoard([...tempUpdates]); + await Sleep(speedRange); + + tempUpdates[row][j].isInvalid = false; // Reset invalid state + setBoard([...tempUpdates]); + + return false; // Invalid placement + } + } + + // Check the 3x3 subgrid for duplicates + const subGridRowStart = Math.floor(row / 3) * 3; + const subGridColStart = Math.floor(col / 3) * 3; + for (let i = 0; i < 3; i++) { + for (let j = 0; j < 3; j++) { + const subGridRow = subGridRowStart + i; + const subGridCol = subGridColStart + j; + + tempUpdates[subGridRow][subGridCol].isValidSubGridItem = true; // Highlight subgrid being checked + setBoard([...tempUpdates]); + + await Sleep(speedRange); // Visualization delay + + // If a duplicate is found in the subgrid, mark it as invalid + if (board[subGridRow][subGridCol].value === c) { + tempUpdates[subGridRow][subGridCol].isInvalid = true; // Highlight invalid cell + setBoard([...tempUpdates]); + await Sleep(speedRange); + + tempUpdates[subGridRow][subGridCol].isInvalid = false; // Reset invalid state + setBoard([...tempUpdates]); + + return false; // Invalid placement + } + } + } + + // Reset all highlighted states before returning true + setBoard((prevBoard) => + prevBoard.map((rowItem) => + rowItem.map((cell) => ({ + ...cell, + isValidColItem: false, + isValidRowItem: false, + isValidSubGridItem: false, + })) + ) + ); + setInputsData((prevBoard) => + prevBoard.map((rowItem) => + rowItem.map((cell) => ({ + ...cell, + isValidColItem: false, + isValidRowItem: false, + isValidSubGridItem: false, + })) + ) + ); + + await Sleep(speedRange); // Visualization delay after reset + return true; // Valid placement + } catch (error) { + if (process.env.NODE_ENV === 'development') { + // eslint-disable-next-line no-console + console.log(error); + } + return false; // Return false in case of an error + } +}; diff --git a/src/app/components/home/HomeComponent.tsx b/src/app/components/home/HomeComponent.tsx new file mode 100644 index 0000000..777a5b4 --- /dev/null +++ b/src/app/components/home/HomeComponent.tsx @@ -0,0 +1,53 @@ +/* eslint-disable indent */ +'use client'; + +import { projectsData } from '@/app/data/dashboardSchemaData'; +import { ProjectSchema } from '@/app/types/commonProps'; +import Link from 'next/link'; +import { ReactNode } from 'react'; + +const HomeComponent = (): ReactNode => { + return ( +
+
+ {projectsData ? ( +
+ {projectsData.map((item: ProjectSchema) => { + return ( + +
+
+

{item.name}

+
+ {item.tags?.length + ? item.tags.map((i) => ( +

+ {i.name} +

+ )) + : null} +
+
+
+ + ); + })} +
+ ) : ( +
+

Loading...

+
+ )} +
+
+ ); +}; + +export default HomeComponent; diff --git a/src/app/components/layouts/footer/FooterComponent.tsx b/src/app/components/layouts/footer/FooterComponent.tsx index c7bb074..a93bb3b 100644 --- a/src/app/components/layouts/footer/FooterComponent.tsx +++ b/src/app/components/layouts/footer/FooterComponent.tsx @@ -23,7 +23,7 @@ const FooterComponent = () => ( alamin66.sit@gmail.com - + { + const ref = useRef(null); + const pathname = usePathname(); + // define local state const [isDrawerOpen, setIsDrawerOpen] = useState(false); - const ref = useRef(null); - // This module is for to detect user outside click of the given id // It will help us to close our open navigation useEffect(() => { @@ -46,10 +48,57 @@ const HeaderComponent = () => {
- HOME + + + + + + {/* old logo */} + {/* + + + + + + + + + + + + + + */}
    { className={`mb-3 pr-4 text-3xl font-semibold tracking-wider text-theme-secondary sm:text-4xl md:pr-0 dark:text-white`} onClick={onCloseDrawerNav} > - ALAMIN + + + +
diff --git a/src/app/components/linked-list/DetectCycle.tsx b/src/app/components/linked-list/DetectCycle.tsx new file mode 100644 index 0000000..0af0973 --- /dev/null +++ b/src/app/components/linked-list/DetectCycle.tsx @@ -0,0 +1,362 @@ +'use client'; + +import { + createACycleMethod, + findCycleEnd, + findCycleStart, + markCycleInList, + resetNodeFlags, + resetNodes, + updatePointersInList, +} from '@/app/algorithm/linked-list/detectCycle'; +import { LinkedList } from '@/app/data-structure/LinkedList/LinkedList'; +import { CYCLE_NODE_DATA } from '@/app/data/linkedListData'; +import { detectCycleFromGivenLinkedList } from '@/app/data/mockData'; +import { clearAllTimeouts, Sleep } from '@/app/lib/sleepMethod'; +import { PageProps } from '@/app/types/linkedListProps'; +import { ITreeNode } from '@/app/types/TreeTypeProps'; +import StatusColorsPlate from '@/app/utils/StatusColorsPlate'; +import React, { useEffect, useState } from 'react'; + +// Create a new type by picking only speedRange +type SpeedRangeProps = Pick; +const PEAK_START_NODES = [2, 3, 4, 5]; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const DetectCycle: React.FC = ({ speedRange }) => { + // define component local state + const [lists, setLists] = useState(); + const [btnLoading, setBtnLoading] = useState(false); + const [rootVisitedNodes, setRootVisitedNodes] = useState>(new Map()); + const [isPerformOperation, setIsPerformOperation] = useState(false); + const [cycleNode] = useState<{ start: number; end: number }>({ + start: PEAK_START_NODES[Math.floor(Math.random() * PEAK_START_NODES.length) % PEAK_START_NODES.length], + end: 7, + }); + + useEffect(() => { + resetVisitedMap(); + }, [speedRange]); + + useEffect(() => { + insertIntoList(); + + return () => { + clearAllTimeouts(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + if (isPerformOperation) { + handleIsCyclePresent(); + setIsPerformOperation(false); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isPerformOperation]); + + /** + * Inserts nodes into the linked list from a predefined data array. + */ + const insertIntoList = () => { + const linkedList = new LinkedList([], 0); + + // initialized a list by nodes + CYCLE_NODE_DATA.forEach((item) => { + linkedList.insertIntoListWithGivenPositionXY(item.x, item.y, item.node); + }); + + if (linkedList.head) { + const head = linkedList.head; + setLists(head); + createACycle(head); + setIsPerformOperation(true); + } + }; + + /** + * Creates a cycle in the linked list. + */ + const createACycle = (rootLists: ITreeNode) => { + resetVisitedMap(); + const head = createACycleMethod(cycleNode.start, 7, resetNodes(JSON.parse(JSON.stringify(rootLists)))); + setLists(head); + }; + + const handleIsCyclePresent = async () => { + try { + setBtnLoading(true); + + const sudoHead = lists as ITreeNode; + + let fast: ITreeNode | null | undefined = sudoHead; + let slow: ITreeNode | null | undefined = sudoHead; + + // Helper function to update pointers and sleep + const updateAndSleep = async (slow: ITreeNode, fast: ITreeNode) => { + resetVisitedMap(); + setLists(() => + updatePointersInList(sudoHead, slow, fast, { + slowPointer: true, + firstPointer: true, + }) + ); + await Sleep(speedRange); + resetVisitedMap(); + }; + + while (slow && fast && fast.next) { + await updateAndSleep(slow, fast); + + slow = slow?.next || null; // Use null as a fallback if slow is undefined + fast = fast.next.next || null; // Use null as a fallback if fast.next.next is undefined + + if (slow === fast) { + // Step 2: Identify the start of the cycle + const startNode = findCycleStart(sudoHead, slow!); + + // Step 3: Identify the end of the cycle + const endNode = findCycleEnd(startNode); + + // Mark nodes within the cycle + await markAndSleepCycle(sudoHead, startNode, endNode); + break; + } + } + + // Step 4: Reset all previous colors and pointers + await resetColorsAndPointers(sudoHead); + + /** restored all previous color */ + } catch (error) { + if (process.env.NODE_ENV === 'development') { + // eslint-disable-next-line no-console + console.error(error); + } + } finally { + setBtnLoading(false); + } + }; + + /** + * Resets the visited nodes map by initializing it with a new empty Map. + * This function is typically used to clear the current state of visited nodes + * before starting a new traversal or operation on the data structure. + */ + const resetVisitedMap = () => setRootVisitedNodes(new Map()); + + /** + * Marks the nodes in a cycle within a linked list and pauses execution for a specified duration. + * + * This function first resets the visited nodes map, marks the start and end points of the cycle, + * and then pauses execution for a brief period to allow for visualization or processing. + * After the pause, it resets the visited nodes map again. + * + * @param head - The head of the linked list. + * @param startNode - The node where the cycle starts. + * @param endNode - The node where the cycle ends. + * @returns A promise that resolves after the sleep duration. + */ + const markAndSleepCycle = async (head: ITreeNode, startNode: ITreeNode, endNode: ITreeNode) => { + resetVisitedMap(); + + // Mark the cycle in the linked list + setLists(() => + markCycleInList(head, startNode, endNode, { + isCycleStartPoint: true, + isCycleEndPoint: true, + isCycle: true, + }) + ); + + // Pause execution for the specified duration + await Sleep(speedRange); + + // Reset the visited nodes map after the pause + resetVisitedMap(); + }; + + /** + * Resets the color flags and pointer states of the nodes in a linked list. + * + * This function resets the visited nodes map, updates the state of each node to clear + * the slow and first pointer flags, pauses execution for a specified duration, + * and resets the visited nodes map again after the pause. + * + * @param head - The head of the linked list. + * @returns A promise that resolves after the sleep duration. + */ + const resetColorsAndPointers = async (head: ITreeNode) => { + // Reset the visited nodes map + resetVisitedMap(); + + // Clear the slow and first pointer flags for all nodes in the linked list + setLists(() => + resetNodeFlags(head, (node: ITreeNode) => { + node.slowPointer = false; + node.firstPointer = false; + }) + ); + + // Pause execution for the specified duration + await Sleep(speedRange); + + // Reset the visited nodes map again after the pause + resetVisitedMap(); + }; + + return ( + <> +
+
+
+ +
+ +
+ + {lists ? ( +
+ + + +
+ ) : ( +
+

Loading...

+
+ )} +
+ + ); +}; + +const radius = 5; // Circle radius + +const RenderNodeRecursively: React.FC<{ node: ITreeNode | null; visited: Map }> = ({ + node, + visited, +}) => { + // Base case: If node is null or already visited, stop rendering + if (!node || visited.has(Number(node.id))) return null; + + visited.set(Number(node.id), 1); + + let cycle_fill_color = 'white'; + let text_fill_color = 'black'; + + if (node.slowPointer) { + cycle_fill_color = `red`; + text_fill_color = `white`; + } + + if (node.firstPointer) { + cycle_fill_color = `blue`; + text_fill_color = `white`; + } + + if (node.isCycle || node.isCycleStartPoint || node.isCycleEndPoint) { + cycle_fill_color = `green`; + text_fill_color = `white`; + } + + return ( + <> + + {node?.next && ( + <> + {/* Calculate the new positions to offset the line */} + {(() => { + const { newX1, newY1, newX2, newY2 } = calculateOffsetLine( + node.cx!, + node.cy!, + node.next.cx!, + node.next.cy!, + radius + ); + + return ( + <> + + + + + + + + ); + })()} + + )} + + {/* Render the circle */} + + + {/* Render the text */} + + {node?.value} + + + + {/* Recursively render the next node */} + {node.next ? : null} + + ); +}; + +const calculateOffsetLine = (x1: number, y1: number, x2: number, y2: number, radius: number) => { + const dx = x2 - x1; + const dy = y2 - y1; + const distance = Math.sqrt(dx * dx + dy * dy); + const offsetX = (dx / distance) * radius; + const offsetY = (dy / distance) * radius; + return { + newX1: x1 + offsetX, + newY1: y1 + offsetY, + newX2: x2 - offsetX, + newY2: y2 - offsetY, + }; +}; + +export default DetectCycle; diff --git a/src/app/components/linked-list/LinkedListBasics.tsx b/src/app/components/linked-list/LinkedListBasics.tsx new file mode 100644 index 0000000..3c4b407 --- /dev/null +++ b/src/app/components/linked-list/LinkedListBasics.tsx @@ -0,0 +1,485 @@ +'use client'; + +import { + updateTreeToDeleteData, + updateTreeToInsertData, + updateTreeToSearchNodeData, +} from '@/app/algorithm/linked-list/singlyLinkedListBasics'; +import { LINKED_LIST_NODE_START_CX, LINKED_LIST_NODE_START_CY } from '@/app/constant'; +import { LinkedList } from '@/app/data-structure/LinkedList/LinkedList'; +import { TreeNode } from '@/app/data-structure/Tree/Node'; +import { basicLinkedListColorsPlate } from '@/app/data/mockData'; +import { appendToMapWithNewValue, hasKey } from '@/app/lib/mapUtils'; +import { LinkedListInputProps, PageProps } from '@/app/types/linkedListProps'; +import { ITreeNode } from '@/app/types/TreeTypeProps'; +import StatusColorsPlate from '@/app/utils/StatusColorsPlate'; +import React, { useEffect, useState } from 'react'; +import { toast } from 'react-toastify'; + +const LinkedListBasics: React.FC = ({ speedRange }) => { + // define component local state + const [insertedData, setInsertedData] = useState([]); + const [root, setRoot] = useState(); + const [btnLoading, setButtonLoading] = useState(false); + const [inputData, setInputData] = useState({ + insertData: '', + searchItem: '-1', + insertAtAnyPosition: '1', + deleteFromAnyPosition: '1', + }); + const [dataMap, setDataMap] = useState>(new Map()); + + useEffect(() => { + const rootValue = Math.floor(Math.floor(Math.random() * 99)); + const newList = new LinkedList([rootValue], LINKED_LIST_NODE_START_CX); + newList.createLinkedList(); + + if (newList.head) { + setRoot(newList.head); + // update setInsertData as well + setInsertedData([rootValue]); + // insert into map + setDataMap(appendToMapWithNewValue(dataMap, rootValue, rootValue)); + setInputData((prv) => ({ + ...prv, + insertData: String(Math.floor(Math.floor(Math.random() * 99))), + searchItem: String(rootValue), + })); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + /** + * Handle input data for insert at last into linked-list + * + * @param {React.ChangeEvent} e + */ + const insertDataOnChangeMethod = (e: React.ChangeEvent): void => { + const value = Number(parseInt(e.target.value)); + + if (value > 999) { + toast.error(`Number is too large.`); + } else if (value < 0) { + toast.error(`Number is too small.`); + } + + setInputData((prv) => ({ ...prv, insertData: String(value || '') })); + }; + + /** + * Handle input change for insert item into any position of given linked list + * + * @param {React.ChangeEvent} e + */ + const insertAtAnyPositionOnChangeMethod = (e: React.ChangeEvent): void => { + const value = Number(parseInt(e.target.value)); + + if (value > dataMap.size + 1 || value <= 0) { + toast.error(`Invalid position`); + } + setInputData((prv) => ({ ...prv, insertAtAnyPosition: String(value || 0) })); + }; + + /** + * get delete node position from users + * + * @param {React.ChangeEvent} e + */ + const deleteItemFromAnyPositionOnChangeMethod = (e: React.ChangeEvent): void => { + const value = Number(parseInt(e.target.value)); + + if (value > dataMap.size || value <= 0) { + toast.error(`Invalid position`); + } + setInputData((prv) => ({ ...prv, deleteFromAnyPosition: String(value || 0) })); + }; + + /** + * Get search node value from users + * + * @param {React.ChangeEvent} e + */ + const searchNodeOnChangeMethod = (e: React.ChangeEvent): void => { + const value = Number(parseInt(e.target.value)); + setInputData((prv) => ({ ...prv, searchItem: String(value || 0) })); + }; + + /** + * Inserts a new node into the tree/linked list at a specified position with validation. + * + * Validates the input position and ensures that the node can be inserted before proceeding + * with the insertion. If the validation fails, it shows an error message using `toast`. + * After a successful insertion, the root and input fields are updated, and the loading + * state is toggled. + * + * @async + * @function insertDataByPosition + * @returns {Promise} No return value, updates the tree structure and UI state. + * + * @throws Will show a toast error for the following cases: + * - Invalid linked list (`root` is not defined) + * - Invalid position (position is out of bounds or <= 0) + * - Exceeding the maximum number of allowed items + * - Empty input field for `insertData` + * - Duplicate value insertion (if the item already exists in `dataMap`) + */ + const insertDataByPosition = async () => { + const { insertAtAnyPosition, insertData } = inputData; + const maxItems = 11; + + // Validation checks + const position = Number(insertAtAnyPosition); + const value = Number(insertData); + + if (position > dataMap.size + 1 || position <= 0) { + toast.error('Invalid position'); + return; + } + + if (insertedData?.length + 1 > maxItems) { + toast.error(`Max limit exceeded. You can add at most ${maxItems} items.`); + return; + } + + if (!value) { + toast.error('Input field cannot be empty'); + return; + } + + if (hasKey(dataMap, value)) { + toast.error('This item already exists'); + return; + } + + // if any linked is not present, then created it + if (!root) { + const newNode = new TreeNode(value); + newNode.cx = LINKED_LIST_NODE_START_CX; + newNode.cy = LINKED_LIST_NODE_START_CY; + setRoot(newNode); + setInsertedData((prv) => [...prv, value]); + // Insert the new value into the map + setDataMap(appendToMapWithNewValue(dataMap, value, value)); + return; + } + + // Indicate loading state + setButtonLoading(true); + + // clearAllTimeouts(); + + // Insert data at the specified position + const updatedRoot = await updateTreeToInsertData( + { ...root }, + value, + position, + speedRange, + dataMap, + // setDataMap, + setRoot + ); + + // Update the root with the new node + setRoot({ ...updatedRoot }); + setInsertedData((prv) => [...prv, value]); + // Insert the new value into the map + setDataMap(appendToMapWithNewValue(dataMap, value, value)); + // Reset input fields with random values + setInputData((prev) => ({ + ...prev, + insertData: String(Math.floor(Math.random() * 499 + 1)), + insertAtAnyPosition: String(Math.floor(Math.random() * dataMap.size + 1)), + })); + + // Toggle loading state + setButtonLoading(false); + }; + + /** + * Asynchronously deletes a node from a linked list at the specified position. + * The function validates the position, updates the linked list by removing the node at that position, and manages loading states. + * It also handles errors and displays appropriate messages to the user. + * + * @async + * @function deleteNodeFromGivenList + * @returns {Promise} A promise that resolves when the deletion process is complete. + * + * @throws {Error} Throws an error if the position is invalid or if there's an issue with updating the tree. + * + * @description + * - Retrieves the position to delete from `inputData` (defaults to -1 if not provided). + * - Validates if the `root` of the linked list is valid and if the position is within bounds. + * - Sets a loading state while performing the deletion. + * - Calls `updateTreeToDeleteData` to handle the actual deletion process. + * - Updates the linked list root and state management hooks (`setRoot`, `setDataMap`, `setInsertedData`). + * - Catches and logs errors during the process, with development-only error logging. + */ + const deleteNodeFromGivenList = async () => { + try { + const { deleteFromAnyPosition = -1 } = inputData; + + // Validation checks + const position = Number(deleteFromAnyPosition); + + if (!root) { + toast.error('Invalid linked list.'); + return; + } + + if (position > dataMap.size || position <= 0) { + toast.error('Invalid position'); + return; + } + + // Indicate loading state + setButtonLoading((prev) => !prev); + + const updateRoot: ITreeNode | null = await updateTreeToDeleteData( + { ...root }, + position, + speedRange, + setRoot, + dataMap, + setDataMap, + setInsertedData + ); + + setRoot(updateRoot); + // Indicate loading state + setButtonLoading((prev) => !prev); + } catch (error) { + if (process.env.NODE_ENV === 'development') { + // eslint-disable-next-line no-console + console.error(error); + } + } + }; + + /** + * Searches for a node in the linked list based on the input search item. + * This function updates the state to reflect the search process, including + * indicating the loading state and handling errors. + * + * The function performs the following tasks: + * 1. Validates the linked list and the existence of the node to search for. + * 2. Sets a loading state to indicate that the search operation is in progress. + * 3. Calls `updateTreeToSearchNodeData` to visually search for the node in the list. + * 4. Updates the input data with a new random search item. + * 5. Handles potential errors and logs them in development mode. + * + * @async + * @function + * @name searchNodeFromGivenList + * @returns {Promise} - Returns a promise that resolves when the search operation is complete. + * + * @throws {Error} - Throws an error if the search process encounters issues not handled by the function. + */ + const searchNodeFromGivenList = async () => { + try { + const { searchItem = -1 } = inputData; + + if (!root) { + toast.error('Invalid linked list.'); + return; + } + + if (!hasKey(dataMap, Number(searchItem))) { + toast.error('Invalid node'); + return; + } + + // Indicate loading state + setButtonLoading((prev) => !prev); + + await updateTreeToSearchNodeData({ ...root }, Number(searchItem), speedRange, setRoot); + const targetItem = String(insertedData[Math.floor(Math.random() + insertedData.length - 1) || 0]); + setInputData((prv) => ({ + ...prv, + searchItem: targetItem, + })); + // setRoot(updateRoot); + // Indicate loading state + setButtonLoading((prev) => !prev); + } catch (error) { + if (process.env.NODE_ENV === 'development') { + // eslint-disable-next-line no-console + console.error(error); + } + } + }; + + return ( + <> +
+
+
+
+
+ + +
+ +
+ + +
+ +
+
+
+ +
+ + +
+
+
+ +
+
+ +
+ + +
+
+
+
+
+ +
+
+ + {root ? ( + + + + ) : ( +
+

Invalid Linked List

+
+ )} +
+ + ); +}; + +const RenderNodeRecursively: React.FC<{ node: ITreeNode | null }> = ({ node }) => { + if (!node) return; + + return ( + <> + + {node?.next && ( + <> + + + + + + + + )} + + + + {node?.value} + + + {node.next ? : null} + + ); +}; + +export default LinkedListBasics; diff --git a/src/app/components/linked-list/LinkedListComponent.tsx b/src/app/components/linked-list/LinkedListComponent.tsx new file mode 100644 index 0000000..a66b25d --- /dev/null +++ b/src/app/components/linked-list/LinkedListComponent.tsx @@ -0,0 +1,106 @@ +'use client'; + +import { uid } from '@/app/lib/uidGenerator'; +import React, { useState } from 'react'; +import LinkedListBasics from './LinkedListBasics'; +import ReverseLinkedList from './ReverseLinkedList'; +import MergeTwoSortedList from './MergeTwoSortedList'; +import DetectCycle from './DetectCycle'; + +const LinkedListComponent = () => { + // define a component local memory + const [activeRootBtnType, setActiveRootBtnType] = useState('merge-two-linked-list'); //reverse-linked-list + const [randomKey, setRandomKey] = useState('1'); + const [speedRange, setSpeedRange] = useState(200); + + /** submit method to perform current task from start */ + const submitMethod = () => { + setRandomKey(uid()); + }; + + /** + * onChange method of select + * + * @param {React.ChangeEvent} e + */ + const handleSelectChange = async (e: React.ChangeEvent) => { + const value = e.target.value; + setActiveRootBtnType(value); + }; + + /** + * input type range method + * + * @param {*} e + */ + const inputRangeMethod = (e: React.ChangeEvent) => { + setSpeedRange(Number(e.target.value)); + }; + + return ( +
+
+
+
+

Speed: {speedRange} (0 to 1500)

+ +
+
+
+

Select type

+ +
+ +
+
+
+ +
+ {activeRootBtnType === 'linked-list-crud' ? ( + + ) : activeRootBtnType === 'reverse-linked-list' ? ( + + ) : activeRootBtnType === 'merge-two-linked-list' ? ( + + ) : activeRootBtnType === 'cycle' ? ( + + ) : null} +
+
+ ); +}; + +export default LinkedListComponent; diff --git a/src/app/components/linked-list/MergeTwoSortedList.tsx b/src/app/components/linked-list/MergeTwoSortedList.tsx new file mode 100644 index 0000000..c144649 --- /dev/null +++ b/src/app/components/linked-list/MergeTwoSortedList.tsx @@ -0,0 +1,271 @@ +'use client'; +import { + processRemainingNodes, + resetNodeStatus, + visualizeCurrentNode, +} from '@/app/algorithm/linked-list/mergeTwoSortedList'; +import { getRandomTreeData, LINKED_LIST_NODE_START_CX, LINKED_LIST_NODE_START_CY } from '@/app/constant'; +import { LinkedList } from '@/app/data-structure/LinkedList/LinkedList'; +import { TreeNode } from '@/app/data-structure/Tree/Node'; +import { mergeTwoListColorsPlate } from '@/app/data/mockData'; +import { clearAllTimeouts, Sleep } from '@/app/lib/sleepMethod'; +import { PageProps } from '@/app/types/linkedListProps'; +import { ITreeNode } from '@/app/types/TreeTypeProps'; +import StatusColorsPlate from '@/app/utils/StatusColorsPlate'; +import React, { useEffect, useState } from 'react'; + +const NODE_LENGTH = 5; + +const MergeTwoSortedList: React.FC = ({ speedRange, updateComponentWithKey }) => { + // define component local state + const [listOne, setListOne] = useState(); + const [listTwo, setListTwo] = useState(); + const [reverseNodes, setReverseNodes] = useState(); + const [isPerformReverseOperation, setIsPerformReverseOperation] = useState(false); + const [btnLoading, setBtnLoading] = useState(false); + + useEffect(() => { + // before initialize new list, if already some macro task are holding, need to removed them first + clearAllTimeouts(); + // create a random array with fixed length, then sort the data + const listOneArr = getRandomTreeData(NODE_LENGTH, 50).sort((a, b) => a - b); + // create a random array with fixed length, then sort the data + const listTwoArr = getRandomTreeData(NODE_LENGTH + 1, 100).sort((a, b) => a - b); + + // create a sorted list one + const ObjOne = new LinkedList(listOneArr, LINKED_LIST_NODE_START_CX); + ObjOne.createLinkedList(); + const head1 = ObjOne.head; + + // create a sorted list one + const ObjTwo = new LinkedList(listTwoArr, LINKED_LIST_NODE_START_CX); + ObjTwo.createLinkedList(); + const head2 = ObjTwo.head; + + if (head1) { + setListOne(head1); + } + if (head2) { + setListTwo(head2); + } + setIsPerformReverseOperation(true); + // clear the previously stored node + if (reverseNodes) { + setReverseNodes(null); + } + + return () => { + clearAllTimeouts(); + }; + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [updateComponentWithKey]); + + useEffect(() => { + if (isPerformReverseOperation) { + handleMergeTwoListMethod(); + setIsPerformReverseOperation(false); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isPerformReverseOperation, updateComponentWithKey]); + + /** + * Handles the merge of two linked lists with visualization. + * It updates the node positions and status to indicate the current step of the merge. + * @async + * @function handleMergeTwoListMethod + * @returns {Promise} + */ + const handleMergeTwoListMethod = async (): Promise => { + try { + // clear before all time-out + clearAllTimeouts(); + + // start the loader + setBtnLoading(true); + + const dummyHeadNode = new TreeNode(0); // Temporary dummy node to act as the head of the merged list. + let mergedListTail: TreeNode | null = dummyHeadNode; + + let list1 = JSON.parse(JSON.stringify(listOne)); // Deep clone listOne to avoid mutation + let list2 = JSON.parse(JSON.stringify(listTwo)); // Deep clone listTwo to avoid mutation + + let currentXPosition = LINKED_LIST_NODE_START_CX; // Initial horizontal position for nodes + + // Iterate over both lists until one of them is exhausted + while (list1 && list2) { + if (Number(list1.value) < Number(list2.value)) { + await visualizeCurrentNode(list1, setListOne, speedRange); // Visualize the current node of list1 + list1.cx = currentXPosition; + list1.cy = LINKED_LIST_NODE_START_CY; + + mergedListTail!.next = { ...list1, next: null }; // Safely assign the next node + list1 = list1.next; // Move to the next node in list1 + } else { + await visualizeCurrentNode(list2, setListTwo, speedRange); // Visualize the current node of list2 + list2.cx = currentXPosition; + list2.cy = LINKED_LIST_NODE_START_CY; + + mergedListTail!.next = { ...list2, next: null }; // Safely assign the next node + list2 = list2.next; // Move to the next node in list2 + } + + mergedListTail = mergedListTail!.next; // Advance the merged list tail + currentXPosition += 25; // Increment the horizontal position for the next node + + if (mergedListTail) { + mergedListTail.isTarget = true; // Mark the current merged node as the target for visualization + } + + // Update the state to visualize the merged list + setReverseNodes(() => ({ ...(dummyHeadNode.next as TreeNode) })); + + await Sleep(speedRange); // Add delay to visualize the current step + } + + // Handle any remaining nodes in list1 + await processRemainingNodes( + list1, + setListOne, + mergedListTail, + currentXPosition, + dummyHeadNode, + speedRange, + setReverseNodes + ); + + // Handle any remaining nodes in list2 + await processRemainingNodes( + list2, + setListTwo, + mergedListTail, + currentXPosition, + dummyHeadNode, + speedRange, + setReverseNodes + ); + + // Restore the original state of both lists by resetting isCurrent flags + resetNodeStatus(setListOne); + resetNodeStatus(setListTwo); + } catch (error) { + if (process.env.NODE_ENV === 'development') { + // eslint-disable-next-line no-console + console.error(error); // Log errors only in development mode + } + } finally { + // start the loader + setBtnLoading((prv) => !prv); + } + }; + + return ( + <> +
+
+
+ +
+ +
+
+ {listOne && listTwo ? ( +
+
+

LIST ONE:

+ + + +
+
+

LIST TWO:

+ + + +
+
+ ) : ( +
+

Loading...

+
+ )} + + {reverseNodes ? ( +
+

MERGE NODES:

+ + + +
+ ) : null} +
+
+ + ); +}; + +const RenderNodeRecursively: React.FC<{ node: ITreeNode | null }> = ({ node }) => { + if (!node) return; + + return ( + <> + + {node?.next && ( + <> + + + + + + + + )} + + + + {node?.value} + + + {node.next ? : null} + + ); +}; + +export default MergeTwoSortedList; diff --git a/src/app/components/linked-list/ReverseLinkedList.tsx b/src/app/components/linked-list/ReverseLinkedList.tsx new file mode 100644 index 0000000..d3a5d2a --- /dev/null +++ b/src/app/components/linked-list/ReverseLinkedList.tsx @@ -0,0 +1,196 @@ +'use client'; + +import { traverseLinkedListRecursively, reverseLinkedListWithPositions } from '@/app/algorithm/linked-list/reverseList'; +import { getRandomTreeData, LINKED_LIST_NODE_START_CX } from '@/app/constant'; +import { LinkedList } from '@/app/data-structure/LinkedList/LinkedList'; +import { TreeNode } from '@/app/data-structure/Tree/Node'; +import { reverseListColorsPlate } from '@/app/data/mockData'; +import { clearAllTimeouts } from '@/app/lib/sleepMethod'; +import { PageProps } from '@/app/types/linkedListProps'; +import { ITreeNode } from '@/app/types/TreeTypeProps'; +import StatusColorsPlate from '@/app/utils/StatusColorsPlate'; +import React, { useEffect, useState } from 'react'; + +const NODE_LENGTH = 11; + +const ReverseLinkedList: React.FC = ({ speedRange, updateComponentWithKey }) => { + // define component local state + const [rootNode, setRootNode] = useState(); + const [root, setRoot] = useState(); + const [reverseNodes, setReverseNodes] = useState(); + const [isPerformReverseOperation, setIsPerformReverseOperation] = useState(false); + const [btnLoading, setBtnLoading] = useState(false); + + useEffect(() => { + // create a new linked list with default cx value + const newList = new LinkedList(getRandomTreeData(NODE_LENGTH), LINKED_LIST_NODE_START_CX); + newList.createLinkedList(); + const head = newList.head; + if (head) { + // before initialize new list, if already some macro task are holding, need to removed them first + clearAllTimeouts(); + setRoot(head); + setRootNode(head); + setIsPerformReverseOperation(true); + } + + return () => { + clearAllTimeouts(); + }; + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [updateComponentWithKey]); + + useEffect(() => { + if (isPerformReverseOperation) { + handleReverseLinkedList(); + setIsPerformReverseOperation(false); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isPerformReverseOperation, updateComponentWithKey]); + + /** + * Handles the process of traversing, reversing a linked list, and updating node positions. + * + * This function performs the following: + * 1. Traverses the linked list recursively for visual highlighting. + * 2. Reverses the linked list and updates the positions of each node. + * 3. Updates the state of the root and reversed list to trigger re-renders. + * + * @returns {Promise} - A promise that resolves when the reversal and state updates are complete. + */ + const handleReverseLinkedList = async (): Promise => { + try { + setBtnLoading(true); + clearAllTimeouts(); + + // Traverse the list recursively for visualizing node traversal + traverseLinkedListRecursively( + JSON.parse(JSON.stringify(rootNode)), // Deep clone of rootNode to avoid direct mutations + setRoot, + speedRange + ); + + // Reverse the list and update node positions + const reversedRoot = await reverseLinkedListWithPositions( + JSON.parse(JSON.stringify(rootNode)), // Deep clone of rootNode for the reversal operation + NODE_LENGTH, // The total number of nodes in the linked list + setReverseNodes, // Function to update the reversed nodes' state + speedRange // Delay duration for visual effects + ); + + // Update the state of the reversed root node to trigger a re-render + setReverseNodes({ ...(reversedRoot as TreeNode) }); + } catch (error) { + // Log the error in development mode for debugging purposes + if (process.env.NODE_ENV === 'development') { + // eslint-disable-next-line no-console + console.error(error); + } + } finally { + setBtnLoading(false); + } + }; + + return ( + <> +
+
+
+ +
+ +
+ +
+ {root ? ( +
+

ACTUAL NODES:

+ + + + {reverseNodes ? ( + <> +

REVERSE NODES:

+ + + + + ) : null} +
+ ) : ( +
+

Loading...

+
+ )} +
+
+ + ); +}; + +const RenderNodeRecursively: React.FC<{ node: ITreeNode | null }> = ({ node }) => { + if (!node) return; + + return ( + <> + + {node?.next && ( + <> + + + + + + + + )} + + + + {node?.value} + + + {node.next ? : null} + + ); +}; + +export default ReverseLinkedList; diff --git a/src/app/components/n-queen/ChessBoard.tsx b/src/app/components/n-queen/ChessBoard.tsx index 13199fd..af878a3 100644 --- a/src/app/components/n-queen/ChessBoard.tsx +++ b/src/app/components/n-queen/ChessBoard.tsx @@ -3,8 +3,9 @@ import { fillGridArray, GRID_SIZE } from '@/app/constant'; import { gridStyle, isPutQueen, updateGrid } from '@/app/lib/nQueens'; import { clearAllTimeouts, Sleep } from '@/app/lib/sleepMethod'; +import { ChessBoardGridInlineStyleProps } from '@/app/types/commonProps'; import { CurrentItemProps, ICell } from '@/app/types/NQueensProps'; -import React, { useMemo, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; const SIZE: number = GRID_SIZE; @@ -18,6 +19,13 @@ const ChessBoard: React.FC = () => { const [highlight, setHighlight] = useState<{ [key: string]: boolean }>({}); const [speedRange, setSpeedRange] = useState(200); + useEffect(() => { + solveNQueens(); + + return () => clearAllTimeouts(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + /** * Root Method to perform n-queen visualization * @@ -125,30 +133,12 @@ const ChessBoard: React.FC = () => { * * @type {*} */ - const memoizedGridStyle = useMemo(() => gridStyle(selectInput), [selectInput]); + const memoizedGridStyle: ChessBoardGridInlineStyleProps = useMemo(() => gridStyle(selectInput), [selectInput]); return ( -
-
+
+
- -
-

Speed: {speedRange} (0 to 1500)

{ max='1500' />
- +
+
+ +
+ + +
@@ -191,7 +202,12 @@ const ChessBoard: React.FC = () => {
{isQueen || isCurrentItem ? (
- + = ({ useRandomKey, speedRange, gridSize }) => { // define component local state const [data, setData] = useState([]); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [queue, setQueue] = useState([]); + const [, setQueue] = useState([]); const [countIslands, setCountIslands] = useState(0); useEffect(() => { @@ -85,38 +86,16 @@ const NoOfIslands: React.FC = ({ useRandomKey, speedRange, }; return ( -
-
-
-
-
- - Water - -
-
-
- - Island - -
-
-
- - Current item - -
-
-
- - valid island - -
+ <> +
+
+

No of islands : {countIslands}

+
{data?.length ? (
@@ -128,6 +107,7 @@ const NoOfIslands: React.FC = ({ useRandomKey, speedRange, if (col.isCurrent) BG_COLOR = 'bg-blue-600 text-white'; if (col.isMarked) BG_COLOR = 'bg-pink-600 text-white'; if (col.isValidPath) BG_COLOR = 'bg-green-600 text-white'; + if (col.islandColor?.length) BG_COLOR = col.islandColor; let borderStyles = `border-b-[0.5px] border-r-[0.5px] border-[#575C6B] text-xl`; @@ -162,7 +142,7 @@ const NoOfIslands: React.FC = ({ useRandomKey, speedRange,
)}
-
+ ); }; diff --git a/src/app/components/pathFind/PathFind.tsx b/src/app/components/pathFind/PathFind.tsx index dabdfa1..d01c346 100644 --- a/src/app/components/pathFind/PathFind.tsx +++ b/src/app/components/pathFind/PathFind.tsx @@ -6,14 +6,14 @@ import UniquePath from './UniquePath'; import { gridRowColSize } from '@/app/lib/helpers'; import { clearAllTimeouts } from '@/app/lib/sleepMethod'; import NoOfIslands from './NoOfIslands'; +import ShortestPath from './ShortestPath'; const PathFind = () => { // define local state - const [buttonType, setButtonType] = useState('unique-path'); - // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [buttonType, setButtonType] = useState('unique-path'); //dijkstra const [randomKey, setRandomKey] = useState('1'); - const [speedRange, setSpeedRange] = useState(200); - const [gridSize, setGridSize] = useState<{ rowSize: number; colSize: number }>({ rowSize: 4, colSize: 4 }); + const [speedRange, setSpeedRange] = useState(100); + const [gridSize, setGridSize] = useState<{ rowSize: number; colSize: number }>({ rowSize: 8, colSize: 10 }); const [submittedGridSize, setSubmittedGridSize] = useState<{ rowSize: number; colSize: number }>(gridSize); // clear times out before component unmount @@ -66,55 +66,59 @@ const PathFind = () => { }; return ( -
-
-
-
-

Speed: {speedRange} (0 to 1500)

- -
-
-
-
-

Row

- -
-
-

Col

- -
+
+
+
+
+
+

Speed: {speedRange} (0 to 1500)

+ +
+
+ {buttonType !== 'shortest-path' ? ( +
+
+

Row

+ +
+
+

Col

+ +
+
+ ) : null}

Select type

@@ -129,6 +133,9 @@ const PathFind = () => { +
{buttonType === 'unique-path' ? ( -
+
) : null} {buttonType === 'no-of-island' ? ( -
+
+ ) : buttonType === 'shortest-path' ? ( + ) : null}
); diff --git a/src/app/components/pathFind/ShortestPath.tsx b/src/app/components/pathFind/ShortestPath.tsx new file mode 100644 index 0000000..8933bb1 --- /dev/null +++ b/src/app/components/pathFind/ShortestPath.tsx @@ -0,0 +1,375 @@ +/* eslint-disable indent */ +'use client'; +import { findShortestPathUsingBellmanFord } from '@/app/algorithm/shortest-path/bellmanFordShortestPath'; +import { findShortestPathUsingDijkstra } from '@/app/algorithm/shortest-path/dijkstraShortestPath'; +import { findShortestPathUsingFloydWarshall } from '@/app/algorithm/shortest-path/floydWarshall'; +import { dijkstraColorsPlate } from '@/app/data/mockData'; +import { generateEdges, generateEdgesForASearch, graphData } from '@/app/data/shortestPathData'; +import { handleResetDataForShortestPath } from '@/app/lib/graph'; +import { clearAllTimeouts } from '@/app/lib/sleepMethod'; +import { IGraphEdge, IGraphNode } from '@/app/types/shortestPathProps'; +import { GraphNodesProps } from '@/app/types/sortingProps'; +import StatusColorsPlate from '@/app/utils/StatusColorsPlate'; +import React, { useEffect, useState } from 'react'; + +// define component Props +interface PageProps { + useRandomKey: string; + speedRange: number; +} + +// initialized a list of possible ending or destination node for graph-2 +const graphTwoDestinationNodes: number[] = [13, 11, 4, 5, 12]; + +const ShortestPath: React.FC = ({ speedRange, useRandomKey }) => { + // define component memory + const [nodes, setNodes] = useState([]); + const [edges, setEdges] = useState([]); + const [shortestPathEdges, setShortestPathEdges] = useState([]); + const [isReadyToPerformOperation, setIsReadyToPerformOperation] = useState(false); + const [initialNodes, setInitialNodes] = useState<{ source: number; destination: number; nodeSizes: number }>({ + source: -1, + destination: -1, + nodeSizes: -1, + }); + const [btnLoading, setBtnLoading] = useState(false); + const [variationOfShortestPath, setVariationOfShortestPath] = useState('dijkstra'); + + // Trigger for component mount as well as dependency changes + useEffect(() => { + clearAllTimeouts(); + + const graphRandomPosition = Math.floor(Math.random() * 2) % 2; + const getGraph = JSON.parse(JSON.stringify(graphData[graphRandomPosition])); + const tempNodes = JSON.parse(JSON.stringify(getGraph.nodes)); + + const sourceNode = getGraph.source; + const idx = Math.floor(Math.random() * graphTwoDestinationNodes?.length + 1) % graphTwoDestinationNodes?.length; + const destinationNode = graphRandomPosition === 1 ? graphTwoDestinationNodes[idx] : getGraph.destination; + + // modify graph for source and destination + const modifyGraph = tempNodes.map((i: IGraphNode) => { + if (i.id === sourceNode) { + return { + ...i, + isSource: true, + }; + } else if (i.id === destinationNode) { + return { + ...i, + isDestination: true, + }; + } else return i; + }); + + // get source & destination node + setInitialNodes({ source: sourceNode, destination: destinationNode, nodeSizes: getGraph.nodeSizes }); + setNodes(modifyGraph); + setEdges(JSON.parse(JSON.stringify(graphRandomPosition === 0 ? generateEdges() : generateEdgesForASearch()))); + setShortestPathEdges([]); + setIsReadyToPerformOperation(true); + + // clear all timeout after component un-mount + return () => { + clearAllTimeouts(); + }; + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [useRandomKey]); + + // Trigger for component mount as well as dependency changes + useEffect(() => { + if (isReadyToPerformOperation) { + handleReVisualized(variationOfShortestPath); + setIsReadyToPerformOperation(false); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isReadyToPerformOperation, useRandomKey, edges, initialNodes]); + + /** + * Handles the execution of Dijkstra's algorithm to find the shortest path between a source and destination node. + * This function sets default values for the source, destination, and node sizes, and invokes the shortest path finding logic. + * It also handles any errors that might occur during execution and logs them in development mode. + * + * @param {Object} options - Options for configuring the Dijkstra algorithm. + * @param {number} [options.source=0] - The ID of the source node (default is 0). + * @param {number} [options.destination=4] - The ID of the destination node (default is 4). + * @param {number} [options.nodeSizes=10] - The total number of nodes in the graph (default is 10). + * + * @returns {Promise} A promise that resolves when Dijkstra's algorithm completes. + */ + const handleDijkstra = async ({ source = 0, destination = 4, nodeSizes = 10 }): Promise => { + try { + setBtnLoading(true); + await findShortestPathUsingDijkstra( + source, + destination, + nodeSizes, + speedRange, + setNodes, + edges, + setShortestPathEdges + ); + } catch (error) { + if (process.env.NODE_ENV === 'development') { + // eslint-disable-next-line no-console + console.error(error); + } + } finally { + setBtnLoading(false); + } + }; + + /** + * Executes the Bellman-Ford algorithm to find the shortest path in a graph. + * This function sets loading state during the operation and handles any errors + * that may occur during the execution of the algorithm. + * + * @param {Object} params - The parameters for the Bellman-Ford algorithm. + * @param {number} [params.source=0] - The starting node for the shortest path search. + * @param {number} [params.destination=4] - The target node for the shortest path search. + * @param {number} [params.nodeSizes=10] - The number of nodes in the graph. + * + * @returns {Promise} A promise that resolves when the algorithm has completed. + */ + const handleBellmanFord = async ({ source = 0, destination = 4, nodeSizes = 10 }): Promise => { + try { + setBtnLoading(true); + await findShortestPathUsingBellmanFord( + source, + destination, + nodeSizes, + nodes, + speedRange, + setNodes, + edges, + setBtnLoading, + setShortestPathEdges + ); + } catch (error) { + if (process.env.NODE_ENV === 'development') { + // eslint-disable-next-line no-console + console.error(error); + } + } finally { + setBtnLoading(false); + } + }; + + /** + * Handles the execution of the Floyd-Warshall algorithm to find the shortest path + * from the source to the destination node. + * + * This function sets the loading state before initiating the algorithm, handles any + * errors that may occur during execution, and resets the loading state afterward. + * + * @async + * @param {Object} params - Parameters for the function. + * @param {number} [params.source=0] - The index of the source node (default is 0). + * @param {number} [params.destination=4] - The index of the destination node (default is 4). + * @param {number} [params.nodeSizes=10] - The number of nodes in the graph (default is 10). + * + * @returns {Promise} A promise that resolves when the function completes. + */ + const handleFloydWarshall = async ({ source = 0, destination = 4, nodeSizes = 9 }): Promise => { + try { + setBtnLoading(true); // Set loading state to true while processing + await findShortestPathUsingFloydWarshall( + source, // Pass the source node + destination, // Pass the destination node + nodeSizes, // Pass the number of nodes + nodes, // Current nodes state + speedRange, // Speed range for visualization + setNodes, // Setter for updating nodes state + edges, // Current edges in the graph + setBtnLoading, // Setter for loading state + setShortestPathEdges // Setter for updating the shortest path edges + ); + } catch (error) { + if (process.env.NODE_ENV === 'development') { + // Log errors in development mode + // eslint-disable-next-line no-console + console.error(error); + } + } finally { + setBtnLoading(false); // Reset loading state to false after processing + } + }; + + /** + * Re-visualizes the graph based on the selected algorithm type. + * Resets the graph to its initial state and calculates the shortest path + * using either Dijkstra's algorithm or the Bellman-Ford algorithm. + * + * @param {string} type - The type of algorithm to use for finding the shortest path. + * Accepted values are 'dijkstra' or 'bellman-ford'. + * + * @returns {Promise} A promise that resolves when the visualization is updated. + */ + + const handleReVisualized = async (type: string) => { + const { source, destination, nodeSizes } = initialNodes; + // initialized all at initial state + setNodes(handleResetDataForShortestPath(nodes)); + setShortestPathEdges([]); + + if (type === 'dijkstra') { + await handleDijkstra({ source, destination, nodeSizes }); + } else if (type === 'bellman-ford') { + await handleBellmanFord({ source, destination, nodeSizes }); + } else { + await handleFloydWarshall({ source, destination, nodeSizes }); + } + }; + + /** + * Handles the selection of an algorithm type for visualizing the shortest path. + * Updates the state to reflect the selected algorithm type and triggers + * the re-visualization of the graph based on the selected type. + * + * @param {string} type - The type of algorithm to use for the shortest path. + * This could be "Dijkstra", "Bellman-Ford", etc. + */ + const handleAlgorithmType = (type: string) => { + setVariationOfShortestPath(type); + handleReVisualized(type); + }; + + return ( +
+
+
+
+ +
+
+
+ + + +
+
+ +
+ {edges?.length && nodes?.length ? ( + + {/* Render Edges */} + {edges.map((edge, index) => { + const sourceNode = nodes.find((n) => n.id === edge.source); + const targetNode = nodes.find((n) => n.id === edge.target); + if (!sourceNode || !targetNode) return null; + + // Check if the edge is part of the shortest path + const isShortestPathEdge = shortestPathEdges.some( + (e) => + (e.source === edge.source && e.target === edge.target) || + (e.source === edge.target && e.target === edge.source) + ); + + return ( + + {/* Draw the edge */} + + {/* Draw the weight */} + + {edge.weight} + + + ); + })} + + {/* Render Nodes */} + {nodes.map((node) => ( + + {/* Node Circle */} + + {/* Node Label */} + + {node.id} + + + {`(`} + {node.distance === Number.MAX_SAFE_INTEGER ? '∞' : node.distance} + {`)`} + + + ))} + + ) : ( +
+

Loading...

+
+ )} +
+
+ ); +}; + +export default ShortestPath; diff --git a/src/app/components/pathFind/UniquePath.tsx b/src/app/components/pathFind/UniquePath.tsx index 9e7a286..e9c6121 100644 --- a/src/app/components/pathFind/UniquePath.tsx +++ b/src/app/components/pathFind/UniquePath.tsx @@ -1,10 +1,12 @@ 'use client'; -import { DFSFindUniquePathMethod } from '@/app/algorithm/uniquePath'; +import { DFSFindUniquePathMethod } from '@/app/algorithm/path-finding/uniquePath'; import { UNIQUE_PATH_GRID_SIZE, UNIQUE_PATH_SVG_ICON_SIZE } from '@/app/constant'; +import { uniquePathFindingSortColorsData } from '@/app/data/mockData'; import { createGridWithUniquePath } from '@/app/data/PathFindingGridData'; import { clearAllTimeouts, Sleep } from '@/app/lib/sleepMethod'; import { GridProps, UniquePathPageProps } from '@/app/types/uniquePathProps'; +import StatusColorsPlate from '@/app/utils/StatusColorsPlate'; import React, { useEffect, useState } from 'react'; import { toast } from 'react-toastify'; @@ -96,33 +98,10 @@ const UniquePath: React.FC = ({ useRandomKey, speedRange, g }; return ( -
-
-
-
-
- - Bricks - -
-
-
- - Valid path - -
-
-
- - Current item - -
-
-
- - valid island - -
+ <> +
+
+

No of unique paths : {validPaths?.length} @@ -248,7 +227,7 @@ const UniquePath: React.FC = ({ useRandomKey, speedRange, g

) : null}
-
+ ); }; diff --git a/src/app/components/searching/BinarySearchTreeComponent.tsx b/src/app/components/searching/BinarySearchTreeComponent.tsx index 65bee23..6ae80e9 100644 --- a/src/app/components/searching/BinarySearchTreeComponent.tsx +++ b/src/app/components/searching/BinarySearchTreeComponent.tsx @@ -6,17 +6,23 @@ import { getRandomTreeData, NODE_POSITION } from '@/app/constant'; import { ITreeNode } from '@/app/types/TreeTypeProps'; import { Tree } from '@/app/data-structure/Tree/TreeNode'; import { clearAllTimeouts } from '@/app/lib/sleepMethod'; -import { performBST } from '@/app/algorithm/binarySearch'; import StatusColorsPlate from '@/app/utils/StatusColorsPlate'; import { bstSearchColorsData } from '@/app/data/mockData'; +import { performBST } from '@/app/algorithm/searching/binarySearch'; +// define component Page Props +interface PageProps { + speedRange: number; +} + +// a fixed array size const ARRAY_SIZE = 31; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const BinarySearchTreeComponent: React.FC<{ speedRange: number }> = ({ speedRange }) => { +const BinarySearchTreeComponent: React.FC = ({ speedRange }) => { // define component local state const [data, setData] = useState(null); const [target, setTarget] = useState(0); + const [btnLoading, setBtnLoading] = useState(false); const isSearching = useRef(false); // Ref to track if the search is already running useEffect(() => { @@ -27,8 +33,10 @@ const BinarySearchTreeComponent: React.FC<{ speedRange: number }> = ({ speedRang * all nodes in an in-order traversal. The nodes are collected and stored in the `steps` state. */ const tempArr = getRandomTreeData(ARRAY_SIZE); + const randomIdx = Math.floor(Math.random() * ARRAY_SIZE + 1) % ARRAY_SIZE; + // store the target item - setTarget(tempArr[Math.floor(Math.random() * 31 + 1)]); + setTarget(tempArr[randomIdx]); const newTree = new Tree(tempArr); // create a valid BST newTree.createBalancedBST(); @@ -41,38 +49,91 @@ const BinarySearchTreeComponent: React.FC<{ speedRange: number }> = ({ speedRang return () => { clearAllTimeouts(); }; - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { // Trigger performBST only when data and target are set and search isn't in progress if (data && target && !isSearching.current) { isSearching.current = true; // Mark search as in progress - performBST(data, setData, speedRange, target); + handleBstMethod(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [data, target]); + /** + * Resets the state of all nodes in a binary tree by setting `isCurrent`, `isTarget`, and `isInvalid` to false. + * + * @param {ITreeNode | null} data - The root node of the binary tree to reset, or null if the tree is empty. + * @returns {ITreeNode | null} A deep copy of the tree with all nodes reset, or null if the input was null. + */ + const resetDataState = (data: ITreeNode | null): ITreeNode | null => { + /** + * Recursively creates a deep clone of a binary tree. + * + * @param {ITreeNode | null} node - The root node of the tree to clone, or null if the tree is empty. + * @returns {ITreeNode | null} A new tree node that is a deep copy of the input tree, or null if the input was null. + */ + const deepCloneTree = (node: ITreeNode | null): ITreeNode | null => { + if (!node) return null; + + // Create a new node with cloned properties, including left and right children recursively + return { + ...node, + isCurrent: false, + isTarget: false, + isInvalid: false, + left: deepCloneTree(node.left), + right: deepCloneTree(node.right), + }; + }; + + // Create a deep copy of the tree to avoid direct mutation + const updatedData = deepCloneTree(data); + + return updatedData; // Return the reset tree data + }; + + /** + * Handles the BST method by resetting the tree data, then performing the BST algorithm on the reset tree. + * + * @returns {Promise} A promise that resolves after the BST algorithm is complete and the button loading state is updated. + */ + const handleBstMethod = async (): Promise => { + setBtnLoading(true); + + // Reset all nodes to their default state and pass the updated tree to performBST + const resetTree = resetDataState(data); + + if (resetTree) { + await performBST(resetTree, setData, speedRange, target); + } + + setBtnLoading(false); + }; + return ( <> -
- -

- TARGET : {target} -

+
+
+ +

+ TARGET : {target} +

+
+
- {data ? ( - <> - - - - - ) : ( -
-

Loading...

-
- )} + <> + + + + ); }; diff --git a/src/app/components/searching/LinearSearchComponent.tsx b/src/app/components/searching/LinearSearchComponent.tsx new file mode 100644 index 0000000..9dd650b --- /dev/null +++ b/src/app/components/searching/LinearSearchComponent.tsx @@ -0,0 +1,171 @@ +'use client'; + +import { LinearSearchToFindTheTarget } from '@/app/algorithm/searching/linearSearch'; +import { LinearSearchColorsProps } from '@/app/data/mockData'; +import { getRandomObject } from '@/app/lib/helpers'; +import { clearAllTimeouts, Sleep } from '@/app/lib/sleepMethod'; +import { LinearSearchDataProps } from '@/app/types/commonProps'; +import StatusColorsPlate from '@/app/utils/StatusColorsPlate'; +import React, { useEffect, useState } from 'react'; + +// define component Page Props +interface PageProps { + speedRange: number; + ueeRandomKey: string; +} + +const LinearSearchComponent: React.FC = ({ speedRange, ueeRandomKey }) => { + const [data, setData] = useState([]); + const [searchItem, setSearchItem] = useState(0); + const [steps, setSteps] = useState(0); + const [isReadyToPerformOperation, setIsReadyToPerformOperation] = useState(false); + + useEffect(() => { + const tempData = JSON.parse(JSON.stringify(getRandomObject(300))); + setData(tempData); + setSearchItem(Math.floor(Math.random() * tempData.length) % tempData.length); + + setIsReadyToPerformOperation(true); + + // clear all timeout after component un-mount + return () => { + clearAllTimeouts(); + }; + }, [ueeRandomKey]); + + useEffect(() => { + if (searchItem && isReadyToPerformOperation) { + handleLinearSearch(); + setIsReadyToPerformOperation(false); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ueeRandomKey, searchItem, isReadyToPerformOperation]); + + /** + * handle user input changes + * + * @param {React.ChangeEvent} e + */ + const handleTextInputChange = (e: React.ChangeEvent): void => { + setSearchItem(Number(e.target.value)); + }; + + /** + * Resets the data array to its initial state by clearing the `isCurrent` and `isTarget` flags for all items, + * and clears all timeouts. This function is used before starting a new search or visualization to ensure a clean state. + * + * @async + * @function resetAllAtInitialState + * @returns {Promise} - Returns a promise that resolves to the reset data array. + */ + const resetAllAtInitialState = async (): Promise => { + // Clear all timeouts to ensure no delayed updates are left from previous runs + clearAllTimeouts(); + + const tempData = data.map((item) => ({ + ...item, + isCurrent: false, + isTarget: false, + })); + + setData(tempData); + // Reset step counter to 0 + setSteps(0); + + // Return the reset data for further use + return tempData; + }; + + /** + * Handles the linear search algorithm visualization. The search is performed on the `data` array to find the `searchItem`. + * This function highlights the current item being checked, and if the target is found, it marks the item and displays a success message. + * The function makes use of an async operation to allow for smooth visualization between each search step. + * + * @async + * @function handleLinearSearch + * @returns {Promise} - Returns a promise that resolves when the linear search process is complete. + */ + const handleLinearSearch = async (): Promise => { + try { + // Reset the array to its initial state and get the reset data + const resetData = await resetAllAtInitialState(); + + // Add a slight delay for smoother visualization + await Sleep(100); + + // Perform the actual linear search algorithm using the reset data + // Passing the necessary state setters and parameters to control the search process + await LinearSearchToFindTheTarget( + resetData, // The data array with reset state + setSteps, // State setter to update the step counter + setData, // State setter to update the data during the search + setSearchItem, // State setter to update the search item if needed + speedRange, // The delay between steps for visualization + searchItem // The target item to search for + ); + } catch (error) { + // Log any errors during development + if (process.env.NODE_ENV === 'development') { + // eslint-disable-next-line no-console + console.error(error); + } + } + }; + + return ( + <> +
+
+
+ +
+ + +
+
+
+ +
+
+
+
+ {data?.length ? ( +
+ {data.map((item) => { + let BG_COLOR = `bg-white text-back`; + if (item.isTarget) BG_COLOR = `bg-green-600 text-white`; + if (item.isCurrent) BG_COLOR = `bg-blue-600 text-white`; + return ( +
+ {item.data} +
+ ); + })} +
+ ) : null} +
+
+

no of steps: {steps}

+
+ + ); +}; + +export default LinearSearchComponent; diff --git a/src/app/components/searching/SearchingComponent.tsx b/src/app/components/searching/SearchingComponent.tsx index ba10a0d..f3901c0 100644 --- a/src/app/components/searching/SearchingComponent.tsx +++ b/src/app/components/searching/SearchingComponent.tsx @@ -8,6 +8,7 @@ import React, { useEffect, useState } from 'react'; import { uid } from '@/app/lib/uidGenerator'; import BinarySearchTreeComponent from './BinarySearchTreeComponent'; import { clearAllTimeouts } from '@/app/lib/sleepMethod'; +import LinearSearchComponent from './LinearSearchComponent'; const SearchingComponent = () => { // define a component local memory @@ -47,7 +48,7 @@ const SearchingComponent = () => { }; return ( -
+
@@ -72,6 +73,9 @@ const SearchingComponent = () => { +
); diff --git a/src/app/components/sorting/BubbleSortComponent.tsx b/src/app/components/sorting/BubbleSortComponent.tsx index bf2c05f..2205bf7 100644 --- a/src/app/components/sorting/BubbleSortComponent.tsx +++ b/src/app/components/sorting/BubbleSortComponent.tsx @@ -1,8 +1,10 @@ -import { bubbleSortAlgo } from '@/app/algorithm/bubbleSort'; +import { bubbleSortAlgo } from '@/app/algorithm/sorting/bubbleSort'; import { MERGE_SORT_SVG_HEIGHT, MERGE_SORT_SVG_WIDTH } from '@/app/constant'; +import { bubbleSortColorsData } from '@/app/data/mockData'; import { sortingData } from '@/app/data/SortData'; import { clearAllTimeouts, Sleep } from '@/app/lib/sleepMethod'; import { SortingDataProps } from '@/app/types/sortingProps'; +import StatusColorsPlate from '@/app/utils/StatusColorsPlate'; import React, { useEffect, useState } from 'react'; // Calculate the maximum value to normalize data @@ -64,20 +66,8 @@ const BubbleSortComponent: React.FC<{ speedRange: number }> = ({ speedRange }) = return (
-
-
-
- - Current Item (Red) - -
- -
-
- - Sorted (Green) - -
+
+
diff --git a/src/app/components/sorting/HeapSortComponent.tsx b/src/app/components/sorting/HeapSortComponent.tsx index 1b74df8..ba6cfc2 100644 --- a/src/app/components/sorting/HeapSortComponent.tsx +++ b/src/app/components/sorting/HeapSortComponent.tsx @@ -1,10 +1,12 @@ 'use client'; -import { heapify, HeapSortApproach } from '@/app/algorithm/heapSort'; +import { heapify, HeapSortApproach } from '@/app/algorithm/sorting/heapSort'; import { getRandomTreeData, NODE_POSITION } from '@/app/constant'; import { Tree } from '@/app/data-structure/Tree/TreeNode'; +import { heapSortColorsData } from '@/app/data/mockData'; import { calculateLinePosition } from '@/app/lib/calculateSvgLinePosition'; import { HeapSortedItemProps } from '@/app/types/sortingProps'; import { ITreeNode } from '@/app/types/TreeTypeProps'; +import StatusColorsPlate from '@/app/utils/StatusColorsPlate'; import React, { useEffect, useState } from 'react'; import { toast } from 'react-toastify'; @@ -54,26 +56,10 @@ const HeapSortComponent: React.FC<{ speedRange: number }> = ({ speedRange }) => return ( <> -
-
-
- - Will be sorted - -
-
-
- - Swap - -
-
-
- - Sorted - -
+
+
+
{sortedData?.length ? (
diff --git a/src/app/components/sorting/MergeSortComponent.tsx b/src/app/components/sorting/MergeSortComponent.tsx index 59682d7..69c13a3 100644 --- a/src/app/components/sorting/MergeSortComponent.tsx +++ b/src/app/components/sorting/MergeSortComponent.tsx @@ -1,9 +1,11 @@ 'use client'; -import { mergeSortMethod } from '@/app/algorithm/mergeSort'; +import { mergeSortMethod } from '@/app/algorithm/sorting/mergeSort'; import { MERGE_SORT_SVG_HEIGHT, MERGE_SORT_SVG_WIDTH } from '@/app/constant'; +import { mergeSortColorsData } from '@/app/data/mockData'; import { sortingData } from '@/app/data/SortData'; import { SortingDataProps } from '@/app/types/sortingProps'; +import StatusColorsPlate from '@/app/utils/StatusColorsPlate'; import React, { useEffect, useState } from 'react'; // Calculate the maximum value to normalize data @@ -52,47 +54,10 @@ const MergeSortComponent: React.FC<{ speedRange: number }> = ({ speedRange = 0 } return (
-
- {/* Color for completed sorting */} -
-
- - Completed Sorting (Green) - -
- - {/* Color for current left index */} -
-
- - Current Left Index (Red) - -
- - {/* Color for current right index */} -
-
- - Current Right Index (Blue) - -
- - {/* Color for current left half */} -
-
- - Current Left Half (Orange) - -
- - {/* Color for current right half */} -
-
- - Current Right Half (Purple) - -
+
+
+
{data.map((item, index) => { diff --git a/src/app/components/sorting/QuickSortComponent.tsx b/src/app/components/sorting/QuickSortComponent.tsx index efcbc66..34f8f29 100644 --- a/src/app/components/sorting/QuickSortComponent.tsx +++ b/src/app/components/sorting/QuickSortComponent.tsx @@ -1,8 +1,10 @@ -import { quickSortAlgo } from '@/app/algorithm/quickSort'; +import { quickSortAlgo } from '@/app/algorithm/sorting/quickSort'; import { MERGE_SORT_SVG_HEIGHT, MERGE_SORT_SVG_WIDTH } from '@/app/constant'; import { sortingData } from '@/app/data//SortData'; +import { quickSortColorsData } from '@/app/data/mockData'; import { clearAllTimeouts, Sleep } from '@/app/lib/sleepMethod'; import { SortingDataProps } from '@/app/types/sortingProps'; +import StatusColorsPlate from '@/app/utils/StatusColorsPlate'; import React, { useEffect, useState } from 'react'; // Calculate the maximum value to normalize data @@ -77,32 +79,10 @@ const QuickSortComponent: React.FC<{ speedRange: number }> = ({ speedRange }) => return (
-
-
-
- - Pivot - -
-
-
- - Less than pivot - -
-
-
- - Greater than pivot - -
-
-
- - Sorted - -
+
+
+
{data.map((item, index) => { diff --git a/src/app/components/sorting/SelectionSortComponent.tsx b/src/app/components/sorting/SelectionSortComponent.tsx index c45ecd6..d21c080 100644 --- a/src/app/components/sorting/SelectionSortComponent.tsx +++ b/src/app/components/sorting/SelectionSortComponent.tsx @@ -1,8 +1,10 @@ -import { selectionSortAlgo } from '@/app/algorithm/selectionSort'; +import { selectionSortAlgo } from '@/app/algorithm/sorting/selectionSort'; import { MERGE_SORT_SVG_HEIGHT, MERGE_SORT_SVG_WIDTH } from '@/app/constant'; +import { selectionSortColorsData } from '@/app/data/mockData'; import { sortingData } from '@/app/data/SortData'; import { clearAllTimeouts, Sleep } from '@/app/lib/sleepMethod'; import { SortingDataProps } from '@/app/types/sortingProps'; +import StatusColorsPlate from '@/app/utils/StatusColorsPlate'; import React, { useEffect, useState } from 'react'; // Calculate the maximum value to normalize data @@ -68,32 +70,10 @@ const SelectionSortComponent: React.FC<{ speedRange: number }> = ({ speedRange } return (
-
-
-
- - Current Item (Red) - -
-
-
- - Candidate (Purple) - -
-
-
- - Current Comparable Item (Orange) - -
-
-
- - Sorted (Green) - -
+
+
+
{data.map((item, index) => { diff --git a/src/app/components/sorting/SortingComponent.tsx b/src/app/components/sorting/SortingComponent.tsx index 64e411a..8bd42f0 100644 --- a/src/app/components/sorting/SortingComponent.tsx +++ b/src/app/components/sorting/SortingComponent.tsx @@ -12,7 +12,6 @@ import { clearAllTimeouts } from '@/app/lib/sleepMethod'; const SortingComponent = () => { // define local state const [buttonType, setButtonType] = useState('merge-sort'); - // eslint-disable-next-line @typescript-eslint/no-unused-vars const [randomKey, setRandomKey] = useState(''); const [speedRange, setSpeedRange] = useState(200); @@ -45,7 +44,7 @@ const SortingComponent = () => { }; return ( -
+
@@ -94,27 +93,27 @@ const SortingComponent = () => {
{buttonType === 'merge-sort' ? ( -
+
) : null} {buttonType === 'bubble-sort' ? ( -
+
) : null} {buttonType === 'selection-sort' ? ( -
+
) : null} {buttonType === 'quick-sort' ? ( -
+
) : null} {buttonType === 'heap-sort' ? ( -
+
) : null} diff --git a/src/app/components/sudoku-solver/SudoKuSolver.tsx b/src/app/components/sudoku-solver/SudoKuSolver.tsx new file mode 100644 index 0000000..2945da2 --- /dev/null +++ b/src/app/components/sudoku-solver/SudoKuSolver.tsx @@ -0,0 +1,70 @@ +'use client'; + +import React, { useEffect, useState } from 'react'; +import SudoKuSolverComponent from './SudoKuSolverComponent'; +import { uid } from '@/app/lib/uidGenerator'; +import { clearAllTimeouts } from '@/app/lib/sleepMethod'; + +const SudoKuSolver = () => { + /** Define component local memory state */ + const [speedRange, setSpeedRange] = useState(200); + const [randomKey, setRandomKey] = useState('1'); + + // Trigger for component mount + useEffect(() => { + // Trigger for component un-mount + return () => clearAllTimeouts(); + }, []); + + /** submit method to perform current task from start */ + const submitMethod = () => { + setRandomKey(uid()); + }; + + /** + * Handles the change event for an input range, updating the speed range state. + * + * @param {React.ChangeEvent} e - The input change event. + */ + const inputRangeMethod = (e: React.ChangeEvent) => { + // Update the speed range state with the numeric value from the input + setSpeedRange(Number(e.target.value)); + }; + + return ( +
+
+
+
+
+
+

Speed: {speedRange} (0 to 1500)

+ +
+
+ +
+
+
+
+ {/* Render component */} + +
+
+ ); +}; + +export default SudoKuSolver; diff --git a/src/app/components/sudoku-solver/SudoKuSolverComponent.tsx b/src/app/components/sudoku-solver/SudoKuSolverComponent.tsx new file mode 100644 index 0000000..d5a4feb --- /dev/null +++ b/src/app/components/sudoku-solver/SudoKuSolverComponent.tsx @@ -0,0 +1,233 @@ +'use client'; + +import { Solve } from '@/app/algorithm/sudoku-solver/sudokuSolver'; +import { sudokuColorsPlate } from '@/app/data/mockData'; +import { SUDOKU_BOARD_DATA } from '@/app/data/sudokuData'; +import { clearAllTimeouts } from '@/app/lib/sleepMethod'; +import { addRandomHashesToBoard, InitialGridWithDefaultValue } from '@/app/lib/sudokuHelperMethod'; +import { SudoKuBoardProps } from '@/app/types/sudokyProps'; +import StatusColorsPlate from '@/app/utils/StatusColorsPlate'; +import React, { ReactNode, useEffect, useState } from 'react'; +import { toast } from 'react-toastify'; + +const SudoKuSolverComponent: React.FC<{ speedRange: number }> = ({ speedRange }) => { + /** Define component local memory state */ + const [board, setBoard] = useState([]); + const [inputsData, setInputsData] = useState([]); + const [btnLoading, setBtnLoading] = useState(false); + const [isPerformOperation, setIsPerformOperation] = useState(false); + const [rootData, setRootData] = useState([]); + + // Trigger for component mount + useEffect(() => { + clearAllTimeouts(); + const tempBoard = addRandomHashesToBoard(JSON.parse(JSON.stringify(SUDOKU_BOARD_DATA)), 4); + setBoard(tempBoard); + setInputsData(tempBoard); + setRootData(tempBoard); + setIsPerformOperation(true); + // Trigger for component un-mount + return () => clearAllTimeouts(); + }, []); + + useEffect(() => { + if (isPerformOperation) { + handleSudoKuSolver(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isPerformOperation]); + + /** + * Handles the input change for Sudoku cells, ensuring only numbers 1-9 or '#' are allowed. + * Updates both the input data and the board data accordingly. + * + * @param {React.ChangeEvent} e - The input change event. + * @param {number} rowIndex - The row index of the Sudoku board cell. + * @param {number} colIndex - The column index of the Sudoku board cell. + * @returns {ReactNode} - Optionally returns a toast error message. + */ + const handleTextInput = (e: React.ChangeEvent, rowIndex: number, colIndex: number): ReactNode => { + const value = e.target.value.trim(); // Get the trimmed input value + + // Ensure the input length is 1 (only one number allowed) + if (value.length > 1) { + return toast.error(`Only 1 number allowed at a time`); + } + + if (!value?.length) { + toast.error(`Field can not be empty`); + } + + // Validate input to accept only numbers 1-9 or a '#' (optional symbol for special cases) + const validValue = /^[1-9|#]$/.test(value); + + // If the value is invalid (not a number between 1-9 or '#' and not empty), show an error + if (!validValue && value !== '') { + toast.error(`Only numbers 1-9 & # are allowed.`); + } + + // Update the `inputsData` state with the valid value or clear it if invalid + setInputsData((prevBoard) => { + const updatedBoard = [...prevBoard]; + updatedBoard[rowIndex][colIndex].value = validValue ? value : ''; // Set the value or clear + updatedBoard[rowIndex][colIndex].isValid = false; // Set the value or clear + return updatedBoard; + }); + + // Update the `board` state with the valid value or clear it if invalid + setBoard((prevBoard) => { + const updatedBoard = [...prevBoard]; + updatedBoard[rowIndex][colIndex].value = validValue ? value : ''; // Set the value or clear + updatedBoard[rowIndex][colIndex].isValid = false; // Set the value or clear + return updatedBoard; + }); + }; + + /** + * Handles the execution of the Sudoku solver algorithm. + * + * This function prepares the Sudoku board by resetting certain properties, + * invokes the solver function, and updates the board state. + */ + const handleSudoKuSolver = async (): Promise => { + try { + // Indicate that the button is in a loading state + setBtnLoading(true); + // clear if any time interval's already are pending + clearAllTimeouts(); + + // Create a deep copy of the current board and reset specific properties + const tempBoard: SudoKuBoardProps[][] = InitialGridWithDefaultValue(board); + // Invoke the Sudoku solving algorithm with the temporary board + await Solve(tempBoard, setBoard, setInputsData, speedRange); + + // Update the board and inputs data with the solved state + setBoard(() => [...tempBoard]); + setInputsData(() => [...tempBoard]); + } catch (error) { + // Log the error in development mode + if (process.env.NODE_ENV === 'development') { + // eslint-disable-next-line no-console + console.log(error); + } + } finally { + // Reset the button loading state regardless of success or failure + setBtnLoading(false); + } + }; + + /** Prevent to perform sudoku solver */ + const handleStopSudokuMethod = () => { + clearAllTimeouts(); + const tempBoard = JSON.parse(JSON.stringify(rootData)); + setBoard([...tempBoard]); + setInputsData([...tempBoard]); + setBtnLoading(false); + }; + + return ( + <> +
+
+ +
+
+ + +
+
+
+ {board?.length ? ( +
+ {board.map((row, rowIndex) => ( +
+ {row.map((col, colIndex) => { + let BG_COLOR = 'bg-white text-black'; // Default background color + + // Apply border styles based on the row and column index + let borderStyles = `border-b-[0.5px] border-r-[0.5px] border-[#575C6B] text-xl`; + + if (rowIndex === 0) borderStyles += ` border-t-[0.5px]`; + if (rowIndex === board.length - 1) borderStyles += ` border-b-[0.5px]`; + if (colIndex === 0) borderStyles += ` border-l-[0.5px]`; + if (colIndex === board[0].length - 1) borderStyles += ` border-r-[0.5px]`; + + // Check if the current column is the last column of any 3x3 grid + if (colIndex % 3 === 2 && colIndex !== board.length - 1) { + borderStyles += ` border-r-[3px]`; + } + + // Check if the current row is the last row of any 3x3 grid + if (rowIndex % 3 === 2 && rowIndex !== board[0].length - 1) { + borderStyles += ` border-b-[3px]`; + } + + // check for valid item + if (board[rowIndex][colIndex].isValid) { + BG_COLOR = `bg-green-600 text-white`; + } + if (board[rowIndex][colIndex].isCurrent) { + BG_COLOR = `bg-blue-600 text-white`; + } + // for valid row check + if (board[rowIndex][colIndex].isValidRowItem) { + BG_COLOR = `bg-[#D895DA] text-white`; + } + // for valid col check + if (board[rowIndex][colIndex].isValidColItem) { + BG_COLOR = `bg-[#EB8317] text-white`; + } + // for valid subgrid check (3 X 3) + if (board[rowIndex][colIndex].isValidSubGridItem) { + BG_COLOR = `bg-purple-600 text-white`; + } + // check for invalid item + if (board[rowIndex][colIndex].isInvalid) { + BG_COLOR = `bg-red-600 text-white`; + } + + return ( +
+ { + handleTextInput(e, rowIndex, colIndex); + }} + max={9} + min={1} + disabled={btnLoading} + /> +
+ ); + })} +
+ ))} +
+ ) : ( +
+

Loading...

+
+ )} +
+ + ); +}; + +export default SudoKuSolverComponent; diff --git a/src/app/constant.ts b/src/app/constant.ts index 9eb8a7b..4cb465f 100644 --- a/src/app/constant.ts +++ b/src/app/constant.ts @@ -25,8 +25,8 @@ export const BFS_DELAY = 600; * @param {number} [size=31] * @returns {*} */ -export const getRandomTreeData = (size: number = 31) => - Array.from({ length: size }).map(() => Math.floor(Math.random() * 500 + 1)); +export const getRandomTreeData = (size: number = 31, range: number = 500) => + Array.from({ length: size }).map(() => Math.floor(Math.random() * range + 1)); // fill the check board according to the grid size export const fillGridArray = (size: number, defaultValue: number = 0): ICell[][] => { @@ -44,7 +44,7 @@ export const GRID_SIZE = 4; /* merge sort board size and other content goes here */ export const MERGE_SORT_SVG_WIDTH = 2000; -export const MERGE_SORT_SVG_HEIGHT = 300; +export const MERGE_SORT_SVG_HEIGHT = 400; // merge data sleep time export const MERGE_SLEEP_DELAY = 0; @@ -56,3 +56,7 @@ export const del_col = [0, -1, +1, 0]; export const directions = ['D', 'L', 'R', 'U']; // Direction string for consistency export const UNIQUE_PATH_SVG_ICON_SIZE = `w-[80%] min-[600px]:h-[20px] min-[600px]:w-[20px]`; export const UNIQUE_PATH_GRID_SIZE = `h-[50px] w-[50px]`; + +// Linked list component necessary constant thing +export const LINKED_LIST_NODE_START_CX = 20; +export const LINKED_LIST_NODE_START_CY = 20; diff --git a/src/app/data-structure/LinkedList/LinkedList.ts b/src/app/data-structure/LinkedList/LinkedList.ts new file mode 100644 index 0000000..467836c --- /dev/null +++ b/src/app/data-structure/LinkedList/LinkedList.ts @@ -0,0 +1,101 @@ +import { ITreeNode } from '@/app/types/TreeTypeProps'; +import { TreeNode } from '../Tree/Node'; + +// // Define the Tree interface +export interface ITree { + head: ITreeNode | null; + arr: (number | null)[]; +} + +/** + * Represents a linked list with optional cyclic references. + * Implements the `ITree` interface. + */ +export class LinkedList implements ITree { + /** The head node of the linked list. */ + head: ITreeNode | null = null; + /** An array representing the node values. */ + arr: (number | null)[] = []; + /** initialize start or root node cx value */ + initialize_cx: number; + + /** + * Creates an instance of the `LinkedList` class. + * + * @param {number | null[]} arr - An array of node values, where `null` represents no node. + * @param {number | undefined} initialize_cx - the initial staring cx position of node. + */ + constructor(arr: (number | null)[], initialize_cx: number = 20, head: ITreeNode | null = null) { + this.head = head; + this.arr = arr; + this.initialize_cx = initialize_cx; + } + + /** + * Creates a linked list from the provided array and sets up a cycle if specified. + * The cycle is created by connecting the node at `cycle2Index` to the node at `cycle1Index`. + * + * @returns {void} + */ + createLinkedList(): void { + if (this.arr.length === 0) return; + + // Insert the first node into the head + const temp = new TreeNode(this.arr[0]); + temp.cx = this.initialize_cx; + temp.cy = 20; + this.head = temp; + + // Create a pseudo copy of the head + let current = this.head; + + for (let i = 1; i < this.arr.length; i++) { + const newNode = new TreeNode(this.arr[i]); + newNode.cx = this.initialize_cx + 25; + newNode.cy = 20; + + current.next = newNode; + current = newNode; + + // Update CX value for positioning + this.initialize_cx += 25; + } + } + + /** + * Inserts a new node with the specified value at the end of the linked list + * and sets its position based on the provided x and y coordinates. + * + * @param {number} [x=20] - The x-coordinate for the new node's position. Defaults to 20. + * @param {number} [y=20] - The y-coordinate for the new node's position. Defaults to 20. + * @param {number} nodeValue - The value to be stored in the new node. + * + * @returns {void} - This method does not return a value. + * + * @example + * const linkedList = new LinkedList(); + * linkedList.insertIntoListWithGivenPositionXY(30, 40, 5); + * // A new node with value 5 is added to the list at the position (30, 40). + */ + insertIntoListWithGivenPositionXY(x: number = 20, y: number = 20, nodeValue: number): void { + // Create a new node + const temp = new TreeNode(nodeValue); + temp.cx = x; + temp.cy = y; + + if (!this.head) { + // If the list is empty, set the new node as the head + this.head = temp; + } else { + // Traverse the list to find the last node + let current = this.head; + + while (current.next) { + current = current.next; + } + + // Set the next pointer of the last node to the new node + current.next = temp; + } + } +} diff --git a/src/app/data-structure/Tree/Node.ts b/src/app/data-structure/Tree/Node.ts index 91eaa56..e8aa25a 100644 --- a/src/app/data-structure/Tree/Node.ts +++ b/src/app/data-structure/Tree/Node.ts @@ -18,6 +18,7 @@ import { ITreeNode } from '@/app/types/TreeTypeProps'; export class TreeNode implements ITreeNode { left: ITreeNode | null = null; right: ITreeNode | null = null; + next: ITreeNode | null = null; parent: ITreeNode | null = null; value: number | null; id: number | null; @@ -29,6 +30,13 @@ export class TreeNode implements ITreeNode { isSorted: boolean; isTarget: boolean; isInvalid: boolean; + isCycle: boolean; + isInsertedPosition: boolean; + destination: { x: number | null; y: number | null }; + slowPointer: boolean; + firstPointer: boolean; + isCycleStartPoint: boolean; + isCycleEndPoint: boolean; constructor( value: number | null = null, @@ -46,5 +54,12 @@ export class TreeNode implements ITreeNode { this.isSorted = false; this.isTarget = false; this.isInvalid = false; + this.isCycle = false; + this.isInsertedPosition = false; + this.destination = { x: -1, y: -1 }; + this.slowPointer = false; + this.firstPointer = false; + this.isCycleStartPoint = false; + this.isCycleEndPoint = false; } } diff --git a/src/app/data-structure/minHeap/MinHeap.ts b/src/app/data-structure/minHeap/MinHeap.ts new file mode 100644 index 0000000..beb9511 --- /dev/null +++ b/src/app/data-structure/minHeap/MinHeap.ts @@ -0,0 +1,113 @@ +/** + * A class representing a Min Heap data structure. + * This implementation is used to efficiently retrieve the node + * with the smallest distance. + */ +export class MinHeap { + heap: { node: number; distance: number }[]; + + /** + * Initializes an empty Min Heap. + */ + constructor() { + this.heap = []; + } + + /** + * Inserts a new node with its associated distance into the heap. + * This method maintains the heap property by bubbling up the new node. + * + * @param {number} node - The identifier of the node to insert. + * @param {number} distance - The distance associated with the node. + */ + insert(node: number, distance: number) { + this.heap.push({ node, distance }); + this.bubbleUp(); + } + + /** + * Extracts the node with the smallest distance from the heap. + * The root node is removed, and the last node is moved to the root + * followed by bubbling down to maintain the heap property. + * + * @returns {{ node: number; distance: number } | undefined} - The node with the smallest distance or undefined if the heap is empty. + */ + extractMin() { + if (this.heap.length === 1) return this.heap.pop(); + const min = this.heap[0]; + this.heap[0] = this.heap.pop()!; + this.bubbleDown(); + return min; + } + + /** + * Gets the parent index of a given node index. + * + * @param {number} index - The index of the current node. + * @returns {number} - The index of the parent node. + */ + getParentIndex(index: number) { + return Math.floor((index - 1) / 2); + } + + /** + * Swaps two elements in the heap. + * + * @param {number} index1 - The index of the first element. + * @param {number} index2 - The index of the second element. + */ + swap(index1: number, index2: number) { + [this.heap[index1], this.heap[index2]] = [this.heap[index2], this.heap[index1]]; + } + + /** + * Bubbles up the last element in the heap to maintain the heap property. + */ + bubbleUp() { + let index = this.heap.length - 1; + while (index > 0) { + const parentIndex = this.getParentIndex(index); + if (this.heap[index].distance >= this.heap[parentIndex].distance) break; + + this.swap(index, parentIndex); + index = parentIndex; + } + } + + /** + * Bubbles down the root element to maintain the heap property. + * This method ensures that the smallest element moves to the root position. + */ + bubbleDown() { + let index = 0; + const length = this.heap.length; + + // eslint-disable-next-line no-constant-condition + while (true) { + const leftChild = 2 * index + 1; + const rightChild = 2 * index + 2; + let smallest = index; + + if (leftChild < length && this.heap[leftChild].distance < this.heap[smallest].distance) { + smallest = leftChild; + } + + if (rightChild < length && this.heap[rightChild].distance < this.heap[smallest].distance) { + smallest = rightChild; + } + + if (smallest === index) break; + this.swap(index, smallest); + index = smallest; + } + } + + /** + * Checks if the heap is empty. + * + * @returns {boolean} - True if the heap is empty, false otherwise. + */ + isEmpty() { + return this.heap.length === 0; + } +} diff --git a/src/app/data/PathFindingGridData.ts b/src/app/data/PathFindingGridData.ts index a1d9076..f620312 100644 --- a/src/app/data/PathFindingGridData.ts +++ b/src/app/data/PathFindingGridData.ts @@ -21,6 +21,7 @@ export const createGridWithUniquePath = (rows: number, cols: number, threshold: isMarked: false, parent: { rowIdx: -1, colIdx: -1 }, isValidPath: false, + islandColor: '', })) ); @@ -42,341 +43,3 @@ export const createGridWithUniquePath = (rows: number, cols: number, threshold: return grid; }; - -// export const pathFindingGridData: GridProps[][] = [ -// [ -// { -// id: 1, -// data: 1, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// { -// id: 2, -// data: 1, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// { -// id: 3, -// data: 1, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// { -// id: 4, -// data: 1, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// { -// id: 5, -// data: 0, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// ], -// [ -// { -// id: 6, -// data: 1, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// { -// id: 7, -// data: 0, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// { -// id: 8, -// data: 1, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// { -// id: 9, -// data: 1, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// { -// id: 10, -// data: 1, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// ], -// [ -// { -// id: 11, -// data: 1, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// { -// id: 12, -// data: 0, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// { -// id: 13, -// data: 0, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// { -// id: 14, -// data: 0, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// { -// id: 15, -// data: 1, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// ], -// [ -// { -// id: 16, -// data: 1, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// { -// id: 17, -// data: 1, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// { -// id: 18, -// data: 0, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// { -// id: 19, -// data: 1, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// { -// id: 20, -// data: 1, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// ], -// [ -// { -// id: 21, -// data: 0, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// { -// id: 22, -// data: 1, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// { -// id: 23, -// data: 1, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// { -// id: 24, -// data: 1, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// { -// id: 25, -// data: 1, -// isVisit: false, -// isCurrent: false, -// isActive: false, -// isAdjacent: false, -// isReachAble: false, -// isInvalid: false, -// isMarked: false, -// parent: { rowIdx: -1, colIdx: -1 }, -// isValidPath: false, -// }, -// ], -// ]; diff --git a/src/app/data/SortData.ts b/src/app/data/SortData.ts index 53dca59..f743f97 100644 --- a/src/app/data/SortData.ts +++ b/src/app/data/SortData.ts @@ -1,218 +1,5 @@ import { SortingDataProps } from '../types/sortingProps'; -// export const mergeSortData: mergeSortDataProps[] = [ -// { -// id: 1, -// data: 130, -// isSorted: false, -// currentItem: false, -// isSwapped: false, -// isFinished: false, -// isCurrentCompareAbleItem: false, -// isCandidate: false, -// isActive: false, -// xPosition: 0, -// isLeft:false, -// isRight: false, -// }, -// { -// id: 2, -// data: 98, -// isSorted: false, -// currentItem: false, -// isSwapped: false, -// isFinished: false, -// isCurrentCompareAbleItem: false, -// isCandidate: false, -// isActive: false, -// xPosition: 0, -// isLeft:false, -// isRight: false, -// }, -// { -// id: 3, -// data: 179, -// isSorted: false, -// currentItem: false, -// isSwapped: false, -// isFinished: false, -// isCurrentCompareAbleItem: false, -// isCandidate: false, -// isActive: false, -// xPosition: 0, -// isLeft:false, -// isRight: false, -// }, -// { -// id: 4, -// data: 163, -// isSorted: false, -// currentItem: false, -// isSwapped: false, -// isFinished: false, -// isCurrentCompareAbleItem: false, -// isCandidate: false, -// isActive: false, -// xPosition: 0, -// isLeft:false, -// isRight: false, -// }, -// { -// id: 5, -// data: 169, -// isSorted: false, -// currentItem: false, -// isSwapped: false, -// isFinished: false, -// isCurrentCompareAbleItem: false, -// isCandidate: false, -// isActive: false, -// xPosition: 0, -// isLeft:false, -// isRight: false, -// }, -// { -// id: 6, -// data: 197, -// isSorted: false, -// currentItem: false, -// isSwapped: false, -// isFinished: false, -// isCurrentCompareAbleItem: false, -// isCandidate: false, -// isActive: false, -// xPosition: 0, -// isLeft:false, -// isRight: false, -// }, -// { -// id: 7, -// data: 158, -// isSorted: false, -// currentItem: false, -// isSwapped: false, -// isFinished: false, -// isCurrentCompareAbleItem: false, -// isCandidate: false, -// isActive: false, -// xPosition: 0, -// isLeft:false, -// isRight: false, -// }, -// { -// id: 8, -// data: 120, -// isSorted: false, -// currentItem: false, -// isSwapped: false, -// isFinished: false, -// isCurrentCompareAbleItem: false, -// isCandidate: false, -// isActive: false, -// xPosition: 0, -// isLeft:false, -// isRight: false, -// }, -// { -// id: 9, -// data: 137, -// isSorted: false, -// currentItem: false, -// isSwapped: false, -// isFinished: false, -// isCurrentCompareAbleItem: false, -// isCandidate: false, -// isActive: false, -// xPosition: 0, -// isLeft:false, -// isRight: false, -// }, -// { -// id: 10, -// data: 70, -// isSorted: false, -// currentItem: false, -// isSwapped: false, -// isFinished: false, -// isCurrentCompareAbleItem: false, -// isCandidate: false, -// isActive: false, -// xPosition: 0, -// isLeft:false, -// isRight: false, -// }, -// { -// id: 11, -// data: 199, -// isSorted: false, -// currentItem: false, -// isSwapped: false, -// isFinished: false, -// isCurrentCompareAbleItem: false, -// isCandidate: false, -// isActive: false, -// xPosition: 0, -// isLeft:false, -// isRight: false, -// }, -// { -// id: 12, -// data: 144, -// isSorted: false, -// currentItem: false, -// isSwapped: false, -// isFinished: false, -// isCurrentCompareAbleItem: false, -// isCandidate: false, -// isActive: false, -// xPosition: 0, -// isLeft:false, -// isRight: false, -// }, -// { -// id: 13, -// data: 100, -// isSorted: false, -// currentItem: false, -// isSwapped: false, -// isFinished: false, -// isCurrentCompareAbleItem: false, -// isCandidate: false, -// isActive: false, -// xPosition: 0, -// isLeft:false, -// isRight: false, -// }, -// { -// id: 14, -// data: 73, -// isSorted: false, -// currentItem: false, -// isSwapped: false, -// isFinished: false, -// isCurrentCompareAbleItem: false, -// isCandidate: false, -// isActive: false, -// xPosition: 0, -// isLeft:false, -// isRight: false, -// }, -// { -// id: 15, -// data: 153, -// isSorted: false, -// currentItem: false, -// isSwapped: false, -// isFinished: false, -// isCurrentCompareAbleItem: false, -// isCandidate: false, -// isActive: false, -// xPosition: 0, -// isLeft:false, -// isRight: false, -// }, -// ]; - export const sortingData: SortingDataProps[] = [ { id: 1, diff --git a/src/app/data/dashboardSchemaData.ts b/src/app/data/dashboardSchemaData.ts new file mode 100644 index 0000000..6aff1d1 --- /dev/null +++ b/src/app/data/dashboardSchemaData.ts @@ -0,0 +1,134 @@ +/* + * A dummy json data for home component. + */ + +import { uid } from '../lib/uidGenerator'; +import { ProjectSchema } from '../types/commonProps'; + +export const projectsData: ProjectSchema[] = [ + { + id: uid(), + name: 'Sudoku Solver', + navigate: '/sudoku-solver', + tags: [ + { + id: uid(), + name: 'DFS', + }, + ], + }, + { + id: uid(), + name: 'Tree', + navigate: '/tree', + tags: [ + { + id: uid(), + name: 'DFS (in-order, pre-order, post-order)', + }, + { + id: uid(), + name: 'BFS', + }, + ], + }, + { + id: uid(), + name: 'N Queens', + tags: [ + { + id: uid(), + name: 'DFS', + }, + ], + navigate: '/n-queens', + }, + { + id: uid(), + name: 'Sorting', + tags: [ + { + id: uid(), + name: 'Merge', + }, + { + id: uid(), + name: 'Quick', + }, + { + id: uid(), + name: 'Heap', + }, + { + id: uid(), + name: 'Bubble', + }, + { + id: uid(), + name: 'Selection', + }, + ], + navigate: '/sorting', + }, + { + id: uid(), + name: 'Path Finding', + tags: [ + { + id: uid(), + name: 'Dijkstra', + }, + { + id: uid(), + name: 'Bellman Ford', + }, + { + id: uid(), + name: 'Floyd Warshall', + }, + { + id: uid(), + name: 'Rat in maze', + }, + { + id: uid(), + name: 'No of Island', + }, + ], + navigate: '/path-finding', + }, + { + id: uid(), + name: 'Searching', + tags: [ + { + id: uid(), + name: 'Binary Search', + }, + ], + navigate: '/searching', + }, + { + id: uid(), + name: 'Linked List', + tags: [ + { + id: uid(), + name: 'Reverse list', + }, + { + id: uid(), + name: 'Merge two list', + }, + { + id: uid(), + name: 'Basic (CRUD)', + }, + { + id: uid(), + name: 'Detect Cycle', + }, + ], + navigate: '/linked-list', + }, +]; diff --git a/src/app/data/linkedListData.ts b/src/app/data/linkedListData.ts new file mode 100644 index 0000000..73e045b --- /dev/null +++ b/src/app/data/linkedListData.ts @@ -0,0 +1,13 @@ +export const CYCLE_NODE_DATA: { + x: number; + y: number; + node: number; +}[] = [ + { x: 160, y: 20, node: 1 }, + { x: 190, y: 35, node: 2 }, + { x: 200, y: 65, node: 3 }, + { x: 180, y: 90, node: 4 }, + { x: 145, y: 100, node: 5 }, + { x: 115, y: 85, node: 6 }, + { x: 110, y: 50, node: 7 }, +]; diff --git a/src/app/data/mockData.ts b/src/app/data/mockData.ts index 0950577..13c606b 100644 --- a/src/app/data/mockData.ts +++ b/src/app/data/mockData.ts @@ -22,3 +22,368 @@ export const bstSearchColorsData: StatusColorsDataProps[] = [ bg_color: 'bg-blue-600', }, ]; + +/** + * A array of object to render bubble sort component color-status + * + * @type {StatusColorsDataProps[]} + */ +export const bubbleSortColorsData: StatusColorsDataProps[] = [ + { + id: 1, + title: 'Current Item', + bg_color: 'bg-red-600', + }, + { + id: 2, + title: ' Sorted', + bg_color: 'bg-green-600', + }, +]; + +/** + * A array of object to render merge sort component color-status + * + * @type {StatusColorsDataProps[]} + */ +export const mergeSortColorsData: StatusColorsDataProps[] = [ + { + id: 1, + title: 'Completed Sorting', + bg_color: 'bg-green-600', + }, + { + id: 2, + title: 'Current Left Index', + bg_color: 'bg-red-600', + }, + { + id: 3, + title: 'Current Right Index', + bg_color: 'bg-blue-600', + }, + { + id: 4, + title: 'Current Left Half', + bg_color: 'bg-orange-600', + }, + { + id: 5, + title: 'Current Right Half', + bg_color: 'bg-purple-600', + }, +]; + +/** + * A array of object to render quick sort component color-status + * + * @type {StatusColorsDataProps[]} + */ +export const quickSortColorsData: StatusColorsDataProps[] = [ + { + id: 1, + title: 'Pivot', + bg_color: 'bg-red-600', + }, + { + id: 2, + title: 'Less than pivot', + bg_color: 'bg-purple-600', + }, + { + id: 3, + title: 'Greater than pivot', + bg_color: 'bg-orange-600', + }, + { + id: 4, + title: ' Sorted', + bg_color: 'bg-green-600', + }, +]; + +/** + * A array of object to render heap sort component color-status + * + * @type {StatusColorsDataProps[]} + */ +export const heapSortColorsData: StatusColorsDataProps[] = [ + { + id: 1, + title: 'Will be sorted', + bg_color: 'bg-red-600', + }, + { + id: 2, + title: 'Swap', + bg_color: 'bg-purple-600', + }, + { + id: 3, + title: 'Sorted', + bg_color: 'bg-green-600', + }, +]; + +/** + * A array of object to render selection sort component color-status + * + * @type {StatusColorsDataProps[]} + */ +export const selectionSortColorsData: StatusColorsDataProps[] = [ + { + id: 1, + title: 'Current Item', + bg_color: 'bg-red-600', + }, + { + id: 2, + title: 'Candidate', + bg_color: 'bg-purple-600', + }, + { + id: 3, + title: 'Current Comparable Item', + bg_color: 'bg-orange-600', + }, + { + id: 4, + title: 'Sorted', + bg_color: 'bg-green-600', + }, +]; + +/** + * A array of object to render unique-path finding component color-status + * + * @type {StatusColorsDataProps[]} + */ +export const uniquePathFindingSortColorsData: StatusColorsDataProps[] = [ + { + id: 1, + title: 'Bricks', + bg_color: 'bg-[#575C6B]', + }, + { + id: 2, + title: 'Valid path', + bg_color: 'bg-black border-[1px]', + }, + { + id: 3, + title: 'Current Item', + bg_color: 'bg-blue-600', + }, + { + id: 4, + title: 'valid island', + bg_color: 'bg-green-600', + }, +]; + +/** + * A array of object to render No-of-islands finding component color-status + * + * @type {StatusColorsDataProps[]} + */ +export const noOfIslandsSortColorsData: StatusColorsDataProps[] = [ + { + id: 1, + title: 'Water', + bg_color: 'bg-[#1ca3ec]', + }, + { + id: 2, + title: 'Island', + bg_color: 'bg-[#E99F0C]', + }, +]; + +/** + * A list of colors plate for islands + * + * @type {string[]} + */ +export const islandColorsPlate: string[] = [ + 'bg-[#8A2BE2] text-white', // BlueViolet (dark, white text) + 'bg-[#5F9EA0] text-white', // CadetBlue (muted, white text) + 'bg-[#D2691E] text-white', // Chocolate (earthy brown, white text) + 'bg-[#8B008B] text-white', // DarkMagenta (dark purple, white text) + 'bg-[#556B2F] text-white', // DarkOliveGreen (dark green, white text) + 'bg-[#FF8C00] text-black', // DarkOrange (bright, black text) + 'bg-[#9932CC] text-white', // DarkOrchid (dark purple, white text) + 'bg-[#8B4513] text-white', // SaddleBrown (dark brown, white text) + 'bg-[#2E8B57] text-white', // SeaGreen (dark green, white text) + 'bg-[#FFD700] text-black', // Gold (bright gold, black text) + 'bg-[#DAA520] text-black', // GoldenRod (muted gold, black text) + 'bg-[#4B0082] text-white', // Indigo (dark, white text) + 'bg-[#696969] text-white', // DimGray (dark gray, white text) + 'bg-[#708090] text-white', // SlateGray (muted gray, white text) + 'bg-[#B8860B] text-white', // DarkGoldenRod (rich gold-brown, white text) + 'bg-[#A52A2A] text-white', // Brown (dark brown, white text) + 'bg-[#6A5ACD] text-white', // SlateBlue (dark blue-purple, white text) + 'bg-[#483D8B] text-white', // DarkSlateBlue (dark, white text) + 'bg-[#7FFF00] text-black', // Chartreuse (bright, black text) + 'bg-[#DDA0DD] text-black', // Plum (light purple, black text) +]; + +/** + * Linked list visualization basic, inserted/delete/search color plates + * + * @type {StatusColorsDataProps[]} + */ +export const basicLinkedListColorsPlate: StatusColorsDataProps[] = [ + { + id: 1, + title: 'Inserted/Delete Position', + bg_color: 'bg-red-600', + }, + { + id: 2, + title: 'Current Item', + bg_color: 'bg-blue-600', + }, + { + id: 3, + title: 'Target', + bg_color: 'bg-green-600', + }, +]; + +/** + * Linked list visualization basic, inserted/delete/search color plates + * + * @type {StatusColorsDataProps[]} + */ +export const reverseListColorsPlate: StatusColorsDataProps[] = [ + { + id: 2, + title: 'Target Node', + bg_color: 'bg-red-600', + }, + { + id: 3, + title: 'Reversed successfully', + bg_color: 'bg-green-600', + }, +]; + +/** + * Linked list visualization merge two sort list + * + * @type {StatusColorsDataProps[]} + */ +export const mergeTwoListColorsPlate: StatusColorsDataProps[] = [ + { + id: 2, + title: 'Target Node', + bg_color: 'bg-red-600', + }, + { + id: 3, + title: 'Merged successfully', + bg_color: 'bg-green-600', + }, +]; + +/** + * Description placeholder + * + * @type {StatusColorsDataProps[]} + */ +export const dijkstraColorsPlate: StatusColorsDataProps[] = [ + { + id: 1, + title: 'Valid Path', + bg_color: 'bg-green-600', + }, + { + id: 2, + title: 'Current Node', + bg_color: 'bg-blue-600', + }, + { + id: 3, + title: 'Source Node', + bg_color: 'bg-[#fb7c06]', + }, + { + id: 4, + title: 'Destination Node', + bg_color: 'bg-[#9458ff]', + }, +]; + +/** + * Linear search colors plate mock data + * + * @type {StatusColorsDataProps[]} + */ +export const LinearSearchColorsProps: StatusColorsDataProps[] = [ + { + id: 1, + title: 'Current Item', + bg_color: 'bg-blue-600', + }, + { + id: 2, + title: 'Target', + bg_color: 'bg-green-600', + }, +]; + +export const detectCycleFromGivenLinkedList: StatusColorsDataProps[] = [ + { + id: 2, + title: 'First Pointer', + bg_color: 'bg-blue-600', + }, + { + id: 3, + title: 'Slow Pointer', + bg_color: 'bg-red-600', + }, + { + id: 1, + title: 'Part Of Cycle', + bg_color: 'bg-green-600', + }, +]; + +/** + * Sudoku solver colors plate for current or other status + * + * @type {StatusColorsDataProps[]} + */ +export const sudokuColorsPlate: StatusColorsDataProps[] = [ + { + id: 2, + title: 'Current', + bg_color: 'bg-blue-600', + }, + { + id: 3, + title: 'Check Row', + bg_color: 'bg-[#D895DA]', + }, + { + id: 4, + title: 'Check Col', + bg_color: 'bg-[#EB8317]', + }, + { + id: 5, + title: 'Check Sub-grid', + bg_color: 'bg-purple-600', + }, + { + id: 6, + title: 'Invalid', + bg_color: 'bg-red-600', + }, + { + id: 1, + title: 'Valid', + bg_color: 'bg-green-600', + }, +]; diff --git a/src/app/data/shortestPathData.ts b/src/app/data/shortestPathData.ts new file mode 100644 index 0000000..dc384a1 --- /dev/null +++ b/src/app/data/shortestPathData.ts @@ -0,0 +1,273 @@ +import { generateRandomWeight } from '../lib/helpers'; +import { IGraphEdge } from '../types/shortestPathProps'; + +/** + * Generates an array of edges for a graph-1. + * Each edge connects two nodes and has a randomly assigned weight. + * + * @function generateEdges + * @returns {IGraphEdge[]} An array of edges, where each edge is represented by an object + * containing the source node, target node, and weight. + */ + +export const generateEdges = (): IGraphEdge[] => { + const directedEdges = [ + { source: 0, target: 1, weight: generateRandomWeight(), weightPosition: { x: -1, y: 45 } }, + { source: 0, target: 7, weight: generateRandomWeight(), weightPosition: { x: -1, y: -1 } }, + { source: 1, target: 2, weight: generateRandomWeight(), weightPosition: { x: -1, y: 18 } }, + { source: 1, target: 7, weight: generateRandomWeight(5), weightPosition: { x: -1, y: -1 } }, + { source: 2, target: 3, weight: generateRandomWeight(), weightPosition: { x: -1, y: 18 } }, + { source: 2, target: 8, weight: generateRandomWeight(), weightPosition: { x: -1, y: 45 } }, + { source: 2, target: 5, weight: generateRandomWeight(), weightPosition: { x: -1, y: -1 } }, + { source: 3, target: 4, weight: generateRandomWeight(5), weightPosition: { x: -1, y: -1 } }, + { source: 3, target: 5, weight: generateRandomWeight(), weightPosition: { x: -1, y: -1 } }, + { source: 4, target: 5, weight: generateRandomWeight(9), weightPosition: { x: -1, y: 85 } }, + { source: 5, target: 6, weight: generateRandomWeight(), weightPosition: { x: -1, y: 108 } }, + { source: 6, target: 7, weight: generateRandomWeight(5), weightPosition: { x: -1, y: 108 } }, + { source: 6, target: 8, weight: generateRandomWeight(), weightPosition: { x: -1, y: -1 } }, + { source: 7, target: 8, weight: generateRandomWeight(), weightPosition: { x: -1, y: 75 } }, + ]; + + // Create undirected edges by duplicating each directed edge + const undirectedEdges: IGraphEdge[] = []; + + directedEdges.forEach((edge) => { + // Add the original directed edge + undirectedEdges.push(edge); + + // Add the reverse directed edge to make it undirected + undirectedEdges.push({ + source: edge.target, + target: edge.source, + weight: edge.weight, + weightPosition: edge.weightPosition, // Optionally adjust weight position for visualization + }); + }); + + return undirectedEdges; +}; + +/** + * Generates an array of edges for a graph-2. + * Each edge connects two nodes and has a randomly assigned weight. + * + * @function generateEdgesForASearch + * @returns {IGraphEdge[]} An array of edges, where each edge is represented by an object + * containing the source node, target node, and weight. + */ +export const generateEdgesForASearch = (): IGraphEdge[] => { + const directedEdges = [ + { source: 0, target: 1, weight: generateRandomWeight(), weightPosition: { x: -1, y: 32 } }, + { source: 0, target: 7, weight: generateRandomWeight(), weightPosition: { x: -1, y: -1 } }, + { source: 1, target: 2, weight: generateRandomWeight(), weightPosition: { x: -1, y: -1 } }, + { source: 2, target: 0, weight: generateRandomWeight(), weightPosition: { x: -1, y: 58 } }, + { source: 2, target: 7, weight: generateRandomWeight(), weightPosition: { x: -1, y: 82 } }, + { source: 7, target: 10, weight: generateRandomWeight(), weightPosition: { x: -1, y: -1 } }, + { source: 9, target: 9, weight: generateRandomWeight(), weightPosition: { x: -1, y: -1 } }, + { source: 3, target: 4, weight: generateRandomWeight(), weightPosition: { x: -1, y: -1 } }, + { source: 11, target: 4, weight: generateRandomWeight(), weightPosition: { x: -1, y: 57 } }, + { source: 4, target: 5, weight: generateRandomWeight(), weightPosition: { x: -1, y: 85 } }, + { source: 12, target: 6, weight: generateRandomWeight(), weightPosition: { x: -1, y: 148 } }, + { source: 6, target: 9, weight: generateRandomWeight(), weightPosition: { x: -1, y: -1 } }, + { source: 3, target: 8, weight: generateRandomWeight(), weightPosition: { x: -1, y: 18 } }, + { source: 1, target: 8, weight: generateRandomWeight(5), weightPosition: { x: -1, y: 18 } }, + { source: 2, target: 9, weight: generateRandomWeight(), weightPosition: { x: -1, y: -1 } }, + { source: 10, target: 9, weight: generateRandomWeight(), weightPosition: { x: -1, y: 127 } }, + { source: 3, target: 11, weight: generateRandomWeight(), weightPosition: { x: -1, y: 45 } }, + { source: 5, target: 11, weight: generateRandomWeight(), weightPosition: { x: -1, y: -1 } }, + { source: 5, target: 12, weight: generateRandomWeight(), weightPosition: { x: -1, y: -1 } }, + { source: 6, target: 11, weight: generateRandomWeight(), weightPosition: { x: -1, y: -1 } }, + { source: 13, target: 11, weight: generateRandomWeight(), weightPosition: { x: -1, y: 58 } }, + ]; + + // Create undirected edges by duplicating each directed edge + const undirectedEdges: IGraphEdge[] = []; + + directedEdges.forEach((edge) => { + // Add the original directed edge + undirectedEdges.push(edge); + + // Add the reverse directed edge to make it undirected + undirectedEdges.push({ + source: edge.target, + target: edge.source, + weight: edge.weight, + weightPosition: edge.weightPosition, // Optionally adjust weight position for visualization + }); + }); + + return undirectedEdges; +}; + +/** + * Root data of graph data + */ +// Define the common properties for nodes +const nodeProperties = { + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + distance: Number.MAX_SAFE_INTEGER, + isTargetNode: false, +}; + +// Updated graphData using the spread operator +export const graphData = [ + { + nodes: [ + { + id: 0, + x: 50, + y: 60, + ...nodeProperties, + }, + { + id: 1, + x: 100, + y: 20, + ...nodeProperties, + }, + { + id: 2, + x: 200, + y: 20, + ...nodeProperties, + }, + { + id: 3, + x: 300, + y: 20, + ...nodeProperties, + }, + { + id: 4, + x: 350, + y: 60, + ...nodeProperties, + }, + { + id: 5, + x: 300, + y: 100, + ...nodeProperties, + }, + { + id: 6, + x: 200, + y: 100, + ...nodeProperties, + }, + { + id: 7, + x: 100, + y: 100, + ...nodeProperties, + }, + { + id: 8, + x: 200, + y: 60, + ...nodeProperties, + }, + ], + edges: [...generateEdges()], + source: 0, + destination: 4, + nodeSizes: 9, + }, + { + nodes: [ + { + id: 0, + x: 50, + y: 60, + ...nodeProperties, + }, + { + id: 1, + x: 100, + y: 20, + ...nodeProperties, + }, + { + id: 2, + x: 100, + y: 60, + ...nodeProperties, + }, + { + id: 3, + x: 300, + y: 20, + ...nodeProperties, + }, + { + id: 4, + x: 350, + y: 60, + ...nodeProperties, + }, + { + id: 5, + x: 300, + y: 100, + ...nodeProperties, + }, + { + id: 6, + x: 200, + y: 150, + ...nodeProperties, + }, + { + id: 7, + x: 70, + y: 100, + ...nodeProperties, + }, + { + id: 8, + x: 200, + y: 20, + ...nodeProperties, + }, + { + id: 9, + x: 130, + y: 100, + ...nodeProperties, + }, + { + id: 10, + x: 100, + y: 150, + ...nodeProperties, + }, + { + id: 11, + x: 250, + y: 60, + ...nodeProperties, + }, + { + id: 12, + x: 350, + y: 150, + ...nodeProperties, + }, + { + id: 13, + x: 180, + y: 60, + ...nodeProperties, + }, + ], + edges: [...generateEdgesForASearch()], + source: 0, + destination: 13, + nodeSizes: 14, + }, +]; diff --git a/src/app/data/sudokuData.ts b/src/app/data/sudokuData.ts new file mode 100644 index 0000000..91abfcd --- /dev/null +++ b/src/app/data/sudokuData.ts @@ -0,0 +1,116 @@ +import { SudoKuBoardProps } from '../types/sudokyProps'; + +// Object with default properties +const objectProperty: Omit = { + isValid: false, + isActive: false, + isCurrent: false, + isTarget: false, + isValidRowItem: false, + isValidColItem: false, + isValidSubGridItem: false, + isInvalid: false, +}; + +// Two-dimensional array for the Sudoku board +export const SUDOKU_BOARD_DATA: SudoKuBoardProps[][] = [ + [ + { value: '9', id: '0-0', ...objectProperty }, + { value: '5', id: '0-1', ...objectProperty }, + { value: '7', id: '0-2', ...objectProperty }, + { value: '#', id: '0-3', ...objectProperty }, + { value: '1', id: '0-4', ...objectProperty }, + { value: '3', id: '0-5', ...objectProperty }, + { value: '#', id: '0-6', ...objectProperty }, + { value: '8', id: '0-7', ...objectProperty }, + { value: '4', id: '0-8', ...objectProperty }, + ], + [ + { value: '4', id: '1-0', ...objectProperty }, + { value: '8', id: '1-1', ...objectProperty }, + { value: '3', id: '1-2', ...objectProperty }, + { value: '#', id: '1-3', ...objectProperty }, + { value: '5', id: '1-4', ...objectProperty }, + { value: '7', id: '1-5', ...objectProperty }, + { value: '1', id: '1-6', ...objectProperty }, + { value: '#', id: '1-7', ...objectProperty }, + { value: '6', id: '1-8', ...objectProperty }, + ], + [ + { value: '#', id: '2-0', ...objectProperty }, + { value: '1', id: '2-1', ...objectProperty }, + { value: '2', id: '2-2', ...objectProperty }, + { value: '#', id: '2-3', ...objectProperty }, + { value: '4', id: '2-4', ...objectProperty }, + { value: '9', id: '2-5', ...objectProperty }, + { value: '5', id: '2-6', ...objectProperty }, + { value: '3', id: '2-7', ...objectProperty }, + { value: '7', id: '2-8', ...objectProperty }, + ], + [ + { value: '1', id: '3-0', ...objectProperty }, + { value: '7', id: '3-1', ...objectProperty }, + { value: '#', id: '3-2', ...objectProperty }, + { value: '3', id: '3-3', ...objectProperty }, + { value: '#', id: '3-4', ...objectProperty }, + { value: '4', id: '3-5', ...objectProperty }, + { value: '9', id: '3-6', ...objectProperty }, + { value: '#', id: '3-7', ...objectProperty }, + { value: '2', id: '3-8', ...objectProperty }, + ], + [ + { value: '5', id: '4-0', ...objectProperty }, + { value: '#', id: '4-1', ...objectProperty }, + { value: '4', id: '4-2', ...objectProperty }, + { value: '9', id: '4-3', ...objectProperty }, + { value: '7', id: '4-4', ...objectProperty }, + { value: '#', id: '4-5', ...objectProperty }, + { value: '3', id: '4-6', ...objectProperty }, + { value: '6', id: '4-7', ...objectProperty }, + { value: '#', id: '4-8', ...objectProperty }, + ], + [ + { value: '3', id: '5-0', ...objectProperty }, + { value: '#', id: '5-1', ...objectProperty }, + { value: '9', id: '5-2', ...objectProperty }, + { value: '5', id: '5-3', ...objectProperty }, + { value: '#', id: '5-4', ...objectProperty }, + { value: '8', id: '5-5', ...objectProperty }, + { value: '7', id: '5-6', ...objectProperty }, + { value: '#', id: '5-7', ...objectProperty }, + { value: '1', id: '5-8', ...objectProperty }, + ], + [ + { value: '8', id: '6-0', ...objectProperty }, + { value: '4', id: '6-1', ...objectProperty }, + { value: '5', id: '6-2', ...objectProperty }, + { value: '7', id: '6-3', ...objectProperty }, + { value: '9', id: '6-4', ...objectProperty }, + { value: '#', id: '6-5', ...objectProperty }, + { value: '6', id: '6-6', ...objectProperty }, + { value: '1', id: '6-7', ...objectProperty }, + { value: '3', id: '6-8', ...objectProperty }, + ], + [ + { value: '#', id: '7-0', ...objectProperty }, + { value: '9', id: '7-1', ...objectProperty }, + { value: '1', id: '7-2', ...objectProperty }, + { value: '#', id: '7-3', ...objectProperty }, + { value: '3', id: '7-4', ...objectProperty }, + { value: '6', id: '7-5', ...objectProperty }, + { value: '#', id: '7-6', ...objectProperty }, + { value: '7', id: '7-7', ...objectProperty }, + { value: '5', id: '7-8', ...objectProperty }, + ], + [ + { value: '7', id: '8-0', ...objectProperty }, + { value: '#', id: '8-1', ...objectProperty }, + { value: '6', id: '8-2', ...objectProperty }, + { value: '1', id: '8-3', ...objectProperty }, + { value: '8', id: '8-4', ...objectProperty }, + { value: '5', id: '8-5', ...objectProperty }, + { value: '4', id: '8-6', ...objectProperty }, + { value: '#', id: '8-7', ...objectProperty }, + { value: '9', id: '8-8', ...objectProperty }, + ], +]; diff --git a/src/app/globals.scss b/src/app/globals.scss index 63559de..c9bf983 100644 --- a/src/app/globals.scss +++ b/src/app/globals.scss @@ -2,20 +2,6 @@ @tailwind components; @tailwind utilities; -:root { - /* --foreground-rgb: 0, 0, 0; - --background-start-rgb: 214, 219, 220; - --background-end-rgb: 255, 255, 255; */ -} - -@media (prefers-color-scheme: dark) { - :root { - /* --foreground-rgb: 255, 255, 255; - --background-start-rgb: 0, 0, 0; - --background-end-rgb: 0, 0, 0; */ - } -} - body { background-color: #fff; } @@ -31,7 +17,7 @@ body { } .nav-list-item-link { - @apply relative my-2 inline-block whitespace-nowrap px-1 pb-2 text-lg font-light text-theme-primary duration-300 after:absolute after:bottom-0 after:left-0 after:h-[1px] after:w-0 after:bg-theme-secondary after:duration-200 hover:after:w-full lg:mx-[12px] lg:my-0 lg:pb-1 dark:text-theme-dark-primary dark:after:bg-theme-white; + @apply relative my-2 inline-block whitespace-nowrap px-0 pb-2 text-lg font-light text-theme-primary duration-300 after:absolute after:bottom-0 after:left-0 after:h-[1px] after:w-0 after:bg-theme-secondary after:duration-200 hover:after:w-full lg:mx-[12px] lg:my-0 lg:pb-1 dark:text-theme-dark-primary dark:after:bg-theme-white; } .title-txt { @@ -77,3 +63,15 @@ body { .footer-nav-link { @apply relative inline-block px-1 pb-1 text-base text-theme-secondary duration-300 after:absolute after:bottom-0 after:left-0 after:h-[1px] after:w-0 after:bg-theme-secondary after:duration-300 hover:after:w-full lg:text-lg dark:text-theme-dark-secondary dark:after:bg-theme-white; } + +@layer utilities { + input[type='number']::-webkit-outer-spin-button, + input[type='number']::-webkit-inner-spin-button { + @apply appearance-none; /* Hide number spinner for Webkit browsers */ + } + + /* For Firefox */ + input[type='number'] { + -moz-appearance: textfield; /* Hide spinner for Firefox */ + } +} diff --git a/src/app/lib/graph.ts b/src/app/lib/graph.ts new file mode 100644 index 0000000..95fe6d3 --- /dev/null +++ b/src/app/lib/graph.ts @@ -0,0 +1,26 @@ +import { GraphNodesProps } from '../types/sortingProps'; + +/** + * Resets the properties of each node in the graph to their initial state. + * This is useful for preparing the nodes for a new graph algorithm execution + * by clearing previous statuses and setting default values. + * + * @param {GraphNodesProps[]} nodes - An array of graph nodes that need to be reset. + * Each node should have properties like `isVisited`, `isCurrentNode`, `isShortestPath`, + * `isInvalidPath`, `isDestination`, `isSource`, `distance`, and `isTargetNode`. + * + * @returns {GraphNodesProps[]} - A new array of graph nodes with reset properties. + */ +export const handleResetDataForShortestPath = (nodes: GraphNodesProps[]): GraphNodesProps[] => { + return nodes.map((node) => ({ + ...node, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + distance: Number.MAX_SAFE_INTEGER, + isTargetNode: false, + })); +}; diff --git a/src/app/lib/helpers.ts b/src/app/lib/helpers.ts index 681568f..139c20b 100644 --- a/src/app/lib/helpers.ts +++ b/src/app/lib/helpers.ts @@ -46,3 +46,13 @@ export const gridRowColSize = (maxSize: number = 12) => { }; }); }; + +/** + * get a random weight for graph + * + * @returns {number} + */ +export const generateRandomWeight = (maxNum: number = 2): number => { + const timestamp = new Date().getTime(); // Get current time in milliseconds + return Math.floor((((timestamp % 1000) + Math.random() * maxNum + 1) % maxNum) + 1); // Use time to influence randomness +}; diff --git a/src/app/lib/mapUtils.ts b/src/app/lib/mapUtils.ts new file mode 100644 index 0000000..f17e43f --- /dev/null +++ b/src/app/lib/mapUtils.ts @@ -0,0 +1,57 @@ +/** + * Creates a new map by adding a key-value pair to a copy of the original map. + * + * This function does not modify the original map but returns a new map with the new key-value pair added. + * + * @template K - The type of keys in the map. + * @template V - The type of values in the map. + * @param {Map} originalMap - The original map to which the key-value pair will be added. + * @param {K} key - The key to be added to the map. + * @param {V} value - The value associated with the key. + * @returns {Map} - A new map with the key-value pair added. + */ +export const appendToMapWithNewValue = (originalMap: Map, key: K, value: V): Map => { + // Create a new map from the original one + const newMap = new Map(originalMap); + + // Set the new key-value pair + newMap.set(key, value); + + // Return the new map + return newMap; +}; + +/** + * Checks if a key exists in a map. + * + * @template K - The type of keys in the map. + * @template V - The type of values in the map. + * @param {Map} map - The map to check. + * @param {K} key - The key to check for existence. + * @returns {boolean} - Returns true if the key exists in the map, otherwise false. + */ +export const hasKey = (map: Map, key: K): boolean => { + return map.has(key); +}; + +/** + * Deletes a key from a map and returns a new map with the key removed. + * + * @template K - The type of keys in the map. + * @template V - The type of values in the map. + * @param {Map} dataMap - The original map from which to delete the key. + * @param {K} key - The key to be deleted. + * @returns {Map} - Returns a new map with the key removed. If the key was not found, returns the original map. + */ +export const deleteKey = (dataMap: Map, key: K): Map => { + // Create a new map from the existing map + const newMap = new Map(dataMap); + + // Check if the key exists and delete it + if (newMap.has(key)) { + newMap.delete(key); + } + + // Return the new map + return newMap; +}; diff --git a/src/app/lib/sudokuHelperMethod.ts b/src/app/lib/sudokuHelperMethod.ts new file mode 100644 index 0000000..9c44b48 --- /dev/null +++ b/src/app/lib/sudokuHelperMethod.ts @@ -0,0 +1,69 @@ +import { SudoKuBoardProps } from '../types/sudokyProps'; + +/** + * Generates an array of unique random indices within a specified range for a 2D grid. + * + * @param {number} count - The number of unique random indices to generate. + * @param {number} maxRow - The maximum number of rows in the grid. + * @param {number} maxCol - The maximum number of columns in the grid. + * @returns {number[][]} An array of unique random indices, where each index is represented as an array of two numbers [row, col]. + * + * @example + * const randomIndices = getRandomIndices(5, 9, 9); + * // Example output: [[0, 1], [3, 4], [7, 2], [5, 8], [1, 0]] + */ +const getRandomIndices = (count: number, maxRow: number, maxCol: number) => { + const positions = new Set(); + + while (positions.size < count) { + const row = Math.floor(Math.random() * maxRow); + const col = Math.floor(Math.random() * maxCol); + positions.add(`${row}-${col}`); // Add the position as a string to ensure uniqueness + } + + return Array.from(positions).map((pos) => pos.split('-').map(Number)); +}; + +/** + * Adds a specified number of random '#' symbols to the Sudoku board by replacing values at random positions. + * + * @param {SudoKuBoardProps[][]} tempBoard - A 9x9 Sudoku board represented as a 2D array of SudoKuBoardProps objects. + * @param {number} [maxHashes=4] - The maximum number of '#' symbols to add to the board. Default is 4. + * @returns {SudoKuBoardProps[][]} The updated Sudoku board with randomly placed '#' symbols. + * + * @example + * const board = JSON.parse(JSON.stringify(SUDOKU_BOARD_DATA)); + * const updatedBoard = addRandomHashesToBoard(board, 5); // Adds 5 random '#' symbols + */ +export const addRandomHashesToBoard = (tempBoard: SudoKuBoardProps[][], maxHashes: number = 4) => { + const randomPositions = getRandomIndices(maxHashes, 9, 9); // Get up to 4 random positions + + randomPositions.forEach(([row, col]) => { + // Replace the value with '#' + tempBoard[row][col].value = '#'; + }); + + return tempBoard; +}; + +/** + * Initiate board with it's default value + * + * @param {SudoKuBoardProps[][]} board + * @returns {*} + */ +export const InitialGridWithDefaultValue = (board: SudoKuBoardProps[][]) => { + return JSON.parse(JSON.stringify(board)).map((row: SudoKuBoardProps[]) => + row.map((cell) => ({ + ...cell, + isValid: false, // Reset the validity of the cell + isActive: false, // Reset the active state of the cell + isCurrent: false, // Reset the current state of the cell + isTarget: false, // Reset the target state of the cell + isValidRowItem: false, // Reset the row validity flag + isValidColItem: false, // Reset the column validity flag + isValidSubGridItem: false, // Reset the sub-grid validity flag + isInvalid: false, // Reset the invalid state of the cell + })) + ); +}; diff --git a/src/app/linked-list/page.tsx b/src/app/linked-list/page.tsx new file mode 100644 index 0000000..dbed433 --- /dev/null +++ b/src/app/linked-list/page.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import LinkedListComponent from '../components/linked-list/LinkedListComponent'; + +const page = () => { + return ( +
+ +
+ ); +}; + +export default page; diff --git a/src/app/page.tsx b/src/app/page.tsx index bbbd70d..1eba14e 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,96 +1,12 @@ import React from 'react'; -// import TreeComponent from './components/Tree/TreeComponent'; +import HomeComponent from './components/home/HomeComponent'; const page = () => { return ( <> -
- {/* for jest */} -

Hello world!

-
- -
- -
+ ); }; export default page; - -// Define the types for nodes and edges -type Node = { - cx: number; - cy: number; -}; - -type Nodes = { - [key: string]: Node; -}; - -type Edge = { - from: string; - to: string; - weight: number; -}; - -const Graph: React.FC = () => { - // Define node positions - const nodes: Nodes = { - A: { cx: 50, cy: 150 }, - B: { cx: 150, cy: 50 }, - C: { cx: 150, cy: 250 }, - D: { cx: 250, cy: 50 }, - E: { cx: 250, cy: 250 }, - F: { cx: 350, cy: 150 }, - }; - - // Define edges with their weights - const edges: Edge[] = [ - { from: 'A', to: 'B', weight: 4 }, - { from: 'A', to: 'C', weight: 5 }, - { from: 'B', to: 'C', weight: 11 }, - { from: 'B', to: 'D', weight: 9 }, - { from: 'C', to: 'E', weight: 3 }, - { from: 'D', to: 'E', weight: 13 }, - { from: 'D', to: 'F', weight: 2 }, - { from: 'E', to: 'F', weight: 6 }, - ]; - - return ( - - {/* Draw edges */} - {edges.map((edge, index) => { - const start = nodes[edge.from]; - const end = nodes[edge.to]; - return ( - - - {/* Label the edge with its weight */} - - {edge.weight} - - - ); - })} - - {/* Draw nodes */} - {Object.keys(nodes).map((key) => ( - - - {/* Label the node */} - - {key} - - - ))} - - ); -}; diff --git a/src/app/sudoku-solver/page.tsx b/src/app/sudoku-solver/page.tsx new file mode 100644 index 0000000..742a946 --- /dev/null +++ b/src/app/sudoku-solver/page.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import SudoKuSolver from '../components/sudoku-solver/SudoKuSolver'; + +const page = () => { + return ( + <> + + + ); +}; + +export default page; diff --git a/src/app/types/TreeTypeProps.ts b/src/app/types/TreeTypeProps.ts index 28b7a36..f7d6850 100644 --- a/src/app/types/TreeTypeProps.ts +++ b/src/app/types/TreeTypeProps.ts @@ -28,6 +28,14 @@ export interface ITreeNode { isSorted: boolean; isTarget: boolean; isInvalid: boolean; + next: ITreeNode | null; + isCycle: boolean; + isInsertedPosition: boolean; + destination: { x: number | null; y: number | null }; + slowPointer: boolean; + firstPointer: boolean; + isCycleStartPoint: boolean; + isCycleEndPoint: boolean; } /** @@ -54,3 +62,29 @@ export interface TreeDFSTraversalProps { export interface TreeBFSTraversalProps { root: TreeNode | null; } + +/** + * Interface representing the flags used to mark nodes involved in a cycle. + * + * @interface CycleFlags + * @property {boolean} [isCycleStartPoint] - Indicates if the node is the start of the cycle. + * @property {boolean} [isCycleEndPoint] - Indicates if the node is the end of the cycle. + * @property {boolean} [isCycle] - Indicates if the node is part of the cycle. + */ +export interface CycleFlags { + isCycleStartPoint?: boolean; + isCycleEndPoint?: boolean; + isCycle?: boolean; +} + +/** + * Interface representing flags used to indicate the state of pointers in a linked list traversal. + * + * @interface PointerFlags + * @property {boolean} [slowPointer] - Indicates if the node is currently being pointed to by the slow pointer. + * @property {boolean} [firstPointer] - Indicates if the node is currently being pointed to by the fast pointer. + */ +export interface PointerFlags { + slowPointer?: boolean; + firstPointer?: boolean; +} diff --git a/src/app/types/commonProps.ts b/src/app/types/commonProps.ts index 4c822f3..d9107b4 100644 --- a/src/app/types/commonProps.ts +++ b/src/app/types/commonProps.ts @@ -1,3 +1,5 @@ +import { ITreeNode } from './TreeTypeProps'; + /** * Represents a color configuration for status indicators. * @@ -11,3 +13,62 @@ export interface StatusColorsDataProps { title: string; bg_color: string; } + +/** + * N-Queens grid inline styles types props + * + * @interface ChessBoardGridInlineStyleProps + * @property {string} display - css property + * @property {string} gap - css grid property + * @property {string} gridTemplateColumns - css grid property + * @property {width} display - css to control the width property + * @property {string} height - css to control the hight property + */ +export interface ChessBoardGridInlineStyleProps { + display: string; + gap: string; + gridTemplateColumns: string; + width: string; + height: string; +} + +/** + * Represents a single list item with an ID and a name. + * + * @interface ListProps + * @property {string} id - Unique identifier for the list item. + * @property {string} name - Name of the list item. + */ +export interface ListProps { + id: string; + name: string; +} + +/** + * Represents a project schema containing details about the project, including its ID, name, associated lists, and navigation path. + * + * @interface ProjectSchema + * @property {string} id - Unique identifier for the project. + * @property {string} name - Name of the project. + * @property {ListProps[]} lists - Array of lists associated with the project. + * @property {string} navigate - Navigation path or route related to the project. + */ +export interface ProjectSchema { + id: string; + name: string; + tags: ListProps[]; + navigate: string; +} + +/** + * Interface representing the data structure for each item in the linear search. + * It extends from the `ITreeNode` interface and includes an additional `data` property + * which is the numerical value being searched. + * + * @interface LinearSearchDataProps + * @extends {ITreeNode} + * @property {number} data - The value of the current node used in the linear search. + */ +export interface LinearSearchDataProps extends ITreeNode { + data: number; // The numerical data value to be searched in the linear search algorithm +} diff --git a/src/app/types/linkedListProps.ts b/src/app/types/linkedListProps.ts new file mode 100644 index 0000000..2bba4d7 --- /dev/null +++ b/src/app/types/linkedListProps.ts @@ -0,0 +1,28 @@ +/** + * LinkedListInputProps represents the input data structure for the linked list operations. + * It holds the values related to insertion and deletion of nodes at specified positions. + * + * @interface LinkedListInputProps + * + * @property {string} searchItem - The value of the node to be searched from given list. + * @property {string} insertAtAnyPosition - The position where the node should be inserted in the list. + * @property {string} deleteFromAnyPosition - The position from which the node should be deleted in the list. + */ +export interface LinkedListInputProps { + insertData: string; + searchItem: string; + insertAtAnyPosition: string; + deleteFromAnyPosition: string; +} + +/** + * PageProps represents the properties for controlling the overall page's linked list visualization. + * + * @interface PageProps + * + * @property {number} speedRange - The speed range used to control the animation of the linked list operations. + */ +export interface PageProps { + speedRange: number; + updateComponentWithKey?: string; +} diff --git a/src/app/types/shortestPathProps.ts b/src/app/types/shortestPathProps.ts new file mode 100644 index 0000000..6e6e523 --- /dev/null +++ b/src/app/types/shortestPathProps.ts @@ -0,0 +1,40 @@ +/** + * Represents an edge in a graph. + * + * @interface IGraphEdge + * @property {number} source - The starting node of the edge. + * @property {number} target - The ending node of the edge. + * @property {number} weight - The weight or cost associated with the edge. + */ +export interface IGraphEdge { + source: number; + target: number; + weight: number; + weightPosition: { x: number; y: number }; +} + +/** + * Represents a node in a graph with various properties to indicate its state. + * + * @interface IGraphNode + * @property {number} id - The unique identifier of the node. + * @property {number} x - The x-coordinate of the node's position. + * @property {number} y - The y-coordinate of the node's position. + * @property {boolean} isVisited - Indicates if the node has been visited in a traversal. + * @property {boolean} isCurrentNode - Indicates if the node is the currently processing node. + * @property {boolean} isShortestPath - Indicates if the node is part of the shortest path. + * @property {boolean} isInvalidPath - Indicates if the node is part of an invalid path. + * @property {boolean} isDestination - Indicates if the node is the destination. + * @property {boolean} isSource - Indicates if the node is the source. + */ +export interface IGraphNode { + id: number; + x: number; + y: number; + isVisited: boolean; + isCurrentNode: boolean; + isShortestPath: boolean; + isInvalidPath: boolean; + isDestination: boolean; + isSource: boolean; +} diff --git a/src/app/types/sortingProps.ts b/src/app/types/sortingProps.ts index 64a8d81..9509b75 100644 --- a/src/app/types/sortingProps.ts +++ b/src/app/types/sortingProps.ts @@ -1,3 +1,5 @@ +import { IGraphNode } from './shortestPathProps'; + /** * Interface representing the structure of each item in the merge sort data array. * @@ -44,3 +46,16 @@ export interface HeapSortedItemProps { data: number; id: number; } + +/** + * shortest path graph creation types + * + * @export + * @interface GraphNodesProps + * @typedef {GraphNodesProps} + * @extends {IGraphNode} + */ +export interface GraphNodesProps extends IGraphNode { + distance: number; + isTargetNode: boolean; +} diff --git a/src/app/types/sudokyProps.ts b/src/app/types/sudokyProps.ts new file mode 100644 index 0000000..65654b8 --- /dev/null +++ b/src/app/types/sudokyProps.ts @@ -0,0 +1,28 @@ +/** + * Interface representing the properties of a single cell in a Sudoku board. + * + * @interface SudoKuBoardProps + * + * @property {number | string} id - A unique identifier for the cell. + * @property {string} value - The current value of the cell (typically a number from 1-9 or empty). + * @property {boolean} isValid - Indicates whether the current value of the cell is valid according to Sudoku rules. + * @property {boolean} isActive - Specifies if the cell is currently selected or active for input. + * @property {boolean} isCurrent - Highlights the cell as the one being operated on in the current algorithm step. + * @property {boolean} isTarget - Marks the cell as the target for a specific algorithm or validation process. + * @property {boolean} isValidRowItem - Highlights the cell as part of the row being validated. + * @property {boolean} isValidColItem - Highlights the cell as part of the column being validated. + * @property {boolean} isValidSubGridItem - Highlights the cell as part of the 3x3 subgrid being validated. + * @property {boolean} isInvalid - Marks the cell as invalid if a conflict is detected (e.g., duplicate value in row, column, or subgrid). + */ +export interface SudoKuBoardProps { + id: number | string; + value: string; + isValid: boolean; + isActive: boolean; + isCurrent: boolean; + isTarget: boolean; + isValidRowItem: boolean; + isValidColItem: boolean; + isValidSubGridItem: boolean; + isInvalid: boolean; +} diff --git a/src/app/types/uniquePathProps.ts b/src/app/types/uniquePathProps.ts index 686636c..aeece3e 100644 --- a/src/app/types/uniquePathProps.ts +++ b/src/app/types/uniquePathProps.ts @@ -13,15 +13,9 @@ * @property {boolean} isMarked - Indicates if the cell is marked in the visualization. * @property {PathFindingQueueProps} parent - The parent cell's coordinates from which this cell was reached. * @property {boolean} isValidPath - Indicates if the cell is part of a valid path from the start to the end. + * @property {boolean} islandColor - Indicates each valid islands with different color */ -/** - * Represents the coordinates of a cell in the grid for path finding operations. - * - * @interface PathFindingQueueProps - * @property {number} rowIdx - The row index of the cell in the grid. - * @property {number} colIdx - The column index of the cell in the grid. - */ export interface GridProps { id: number; data: number; @@ -34,6 +28,7 @@ export interface GridProps { isMarked: boolean; parent: PathFindingQueueProps; isValidPath: boolean; + islandColor?: string; } /** diff --git a/src/app/utils/StatusColorsPlate.tsx b/src/app/utils/StatusColorsPlate.tsx index 01e6302..5eebcab 100644 --- a/src/app/utils/StatusColorsPlate.tsx +++ b/src/app/utils/StatusColorsPlate.tsx @@ -19,7 +19,7 @@ const StatusColorsPlate: React.FC = ({ data = [] }) => { {data.map((item) => { return (
-
+
{item.title} diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 0000000..e4ee6d1 --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,20 @@ +import { NextResponse } from 'next/server'; +import type { NextRequest } from 'next/server'; + +/** + * Prevent user to do or restricted some tasks. + * + * @learn - https://nextjs.org/docs/app/building-your-application/routing/middleware#matching-paths + * @param {NextRequest} request + * @returns {*} + */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function middleware(request: NextRequest) { + // redirect with other urls + return NextResponse.next(); +} + +// this matcher will help us to trigger middle for every route +export const config = { + matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], +};