From d4c2fd6211fc5aa87302d1a2dc0968d30a341373 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Wed, 11 Sep 2024 14:30:51 +0600 Subject: [PATCH 01/65] converted code plated into utils for reusable purposes, --- src/app/components/pathFind/NoOfIslands.tsx | 32 +-- src/app/components/pathFind/PathFind.tsx | 6 +- src/app/components/pathFind/UniquePath.tsx | 31 +-- .../sorting/BubbleSortComponent.tsx | 18 +- .../components/sorting/HeapSortComponent.tsx | 24 +-- .../components/sorting/MergeSortComponent.tsx | 45 +---- .../components/sorting/QuickSortComponent.tsx | 30 +-- .../sorting/SelectionSortComponent.tsx | 30 +-- .../components/sorting/SortingComponent.tsx | 12 +- src/app/data/mockData.ts | 186 ++++++++++++++++++ src/app/utils/StatusColorsPlate.tsx | 2 +- 11 files changed, 231 insertions(+), 185 deletions(-) diff --git a/src/app/components/pathFind/NoOfIslands.tsx b/src/app/components/pathFind/NoOfIslands.tsx index 3951e68..e8e5f30 100644 --- a/src/app/components/pathFind/NoOfIslands.tsx +++ b/src/app/components/pathFind/NoOfIslands.tsx @@ -2,9 +2,11 @@ import { findNoOfIslands } from '@/app/algorithm/noOfValidIslands'; import { UNIQUE_PATH_GRID_SIZE } from '@/app/constant'; +import { noOfIslandsSortColorsData } from '@/app/data/mockData'; import { createGridWithUniquePath } from '@/app/data/PathFindingGridData'; import { clearAllTimeouts } from '@/app/lib/sleepMethod'; import { GridProps, PathFindingQueueProps, UniquePathPageProps } from '@/app/types/uniquePathProps'; +import StatusColorsPlate from '@/app/utils/StatusColorsPlate'; import React, { useEffect, useState } from 'react'; import { toast } from 'react-toastify'; @@ -86,37 +88,15 @@ const NoOfIslands: React.FC = ({ useRandomKey, speedRange, return (
-
-
-
-
- - Water - -
-
-
- - Island - -
-
-
- - Current item - -
-
-
- - valid island - -
+
+
+

No of islands : {countIslands}

+
{data?.length ? (
diff --git a/src/app/components/pathFind/PathFind.tsx b/src/app/components/pathFind/PathFind.tsx index dabdfa1..c7b99ca 100644 --- a/src/app/components/pathFind/PathFind.tsx +++ b/src/app/components/pathFind/PathFind.tsx @@ -68,7 +68,7 @@ const PathFind = () => { return (
-
+

Speed: {speedRange} (0 to 1500)

{
{buttonType === 'unique-path' ? ( -
+
) : null} {buttonType === 'no-of-island' ? ( -
+
) : null} diff --git a/src/app/components/pathFind/UniquePath.tsx b/src/app/components/pathFind/UniquePath.tsx index 9e7a286..483d44b 100644 --- a/src/app/components/pathFind/UniquePath.tsx +++ b/src/app/components/pathFind/UniquePath.tsx @@ -2,9 +2,11 @@ import { DFSFindUniquePathMethod } from '@/app/algorithm/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'; @@ -97,32 +99,9 @@ const UniquePath: React.FC = ({ useRandomKey, speedRange, g return (
-
-
-
-
- - Bricks - -
-
-
- - Valid path - -
-
-
- - Current item - -
-
-
- - valid island - -
+
+
+

No of unique paths : {validPaths?.length} diff --git a/src/app/components/sorting/BubbleSortComponent.tsx b/src/app/components/sorting/BubbleSortComponent.tsx index bf2c05f..a02b1c3 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 { 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..fdd42d9 100644 --- a/src/app/components/sorting/HeapSortComponent.tsx +++ b/src/app/components/sorting/HeapSortComponent.tsx @@ -2,9 +2,11 @@ import { heapify, HeapSortApproach } from '@/app/algorithm/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..f6bcd2a 100644 --- a/src/app/components/sorting/MergeSortComponent.tsx +++ b/src/app/components/sorting/MergeSortComponent.tsx @@ -2,8 +2,10 @@ import { mergeSortMethod } from '@/app/algorithm/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..3644e88 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 { 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..6c6c649 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 { 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..5945186 100644 --- a/src/app/components/sorting/SortingComponent.tsx +++ b/src/app/components/sorting/SortingComponent.tsx @@ -45,7 +45,7 @@ const SortingComponent = () => { }; return ( -
+
@@ -94,27 +94,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/data/mockData.ts b/src/app/data/mockData.ts index 0950577..0668974 100644 --- a/src/app/data/mockData.ts +++ b/src/app/data/mockData.ts @@ -22,3 +22,189 @@ 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-white 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]', + }, + { + id: 3, + title: 'Current Item', + bg_color: 'bg-blue-600', + }, + { + id: 4, + title: 'valid island', + bg_color: 'bg-green-600', + }, +]; diff --git a/src/app/utils/StatusColorsPlate.tsx b/src/app/utils/StatusColorsPlate.tsx index 01e6302..d332522 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} From 0809fa41fc902456af837e5f10d81d109e990afd Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Wed, 11 Sep 2024 15:04:29 +0600 Subject: [PATCH 02/65] header component active route features is implemented, resolved some design --- .../layouts/header/HeaderComponent.tsx | 50 ++++++--------- src/app/components/n-queen/ChessBoard.tsx | 62 +++++++++++-------- src/app/globals.scss | 16 +---- 3 files changed, 55 insertions(+), 73 deletions(-) diff --git a/src/app/components/layouts/header/HeaderComponent.tsx b/src/app/components/layouts/header/HeaderComponent.tsx index e42c693..663c7d0 100644 --- a/src/app/components/layouts/header/HeaderComponent.tsx +++ b/src/app/components/layouts/header/HeaderComponent.tsx @@ -1,14 +1,16 @@ 'use client'; import Link from 'next/link'; +import { usePathname } from 'next/navigation'; import React, { useEffect, useRef, useState } from 'react'; const HeaderComponent = () => { + 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(() => { @@ -81,9 +83,8 @@ const HeaderComponent = () => {
  • Tree @@ -91,9 +92,8 @@ const HeaderComponent = () => {
  • N-Queens @@ -101,9 +101,8 @@ const HeaderComponent = () => {
  • Sorting @@ -112,14 +111,23 @@ const HeaderComponent = () => {
  • Path finding
  • +
  • + + Searching + +
  • +
  • { viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg' - className='' + className='h-[95%] w-[95%]' > {
  • - - {/*
  • - - Blog - -
  • -
  • - - Contact - -
  • */}
    diff --git a/src/app/components/n-queen/ChessBoard.tsx b/src/app/components/n-queen/ChessBoard.tsx index 13199fd..a97d968 100644 --- a/src/app/components/n-queen/ChessBoard.tsx +++ b/src/app/components/n-queen/ChessBoard.tsx @@ -128,27 +128,9 @@ const ChessBoard: React.FC = () => { const memoizedGridStyle = useMemo(() => gridStyle(selectInput), [selectInput]); return ( -
    -
    +
    +
    - -
    -

    Speed: {speedRange} (0 to 1500)

    { max='1500' />
    - +
    +
    + +
    + + +
    @@ -191,7 +194,12 @@ const ChessBoard: React.FC = () => {
    {isQueen || isCurrentItem ? (
    - + Date: Wed, 11 Sep 2024 16:50:28 +0600 Subject: [PATCH 03/65] on-of-islands each island different color appraoch is implemented, updated chess board some design with functionality --- src/app/algorithm/noOfValidIslands.ts | 11 + src/app/components/n-queen/ChessBoard.tsx | 3 +- src/app/components/pathFind/NoOfIslands.tsx | 1 + src/app/constant.ts | 2 +- src/app/data/PathFindingGridData.ts | 339 +------------------- src/app/data/SortData.ts | 213 ------------ src/app/data/mockData.ts | 50 ++- src/app/types/commonProps.ts | 18 ++ src/app/types/uniquePathProps.ts | 9 +- 9 files changed, 75 insertions(+), 571 deletions(-) diff --git a/src/app/algorithm/noOfValidIslands.ts b/src/app/algorithm/noOfValidIslands.ts index a6cef7c..1a8709a 100644 --- a/src/app/algorithm/noOfValidIslands.ts +++ b/src/app/algorithm/noOfValidIslands.ts @@ -3,6 +3,9 @@ import { GridProps, PathFindingQueueProps } from '../types/uniquePathProps'; import { Sleep } from '../lib/sleepMethod'; import { del_col, del_row } from '../constant'; import { isValidDirection } from '../lib/helpers'; +import { islandColorsPlate } from '../data/mockData'; + +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/components/n-queen/ChessBoard.tsx b/src/app/components/n-queen/ChessBoard.tsx index a97d968..b86d226 100644 --- a/src/app/components/n-queen/ChessBoard.tsx +++ b/src/app/components/n-queen/ChessBoard.tsx @@ -3,6 +3,7 @@ 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'; @@ -125,7 +126,7 @@ const ChessBoard: React.FC = () => { * * @type {*} */ - const memoizedGridStyle = useMemo(() => gridStyle(selectInput), [selectInput]); + const memoizedGridStyle: ChessBoardGridInlineStyleProps = useMemo(() => gridStyle(selectInput), [selectInput]); return (
    diff --git a/src/app/components/pathFind/NoOfIslands.tsx b/src/app/components/pathFind/NoOfIslands.tsx index e8e5f30..05c21fa 100644 --- a/src/app/components/pathFind/NoOfIslands.tsx +++ b/src/app/components/pathFind/NoOfIslands.tsx @@ -108,6 +108,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`; diff --git a/src/app/constant.ts b/src/app/constant.ts index 9eb8a7b..cd19c06 100644 --- a/src/app/constant.ts +++ b/src/app/constant.ts @@ -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; 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/mockData.ts b/src/app/data/mockData.ts index 0668974..60e7fda 100644 --- a/src/app/data/mockData.ts +++ b/src/app/data/mockData.ts @@ -167,7 +167,7 @@ export const uniquePathFindingSortColorsData: StatusColorsDataProps[] = [ { id: 2, title: 'Valid path', - bg_color: 'bg-white border-[1px]', + bg_color: 'bg-black border-[1px]', }, { id: 3, @@ -197,14 +197,42 @@ export const noOfIslandsSortColorsData: StatusColorsDataProps[] = [ title: 'Island', bg_color: 'bg-[#E99F0C]', }, - { - id: 3, - title: 'Current Item', - bg_color: 'bg-blue-600', - }, - { - id: 4, - title: 'valid island', - bg_color: 'bg-green-600', - }, + // { + // id: 3, + // title: 'Current Item', + // bg_color: 'bg-blue-600', + // }, + // { + // id: 4, + // title: 'valid island', + // bg_color: 'bg-green-600', + // }, +]; + +/** + * 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) ]; diff --git a/src/app/types/commonProps.ts b/src/app/types/commonProps.ts index 4c822f3..3130f01 100644 --- a/src/app/types/commonProps.ts +++ b/src/app/types/commonProps.ts @@ -11,3 +11,21 @@ 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; +} 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; } /** From 33d446b9d267a0f67645d979cfaa36b8fe4fc618 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Thu, 12 Sep 2024 10:28:02 +0600 Subject: [PATCH 04/65] a middleware file is created, with some configuration, removed loading markup from BST search component --- .../searching/BinarySearchTreeComponent.tsx | 28 +++++++++---------- src/middleware.ts | 24 ++++++++++++++++ 2 files changed, 37 insertions(+), 15 deletions(-) create mode 100644 src/middleware.ts diff --git a/src/app/components/searching/BinarySearchTreeComponent.tsx b/src/app/components/searching/BinarySearchTreeComponent.tsx index 65bee23..89f6588 100644 --- a/src/app/components/searching/BinarySearchTreeComponent.tsx +++ b/src/app/components/searching/BinarySearchTreeComponent.tsx @@ -10,10 +10,13 @@ import { performBST } from '@/app/algorithm/binarySearch'; import StatusColorsPlate from '@/app/utils/StatusColorsPlate'; import { bstSearchColorsData } from '@/app/data/mockData'; +interface PageProps { + speedRange: number; +} + 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); @@ -27,8 +30,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,7 +46,6 @@ const BinarySearchTreeComponent: React.FC<{ speedRange: number }> = ({ speedRang return () => { clearAllTimeouts(); }; - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { @@ -62,17 +66,11 @@ const BinarySearchTreeComponent: React.FC<{ speedRange: number }> = ({ speedRang

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

    Loading...

    -
    - )} + <> + + + + ); }; diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 0000000..e905636 --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,24 @@ +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 {*} + */ +export function middleware(request: NextRequest) { + // prevent to visit home component + if (process.env.NODE_ENV !== 'development' && request.nextUrl.pathname === '/') { + return NextResponse.redirect(new URL('/tree', request.url)); + } + + // 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).*)'], +}; From 30403c417f938fb0730f3541b97c6116b7c37728 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Thu, 12 Sep 2024 11:17:48 +0600 Subject: [PATCH 05/65] home component a card view is initialized, for dev mode --- src/app/components/home/HomeComponent.tsx | 98 +++++++++++++++++++ .../searching/BinarySearchTreeComponent.tsx | 2 + src/app/page.tsx | 88 +---------------- 3 files changed, 102 insertions(+), 86 deletions(-) create mode 100644 src/app/components/home/HomeComponent.tsx diff --git a/src/app/components/home/HomeComponent.tsx b/src/app/components/home/HomeComponent.tsx new file mode 100644 index 0000000..edce8d7 --- /dev/null +++ b/src/app/components/home/HomeComponent.tsx @@ -0,0 +1,98 @@ +'use client'; + +import React, { ReactNode } from 'react'; +import Link from 'next/link'; + +const TreeIcon = ( + + + +); + +const SortIcon = ( + + + +); + +const PathFindingIcon = ( + + + +); + +const SearchIcon = ( + + + +); + +const algorithmCategories = [ + { + title: 'Tree Algorithms', + description: 'Explore tree traversal algorithms like BFS and DFS.', + link: '/tree', + icon: TreeIcon, + }, + { + title: 'Sorting Algorithms', + description: 'Visualize sorting algorithms like Merge, Quick, Heap, etc.', + link: '/sorting', + icon: SortIcon, + }, + { + title: 'Path-Finding Algorithms', + description: 'Discover path-finding algorithms like A*, Dijkstra.', + link: '/path-finding', + icon: PathFindingIcon, + }, + { + title: 'Searching Algorithms', + description: 'Search algorithms including Binary Search, Linear Search.', + link: '/searching', + icon: SearchIcon, + }, +]; + +const HomeComponent = () => { + return ( +
    +
    +
    +
    + {algorithmCategories.map((category, index) => ( + + ))} +
    +
    +
    +
    + ); +}; + +export default HomeComponent; + +interface AlgorithmCardProps { + title: string; + description: string; + link: string; + icon: ReactNode; +} + +const AlgorithmCard: React.FC = ({ title, description, link, icon }) => { + return ( + +
    +
    {icon}
    +

    {title}

    +

    {description}

    +
    + + ); +}; diff --git a/src/app/components/searching/BinarySearchTreeComponent.tsx b/src/app/components/searching/BinarySearchTreeComponent.tsx index 89f6588..931dfeb 100644 --- a/src/app/components/searching/BinarySearchTreeComponent.tsx +++ b/src/app/components/searching/BinarySearchTreeComponent.tsx @@ -10,10 +10,12 @@ import { performBST } from '@/app/algorithm/binarySearch'; import StatusColorsPlate from '@/app/utils/StatusColorsPlate'; import { bstSearchColorsData } from '@/app/data/mockData'; +// define component Page Props interface PageProps { speedRange: number; } +// a fixed array size const ARRAY_SIZE = 31; const BinarySearchTreeComponent: React.FC = ({ speedRange }) => { 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} - - - ))} - - ); -}; From 61c54eb82e5326f9a5c925a610fd61f498faf95b Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Thu, 12 Sep 2024 17:07:23 +0600 Subject: [PATCH 06/65] a linked list component is initialized with list item, thar are rendered using svg --- .../data-structure/LinkedList/LinkedList.ts | 91 +++++++++++++++++++ src/app/data-structure/Tree/Node.ts | 3 + src/app/linked-list/page.tsx | 70 ++++++++++++++ src/app/types/TreeTypeProps.ts | 2 + 4 files changed, 166 insertions(+) create mode 100644 src/app/data-structure/LinkedList/LinkedList.ts create mode 100644 src/app/linked-list/page.tsx diff --git a/src/app/data-structure/LinkedList/LinkedList.ts b/src/app/data-structure/LinkedList/LinkedList.ts new file mode 100644 index 0000000..26af676 --- /dev/null +++ b/src/app/data-structure/LinkedList/LinkedList.ts @@ -0,0 +1,91 @@ +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)[] = []; + + /** The index of the first node to create a cycle. */ + cycle1Index: number; + + /** The index of the second node to create a cycle. */ + cycle2Index: number; + + /** + * Creates an instance of the `LinkedList` class. + * + * @param {number | null[]} arr - An array of node values, where `null` represents no node. + * @param {number} cycle1_idx - The index of the first node to create a cycle. + * @param {number} cycle2_idx - The index of the second node to create a cycle. + */ + constructor(arr: (number | null)[], cycle1_idx: number, cycle2_idx: number) { + this.head = null; + this.arr = arr; + this.cycle1Index = cycle1_idx; + this.cycle2Index = cycle2_idx; + } + + /** + * 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; + + let CX = 20; + + // Insert the first node into the head + const temp = new TreeNode(this.arr[0]); + temp.cx = 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 = CX + 25; + newNode.cy = 20; + + current.next = newNode; + current = newNode; + + // Update CX value for positioning + CX += 25; + } + + // Create a cycle if needed + // if (this.cycle1Index !== undefined && this.cycle2Index !== undefined) { + // let cycle1Node = this.head; + // let cycle2Node = this.head; + + // for (let i = 0; i < this.cycle1Index; i++) { + // if (cycle1Node) cycle1Node = cycle1Node.next; + // } + + // for (let i = 0; i < this.cycle2Index; i++) { + // if (cycle2Node) cycle2Node = cycle2Node.next; + // } + + // if (cycle1Node && cycle2Node) { + // cycle2Node.next = cycle1Node; + // } + // } + } +} diff --git a/src/app/data-structure/Tree/Node.ts b/src/app/data-structure/Tree/Node.ts index 91eaa56..72afeac 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,7 @@ export class TreeNode implements ITreeNode { isSorted: boolean; isTarget: boolean; isInvalid: boolean; + isCycle: boolean; constructor( value: number | null = null, @@ -46,5 +48,6 @@ export class TreeNode implements ITreeNode { this.isSorted = false; this.isTarget = false; this.isInvalid = false; + this.isCycle = false; } } diff --git a/src/app/linked-list/page.tsx b/src/app/linked-list/page.tsx new file mode 100644 index 0000000..8fd9b9f --- /dev/null +++ b/src/app/linked-list/page.tsx @@ -0,0 +1,70 @@ +'use client'; + +import React, { ReactNode, useEffect, useState } from 'react'; +import { LinkedList } from '../data-structure/LinkedList/LinkedList'; +import { ITreeNode } from '../types/TreeTypeProps'; + +const TEMP_DATA: number[] = [15, 20, 45, 87, 98, 10, 75, 68, 30, 75, 68]; + +const Page = () => { + // define component local state + const [data] = useState(TEMP_DATA); + const [node, setNode] = useState(null); + + useEffect(() => { + const newList = new LinkedList(TEMP_DATA, 3, 6); + newList.createLinkedList(); + + if (newList.head) { + setNode(newList.head); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const renderNode = (node: ITreeNode | null): ReactNode => { + if (!node) return null; + + return ( + <> + + {node?.next && ( + + )} + + + + {node?.value} + + + {node.next ? renderNode(node.next) : null} + + ); + }; + + return ( + <> +
    + {data?.length ? ( +
    +

    Insert Into List :

    +
    + {data.map((item, i) => { + return ( +

    + {item} +

    + ); + })} +
    +
    + ) : null} + {data ? {renderNode(node)} : null} +
    + + ); +}; + +export default Page; diff --git a/src/app/types/TreeTypeProps.ts b/src/app/types/TreeTypeProps.ts index 28b7a36..405177d 100644 --- a/src/app/types/TreeTypeProps.ts +++ b/src/app/types/TreeTypeProps.ts @@ -28,6 +28,8 @@ export interface ITreeNode { isSorted: boolean; isTarget: boolean; isInvalid: boolean; + next: ITreeNode | null; + isCycle: boolean; } /** From b53e1d4e6a7424b001f8480afe11f1e8224b06e8 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Thu, 12 Sep 2024 17:44:06 +0600 Subject: [PATCH 07/65] create insertInto list at last position method is initialized --- .../data-structure/LinkedList/LinkedList.ts | 19 ++++++++++ src/app/linked-list/page.tsx | 36 ++++++++++++++----- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/app/data-structure/LinkedList/LinkedList.ts b/src/app/data-structure/LinkedList/LinkedList.ts index 26af676..e98c11f 100644 --- a/src/app/data-structure/LinkedList/LinkedList.ts +++ b/src/app/data-structure/LinkedList/LinkedList.ts @@ -88,4 +88,23 @@ export class LinkedList implements ITree { // } // } } + + // have to calculate new x, y position + insertNodeAtLast(value: number): void { + if (!this.head) { + this.head = new TreeNode(value); + return; + } + + const newNode = new TreeNode(value); + let current = this.head; + + // Traverse to the last node + while (current.next) { + current = current.next; + } + + // Insert the new node at the end + current.next = newNode; + } } diff --git a/src/app/linked-list/page.tsx b/src/app/linked-list/page.tsx index 8fd9b9f..6a84289 100644 --- a/src/app/linked-list/page.tsx +++ b/src/app/linked-list/page.tsx @@ -4,25 +4,29 @@ import React, { ReactNode, useEffect, useState } from 'react'; import { LinkedList } from '../data-structure/LinkedList/LinkedList'; import { ITreeNode } from '../types/TreeTypeProps'; -const TEMP_DATA: number[] = [15, 20, 45, 87, 98, 10, 75, 68, 30, 75, 68]; +const TEMP_DATA: number[] = [15, 20]; const Page = () => { // define component local state const [data] = useState(TEMP_DATA); - const [node, setNode] = useState(null); + const [node, setNode] = useState(); + const [root, setRoot] = useState(); useEffect(() => { const newList = new LinkedList(TEMP_DATA, 3, 6); newList.createLinkedList(); - if (newList.head) { - setNode(newList.head); + if (newList) { + setNode(newList); + if (newList.head) { + setRoot(newList.head); + } } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const renderNode = (node: ITreeNode | null): ReactNode => { - if (!node) return null; + if (!node) return; return ( <> @@ -41,17 +45,31 @@ const Page = () => { ); }; + const insertNewItem = () => { + if (!node) return; + + // Insert the new node + node.insertNodeAtLast(10); + + console.log(node); + setRoot(node.head); + + // Update state with the new linked list instance + setNode(node); + }; + return ( <>
    + {data?.length ? (
    -

    Insert Into List :

    -
    +

    Insert Into List :

    +
    {data.map((item, i) => { return (

    {item} @@ -61,7 +79,7 @@ const Page = () => {

    ) : null} - {data ? {renderNode(node)} : null} + {root ? {renderNode(root)} : null}
    ); From 9a002b3ef0fb8254c47d85863230f1d6f4e0ba7e Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Thu, 12 Sep 2024 17:45:14 +0600 Subject: [PATCH 08/65] prevent user visit at linked-list component, cause it's under development --- src/middleware.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/middleware.ts b/src/middleware.ts index e905636..2cac3dd 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -13,6 +13,10 @@ export function middleware(request: NextRequest) { if (process.env.NODE_ENV !== 'development' && request.nextUrl.pathname === '/') { return NextResponse.redirect(new URL('/tree', request.url)); } + // prevent to visit linked-list, cause it's under development + else if (process.env.NODE_ENV !== 'development' && request.nextUrl.pathname === '/linked-list') { + return NextResponse.redirect(new URL('/tree', request.url)); + } // redirect with other urls return NextResponse.next(); From c3d23f842574c74c802554dfe3a72de04eb27580 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Sun, 15 Sep 2024 17:42:44 +0600 Subject: [PATCH 09/65] Linked-list component inital render procedure is implemented, insert into tail approach is initialized --- .../linked-list/LinkedListComponent.tsx | 216 ++++++++++++++++++ .../data-structure/LinkedList/LinkedList.ts | 31 +-- src/app/data/mockData.ts | 10 - src/app/linked-list/page.tsx | 90 +------- 4 files changed, 227 insertions(+), 120 deletions(-) create mode 100644 src/app/components/linked-list/LinkedListComponent.tsx diff --git a/src/app/components/linked-list/LinkedListComponent.tsx b/src/app/components/linked-list/LinkedListComponent.tsx new file mode 100644 index 0000000..00db3b6 --- /dev/null +++ b/src/app/components/linked-list/LinkedListComponent.tsx @@ -0,0 +1,216 @@ +'use client'; + +import { LinkedList } from '@/app/data-structure/LinkedList/LinkedList'; +import { TreeNode } from '@/app/data-structure/Tree/Node'; +import { Sleep } from '@/app/lib/sleepMethod'; +import { ITreeNode } from '@/app/types/TreeTypeProps'; +import React, { useEffect, useState } from 'react'; +import { toast } from 'react-toastify'; + +const speedRange = 150; + +interface LinkedListInputProps { + insertAtLast: string; + insertAtAnyPosition: string; + deleteFromAnyPosition: string; +} + +const LinkedListComponent = () => { + // define component local state + const [insertedData, setInsertedData] = useState([]); + const [root, setRoot] = useState(); + const [btnLoading, setButtonLoading] = useState(false); + const [inputData, setInputData] = useState({ + insertAtLast: String(Math.floor(Math.random() * 99 + 1)), + insertAtAnyPosition: '', + deleteFromAnyPosition: '', + }); + + useEffect(() => { + const newList = new LinkedList([15]); + newList.createLinkedList(); + + if (newList.head) { + setRoot(newList.head); + // update setInsertData as well + setInsertedData((prv) => [...prv, 15]); + } + }, []); + + /** + * A recursive approach to insert new node at last position with given value. + * + * @param {TreeNode} currentNode + * @param {number} cx + * @param {number} cy + * @param {number} value + * @returns {TreeNode} + */ + const updateTree = async (currentNode: TreeNode, cx: number, cy: number, value: number): Promise => { + const newNode = new TreeNode(value); + let current = currentNode; + + current.isCurrent = true; + setRoot({ ...currentNode }); + await Sleep(speedRange); + + current.isCurrent = false; + + // Traverse to the last node + while (current.next) { + current = current.next; + + current.isCurrent = true; + setRoot({ ...currentNode }); + + await Sleep(speedRange); + + current.isCurrent = false; + setRoot({ ...currentNode }); + } + + // mark is inserted position + current.isCurrent = true; + setRoot({ ...currentNode }); + await Sleep(speedRange); + current.isCurrent = false; + + newNode.cx = (current.cx || 0) + 25; + newNode.cy = 20; + newNode.isTarget = true; + + // Insert the new node at the end + current.next = newNode; + + // Update the new node + setRoot({ ...currentNode }); + await Sleep(speedRange); + + // Mark the new node as not the target + newNode.isTarget = false; + setRoot({ ...currentNode }); + + return currentNode; + }; + + const insertNewItem = async () => { + if (!root) return; + + if (insertedData?.length > 11) { + toast.error('Max limit exceed. You can add at most 11 items'); + return; + } else if (!inputData.insertAtLast) { + toast.error('Input field can not be empty'); + return; + } + + setButtonLoading((prv) => !prv); + + const value = Number(inputData.insertAtLast); + + // Starting at depth 20 and position 0 for the new node + const updatedRoot = await updateTree({ ...root }, Number(root.cx), Number(root.cy), value); + + // mark is inserted position + setRoot({ ...updatedRoot }); + await Sleep(300); + + // update setInsertData as well + setInsertedData((prv) => [...prv, value]); + + setButtonLoading((prv) => !prv); + // update input field with random value + setInputData((prv) => ({ ...prv, insertAtLast: String(Math.floor(Math.random() * 499 + 1)) })); + }; + + /** + * Handle input data for insert at last into linked-list + * + * @param {React.ChangeEvent} e + */ + const insertAtLastOnChangeMethod = (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, insertAtLast: String(value || '') })); + }; + + return ( + <> +
    +
    + + +
    + + {root ? ( + + + + ) : null} +
    + + ); +}; + +const RenderNodeRecursively: React.FC<{ node: ITreeNode | null }> = ({ node }) => { + if (!node) return; + + return ( + <> + + {node?.next && ( + + )} + + + + {node?.value} + + + {node.next ? : null} + + ); +}; + +export default LinkedListComponent; diff --git a/src/app/data-structure/LinkedList/LinkedList.ts b/src/app/data-structure/LinkedList/LinkedList.ts index e98c11f..fb0b785 100644 --- a/src/app/data-structure/LinkedList/LinkedList.ts +++ b/src/app/data-structure/LinkedList/LinkedList.ts @@ -18,12 +18,6 @@ export class LinkedList implements ITree { /** An array representing the node values. */ arr: (number | null)[] = []; - /** The index of the first node to create a cycle. */ - cycle1Index: number; - - /** The index of the second node to create a cycle. */ - cycle2Index: number; - /** * Creates an instance of the `LinkedList` class. * @@ -31,11 +25,9 @@ export class LinkedList implements ITree { * @param {number} cycle1_idx - The index of the first node to create a cycle. * @param {number} cycle2_idx - The index of the second node to create a cycle. */ - constructor(arr: (number | null)[], cycle1_idx: number, cycle2_idx: number) { + constructor(arr: (number | null)[]) { this.head = null; this.arr = arr; - this.cycle1Index = cycle1_idx; - this.cycle2Index = cycle2_idx; } /** @@ -69,24 +61,6 @@ export class LinkedList implements ITree { // Update CX value for positioning CX += 25; } - - // Create a cycle if needed - // if (this.cycle1Index !== undefined && this.cycle2Index !== undefined) { - // let cycle1Node = this.head; - // let cycle2Node = this.head; - - // for (let i = 0; i < this.cycle1Index; i++) { - // if (cycle1Node) cycle1Node = cycle1Node.next; - // } - - // for (let i = 0; i < this.cycle2Index; i++) { - // if (cycle2Node) cycle2Node = cycle2Node.next; - // } - - // if (cycle1Node && cycle2Node) { - // cycle2Node.next = cycle1Node; - // } - // } } // have to calculate new x, y position @@ -104,6 +78,9 @@ export class LinkedList implements ITree { current = current.next; } + newNode.cx = (current.cx || 0) + 25; + newNode.cy = 20; + // Insert the new node at the end current.next = newNode; } diff --git a/src/app/data/mockData.ts b/src/app/data/mockData.ts index 60e7fda..c378e60 100644 --- a/src/app/data/mockData.ts +++ b/src/app/data/mockData.ts @@ -197,16 +197,6 @@ export const noOfIslandsSortColorsData: StatusColorsDataProps[] = [ title: 'Island', bg_color: 'bg-[#E99F0C]', }, - // { - // id: 3, - // title: 'Current Item', - // bg_color: 'bg-blue-600', - // }, - // { - // id: 4, - // title: 'valid island', - // bg_color: 'bg-green-600', - // }, ]; /** diff --git a/src/app/linked-list/page.tsx b/src/app/linked-list/page.tsx index 6a84289..dbed433 100644 --- a/src/app/linked-list/page.tsx +++ b/src/app/linked-list/page.tsx @@ -1,88 +1,12 @@ -'use client'; - -import React, { ReactNode, useEffect, useState } from 'react'; -import { LinkedList } from '../data-structure/LinkedList/LinkedList'; -import { ITreeNode } from '../types/TreeTypeProps'; - -const TEMP_DATA: number[] = [15, 20]; - -const Page = () => { - // define component local state - const [data] = useState(TEMP_DATA); - const [node, setNode] = useState(); - const [root, setRoot] = useState(); - - useEffect(() => { - const newList = new LinkedList(TEMP_DATA, 3, 6); - newList.createLinkedList(); - - if (newList) { - setNode(newList); - if (newList.head) { - setRoot(newList.head); - } - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const renderNode = (node: ITreeNode | null): ReactNode => { - if (!node) return; - - return ( - <> - - {node?.next && ( - - )} - - - - {node?.value} - - - {node.next ? renderNode(node.next) : null} - - ); - }; - - const insertNewItem = () => { - if (!node) return; - - // Insert the new node - node.insertNodeAtLast(10); - - console.log(node); - setRoot(node.head); - - // Update state with the new linked list instance - setNode(node); - }; +import React from 'react'; +import LinkedListComponent from '../components/linked-list/LinkedListComponent'; +const page = () => { return ( - <> -
    - - {data?.length ? ( -
    -

    Insert Into List :

    -
    - {data.map((item, i) => { - return ( -

    - {item} -

    - ); - })} -
    -
    - ) : null} - {root ? {renderNode(root)} : null} -
    - +
    + +
    ); }; -export default Page; +export default page; From 7c796bc55e0123f84ac95ccb85926b7986186f47 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Tue, 17 Sep 2024 13:01:24 +0600 Subject: [PATCH 10/65] linked-list insert at any positions include last, functionality is integrated, create a mapUtils for reusability --- .../linked-list/LinkedListComponent.tsx | 247 ++++++++++++++++-- src/app/data-structure/Tree/Node.ts | 2 + src/app/lib/mapUtils.ts | 35 +++ src/app/types/TreeTypeProps.ts | 1 + 4 files changed, 258 insertions(+), 27 deletions(-) create mode 100644 src/app/lib/mapUtils.ts diff --git a/src/app/components/linked-list/LinkedListComponent.tsx b/src/app/components/linked-list/LinkedListComponent.tsx index 00db3b6..51a8bbb 100644 --- a/src/app/components/linked-list/LinkedListComponent.tsx +++ b/src/app/components/linked-list/LinkedListComponent.tsx @@ -2,12 +2,13 @@ import { LinkedList } from '@/app/data-structure/LinkedList/LinkedList'; import { TreeNode } from '@/app/data-structure/Tree/Node'; +import { appendToMapWithNewValue, hasKey } from '@/app/lib/mapUtils'; import { Sleep } from '@/app/lib/sleepMethod'; import { ITreeNode } from '@/app/types/TreeTypeProps'; import React, { useEffect, useState } from 'react'; import { toast } from 'react-toastify'; -const speedRange = 150; +const speedRange = 300; interface LinkedListInputProps { insertAtLast: string; @@ -21,10 +22,11 @@ const LinkedListComponent = () => { const [root, setRoot] = useState(); const [btnLoading, setButtonLoading] = useState(false); const [inputData, setInputData] = useState({ - insertAtLast: String(Math.floor(Math.random() * 99 + 1)), - insertAtAnyPosition: '', + insertAtLast: '', + insertAtAnyPosition: '1', deleteFromAnyPosition: '', }); + const [dataMap, setDataMap] = useState>(new Map()); useEffect(() => { const newList = new LinkedList([15]); @@ -34,7 +36,11 @@ const LinkedListComponent = () => { setRoot(newList.head); // update setInsertData as well setInsertedData((prv) => [...prv, 15]); + // insert into map + setDataMap(appendToMapWithNewValue(dataMap, 15, 15)); + setInputData((prv) => ({ ...prv, insertAtLast: String(Math.floor(Math.random() * 99 + 1)) })); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); /** @@ -93,9 +99,17 @@ const LinkedListComponent = () => { return currentNode; }; - const insertNewItem = async () => { - if (!root) return; - + /** + * Insert new data into tail of a linked list. Before inserted item need an recursive approach to get the positions. + * + * @returns {*} + */ + const insertDataAtTail = async () => { + // check some validation before insert at tail of given linked list + if (!root) { + toast.error('Invalid linked list.'); + return; + } if (insertedData?.length > 11) { toast.error('Max limit exceed. You can add at most 11 items'); return; @@ -104,10 +118,15 @@ const LinkedListComponent = () => { return; } - setButtonLoading((prv) => !prv); - const value = Number(inputData.insertAtLast); + if (hasKey(dataMap, value)) { + toast.error('This item is already exists'); + return; + } + + setButtonLoading((prv) => !prv); + // Starting at depth 20 and position 0 for the new node const updatedRoot = await updateTree({ ...root }, Number(root.cx), Number(root.cy), value); @@ -118,6 +137,9 @@ const LinkedListComponent = () => { // update setInsertData as well setInsertedData((prv) => [...prv, value]); + // insert into map + setDataMap(appendToMapWithNewValue(dataMap, value, value)); + setButtonLoading((prv) => !prv); // update input field with random value setInputData((prv) => ({ ...prv, insertAtLast: String(Math.floor(Math.random() * 499 + 1)) })); @@ -140,26 +162,197 @@ const LinkedListComponent = () => { setInputData((prv) => ({ ...prv, insertAtLast: 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)); + console.log(dataMap.size); + + if (value > dataMap.size + 1 || value <= 0) { + toast.error(`Invalid position`); + } + + // const position = String(Math.floor(Math.random() + dataMap.size + 1)); + // console.log(position, 'position'); + + setInputData((prv) => ({ ...prv, insertAtAnyPosition: String(value || 0) })); + }; + + const insertDataByPosition = async () => { + // check some validation before insert at tail of given linked list + if (!root) { + toast.error('Invalid linked list.'); + return; + } else if (Number(inputData.insertAtAnyPosition) > dataMap.size + 1 || Number(inputData.insertAtAnyPosition) <= 0) { + toast.error(`Invalid position`); + return; + } else if (insertedData?.length > 11) { + toast.error('Max limit exceed. You can add at most 11 items'); + return; + } else if (!inputData.insertAtLast) { + toast.error('Input field can not be empty'); + return; + } + + const value = Number(inputData.insertAtLast); + const position = Number(inputData.insertAtAnyPosition); + + if (dataMap.get(value)) { + toast.error('This item is already exists'); + return; + } + + const updateTreeToInsertData = async (root: TreeNode, value: number, position: number): Promise => { + let sudoHead: TreeNode | null = root; + + const newNode = new TreeNode(value); + newNode.cx = 20; + newNode.cy = 20; + + // insert into map + setDataMap(appendToMapWithNewValue(dataMap, value, value)); + + // If the position is for the head + if (position === 1) { + sudoHead.isInsertedPosition = true; + setRoot({ ...sudoHead }); + + await Sleep(speedRange); + + sudoHead.isInsertedPosition = false; + setRoot({ ...sudoHead }); + + newNode.next = sudoHead; + sudoHead = newNode; + + // Shift cx values of the existing nodes + let current: TreeNode | null = newNode.next; + while (current) { + current.cx = (current.cx || 0) + 25; // Shift cx value + current = current.next; // Traverse to the next node + } + + return sudoHead; + } else { + let prev: TreeNode | null = null; + let current: TreeNode | null = sudoHead; + let counter = position - 1; + + // Traverse to the node at the specified position + while (counter-- && current) { + current.isCurrent = true; + setRoot({ ...root }); + + await Sleep(speedRange); + + current.isCurrent = false; + setRoot({ ...root }); + + prev = current; + current = current.next; + } + + // Ensure valid position and insertion + if (prev && counter < 0) { + if (current) { + current.isInsertedPosition = true; + setRoot({ ...root }); + + await Sleep(speedRange); + + current.isInsertedPosition = false; + setRoot({ ...root }); + } + + // Insert the new node between `prev` and `current` + newNode.next = current; // current could be null if it's the end of the list + prev.next = newNode; // previous node now points to newNode + + // Adjust cx of the inserted node and all subsequent nodes + if (current) { + newNode.cx = current.cx || 25; // Inherit cx if not the last + newNode.cy = current.cy || 20; + } else { + newNode.cx = (prev.cx || 0) + 25; // If inserting at the end + newNode.cy = prev.cy || 20; + } + // Shift cx values of subsequent nodes + let next: TreeNode | null = newNode.next; + + while (next) { + next.cx = (next.cx || 0) + 25; // Shift cx value for each next node + next = next.next; // Traverse to the next node + } + } else if (!current && position > 1) { + // Append to the end if position exceeds the list size + prev!.next = newNode; + newNode.cx = (prev!.cx || 0) + 25; // Adjust cx for the new tail node + } + + return root; // Return the original root node + } + }; + + // Starting at depth 20 and position 0 for the new node + const updatedRoot = await updateTreeToInsertData({ ...root }, value, position); + + // mark is inserted position + setRoot({ ...updatedRoot }); + // update input field with random value + setInputData((prv) => ({ + ...prv, + insertAtLast: String(Math.floor(Math.random() * 499 + 1)), + insertAtAnyPosition: String(Math.floor(Math.random() * dataMap.size + 1)), + })); + await Sleep(300); + + console.log(value, position); + }; + return ( <>
    -
    - - +
    +
    + + +
    + +
    + + +
    {root ? ( @@ -193,7 +386,7 @@ const RenderNodeRecursively: React.FC<{ node: ITreeNode | null }> = ({ node }) = cx={node.cx!} cy={node.cy!} r={6} - fill={node.isCurrent ? 'blue' : node.isTarget ? 'green' : 'white'} + fill={node.isInsertedPosition ? 'red' : node.isCurrent ? 'blue' : node.isTarget ? 'green' : 'white'} stroke={'black'} strokeWidth={'0.2'} > @@ -203,7 +396,7 @@ const RenderNodeRecursively: React.FC<{ node: ITreeNode | null }> = ({ node }) = dy={2} textAnchor='middle' className='text-center text-[4px]' - fill={node.isCurrent || node.isTarget ? 'white' : 'black'} + fill={node.isInsertedPosition || node.isCurrent || node.isTarget ? 'white' : 'black'} > {node?.value} diff --git a/src/app/data-structure/Tree/Node.ts b/src/app/data-structure/Tree/Node.ts index 72afeac..68e8f1e 100644 --- a/src/app/data-structure/Tree/Node.ts +++ b/src/app/data-structure/Tree/Node.ts @@ -31,6 +31,7 @@ export class TreeNode implements ITreeNode { isTarget: boolean; isInvalid: boolean; isCycle: boolean; + isInsertedPosition: boolean; constructor( value: number | null = null, @@ -49,5 +50,6 @@ export class TreeNode implements ITreeNode { this.isTarget = false; this.isInvalid = false; this.isCycle = false; + this.isInsertedPosition = false; } } diff --git a/src/app/lib/mapUtils.ts b/src/app/lib/mapUtils.ts new file mode 100644 index 0000000..3ee0e57 --- /dev/null +++ b/src/app/lib/mapUtils.ts @@ -0,0 +1,35 @@ +/** + * 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); +}; diff --git a/src/app/types/TreeTypeProps.ts b/src/app/types/TreeTypeProps.ts index 405177d..43fdf29 100644 --- a/src/app/types/TreeTypeProps.ts +++ b/src/app/types/TreeTypeProps.ts @@ -30,6 +30,7 @@ export interface ITreeNode { isInvalid: boolean; next: ITreeNode | null; isCycle: boolean; + isInsertedPosition: boolean; } /** From 51cf73095203795a64fbfaafe33b62f998e53356 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Tue, 17 Sep 2024 15:39:46 +0600 Subject: [PATCH 11/65] split code into separate file --- .../algorithm/linked-list/singlyLinkedList.ts | 118 +++++ .../linked-list/LinkedListComponent.tsx | 427 ++---------------- .../linked-list/SinglyLinkedList.tsx | 274 +++++++++++ src/app/types/linkedListProps.ts | 27 ++ 4 files changed, 469 insertions(+), 377 deletions(-) create mode 100644 src/app/algorithm/linked-list/singlyLinkedList.ts create mode 100644 src/app/components/linked-list/SinglyLinkedList.tsx create mode 100644 src/app/types/linkedListProps.ts diff --git a/src/app/algorithm/linked-list/singlyLinkedList.ts b/src/app/algorithm/linked-list/singlyLinkedList.ts new file mode 100644 index 0000000..ab64376 --- /dev/null +++ b/src/app/algorithm/linked-list/singlyLinkedList.ts @@ -0,0 +1,118 @@ +import { TreeNode } from '@/app/data-structure/Tree/Node'; +import { appendToMapWithNewValue } 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>>} 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 tree structure. + */ +export const updateTreeToInsertData = async ( + root: TreeNode, + value: number, + position: number, + speedRange: number, + dataMap: Map, + setDataMap: React.Dispatch>>, + setRoot: React.Dispatch> +): Promise => { + let currentRoot: TreeNode | null = root; + const newNode = new TreeNode(value); + newNode.cx = 20; + newNode.cy = 20; + + // Insert the new value into the map + setDataMap(appendToMapWithNewValue(dataMap, value, value)); + + // 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 + } +}; diff --git a/src/app/components/linked-list/LinkedListComponent.tsx b/src/app/components/linked-list/LinkedListComponent.tsx index 51a8bbb..2a94217 100644 --- a/src/app/components/linked-list/LinkedListComponent.tsx +++ b/src/app/components/linked-list/LinkedListComponent.tsx @@ -1,408 +1,81 @@ 'use client'; -import { LinkedList } from '@/app/data-structure/LinkedList/LinkedList'; -import { TreeNode } from '@/app/data-structure/Tree/Node'; -import { appendToMapWithNewValue, hasKey } from '@/app/lib/mapUtils'; -import { Sleep } from '@/app/lib/sleepMethod'; -import { ITreeNode } from '@/app/types/TreeTypeProps'; -import React, { useEffect, useState } from 'react'; -import { toast } from 'react-toastify'; - -const speedRange = 300; - -interface LinkedListInputProps { - insertAtLast: string; - insertAtAnyPosition: string; - deleteFromAnyPosition: string; -} +import { uid } from '@/app/lib/uidGenerator'; +import React, { useState } from 'react'; +import SinglyLinkedList from './SinglyLinkedList'; const LinkedListComponent = () => { - // define component local state - const [insertedData, setInsertedData] = useState([]); - const [root, setRoot] = useState(); - const [btnLoading, setButtonLoading] = useState(false); - const [inputData, setInputData] = useState({ - insertAtLast: '', - insertAtAnyPosition: '1', - deleteFromAnyPosition: '', - }); - const [dataMap, setDataMap] = useState>(new Map()); - - useEffect(() => { - const newList = new LinkedList([15]); - newList.createLinkedList(); - - if (newList.head) { - setRoot(newList.head); - // update setInsertData as well - setInsertedData((prv) => [...prv, 15]); - // insert into map - setDataMap(appendToMapWithNewValue(dataMap, 15, 15)); - setInputData((prv) => ({ ...prv, insertAtLast: String(Math.floor(Math.random() * 99 + 1)) })); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - /** - * A recursive approach to insert new node at last position with given value. - * - * @param {TreeNode} currentNode - * @param {number} cx - * @param {number} cy - * @param {number} value - * @returns {TreeNode} - */ - const updateTree = async (currentNode: TreeNode, cx: number, cy: number, value: number): Promise => { - const newNode = new TreeNode(value); - let current = currentNode; - - current.isCurrent = true; - setRoot({ ...currentNode }); - await Sleep(speedRange); - - current.isCurrent = false; - - // Traverse to the last node - while (current.next) { - current = current.next; - - current.isCurrent = true; - setRoot({ ...currentNode }); - - await Sleep(speedRange); - - current.isCurrent = false; - setRoot({ ...currentNode }); - } - - // mark is inserted position - current.isCurrent = true; - setRoot({ ...currentNode }); - await Sleep(speedRange); - current.isCurrent = false; - - newNode.cx = (current.cx || 0) + 25; - newNode.cy = 20; - newNode.isTarget = true; - - // Insert the new node at the end - current.next = newNode; - - // Update the new node - setRoot({ ...currentNode }); - await Sleep(speedRange); - - // Mark the new node as not the target - newNode.isTarget = false; - setRoot({ ...currentNode }); - - return currentNode; + // define a component local memory + const [activeRootBtnType, setActiveRootBtnType] = useState('linked-list-crud'); + const [randomKey, setRandomKey] = useState('1'); + const [speedRange, setSpeedRange] = useState(200); + + /** submit method to perform current task from start */ + const submitMethod = () => { + setRandomKey(uid()); }; /** - * Insert new data into tail of a linked list. Before inserted item need an recursive approach to get the positions. + * onChange method of select * - * @returns {*} + * @param {React.ChangeEvent} e */ - const insertDataAtTail = async () => { - // check some validation before insert at tail of given linked list - if (!root) { - toast.error('Invalid linked list.'); - return; - } - if (insertedData?.length > 11) { - toast.error('Max limit exceed. You can add at most 11 items'); - return; - } else if (!inputData.insertAtLast) { - toast.error('Input field can not be empty'); - return; - } - - const value = Number(inputData.insertAtLast); - - if (hasKey(dataMap, value)) { - toast.error('This item is already exists'); - return; - } - - setButtonLoading((prv) => !prv); - - // Starting at depth 20 and position 0 for the new node - const updatedRoot = await updateTree({ ...root }, Number(root.cx), Number(root.cy), value); - - // mark is inserted position - setRoot({ ...updatedRoot }); - await Sleep(300); - - // update setInsertData as well - setInsertedData((prv) => [...prv, value]); - - // insert into map - setDataMap(appendToMapWithNewValue(dataMap, value, value)); - - setButtonLoading((prv) => !prv); - // update input field with random value - setInputData((prv) => ({ ...prv, insertAtLast: String(Math.floor(Math.random() * 499 + 1)) })); + const handleSelectChange = async (e: React.ChangeEvent) => { + const value = e.target.value; + setActiveRootBtnType(value); }; /** - * Handle input data for insert at last into linked-list + * input type range method * - * @param {React.ChangeEvent} e + * @param {*} e */ - const insertAtLastOnChangeMethod = (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, insertAtLast: 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)); - console.log(dataMap.size); - - if (value > dataMap.size + 1 || value <= 0) { - toast.error(`Invalid position`); - } - - // const position = String(Math.floor(Math.random() + dataMap.size + 1)); - // console.log(position, 'position'); - - setInputData((prv) => ({ ...prv, insertAtAnyPosition: String(value || 0) })); - }; - - const insertDataByPosition = async () => { - // check some validation before insert at tail of given linked list - if (!root) { - toast.error('Invalid linked list.'); - return; - } else if (Number(inputData.insertAtAnyPosition) > dataMap.size + 1 || Number(inputData.insertAtAnyPosition) <= 0) { - toast.error(`Invalid position`); - return; - } else if (insertedData?.length > 11) { - toast.error('Max limit exceed. You can add at most 11 items'); - return; - } else if (!inputData.insertAtLast) { - toast.error('Input field can not be empty'); - return; - } - - const value = Number(inputData.insertAtLast); - const position = Number(inputData.insertAtAnyPosition); - - if (dataMap.get(value)) { - toast.error('This item is already exists'); - return; - } - - const updateTreeToInsertData = async (root: TreeNode, value: number, position: number): Promise => { - let sudoHead: TreeNode | null = root; - - const newNode = new TreeNode(value); - newNode.cx = 20; - newNode.cy = 20; - - // insert into map - setDataMap(appendToMapWithNewValue(dataMap, value, value)); - - // If the position is for the head - if (position === 1) { - sudoHead.isInsertedPosition = true; - setRoot({ ...sudoHead }); - - await Sleep(speedRange); - - sudoHead.isInsertedPosition = false; - setRoot({ ...sudoHead }); - - newNode.next = sudoHead; - sudoHead = newNode; - - // Shift cx values of the existing nodes - let current: TreeNode | null = newNode.next; - while (current) { - current.cx = (current.cx || 0) + 25; // Shift cx value - current = current.next; // Traverse to the next node - } - - return sudoHead; - } else { - let prev: TreeNode | null = null; - let current: TreeNode | null = sudoHead; - let counter = position - 1; - - // Traverse to the node at the specified position - while (counter-- && current) { - current.isCurrent = true; - setRoot({ ...root }); - - await Sleep(speedRange); - - current.isCurrent = false; - setRoot({ ...root }); - - prev = current; - current = current.next; - } - - // Ensure valid position and insertion - if (prev && counter < 0) { - if (current) { - current.isInsertedPosition = true; - setRoot({ ...root }); - - await Sleep(speedRange); - - current.isInsertedPosition = false; - setRoot({ ...root }); - } - - // Insert the new node between `prev` and `current` - newNode.next = current; // current could be null if it's the end of the list - prev.next = newNode; // previous node now points to newNode - - // Adjust cx of the inserted node and all subsequent nodes - if (current) { - newNode.cx = current.cx || 25; // Inherit cx if not the last - newNode.cy = current.cy || 20; - } else { - newNode.cx = (prev.cx || 0) + 25; // If inserting at the end - newNode.cy = prev.cy || 20; - } - // Shift cx values of subsequent nodes - let next: TreeNode | null = newNode.next; - - while (next) { - next.cx = (next.cx || 0) + 25; // Shift cx value for each next node - next = next.next; // Traverse to the next node - } - } else if (!current && position > 1) { - // Append to the end if position exceeds the list size - prev!.next = newNode; - newNode.cx = (prev!.cx || 0) + 25; // Adjust cx for the new tail node - } - - return root; // Return the original root node - } - }; - - // Starting at depth 20 and position 0 for the new node - const updatedRoot = await updateTreeToInsertData({ ...root }, value, position); - - // mark is inserted position - setRoot({ ...updatedRoot }); - // update input field with random value - setInputData((prv) => ({ - ...prv, - insertAtLast: String(Math.floor(Math.random() * 499 + 1)), - insertAtAnyPosition: String(Math.floor(Math.random() * dataMap.size + 1)), - })); - await Sleep(300); - - console.log(value, position); + const inputRangeMethod = (e: React.ChangeEvent) => { + setSpeedRange(Number(e.target.value)); }; return ( - <> -
    -
    -
    +
    +
    +
    +
    +

    Speed: {speedRange} (0 to 1500)

    -
    +
    +

    Select type

    + -
    -
    - - {root ? ( - - - - ) : null}
    - - ); -}; -const RenderNodeRecursively: React.FC<{ node: ITreeNode | null }> = ({ node }) => { - if (!node) return; - - return ( - <> - - {node?.next && ( - - )} - - - - {node?.value} - - - {node.next ? : null} - +
    + {activeRootBtnType === 'linked-list-crud' ? : null} +
    +
    ); }; diff --git a/src/app/components/linked-list/SinglyLinkedList.tsx b/src/app/components/linked-list/SinglyLinkedList.tsx new file mode 100644 index 0000000..0a21ca1 --- /dev/null +++ b/src/app/components/linked-list/SinglyLinkedList.tsx @@ -0,0 +1,274 @@ +'use client'; + +import { updateTreeToInsertData } from '@/app/algorithm/linked-list/singlyLinkedList'; +import { LinkedList } from '@/app/data-structure/LinkedList/LinkedList'; +import { appendToMapWithNewValue } from '@/app/lib/mapUtils'; +import { LinkedListInputProps, PageProps } from '@/app/types/linkedListProps'; +import { ITreeNode } from '@/app/types/TreeTypeProps'; +import React, { useEffect, useState } from 'react'; +import { toast } from 'react-toastify'; + +const SinglyLinkedList: 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: '', + insertAtLast: '', + insertAtAnyPosition: '1', + deleteFromAnyPosition: '-1', + }); + const [dataMap, setDataMap] = useState>(new Map()); + + useEffect(() => { + const rootValue = Math.floor(Math.random() + 100); + const newList = new LinkedList([rootValue]); + newList.createLinkedList(); + + if (newList.head) { + setRoot(newList.head); + // update setInsertData as well + setInsertedData((prv) => [...prv, rootValue]); + // insert into map + setDataMap(appendToMapWithNewValue(dataMap, rootValue, rootValue)); + setInputData((prv) => ({ ...prv, insertData: String(Math.floor(Math.random() * 99 + 1)) })); + } + // 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) })); + }; + + /** + * 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 (!root) { + toast.error('Invalid linked list.'); + return; + } + + if (position > dataMap.size + 1 || position <= 0) { + toast.error('Invalid position'); + return; + } + + if (insertedData?.length > 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 (dataMap.has(value)) { + toast.error('This item already exists'); + return; + } + + // Indicate loading state + setButtonLoading((prev) => !prev); + + // 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 }); + + // 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((prev) => !prev); + }; + + return ( + <> +
    +
    +
    +
    + + +
    + +
    + + +
    + +
    +
    +
    + +
    + + +
    +
    +
    +
    + + {root ? ( + + + + ) : null} +
    + + ); +}; + +const RenderNodeRecursively: React.FC<{ node: ITreeNode | null }> = ({ node }) => { + if (!node) return; + + return ( + <> + + {node?.next && ( + + )} + + + + {node?.value} + + + {node.next ? : null} + + ); +}; + +export default SinglyLinkedList; diff --git a/src/app/types/linkedListProps.ts b/src/app/types/linkedListProps.ts new file mode 100644 index 0000000..526091b --- /dev/null +++ b/src/app/types/linkedListProps.ts @@ -0,0 +1,27 @@ +/** + * 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} insertAtLast - The value of the node to be inserted at the end of the 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; + insertAtLast: 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; +} From 1c312c22fa97f93faea78e7b32d9ee88ec8072c3 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Tue, 17 Sep 2024 17:29:53 +0600 Subject: [PATCH 12/65] removed node from any given valid position functionality with UI is implemented --- .../algorithm/linked-list/singlyLinkedList.ts | 108 ++++++++++++++- .../linked-list/SinglyLinkedList.tsx | 124 ++++++++++++++---- src/app/lib/mapUtils.ts | 22 ++++ 3 files changed, 226 insertions(+), 28 deletions(-) diff --git a/src/app/algorithm/linked-list/singlyLinkedList.ts b/src/app/algorithm/linked-list/singlyLinkedList.ts index ab64376..0280cf5 100644 --- a/src/app/algorithm/linked-list/singlyLinkedList.ts +++ b/src/app/algorithm/linked-list/singlyLinkedList.ts @@ -1,5 +1,5 @@ import { TreeNode } from '@/app/data-structure/Tree/Node'; -import { appendToMapWithNewValue } from '@/app/lib/mapUtils'; +import { deleteKey } from '@/app/lib/mapUtils'; import { Sleep } from '@/app/lib/sleepMethod'; import { ITreeNode } from '@/app/types/TreeTypeProps'; import React from 'react'; @@ -14,7 +14,6 @@ import React from 'react'; * @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>>} 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 tree structure. @@ -25,7 +24,6 @@ export const updateTreeToInsertData = async ( position: number, speedRange: number, dataMap: Map, - setDataMap: React.Dispatch>>, setRoot: React.Dispatch> ): Promise => { let currentRoot: TreeNode | null = root; @@ -33,9 +31,6 @@ export const updateTreeToInsertData = async ( newNode.cx = 20; newNode.cy = 20; - // Insert the new value into the map - setDataMap(appendToMapWithNewValue(dataMap, value, value)); - // If the position is 1, insert the node as the new head if (position === 1) { currentRoot.isInsertedPosition = true; @@ -116,3 +111,104 @@ export const updateTreeToInsertData = async ( 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 +}; diff --git a/src/app/components/linked-list/SinglyLinkedList.tsx b/src/app/components/linked-list/SinglyLinkedList.tsx index 0a21ca1..aabc114 100644 --- a/src/app/components/linked-list/SinglyLinkedList.tsx +++ b/src/app/components/linked-list/SinglyLinkedList.tsx @@ -1,8 +1,9 @@ 'use client'; -import { updateTreeToInsertData } from '@/app/algorithm/linked-list/singlyLinkedList'; +import { updateTreeToDeleteData, updateTreeToInsertData } from '@/app/algorithm/linked-list/singlyLinkedList'; import { LinkedList } from '@/app/data-structure/LinkedList/LinkedList'; -import { appendToMapWithNewValue } from '@/app/lib/mapUtils'; +import { TreeNode } from '@/app/data-structure/Tree/Node'; +import { appendToMapWithNewValue, hasKey } from '@/app/lib/mapUtils'; import { LinkedListInputProps, PageProps } from '@/app/types/linkedListProps'; import { ITreeNode } from '@/app/types/TreeTypeProps'; import React, { useEffect, useState } from 'react'; @@ -17,22 +18,22 @@ const SinglyLinkedList: React.FC = ({ speedRange }) => { insertData: '', insertAtLast: '', insertAtAnyPosition: '1', - deleteFromAnyPosition: '-1', + deleteFromAnyPosition: '1', }); const [dataMap, setDataMap] = useState>(new Map()); useEffect(() => { - const rootValue = Math.floor(Math.random() + 100); + const rootValue = Math.floor(Math.floor(Math.random() * 99)); const newList = new LinkedList([rootValue]); newList.createLinkedList(); if (newList.head) { setRoot(newList.head); // update setInsertData as well - setInsertedData((prv) => [...prv, rootValue]); + setInsertedData([rootValue]); // insert into map setDataMap(appendToMapWithNewValue(dataMap, rootValue, rootValue)); - setInputData((prv) => ({ ...prv, insertData: String(Math.floor(Math.random() * 99 + 1)) })); + setInputData((prv) => ({ ...prv, insertData: String(Math.floor(Math.floor(Math.random() * 99))) })); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -68,6 +69,15 @@ const SinglyLinkedList: React.FC = ({ speedRange }) => { setInputData((prv) => ({ ...prv, insertAtAnyPosition: String(value || 0) })); }; + 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) })); + }; + /** * Inserts a new node into the tree/linked list at a specified position with validation. * @@ -95,17 +105,12 @@ const SinglyLinkedList: React.FC = ({ speedRange }) => { const position = Number(insertAtAnyPosition); const value = Number(insertData); - if (!root) { - toast.error('Invalid linked list.'); - return; - } - if (position > dataMap.size + 1 || position <= 0) { toast.error('Invalid position'); return; } - if (insertedData?.length > maxItems) { + if (insertedData?.length + 1 > maxItems) { toast.error(`Max limit exceeded. You can add at most ${maxItems} items.`); return; } @@ -115,11 +120,23 @@ const SinglyLinkedList: React.FC = ({ speedRange }) => { return; } - if (dataMap.has(value)) { + 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 = 20; + newNode.cy = 20; + setRoot(newNode); + setInsertedData((prv) => [...prv, value]); + // Insert the new value into the map + setDataMap(appendToMapWithNewValue(dataMap, value, value)); + return; + } + // Indicate loading state setButtonLoading((prev) => !prev); @@ -130,13 +147,15 @@ const SinglyLinkedList: React.FC = ({ speedRange }) => { position, speedRange, dataMap, - setDataMap, + // 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, @@ -148,6 +167,66 @@ const SinglyLinkedList: React.FC = ({ speedRange }) => { setButtonLoading((prev) => !prev); }; + /** + * 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); + } + } + }; + return ( <>
    @@ -164,7 +243,6 @@ const SinglyLinkedList: React.FC = ({ speedRange }) => { max={999} onChange={insertDataOnChangeMethod} value={inputData.insertData} - required disabled={btnLoading} id='input-data' /> @@ -181,7 +259,6 @@ const SinglyLinkedList: React.FC = ({ speedRange }) => { max={999} onChange={insertAtAnyPositionOnChangeMethod} value={inputData.insertAtAnyPosition} - required disabled={btnLoading} id='insert-position' /> @@ -204,13 +281,12 @@ const SinglyLinkedList: React.FC = ({ speedRange }) => { type='number' min={1} max={999} - onChange={insertAtAnyPositionOnChangeMethod} + onChange={deleteItemFromAnyPositionOnChangeMethod} value={inputData.deleteFromAnyPosition} - required disabled={btnLoading} />
    {root ? ( - + - ) : null} + ) : ( +
    +

    Invalid Linked List

    +
    + )}
    ); diff --git a/src/app/lib/mapUtils.ts b/src/app/lib/mapUtils.ts index 3ee0e57..f17e43f 100644 --- a/src/app/lib/mapUtils.ts +++ b/src/app/lib/mapUtils.ts @@ -33,3 +33,25 @@ export const appendToMapWithNewValue = (originalMap: Map, key: K, va 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; +}; From 616932e669838b70f35853d5b854e0bdebf75f57 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Wed, 18 Sep 2024 10:56:02 +0600 Subject: [PATCH 13/65] update linked-list basic visualization component name --- .../layouts/header/HeaderComponent.tsx | 11 +++++- ...glyLinkedList.tsx => LinkedListBasics.tsx} | 37 ++++++++++++++----- .../linked-list/LinkedListComponent.tsx | 6 +-- src/middleware.ts | 4 -- 4 files changed, 40 insertions(+), 18 deletions(-) rename src/app/components/linked-list/{SinglyLinkedList.tsx => LinkedListBasics.tsx} (93%) diff --git a/src/app/components/layouts/header/HeaderComponent.tsx b/src/app/components/layouts/header/HeaderComponent.tsx index 663c7d0..1842fa9 100644 --- a/src/app/components/layouts/header/HeaderComponent.tsx +++ b/src/app/components/layouts/header/HeaderComponent.tsx @@ -127,8 +127,17 @@ const HeaderComponent = () => { Searching +
  • + + Linked list + +
  • -
  • +
  • = ({ speedRange }) => { +const LinkedListBasics: React.FC = ({ speedRange }) => { // define component local state const [insertedData, setInsertedData] = useState([]); const [root, setRoot] = useState(); @@ -317,14 +317,31 @@ const RenderNodeRecursively: React.FC<{ node: ITreeNode | null }> = ({ node }) = <> {node?.next && ( - + <> + + + + + + + )} = ({ node }) = ); }; -export default SinglyLinkedList; +export default LinkedListBasics; diff --git a/src/app/components/linked-list/LinkedListComponent.tsx b/src/app/components/linked-list/LinkedListComponent.tsx index 2a94217..b48378b 100644 --- a/src/app/components/linked-list/LinkedListComponent.tsx +++ b/src/app/components/linked-list/LinkedListComponent.tsx @@ -2,7 +2,7 @@ import { uid } from '@/app/lib/uidGenerator'; import React, { useState } from 'react'; -import SinglyLinkedList from './SinglyLinkedList'; +import LinkedListBasics from './LinkedListBasics'; const LinkedListComponent = () => { // define a component local memory @@ -58,7 +58,7 @@ const LinkedListComponent = () => { className='text-md cursor-pointer rounded-sm border-[1px] border-theme-primary px-[5px] py-[4px] outline-none transition-all duration-200 hover:border-theme-btn-secondary' > @@ -73,7 +73,7 @@ const LinkedListComponent = () => {
  • - {activeRootBtnType === 'linked-list-crud' ? : null} + {activeRootBtnType === 'linked-list-crud' ? : null}
    ); diff --git a/src/middleware.ts b/src/middleware.ts index 2cac3dd..e905636 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -13,10 +13,6 @@ export function middleware(request: NextRequest) { if (process.env.NODE_ENV !== 'development' && request.nextUrl.pathname === '/') { return NextResponse.redirect(new URL('/tree', request.url)); } - // prevent to visit linked-list, cause it's under development - else if (process.env.NODE_ENV !== 'development' && request.nextUrl.pathname === '/linked-list') { - return NextResponse.redirect(new URL('/tree', request.url)); - } // redirect with other urls return NextResponse.next(); From 682d1e231cb3803c39f015edde673ffabcd3d994 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Wed, 18 Sep 2024 11:45:14 +0600 Subject: [PATCH 14/65] search node features is implemented in basic linked list visualization --- ...inkedList.ts => singlyLinkedListBasics.ts} | 46 ++++++++ .../linked-list/LinkedListBasics.tsx | 109 +++++++++++++++++- src/app/types/linkedListProps.ts | 4 +- 3 files changed, 153 insertions(+), 6 deletions(-) rename src/app/algorithm/linked-list/{singlyLinkedList.ts => singlyLinkedListBasics.ts} (82%) diff --git a/src/app/algorithm/linked-list/singlyLinkedList.ts b/src/app/algorithm/linked-list/singlyLinkedListBasics.ts similarity index 82% rename from src/app/algorithm/linked-list/singlyLinkedList.ts rename to src/app/algorithm/linked-list/singlyLinkedListBasics.ts index 0280cf5..ef358dc 100644 --- a/src/app/algorithm/linked-list/singlyLinkedList.ts +++ b/src/app/algorithm/linked-list/singlyLinkedListBasics.ts @@ -212,3 +212,49 @@ const updateTreeToDeleteData = async ( 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/components/linked-list/LinkedListBasics.tsx b/src/app/components/linked-list/LinkedListBasics.tsx index 3555430..019997a 100644 --- a/src/app/components/linked-list/LinkedListBasics.tsx +++ b/src/app/components/linked-list/LinkedListBasics.tsx @@ -1,6 +1,10 @@ 'use client'; -import { updateTreeToDeleteData, updateTreeToInsertData } from '@/app/algorithm/linked-list/singlyLinkedList'; +import { + updateTreeToDeleteData, + updateTreeToInsertData, + updateTreeToSearchNodeData, +} from '@/app/algorithm/linked-list/singlyLinkedListBasics'; import { LinkedList } from '@/app/data-structure/LinkedList/LinkedList'; import { TreeNode } from '@/app/data-structure/Tree/Node'; import { appendToMapWithNewValue, hasKey } from '@/app/lib/mapUtils'; @@ -16,7 +20,7 @@ const LinkedListBasics: React.FC = ({ speedRange }) => { const [btnLoading, setButtonLoading] = useState(false); const [inputData, setInputData] = useState({ insertData: '', - insertAtLast: '', + searchItem: '-1', insertAtAnyPosition: '1', deleteFromAnyPosition: '1', }); @@ -33,7 +37,11 @@ const LinkedListBasics: React.FC = ({ speedRange }) => { setInsertedData([rootValue]); // insert into map setDataMap(appendToMapWithNewValue(dataMap, rootValue, rootValue)); - setInputData((prv) => ({ ...prv, insertData: String(Math.floor(Math.floor(Math.random() * 99))) })); + setInputData((prv) => ({ + ...prv, + insertData: String(Math.floor(Math.floor(Math.random() * 99))), + searchItem: String(rootValue), + })); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -69,6 +77,11 @@ const LinkedListBasics: React.FC = ({ speedRange }) => { 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)); @@ -78,6 +91,16 @@ const LinkedListBasics: React.FC = ({ speedRange }) => { 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. * @@ -227,12 +250,65 @@ const LinkedListBasics: React.FC = ({ speedRange }) => { } }; + /** + * 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 ( <>
    -
    +
    @@ -294,6 +370,31 @@ const LinkedListBasics: React.FC = ({ speedRange }) => {
    + +
    +
    + +
    + + +
    +
    +
    {root ? ( diff --git a/src/app/types/linkedListProps.ts b/src/app/types/linkedListProps.ts index 526091b..acfca61 100644 --- a/src/app/types/linkedListProps.ts +++ b/src/app/types/linkedListProps.ts @@ -4,13 +4,13 @@ * * @interface LinkedListInputProps * - * @property {string} insertAtLast - The value of the node to be inserted at the end of the list. + * @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; - insertAtLast: string; + searchItem: string; insertAtAnyPosition: string; deleteFromAnyPosition: string; } From c3000a52375e065ce409b7ed128217a603a9ddd9 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Wed, 18 Sep 2024 12:47:32 +0600 Subject: [PATCH 15/65] removed unnecessary log --- .../linked-list/LinkedListBasics.tsx | 160 ++++++++++-------- src/app/data/mockData.ts | 23 +++ 2 files changed, 109 insertions(+), 74 deletions(-) diff --git a/src/app/components/linked-list/LinkedListBasics.tsx b/src/app/components/linked-list/LinkedListBasics.tsx index 019997a..b66adb3 100644 --- a/src/app/components/linked-list/LinkedListBasics.tsx +++ b/src/app/components/linked-list/LinkedListBasics.tsx @@ -7,9 +7,11 @@ import { } from '@/app/algorithm/linked-list/singlyLinkedListBasics'; 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'; @@ -161,7 +163,9 @@ const LinkedListBasics: React.FC = ({ speedRange }) => { } // Indicate loading state - setButtonLoading((prev) => !prev); + setButtonLoading(true); + + // clearAllTimeouts(); // Insert data at the specified position const updatedRoot = await updateTreeToInsertData( @@ -187,7 +191,7 @@ const LinkedListBasics: React.FC = ({ speedRange }) => { })); // Toggle loading state - setButtonLoading((prev) => !prev); + setButtonLoading(false); }; /** @@ -306,94 +310,102 @@ const LinkedListBasics: React.FC = ({ speedRange }) => { return ( <>
    -
    -
    -
    - - -
    - -
    - - -
    - -
    -
    -
    - -
    +
    +
    +
    +
    + -
    -
    -
    -
    -
    - -
    +
    + - +
    + +
    +
    +
    + +
    + + +
    + +
    +
    + +
    + + +
    +
    +
    +
    +
    +
    diff --git a/src/app/data/mockData.ts b/src/app/data/mockData.ts index c378e60..48feff2 100644 --- a/src/app/data/mockData.ts +++ b/src/app/data/mockData.ts @@ -226,3 +226,26 @@ export const islandColorsPlate: string[] = [ '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', + }, +]; From c17cb4075d201d93ac656e6e453407e186912699 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Wed, 18 Sep 2024 17:45:38 +0600 Subject: [PATCH 16/65] reverse linked visualization is implemented --- .../linked-list/LinkedListComponent.tsx | 14 +- .../linked-list/ReverseLinkedList.tsx | 225 ++++++++++++++++++ src/app/data/mockData.ts | 18 ++ src/app/types/linkedListProps.ts | 1 + 4 files changed, 255 insertions(+), 3 deletions(-) create mode 100644 src/app/components/linked-list/ReverseLinkedList.tsx diff --git a/src/app/components/linked-list/LinkedListComponent.tsx b/src/app/components/linked-list/LinkedListComponent.tsx index b48378b..ecfc7a6 100644 --- a/src/app/components/linked-list/LinkedListComponent.tsx +++ b/src/app/components/linked-list/LinkedListComponent.tsx @@ -3,10 +3,11 @@ import { uid } from '@/app/lib/uidGenerator'; import React, { useState } from 'react'; import LinkedListBasics from './LinkedListBasics'; +import ReverseLinkedList from './ReverseLinkedList'; const LinkedListComponent = () => { // define a component local memory - const [activeRootBtnType, setActiveRootBtnType] = useState('linked-list-crud'); + const [activeRootBtnType, setActiveRootBtnType] = useState('reverse-linked-list'); const [randomKey, setRandomKey] = useState('1'); const [speedRange, setSpeedRange] = useState(200); @@ -57,9 +58,12 @@ const LinkedListComponent = () => { value={activeRootBtnType} className='text-md cursor-pointer rounded-sm border-[1px] border-theme-primary px-[5px] py-[4px] outline-none transition-all duration-200 hover:border-theme-btn-secondary' > - +
    - {activeRootBtnType === 'linked-list-crud' ? : null} + {activeRootBtnType === 'linked-list-crud' ? ( + + ) : activeRootBtnType === 'reverse-linked-list' ? ( + + ) : null}
    ); diff --git a/src/app/components/linked-list/ReverseLinkedList.tsx b/src/app/components/linked-list/ReverseLinkedList.tsx new file mode 100644 index 0000000..89ea95f --- /dev/null +++ b/src/app/components/linked-list/ReverseLinkedList.tsx @@ -0,0 +1,225 @@ +'use client'; + +import { getRandomTreeData } 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 { 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 = 11; + +const ReverseLinkedList: React.FC = ({ speedRange }) => { + // define component local state + const [rootNode, setRootNode] = useState(); + const [root, setRoot] = useState(); + const [reverseNodes, setReverseNodes] = useState(); + const [isPerformReverseOperation, setIsPerformReverseOperation] = useState(false); + + useEffect(() => { + const newList = new LinkedList(JSON.parse(JSON.stringify(getRandomTreeData(NODE_LENGTH)))); + newList.createLinkedList(); + + if (newList.head) { + setRoot(newList.head); + setRootNode(JSON.parse(JSON.stringify(newList.head))); + setIsPerformReverseOperation(true); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + if (root && rootNode && isPerformReverseOperation) { + reverseMethod(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isPerformReverseOperation]); + + const updateListInReverse = async (node: TreeNode | null, totalNodes: number): Promise => { + let prev: TreeNode | null = null; + let index = 0; // To manage the position index + const nodeSpacing = 25; // Horizontal spacing between nodes + const startX = 20; // Starting x position for the first node + + // Reverse the linked list and update positions + while (node) { + const tempNode = node.next; // Save the next node before reversing + // Reverse the next pointer + node.next = prev; + // Update the node's position (cx) based on its new position in the reversed list + node.cx = startX + (totalNodes - index - 1) * nodeSpacing; + prev = node; + node = tempNode; + + prev.isTarget = true; + setReverseNodes(JSON.parse(JSON.stringify(prev))); + await Sleep(speedRange); + prev.isTarget = false; + setReverseNodes(JSON.parse(JSON.stringify(prev))); + + // Increment the index for position swapping + index++; + } + + // Return the new head of the reversed list + return prev; + }; + + const recursiveApproachToTraverseList = async (node: TreeNode | null) => { + if (!node) return; + + // Highlight the current node + // node.isCurrent = true; + setRoot((prevRoot) => { + // Create a deep copy of the root node to trigger re-render + const updatedRoot = JSON.parse(JSON.stringify(prevRoot)); + let currentNode: TreeNode | null = updatedRoot as TreeNode; + + while (currentNode) { + if (currentNode.id === node.id) { + currentNode.isCurrent = true; + } else { + currentNode.isCurrent = false; + } + currentNode = currentNode.next; + } + return updatedRoot; + }); + + // Delay to visualize the current node + await Sleep(speedRange); + + // Reset the current node highlight + node.isCurrent = false; + setRoot((prevRoot) => { + // Create a deep copy of the root node to trigger re-render + const updatedRoot = JSON.parse(JSON.stringify(prevRoot)); + let currentNode: TreeNode | null = updatedRoot as TreeNode; + while (currentNode) { + if (currentNode.id === node.id) { + currentNode.isCurrent = false; + } + currentNode = currentNode.next; + } + return updatedRoot; + }); + + // Recursively process the next node first + await recursiveApproachToTraverseList(node.next); + }; + + const reverseMethod = async () => { + try { + recursiveApproachToTraverseList(JSON.parse(JSON.stringify(rootNode))); + + // // Reverse the list and update node positions + const updatedRoot = await updateListInReverse(JSON.parse(JSON.stringify(rootNode)), NODE_LENGTH); + // // Update the root of the reversed list to trigger a re-render + setReverseNodes({ ...(updatedRoot as TreeNode) }); + setRootNode({ ...(updatedRoot as TreeNode) }); + + // // update root node + setRoot(JSON.parse(JSON.stringify(updatedRoot))); + } catch (error) { + if (process.env.NODE_ENV === 'development') { + // eslint-disable-next-line no-console + console.error(error); + } + } + }; + + return ( + <> +
    +
    + +
    +
    + {root ? ( +
    +

    ACTUAL NODES:

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

    REVERSE NODES:

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

    Invalid Linked List

    +
    + )} +
    +
    + + ); +}; + +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/data/mockData.ts b/src/app/data/mockData.ts index 48feff2..9b5be62 100644 --- a/src/app/data/mockData.ts +++ b/src/app/data/mockData.ts @@ -249,3 +249,21 @@ export const basicLinkedListColorsPlate: StatusColorsDataProps[] = [ 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', + }, +]; diff --git a/src/app/types/linkedListProps.ts b/src/app/types/linkedListProps.ts index acfca61..2bba4d7 100644 --- a/src/app/types/linkedListProps.ts +++ b/src/app/types/linkedListProps.ts @@ -24,4 +24,5 @@ export interface LinkedListInputProps { */ export interface PageProps { speedRange: number; + updateComponentWithKey?: string; } From b8528384d8c1d5a41f40fa5285133bdfb5fd67a3 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Thu, 19 Sep 2024 11:20:56 +0600 Subject: [PATCH 17/65] linked list reverse node, and other necessary code split into multiple file, do some improvement on clean code, a dynamic position of node approach is implemented --- src/app/algorithm/linked-list/reverseList.ts | 112 +++++++++++++ .../linked-list/singlyLinkedListBasics.ts | 5 +- .../linked-list/LinkedListBasics.tsx | 7 +- .../linked-list/LinkedListComponent.tsx | 4 +- .../linked-list/ReverseLinkedList.tsx | 151 ++++++------------ src/app/constant.ts | 4 + .../data-structure/LinkedList/LinkedList.ts | 39 +---- 7 files changed, 186 insertions(+), 136 deletions(-) create mode 100644 src/app/algorithm/linked-list/reverseList.ts 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 index ef358dc..915507f 100644 --- a/src/app/algorithm/linked-list/singlyLinkedListBasics.ts +++ b/src/app/algorithm/linked-list/singlyLinkedListBasics.ts @@ -1,3 +1,4 @@ +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'; @@ -28,8 +29,8 @@ export const updateTreeToInsertData = async ( ): Promise => { let currentRoot: TreeNode | null = root; const newNode = new TreeNode(value); - newNode.cx = 20; - newNode.cy = 20; + 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) { diff --git a/src/app/components/linked-list/LinkedListBasics.tsx b/src/app/components/linked-list/LinkedListBasics.tsx index b66adb3..49d63ba 100644 --- a/src/app/components/linked-list/LinkedListBasics.tsx +++ b/src/app/components/linked-list/LinkedListBasics.tsx @@ -5,6 +5,7 @@ import { 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'; @@ -30,7 +31,7 @@ const LinkedListBasics: React.FC = ({ speedRange }) => { useEffect(() => { const rootValue = Math.floor(Math.floor(Math.random() * 99)); - const newList = new LinkedList([rootValue]); + const newList = new LinkedList([rootValue], LINKED_LIST_NODE_START_CX); newList.createLinkedList(); if (newList.head) { @@ -153,8 +154,8 @@ const LinkedListBasics: React.FC = ({ speedRange }) => { // if any linked is not present, then created it if (!root) { const newNode = new TreeNode(value); - newNode.cx = 20; - newNode.cy = 20; + 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 diff --git a/src/app/components/linked-list/LinkedListComponent.tsx b/src/app/components/linked-list/LinkedListComponent.tsx index ecfc7a6..defd49c 100644 --- a/src/app/components/linked-list/LinkedListComponent.tsx +++ b/src/app/components/linked-list/LinkedListComponent.tsx @@ -7,7 +7,7 @@ import ReverseLinkedList from './ReverseLinkedList'; const LinkedListComponent = () => { // define a component local memory - const [activeRootBtnType, setActiveRootBtnType] = useState('reverse-linked-list'); + const [activeRootBtnType, setActiveRootBtnType] = useState('linked-list-crud'); const [randomKey, setRandomKey] = useState('1'); const [speedRange, setSpeedRange] = useState(200); @@ -80,7 +80,7 @@ const LinkedListComponent = () => { {activeRootBtnType === 'linked-list-crud' ? ( ) : activeRootBtnType === 'reverse-linked-list' ? ( - + ) : null}
    diff --git a/src/app/components/linked-list/ReverseLinkedList.tsx b/src/app/components/linked-list/ReverseLinkedList.tsx index 89ea95f..e746187 100644 --- a/src/app/components/linked-list/ReverseLinkedList.tsx +++ b/src/app/components/linked-list/ReverseLinkedList.tsx @@ -1,10 +1,11 @@ 'use client'; -import { getRandomTreeData } from '@/app/constant'; +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 { Sleep } from '@/app/lib/sleepMethod'; +import { clearAllTimeouts } from '@/app/lib/sleepMethod'; import { PageProps } from '@/app/types/linkedListProps'; import { ITreeNode } from '@/app/types/TreeTypeProps'; import StatusColorsPlate from '@/app/utils/StatusColorsPlate'; @@ -12,7 +13,7 @@ import React, { useEffect, useState } from 'react'; const NODE_LENGTH = 11; -const ReverseLinkedList: React.FC = ({ speedRange }) => { +const ReverseLinkedList: React.FC = ({ speedRange, updateComponentWithKey }) => { // define component local state const [rootNode, setRootNode] = useState(); const [root, setRoot] = useState(); @@ -20,110 +21,64 @@ const ReverseLinkedList: React.FC = ({ speedRange }) => { const [isPerformReverseOperation, setIsPerformReverseOperation] = useState(false); useEffect(() => { - const newList = new LinkedList(JSON.parse(JSON.stringify(getRandomTreeData(NODE_LENGTH)))); + // create a new linked list with default cx value + const newList = new LinkedList(getRandomTreeData(NODE_LENGTH), LINKED_LIST_NODE_START_CX); newList.createLinkedList(); - - if (newList.head) { - setRoot(newList.head); - setRootNode(JSON.parse(JSON.stringify(newList.head))); + 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 (root && rootNode && isPerformReverseOperation) { - reverseMethod(); + if (isPerformReverseOperation) { + handleReverseLinkedList(); + setIsPerformReverseOperation(false); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isPerformReverseOperation]); - - const updateListInReverse = async (node: TreeNode | null, totalNodes: number): Promise => { - let prev: TreeNode | null = null; - let index = 0; // To manage the position index - const nodeSpacing = 25; // Horizontal spacing between nodes - const startX = 20; // Starting x position for the first node - - // Reverse the linked list and update positions - while (node) { - const tempNode = node.next; // Save the next node before reversing - // Reverse the next pointer - node.next = prev; - // Update the node's position (cx) based on its new position in the reversed list - node.cx = startX + (totalNodes - index - 1) * nodeSpacing; - prev = node; - node = tempNode; - - prev.isTarget = true; - setReverseNodes(JSON.parse(JSON.stringify(prev))); - await Sleep(speedRange); - prev.isTarget = false; - setReverseNodes(JSON.parse(JSON.stringify(prev))); - - // Increment the index for position swapping - index++; - } - - // Return the new head of the reversed list - return prev; - }; - - const recursiveApproachToTraverseList = async (node: TreeNode | null) => { - if (!node) return; - - // Highlight the current node - // node.isCurrent = true; - setRoot((prevRoot) => { - // Create a deep copy of the root node to trigger re-render - const updatedRoot = JSON.parse(JSON.stringify(prevRoot)); - let currentNode: TreeNode | null = updatedRoot as TreeNode; - - while (currentNode) { - if (currentNode.id === node.id) { - currentNode.isCurrent = true; - } else { - currentNode.isCurrent = false; - } - currentNode = currentNode.next; - } - return updatedRoot; - }); - - // Delay to visualize the current node - await Sleep(speedRange); - - // Reset the current node highlight - node.isCurrent = false; - setRoot((prevRoot) => { - // Create a deep copy of the root node to trigger re-render - const updatedRoot = JSON.parse(JSON.stringify(prevRoot)); - let currentNode: TreeNode | null = updatedRoot as TreeNode; - while (currentNode) { - if (currentNode.id === node.id) { - currentNode.isCurrent = false; - } - currentNode = currentNode.next; - } - return updatedRoot; - }); - - // Recursively process the next node first - await recursiveApproachToTraverseList(node.next); - }; - - const reverseMethod = async () => { + }, [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 { - recursiveApproachToTraverseList(JSON.parse(JSON.stringify(rootNode))); - - // // Reverse the list and update node positions - const updatedRoot = await updateListInReverse(JSON.parse(JSON.stringify(rootNode)), NODE_LENGTH); - // // Update the root of the reversed list to trigger a re-render - setReverseNodes({ ...(updatedRoot as TreeNode) }); - setRootNode({ ...(updatedRoot as TreeNode) }); - - // // update root node - setRoot(JSON.parse(JSON.stringify(updatedRoot))); + // 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); @@ -155,7 +110,7 @@ const ReverseLinkedList: React.FC = ({ speedRange }) => {
    ) : (
    -

    Invalid Linked List

    +

    Loading...

    )}
    diff --git a/src/app/constant.ts b/src/app/constant.ts index cd19c06..e1fc567 100644 --- a/src/app/constant.ts +++ b/src/app/constant.ts @@ -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 index fb0b785..df92be5 100644 --- a/src/app/data-structure/LinkedList/LinkedList.ts +++ b/src/app/data-structure/LinkedList/LinkedList.ts @@ -14,20 +14,21 @@ export interface ITree { 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} cycle1_idx - The index of the first node to create a cycle. - * @param {number} cycle2_idx - The index of the second node to create a cycle. + * @param {number | undefined} initialize_cx - the initial staring cx position of node. */ - constructor(arr: (number | null)[]) { + constructor(arr: (number | null)[], initialize_cx: number = 20) { this.head = null; this.arr = arr; + this.initialize_cx = initialize_cx; } /** @@ -39,11 +40,9 @@ export class LinkedList implements ITree { createLinkedList(): void { if (this.arr.length === 0) return; - let CX = 20; - // Insert the first node into the head const temp = new TreeNode(this.arr[0]); - temp.cx = CX; + temp.cx = this.initialize_cx; temp.cy = 20; this.head = temp; @@ -52,36 +51,14 @@ export class LinkedList implements ITree { for (let i = 1; i < this.arr.length; i++) { const newNode = new TreeNode(this.arr[i]); - newNode.cx = CX + 25; + newNode.cx = this.initialize_cx + 25; newNode.cy = 20; current.next = newNode; current = newNode; // Update CX value for positioning - CX += 25; - } - } - - // have to calculate new x, y position - insertNodeAtLast(value: number): void { - if (!this.head) { - this.head = new TreeNode(value); - return; + this.initialize_cx += 25; } - - const newNode = new TreeNode(value); - let current = this.head; - - // Traverse to the last node - while (current.next) { - current = current.next; - } - - newNode.cx = (current.cx || 0) + 25; - newNode.cy = 20; - - // Insert the new node at the end - current.next = newNode; } } From 0ecbd19e1e807aeecc7de2f621548b2a63b1dc90 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Thu, 19 Sep 2024 11:37:03 +0600 Subject: [PATCH 18/65] pathFinding component, initial grid size is updated, linked list component default component visibility is changes --- src/app/components/linked-list/LinkedListComponent.tsx | 8 ++++---- src/app/components/pathFind/PathFind.tsx | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/components/linked-list/LinkedListComponent.tsx b/src/app/components/linked-list/LinkedListComponent.tsx index defd49c..381a3ee 100644 --- a/src/app/components/linked-list/LinkedListComponent.tsx +++ b/src/app/components/linked-list/LinkedListComponent.tsx @@ -7,7 +7,7 @@ import ReverseLinkedList from './ReverseLinkedList'; const LinkedListComponent = () => { // define a component local memory - const [activeRootBtnType, setActiveRootBtnType] = useState('linked-list-crud'); + const [activeRootBtnType, setActiveRootBtnType] = useState('reverse-linked-list'); const [randomKey, setRandomKey] = useState('1'); const [speedRange, setSpeedRange] = useState(200); @@ -58,12 +58,12 @@ const LinkedListComponent = () => { value={activeRootBtnType} className='text-md cursor-pointer rounded-sm border-[1px] border-theme-primary px-[5px] py-[4px] outline-none transition-all duration-200 hover:border-theme-btn-secondary' > - +
    diff --git a/src/app/components/linked-list/MergeTwoSortedList.tsx b/src/app/components/linked-list/MergeTwoSortedList.tsx new file mode 100644 index 0000000..1dbb4df --- /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 { reverseListColorsPlate } 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/pathFind/NoOfIslands.tsx b/src/app/components/pathFind/NoOfIslands.tsx index 05c21fa..92995b2 100644 --- a/src/app/components/pathFind/NoOfIslands.tsx +++ b/src/app/components/pathFind/NoOfIslands.tsx @@ -13,8 +13,7 @@ import { toast } from 'react-toastify'; const NoOfIslands: React.FC = ({ 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(() => { diff --git a/src/app/components/pathFind/PathFind.tsx b/src/app/components/pathFind/PathFind.tsx index 656b54c..9814f6d 100644 --- a/src/app/components/pathFind/PathFind.tsx +++ b/src/app/components/pathFind/PathFind.tsx @@ -10,7 +10,6 @@ import NoOfIslands from './NoOfIslands'; const PathFind = () => { // define local state const [buttonType, setButtonType] = useState('unique-path'); - // eslint-disable-next-line @typescript-eslint/no-unused-vars const [randomKey, setRandomKey] = useState('1'); const [speedRange, setSpeedRange] = useState(100); const [gridSize, setGridSize] = useState<{ rowSize: number; colSize: number }>({ rowSize: 8, colSize: 10 }); diff --git a/src/app/components/sorting/SortingComponent.tsx b/src/app/components/sorting/SortingComponent.tsx index 5945186..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); diff --git a/src/app/constant.ts b/src/app/constant.ts index e1fc567..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[][] => { diff --git a/src/app/data/mockData.ts b/src/app/data/mockData.ts index 9b5be62..7c3b77a 100644 --- a/src/app/data/mockData.ts +++ b/src/app/data/mockData.ts @@ -267,3 +267,21 @@ export const reverseListColorsPlate: StatusColorsDataProps[] = [ 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', + }, +]; From f70a9361ee0aa4a93263894a0574b54ae223f3eb Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Thu, 19 Sep 2024 17:15:44 +0600 Subject: [PATCH 20/65] a revisualized approach is implemented is reverse-linked list --- .../linked-list/MergeTwoSortedList.tsx | 6 +++--- .../linked-list/ReverseLinkedList.tsx | 20 +++++++++++++++++-- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/app/components/linked-list/MergeTwoSortedList.tsx b/src/app/components/linked-list/MergeTwoSortedList.tsx index 1dbb4df..c144649 100644 --- a/src/app/components/linked-list/MergeTwoSortedList.tsx +++ b/src/app/components/linked-list/MergeTwoSortedList.tsx @@ -7,7 +7,7 @@ import { 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 { reverseListColorsPlate } from '@/app/data/mockData'; +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'; @@ -164,14 +164,14 @@ const MergeTwoSortedList: React.FC = ({ speedRange, updateComponentWi
    - +
    diff --git a/src/app/components/linked-list/ReverseLinkedList.tsx b/src/app/components/linked-list/ReverseLinkedList.tsx index e746187..d3a5d2a 100644 --- a/src/app/components/linked-list/ReverseLinkedList.tsx +++ b/src/app/components/linked-list/ReverseLinkedList.tsx @@ -19,6 +19,7 @@ const ReverseLinkedList: React.FC = ({ speedRange, updateComponentWit 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 @@ -60,6 +61,9 @@ const ReverseLinkedList: React.FC = ({ speedRange, updateComponentWit */ 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 @@ -83,15 +87,27 @@ const ReverseLinkedList: React.FC = ({ speedRange, updateComponentWit // eslint-disable-next-line no-console console.error(error); } + } finally { + setBtnLoading(false); } }; return ( <>
    -
    - +
    +
    + +
    +
    +
    {root ? (
    From 36978367743845e21d26422d715c0396f306794c Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Sun, 22 Sep 2024 12:24:31 +0600 Subject: [PATCH 21/65] linked list root component some design issue improved --- .../linked-list/LinkedListBasics.tsx | 12 ++--- .../linked-list/LinkedListComponent.tsx | 44 ++++++++++--------- 2 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/app/components/linked-list/LinkedListBasics.tsx b/src/app/components/linked-list/LinkedListBasics.tsx index 49d63ba..3c4b407 100644 --- a/src/app/components/linked-list/LinkedListBasics.tsx +++ b/src/app/components/linked-list/LinkedListBasics.tsx @@ -358,9 +358,9 @@ const LinkedListBasics: React.FC = ({ speedRange }) => { -
    +
    = ({ speedRange }) => { />
    -
    -

    Select type

    - - +
    +
    +

    Select type

    + +
    From 70898d0c19f1ee810c294389cd07116068ff677d Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Mon, 23 Sep 2024 13:19:00 +0600 Subject: [PATCH 22/65] Minheap data-structure is created to perform Disjkstra algorihhm to find shortest path from given graph, disjkstra shortest path component is created --- .../components/pathFind/DijkstraComponent.tsx | 216 ++++++++++++++++++ src/app/components/pathFind/PathFind.tsx | 76 +++--- src/app/data-structure/minHeap/MinHeap.ts | 113 +++++++++ src/app/data/shortestPathData.ts | 146 ++++++++++++ src/app/types/shortestPathProps.ts | 39 ++++ 5 files changed, 556 insertions(+), 34 deletions(-) create mode 100644 src/app/components/pathFind/DijkstraComponent.tsx create mode 100644 src/app/data-structure/minHeap/MinHeap.ts create mode 100644 src/app/data/shortestPathData.ts create mode 100644 src/app/types/shortestPathProps.ts diff --git a/src/app/components/pathFind/DijkstraComponent.tsx b/src/app/components/pathFind/DijkstraComponent.tsx new file mode 100644 index 0000000..a845564 --- /dev/null +++ b/src/app/components/pathFind/DijkstraComponent.tsx @@ -0,0 +1,216 @@ +'use client'; +import { MinHeap } from '@/app/data-structure/minHeap/MinHeap'; +import { generateEdges, nodesData } from '@/app/data/shortestPathData'; +import { clearAllTimeouts, Sleep } from '@/app/lib/sleepMethod'; +import { IGraphEdge, IGraphNode } from '@/app/types/shortestPathProps'; +import React, { useEffect, useState } from 'react'; + +// define component Props +interface PageProps { + useRandomKey: string; + speedRange: number; +} + +const DijkstraComponent: React.FC = ({ speedRange, useRandomKey }) => { + const [nodes, setNodes] = useState([]); + const [edges, setEdges] = useState([]); + const [shortestPathEdges, setShortestPathEdges] = useState([]); + const [isReadyToPerformOperation, setIsReadyToPerformOperation] = useState(false); + + useEffect(() => { + clearAllTimeouts(); + setNodes(JSON.parse(JSON.stringify(nodesData))); + setEdges(JSON.parse(JSON.stringify(generateEdges()))); + setShortestPathEdges([]); + setIsReadyToPerformOperation(true); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [useRandomKey]); + + useEffect(() => { + if (isReadyToPerformOperation) { + handleDijkstra({ source: 0, destination: 4 }); + setIsReadyToPerformOperation(false); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isReadyToPerformOperation, useRandomKey, edges]); + + /** + * Executes Dijkstra's algorithm to find the shortest path in a graph. + * The function visualizes the algorithm's steps, updating node states + * and handling asynchronous animations during processing. + * + * @async + * @function handleDijkstra + * @returns {Promise} A promise that resolves when the algorithm completes. + * + * @throws {Error} If any errors occur during the execution, they are logged in development mode. + * + * @example + * // To use this function, ensure that nodes and edges are defined in the context: + * await handleDijkstra(); + */ + const handleDijkstra = async ({ source = 0, destination = 4 }) => { + try { + const pq = new MinHeap(); + const distances = Array(10).fill(Infinity); + const predecessors = Array(10).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 } : 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 = 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 } : 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 } : node))); + await Sleep(500); + currentNode = predecessors[currentNode]; + } + + setNodes((prev) => prev.map((node) => (node.id === source ? { ...node, isShortestPath: true } : node))); + } catch (error) { + if (process.env.NODE_ENV === 'development') { + // eslint-disable-next-line no-console + console.error(error); + } + } + }; + + return ( +
    +
    + {edges?.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} + + + ))} + + ) : ( +
    +

    Loading...

    +
    + )} +
    +
    + ); +}; + +export default DijkstraComponent; diff --git a/src/app/components/pathFind/PathFind.tsx b/src/app/components/pathFind/PathFind.tsx index 9814f6d..470866b 100644 --- a/src/app/components/pathFind/PathFind.tsx +++ b/src/app/components/pathFind/PathFind.tsx @@ -6,10 +6,11 @@ import UniquePath from './UniquePath'; import { gridRowColSize } from '@/app/lib/helpers'; import { clearAllTimeouts } from '@/app/lib/sleepMethod'; import NoOfIslands from './NoOfIslands'; +import DijkstraComponent from './DijkstraComponent'; const PathFind = () => { // define local state - const [buttonType, setButtonType] = useState('unique-path'); + const [buttonType, setButtonType] = useState('dijkstra'); const [randomKey, setRandomKey] = useState('1'); const [speedRange, setSpeedRange] = useState(100); const [gridSize, setGridSize] = useState<{ rowSize: number; colSize: number }>({ rowSize: 8, colSize: 10 }); @@ -81,40 +82,42 @@ const PathFind = () => { />
    -
    -
    -

    Row

    - + {buttonType !== 'dijkstra' ? ( +
    +
    +

    Row

    + +
    +
    +

    Col

    + +
    -
    -

    Col

    - -
    -
    + ) : null}

    Select type

    ); 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/shortestPathData.ts b/src/app/data/shortestPathData.ts new file mode 100644 index 0000000..56b59ea --- /dev/null +++ b/src/app/data/shortestPathData.ts @@ -0,0 +1,146 @@ +import { IGraphEdge, IGraphNode } from '../types/shortestPathProps'; + +/** + * An array of nodes representing the graph's nodes. + * Each node contains information about its position and state. + * + * @constant {IGraphNode[]} nodesData + * @type {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 const nodesData: IGraphNode[] = [ + { + id: 0, + x: 50, + y: 60, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Closer to the top-left + { + id: 1, + x: 100, + y: 20, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Top-middle-left + { + id: 2, + x: 200, + y: 20, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Top-middle-right + { + id: 3, + x: 300, + y: 20, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Top-right + { + id: 4, + x: 350, + y: 60, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Middle-right + { + id: 5, + x: 300, + y: 100, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Middle-right (above node 6) + { + id: 6, + x: 200, + y: 100, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Middle (below node 2) + { + id: 7, + x: 100, + y: 100, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Middle-left + { + id: 8, + x: 200, + y: 60, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Center +]; + +/** + * Generates an array of edges for a graph. + * 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[] => { + return [ + { source: 0, target: 1, weight: Math.floor(Math.random() * 15 + 1) }, + { source: 0, target: 7, weight: Math.floor(Math.random() * 15 + 1) }, + { source: 1, target: 2, weight: Math.floor(Math.random() * 15 + 1) }, + { source: 1, target: 7, weight: Math.floor(Math.random() * 15 + 1) }, + { source: 2, target: 3, weight: Math.floor(Math.random() * 15 + 1) }, + { source: 2, target: 8, weight: Math.floor(Math.random() * 15 + 1) }, + { source: 2, target: 5, weight: Math.floor(Math.random() * 15 + 1) }, + { source: 3, target: 4, weight: Math.floor(Math.random() * 15 + 1) }, + { source: 3, target: 5, weight: Math.floor(Math.random() * 15 + 1) }, + { source: 4, target: 5, weight: Math.floor(Math.random() * 15 + 1) }, + { source: 5, target: 6, weight: Math.floor(Math.random() * 15 + 1) }, + { source: 6, target: 7, weight: Math.floor(Math.random() * 15 + 1) }, + { source: 6, target: 8, weight: Math.floor(Math.random() * 15 + 1) }, + { source: 7, target: 8, weight: Math.floor(Math.random() * 15 + 1) }, + ]; +}; diff --git a/src/app/types/shortestPathProps.ts b/src/app/types/shortestPathProps.ts new file mode 100644 index 0000000..41e2cec --- /dev/null +++ b/src/app/types/shortestPathProps.ts @@ -0,0 +1,39 @@ +/** + * 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; +} + +/** + * 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; +} From 51866b50d14cacc73ed8084132e9b0f00bf532f6 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Mon, 23 Sep 2024 14:45:57 +0600 Subject: [PATCH 23/65] dijkstra shortest path algorithm is created --- src/app/algorithm/dijkstraShortestPath.ts | 105 ++++++++++++++++ .../components/pathFind/DijkstraComponent.tsx | 119 ++++-------------- 2 files changed, 130 insertions(+), 94 deletions(-) create mode 100644 src/app/algorithm/dijkstraShortestPath.ts diff --git a/src/app/algorithm/dijkstraShortestPath.ts b/src/app/algorithm/dijkstraShortestPath.ts new file mode 100644 index 0000000..997186e --- /dev/null +++ b/src/app/algorithm/dijkstraShortestPath.ts @@ -0,0 +1,105 @@ +import React from 'react'; +import { MinHeap } from '../data-structure/minHeap/MinHeap'; +import { Sleep } from '../lib/sleepMethod'; +import { IGraphEdge, IGraphNode } from '../types/shortestPathProps'; + +/** + * 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 => { + // 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 } : 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 = 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 } : 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 } : node))); + await Sleep(speedRange); + currentNode = predecessors[currentNode]; + } + setNodes((prev) => prev.map((node) => (node.id === source ? { ...node, isShortestPath: true } : node))); +}; diff --git a/src/app/components/pathFind/DijkstraComponent.tsx b/src/app/components/pathFind/DijkstraComponent.tsx index a845564..cfcd370 100644 --- a/src/app/components/pathFind/DijkstraComponent.tsx +++ b/src/app/components/pathFind/DijkstraComponent.tsx @@ -1,7 +1,7 @@ 'use client'; -import { MinHeap } from '@/app/data-structure/minHeap/MinHeap'; +import { findShortestPathUsingDijkstra } from '@/app/algorithm/dijkstraShortestPath'; import { generateEdges, nodesData } from '@/app/data/shortestPathData'; -import { clearAllTimeouts, Sleep } from '@/app/lib/sleepMethod'; +import { clearAllTimeouts } from '@/app/lib/sleepMethod'; import { IGraphEdge, IGraphNode } from '@/app/types/shortestPathProps'; import React, { useEffect, useState } from 'react'; @@ -12,121 +12,52 @@ interface PageProps { } const DijkstraComponent: React.FC = ({ speedRange, useRandomKey }) => { + // define component memory const [nodes, setNodes] = useState([]); const [edges, setEdges] = useState([]); const [shortestPathEdges, setShortestPathEdges] = useState([]); const [isReadyToPerformOperation, setIsReadyToPerformOperation] = useState(false); + // Trigger for component mount as well as dependency changes useEffect(() => { clearAllTimeouts(); setNodes(JSON.parse(JSON.stringify(nodesData))); setEdges(JSON.parse(JSON.stringify(generateEdges()))); 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) { - handleDijkstra({ source: 0, destination: 4 }); + handleDijkstra({ source: 0, destination: 4, nodeSizes: 10 }); setIsReadyToPerformOperation(false); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isReadyToPerformOperation, useRandomKey, edges]); /** - * Executes Dijkstra's algorithm to find the shortest path in a graph. - * The function visualizes the algorithm's steps, updating node states - * and handling asynchronous animations during processing. - * - * @async - * @function handleDijkstra - * @returns {Promise} A promise that resolves when the algorithm completes. + * 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. * - * @throws {Error} If any errors occur during the execution, they are logged 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). * - * @example - * // To use this function, ensure that nodes and edges are defined in the context: - * await handleDijkstra(); + * @returns {Promise} A promise that resolves when Dijkstra's algorithm completes. */ - const handleDijkstra = async ({ source = 0, destination = 4 }) => { + const handleDijkstra = async ({ source = 0, destination = 4, nodeSizes = 10 }): Promise => { try { - const pq = new MinHeap(); - const distances = Array(10).fill(Infinity); - const predecessors = Array(10).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 } : 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 = 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 } : 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 } : node))); - await Sleep(500); - currentNode = predecessors[currentNode]; - } - - setNodes((prev) => prev.map((node) => (node.id === source ? { ...node, isShortestPath: true } : node))); + findShortestPathUsingDijkstra(source, destination, nodeSizes, speedRange, setNodes, edges, setShortestPathEdges); } catch (error) { if (process.env.NODE_ENV === 'development') { // eslint-disable-next-line no-console @@ -138,8 +69,8 @@ const DijkstraComponent: React.FC = ({ speedRange, useRandomKey }) => return (
    - {edges?.length ? ( - + {edges?.length && nodes?.length ? ( + {/* Render Edges */} {edges.map((edge, index) => { const sourceNode = nodes.find((n) => n.id === edge.source); @@ -167,7 +98,7 @@ const DijkstraComponent: React.FC = ({ speedRange, useRandomKey }) => {/* Draw the weight */} From 01db83cc35548a7ed873d700abe48c1362f986be Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Mon, 23 Sep 2024 15:10:52 +0600 Subject: [PATCH 24/65] find sohortest path using dijkstra algorithm approach is implemented --- .../components/pathFind/DijkstraComponent.tsx | 12 ++++++-- src/app/components/pathFind/PathFind.tsx | 6 ++-- src/app/data/mockData.ts | 18 ++++++++++++ src/app/data/shortestPathData.ts | 28 +++++++++---------- src/app/types/shortestPathProps.ts | 1 + 5 files changed, 45 insertions(+), 20 deletions(-) diff --git a/src/app/components/pathFind/DijkstraComponent.tsx b/src/app/components/pathFind/DijkstraComponent.tsx index cfcd370..daa561d 100644 --- a/src/app/components/pathFind/DijkstraComponent.tsx +++ b/src/app/components/pathFind/DijkstraComponent.tsx @@ -1,8 +1,10 @@ 'use client'; import { findShortestPathUsingDijkstra } from '@/app/algorithm/dijkstraShortestPath'; +import { dijkstraColorsPlate } from '@/app/data/mockData'; import { generateEdges, nodesData } from '@/app/data/shortestPathData'; import { clearAllTimeouts } from '@/app/lib/sleepMethod'; import { IGraphEdge, IGraphNode } from '@/app/types/shortestPathProps'; +import StatusColorsPlate from '@/app/utils/StatusColorsPlate'; import React, { useEffect, useState } from 'react'; // define component Props @@ -21,7 +23,8 @@ const DijkstraComponent: React.FC = ({ speedRange, useRandomKey }) => // Trigger for component mount as well as dependency changes useEffect(() => { clearAllTimeouts(); - setNodes(JSON.parse(JSON.stringify(nodesData))); + const tempNodes = JSON.parse(JSON.stringify(nodesData)); + setNodes(tempNodes); setEdges(JSON.parse(JSON.stringify(generateEdges()))); setShortestPathEdges([]); setIsReadyToPerformOperation(true); @@ -68,6 +71,9 @@ const DijkstraComponent: React.FC = ({ speedRange, useRandomKey }) => return (
    +
    + +
    {edges?.length && nodes?.length ? ( @@ -97,8 +103,8 @@ const DijkstraComponent: React.FC = ({ speedRange, useRandomKey }) => /> {/* Draw the weight */} diff --git a/src/app/components/pathFind/PathFind.tsx b/src/app/components/pathFind/PathFind.tsx index 470866b..c3cde0a 100644 --- a/src/app/components/pathFind/PathFind.tsx +++ b/src/app/components/pathFind/PathFind.tsx @@ -125,15 +125,15 @@ const PathFind = () => { value={buttonType} className='text-md cursor-pointer rounded-sm border-[1px] border-theme-primary px-[5px] py-[4px] outline-none transition-all duration-200 hover:border-theme-btn-secondary max-[410px]:w-[60%]' > + -
    {edges?.length && nodes?.length ? ( - + {/* Render Edges */} {edges.map((edge, index) => { const sourceNode = nodes.find((n) => n.id === edge.source); @@ -122,8 +160,20 @@ const DijkstraComponent: React.FC = ({ speedRange, useRandomKey }) => cx={node.x} cy={node.y} r='8' - fill={node.isShortestPath ? 'green' : node.isCurrentNode ? 'blue' : 'white'} - stroke={node.isShortestPath || node.isCurrentNode ? 'white' : 'black'} + fill={ + node.isSource + ? '#fb7c06' + : node.isDestination + ? '#9458ff' + : node.isShortestPath + ? 'green' + : node.isCurrentNode + ? 'blue' + : 'white' + } + stroke={ + node.isShortestPath || node.isCurrentNode || node.isDestination || node.isSource ? 'white' : 'black' + } strokeWidth={'0.5'} /> {/* Node Label */} @@ -133,7 +183,9 @@ const DijkstraComponent: React.FC = ({ speedRange, useRandomKey }) => textAnchor='middle' dy='3' fontSize='7' - fill={node.isCurrentNode || node.isShortestPath ? 'white' : 'black'} + fill={ + node.isDestination || node.isSource || node.isCurrentNode || node.isShortestPath ? 'white' : 'black' + } > {node.id} diff --git a/src/app/data/mockData.ts b/src/app/data/mockData.ts index e2b38ea..b45c803 100644 --- a/src/app/data/mockData.ts +++ b/src/app/data/mockData.ts @@ -302,4 +302,14 @@ export const dijkstraColorsPlate: StatusColorsDataProps[] = [ 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]', + }, ]; diff --git a/src/app/data/shortestPathData.ts b/src/app/data/shortestPathData.ts index 332a449..46132d0 100644 --- a/src/app/data/shortestPathData.ts +++ b/src/app/data/shortestPathData.ts @@ -1,146 +1,339 @@ -import { IGraphEdge, IGraphNode } from '../types/shortestPathProps'; +import { generateRandomWeight } from '../lib/helpers'; +import { IGraphEdge } from '../types/shortestPathProps'; /** - * An array of nodes representing the graph's nodes. - * Each node contains information about its position and state. + * Generates an array of edges for a graph-1. + * Each edge connects two nodes and has a randomly assigned weight. * - * @constant {IGraphNode[]} nodesData - * @type {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. + * @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 nodesData: IGraphNode[] = [ - { - id: 0, - x: 50, - y: 60, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Closer to the top-left - { - id: 1, - x: 100, - y: 20, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Top-middle-left - { - id: 2, - x: 200, - y: 20, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Top-middle-right - { - id: 3, - x: 300, - y: 20, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Top-right - { - id: 4, - x: 350, - y: 60, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Middle-right - { - id: 5, - x: 300, - y: 100, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Middle-right (above node 6) - { - id: 6, - x: 200, - y: 100, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Middle (below node 2) - { - id: 7, - x: 100, - y: 100, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Middle-left - { - id: 8, - x: 200, - y: 60, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Center -]; + +export const generateEdges = (): IGraphEdge[] => { + return [ + { 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(), 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(), weightPosition: { x: -1, y: -1 } }, + { source: 3, target: 5, weight: generateRandomWeight(), weightPosition: { x: -1, y: -1 } }, + { source: 4, target: 5, weight: generateRandomWeight(), weightPosition: { x: -1, y: 85 } }, + { source: 5, target: 6, weight: generateRandomWeight(), weightPosition: { x: -1, y: 108 } }, + { source: 6, target: 7, weight: generateRandomWeight(), 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 } }, + ]; +}; /** - * Generates an array of edges for a graph. + * Generates an array of edges for a graph-2. * Each edge connects two nodes and has a randomly assigned weight. * - * @function generateEdges + * @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 generateEdges = (): IGraphEdge[] => { +export const generateEdgesForASearch = (): IGraphEdge[] => { return [ - { source: 0, target: 1, weight: Math.floor(Math.random() * 15 + 1), weightPosition: { x: -1, y: 45 } }, - { source: 0, target: 7, weight: Math.floor(Math.random() * 15 + 1), weightPosition: { x: -1, y: -1 } }, - { source: 1, target: 2, weight: Math.floor(Math.random() * 15 + 1), weightPosition: { x: -1, y: 18 } }, - { source: 1, target: 7, weight: Math.floor(Math.random() * 15 + 1), weightPosition: { x: -1, y: -1 } }, - { source: 2, target: 3, weight: Math.floor(Math.random() * 15 + 1), weightPosition: { x: -1, y: 18 } }, - { source: 2, target: 8, weight: Math.floor(Math.random() * 15 + 1), weightPosition: { x: -1, y: 45 } }, - { source: 2, target: 5, weight: Math.floor(Math.random() * 15 + 1), weightPosition: { x: -1, y: -1 } }, - { source: 3, target: 4, weight: Math.floor(Math.random() * 15 + 1), weightPosition: { x: -1, y: -1 } }, - { source: 3, target: 5, weight: Math.floor(Math.random() * 15 + 1), weightPosition: { x: -1, y: -1 } }, - { source: 4, target: 5, weight: Math.floor(Math.random() * 15 + 1), weightPosition: { x: -1, y: 85 } }, - { source: 5, target: 6, weight: Math.floor(Math.random() * 15 + 1), weightPosition: { x: -1, y: 108 } }, - { source: 6, target: 7, weight: Math.floor(Math.random() * 15 + 1), weightPosition: { x: -1, y: 108 } }, - { source: 6, target: 8, weight: Math.floor(Math.random() * 15 + 1), weightPosition: { x: -1, y: -1 } }, - { source: 7, target: 8, weight: Math.floor(Math.random() * 15 + 1), weightPosition: { x: -1, y: 75 } }, + { 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: 15, 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: 15, weight: generateRandomWeight(), weightPosition: { x: -1, y: -1 } }, + { source: 6, target: 11, weight: generateRandomWeight(), weightPosition: { x: -1, y: -1 } }, + { source: 16, target: 11, weight: generateRandomWeight(), weightPosition: { x: -1, y: 58 } }, ]; }; + +/** + * Root data of graph data + */ +export const graphData = [ + { + nodes: [ + { + id: 0, + x: 50, + y: 60, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Closer to the top-left + { + id: 1, + x: 100, + y: 20, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Top-middle-left + { + id: 2, + x: 200, + y: 20, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Top-middle-right + { + id: 3, + x: 300, + y: 20, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Top-right + { + id: 4, + x: 350, + y: 60, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Middle-right + { + id: 5, + x: 300, + y: 100, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Middle-right (above node 6) + { + id: 6, + x: 200, + y: 100, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Middle (below node 2) + { + id: 7, + x: 100, + y: 100, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Middle-left + { + id: 8, + x: 200, + y: 60, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Center + ], + edges: [...generateEdges()], + source: 0, + destination: 4, + nodeSizes: 10, + }, + { + nodes: [ + { + id: 0, + x: 50, + y: 60, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Closer to the top-left + { + id: 1, + x: 100, + y: 20, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Top-middle-left + { + id: 2, + x: 100, + y: 60, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Top-middle-right + { + id: 3, + x: 300, + y: 20, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Top-right + { + id: 4, + x: 350, + y: 60, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, + { + id: 5, + x: 300, + y: 100, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Middle-right (above node 6) + { + id: 6, + x: 200, + y: 150, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, // Middle (below node 2) + { + id: 7, + x: 70, + y: 100, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, + { + id: 8, + x: 200, + y: 20, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, + { + id: 9, + x: 130, + y: 100, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, + { + id: 10, + x: 100, + y: 150, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, + { + id: 11, + x: 250, + y: 60, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, + { + id: 15, + x: 350, + y: 150, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, + { + id: 16, + x: 180, + y: 60, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }, + ], + edges: [...generateEdgesForASearch()], + source: 0, + destination: 16, + nodeSizes: 17, + }, +]; diff --git a/src/app/lib/helpers.ts b/src/app/lib/helpers.ts index 681568f..f979c74 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 = 15): 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 +}; From 98a7d994bce4d6ae02173b4d0b9fe1af594cedcc Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Thu, 26 Sep 2024 10:48:59 +0600 Subject: [PATCH 27/65] re-visualized features is integrated into dijkstra shortest path --- .../components/pathFind/DijkstraComponent.tsx | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/src/app/components/pathFind/DijkstraComponent.tsx b/src/app/components/pathFind/DijkstraComponent.tsx index ddeaacb..736bf84 100644 --- a/src/app/components/pathFind/DijkstraComponent.tsx +++ b/src/app/components/pathFind/DijkstraComponent.tsx @@ -27,6 +27,7 @@ const DijkstraComponent: React.FC = ({ speedRange, useRandomKey }) => destination: -1, nodeSizes: -1, }); + const [btnLoading, setBtnLoading] = useState(false); // Trigger for component mount as well as dependency changes useEffect(() => { @@ -98,20 +99,65 @@ const DijkstraComponent: React.FC = ({ speedRange, useRandomKey }) => */ const handleDijkstra = async ({ source = 0, destination = 4, nodeSizes = 10 }): Promise => { try { - findShortestPathUsingDijkstra(source, destination, nodeSizes, speedRange, setNodes, edges, setShortestPathEdges); + 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); } }; + const handleReVisualized = async () => { + const { source, destination, nodeSizes } = initialNodes; + // initialized all at initial state + setNodes((prv) => { + return prv.map((i) => { + return { + ...i, + isVisited: false, + isCurrentNode: false, + isShortestPath: false, + isInvalidPath: false, + isDestination: false, + isSource: false, + }; + }); + }); + setShortestPathEdges([]); + await handleDijkstra({ source, destination, nodeSizes }); + }; + return (
    -
    + {/*
    +
    */} + +
    +
    + +
    +
    +
    {edges?.length && nodes?.length ? ( From 8deaa03a6b98d3316aff8e551d4b45f5227463a1 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Thu, 26 Sep 2024 10:54:04 +0600 Subject: [PATCH 28/65] updated changelog.mod --- CHANGELOG.md | 16 ++++++++++++++++ package.json | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 910bdb1..d4ca128 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +# Changelog + +## 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/package.json b/package.json index d2a6b0b..4f30b12 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "algorithm-visualizer", - "version": "1.0.0", + "version": "1.1.0", "private": true, "scripts": { "dev": "next dev", From 1119cdfe1363ffe3f84f15c13ce71be518c37bed Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Thu, 26 Sep 2024 15:05:01 +0600 Subject: [PATCH 29/65] home component card section is created --- src/app/components/home/HomeComponent.tsx | 125 +++++++--------------- src/app/data/dashboardSchemaData.ts | 106 ++++++++++++++++++ src/app/types/commonProps.ts | 28 +++++ 3 files changed, 174 insertions(+), 85 deletions(-) create mode 100644 src/app/data/dashboardSchemaData.ts diff --git a/src/app/components/home/HomeComponent.tsx b/src/app/components/home/HomeComponent.tsx index edce8d7..803d29b 100644 --- a/src/app/components/home/HomeComponent.tsx +++ b/src/app/components/home/HomeComponent.tsx @@ -1,98 +1,53 @@ +/* eslint-disable indent */ 'use client'; -import React, { ReactNode } from 'react'; +import { projectsData } from '@/app/data/dashboardSchemaData'; +import { ProjectSchema } from '@/app/types/commonProps'; import Link from 'next/link'; +import { ReactNode } from 'react'; -const TreeIcon = ( - - - -); - -const SortIcon = ( - - - -); - -const PathFindingIcon = ( - - - -); - -const SearchIcon = ( - - - -); - -const algorithmCategories = [ - { - title: 'Tree Algorithms', - description: 'Explore tree traversal algorithms like BFS and DFS.', - link: '/tree', - icon: TreeIcon, - }, - { - title: 'Sorting Algorithms', - description: 'Visualize sorting algorithms like Merge, Quick, Heap, etc.', - link: '/sorting', - icon: SortIcon, - }, - { - title: 'Path-Finding Algorithms', - description: 'Discover path-finding algorithms like A*, Dijkstra.', - link: '/path-finding', - icon: PathFindingIcon, - }, - { - title: 'Searching Algorithms', - description: 'Search algorithms including Binary Search, Linear Search.', - link: '/searching', - icon: SearchIcon, - }, -]; - -const HomeComponent = () => { +const HomeComponent = (): ReactNode => { return (
    -
    -
    -
    - {algorithmCategories.map((category, index) => ( - - ))} +
    + {projectsData ? ( +
    + {projectsData.map((item: ProjectSchema) => { + return ( + +
    +
    +

    {item.name}

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

    + {i.name} +

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

    Loading...

    -
    + )}
    ); }; export default HomeComponent; - -interface AlgorithmCardProps { - title: string; - description: string; - link: string; - icon: ReactNode; -} - -const AlgorithmCard: React.FC = ({ title, description, link, icon }) => { - return ( - -
    -
    {icon}
    -

    {title}

    -

    {description}

    -
    - - ); -}; diff --git a/src/app/data/dashboardSchemaData.ts b/src/app/data/dashboardSchemaData.ts new file mode 100644 index 0000000..d9c7d7c --- /dev/null +++ b/src/app/data/dashboardSchemaData.ts @@ -0,0 +1,106 @@ +/* + * A dummy json data for home component. + */ + +import { uid } from '../lib/uidGenerator'; +import { ProjectSchema } from '../types/commonProps'; + +export const projectsData: ProjectSchema[] = [ + { + id: uid(), + name: 'Tree', + navigate: '/tree', + lists: [ + { + id: uid(), + name: 'DFS (in-order, pre-order, post-order)', + }, + { + id: uid(), + name: 'BFS', + }, + ], + }, + { + id: uid(), + name: 'N Queens', + lists: [], + navigate: '/n-queens', + }, + { + id: uid(), + name: 'Sorting', + lists: [ + { + 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', + lists: [ + { + id: uid(), + name: 'Dijkstra', + }, + { + id: uid(), + name: 'Rate in maze', + }, + { + id: uid(), + name: 'No of Island', + }, + ], + navigate: '/path-finding', + }, + { + id: uid(), + name: 'Searching', + lists: [ + { + id: uid(), + name: 'Binary Search', + }, + ], + navigate: '/searching', + }, + { + id: uid(), + name: 'Linked List', + lists: [ + { + id: uid(), + name: 'Reverse list', + }, + { + id: uid(), + name: 'Merge two list', + }, + { + id: uid(), + name: 'Basic (CRUD)', + }, + ], + navigate: '/linked-list', + }, +]; diff --git a/src/app/types/commonProps.ts b/src/app/types/commonProps.ts index 3130f01..9946c73 100644 --- a/src/app/types/commonProps.ts +++ b/src/app/types/commonProps.ts @@ -29,3 +29,31 @@ export interface ChessBoardGridInlineStyleProps { 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; + lists: ListProps[]; + navigate: string; +} From 9ba9ea1fcb30f32c3728b94d5edd00fd4d6f75a0 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Thu, 26 Sep 2024 15:09:24 +0600 Subject: [PATCH 30/65] change log updated --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4ca128..3df33fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.2.0 (Sep 26, 2024) + +- Home component hero section is integrated. + ## 1.1.0 (Sep 26, 2024) ### New Features: diff --git a/package.json b/package.json index 4f30b12..e3b7747 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "algorithm-visualizer", - "version": "1.1.0", + "version": "1.2.0", "private": true, "scripts": { "dev": "next dev", From b1855e66a628502f8646f62ef97c58cf1760215d Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Thu, 26 Sep 2024 15:48:31 +0600 Subject: [PATCH 31/65] removed home page visibility functionality for users --- src/middleware.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/middleware.ts b/src/middleware.ts index e905636..e4ee6d1 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -8,12 +8,8 @@ import type { NextRequest } from 'next/server'; * @param {NextRequest} request * @returns {*} */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars export function middleware(request: NextRequest) { - // prevent to visit home component - if (process.env.NODE_ENV !== 'development' && request.nextUrl.pathname === '/') { - return NextResponse.redirect(new URL('/tree', request.url)); - } - // redirect with other urls return NextResponse.next(); } From 6676232d980d8a1617c1eb9c770af4868ef28554 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Thu, 26 Sep 2024 16:30:47 +0600 Subject: [PATCH 32/65] a small word mistake is corrected --- src/app/data/dashboardSchemaData.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/data/dashboardSchemaData.ts b/src/app/data/dashboardSchemaData.ts index d9c7d7c..20009d9 100644 --- a/src/app/data/dashboardSchemaData.ts +++ b/src/app/data/dashboardSchemaData.ts @@ -64,7 +64,7 @@ export const projectsData: ProjectSchema[] = [ }, { id: uid(), - name: 'Rate in maze', + name: 'Rat in maze', }, { id: uid(), From 590fc549d54007e7806d2e8ae34a8d69f871b9a6 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Sun, 29 Sep 2024 14:33:47 +0600 Subject: [PATCH 33/65] move dijkstra into shortest path --- .../algorithm/{ => shortest-path}/dijkstraShortestPath.ts | 8 ++++---- src/app/components/pathFind/DijkstraComponent.tsx | 2 +- src/app/lib/helpers.ts | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) rename src/app/algorithm/{ => shortest-path}/dijkstraShortestPath.ts (94%) diff --git a/src/app/algorithm/dijkstraShortestPath.ts b/src/app/algorithm/shortest-path/dijkstraShortestPath.ts similarity index 94% rename from src/app/algorithm/dijkstraShortestPath.ts rename to src/app/algorithm/shortest-path/dijkstraShortestPath.ts index 6c30a4a..fd710e6 100644 --- a/src/app/algorithm/dijkstraShortestPath.ts +++ b/src/app/algorithm/shortest-path/dijkstraShortestPath.ts @@ -1,7 +1,7 @@ import React from 'react'; -import { MinHeap } from '../data-structure/minHeap/MinHeap'; -import { Sleep } from '../lib/sleepMethod'; -import { IGraphEdge, IGraphNode } from '../types/shortestPathProps'; +import { MinHeap } from '../../data-structure/minHeap/MinHeap'; +import { Sleep } from '../../lib/sleepMethod'; +import { IGraphEdge, IGraphNode } from '../../types/shortestPathProps'; /** * Finds the shortest path between a source and a destination node using Dijkstra's algorithm. @@ -56,7 +56,7 @@ export const findShortestPathUsingDijkstra = async ( // 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 = distances[currentNodeId] + edge.weight; + const newDistance = Math.round(distances[currentNodeId] + edge.weight); // If a shorter path to the target node is found if (newDistance < distances[targetNodeId]) { diff --git a/src/app/components/pathFind/DijkstraComponent.tsx b/src/app/components/pathFind/DijkstraComponent.tsx index 736bf84..d371aac 100644 --- a/src/app/components/pathFind/DijkstraComponent.tsx +++ b/src/app/components/pathFind/DijkstraComponent.tsx @@ -1,6 +1,6 @@ /* eslint-disable indent */ 'use client'; -import { findShortestPathUsingDijkstra } from '@/app/algorithm/dijkstraShortestPath'; +import { findShortestPathUsingDijkstra } from '@/app/algorithm/shortest-path/dijkstraShortestPath'; import { dijkstraColorsPlate } from '@/app/data/mockData'; import { generateEdges, generateEdgesForASearch, graphData } from '@/app/data/shortestPathData'; import { clearAllTimeouts } from '@/app/lib/sleepMethod'; diff --git a/src/app/lib/helpers.ts b/src/app/lib/helpers.ts index f979c74..139c20b 100644 --- a/src/app/lib/helpers.ts +++ b/src/app/lib/helpers.ts @@ -52,7 +52,7 @@ export const gridRowColSize = (maxSize: number = 12) => { * * @returns {number} */ -export const generateRandomWeight = (maxNum: number = 15): 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 }; From d816d6a7c7bb0f3f37836d52890983669ea3fdd2 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Mon, 30 Sep 2024 11:36:42 +0600 Subject: [PATCH 34/65] A linear search approach is integrated with necessary functionality --- src/app/algorithm/searching/linearSearch.ts | 59 ++++++ .../searching/LinearSearchComponent.tsx | 173 ++++++++++++++++++ .../searching/SearchingComponent.tsx | 11 +- src/app/types/commonProps.ts | 15 ++ 4 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 src/app/algorithm/searching/linearSearch.ts create mode 100644 src/app/components/searching/LinearSearchComponent.tsx 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/components/searching/LinearSearchComponent.tsx b/src/app/components/searching/LinearSearchComponent.tsx new file mode 100644 index 0000000..6e11eef --- /dev/null +++ b/src/app/components/searching/LinearSearchComponent.tsx @@ -0,0 +1,173 @@ +'use client'; + +import { LinearSearchToFindTheTarget } from '@/app/algorithm/searching/linearSearch'; +import { getRandomObject } from '@/app/lib/helpers'; +import { clearAllTimeouts, Sleep } from '@/app/lib/sleepMethod'; +import { LinearSearchDataProps } from '@/app/types/commonProps'; +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 [btnLoading, setButtonLoading] = useState(false); + 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 { + // Set the button to loading state to disable further interactions + setButtonLoading(true); + + // 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); + } + } finally { + // Reset the button loading state after search completion or error + setButtonLoading(false); + } + }; + + 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..1da62b5 100644 --- a/src/app/components/searching/SearchingComponent.tsx +++ b/src/app/components/searching/SearchingComponent.tsx @@ -8,10 +8,11 @@ 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 - const [activeRootBtnType, setActiveRootBtnType] = useState('bst'); + const [activeRootBtnType, setActiveRootBtnType] = useState('linear-search'); const [randomKey, setRandomKey] = useState('1'); const [speedRange, setSpeedRange] = useState(200); @@ -47,7 +48,7 @@ const SearchingComponent = () => { }; return ( -
    +
    @@ -72,6 +73,9 @@ const SearchingComponent = () => { +
    ); diff --git a/src/app/types/commonProps.ts b/src/app/types/commonProps.ts index 9946c73..094b81e 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. * @@ -57,3 +59,16 @@ export interface ProjectSchema { lists: 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 +} From a7ffb294a547c72d6317d06e7680ecfafe3f0293 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Mon, 30 Sep 2024 11:47:42 +0600 Subject: [PATCH 35/65] moved binary search withing folder, update liner search colors plate --- .../algorithm/{ => searching}/binarySearch.ts | 4 +- .../searching/BinarySearchTreeComponent.tsx | 2 +- .../searching/LinearSearchComponent.tsx | 54 +++++++++---------- src/app/data/mockData.ts | 18 +++++++ 4 files changed, 47 insertions(+), 31 deletions(-) rename src/app/algorithm/{ => searching}/binarySearch.ts (97%) 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/components/searching/BinarySearchTreeComponent.tsx b/src/app/components/searching/BinarySearchTreeComponent.tsx index 931dfeb..4b81645 100644 --- a/src/app/components/searching/BinarySearchTreeComponent.tsx +++ b/src/app/components/searching/BinarySearchTreeComponent.tsx @@ -6,9 +6,9 @@ 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 { diff --git a/src/app/components/searching/LinearSearchComponent.tsx b/src/app/components/searching/LinearSearchComponent.tsx index 6e11eef..9dd650b 100644 --- a/src/app/components/searching/LinearSearchComponent.tsx +++ b/src/app/components/searching/LinearSearchComponent.tsx @@ -1,9 +1,11 @@ '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 @@ -14,7 +16,6 @@ interface PageProps { const LinearSearchComponent: React.FC = ({ speedRange, ueeRandomKey }) => { const [data, setData] = useState([]); - const [btnLoading, setButtonLoading] = useState(false); const [searchItem, setSearchItem] = useState(0); const [steps, setSteps] = useState(0); const [isReadyToPerformOperation, setIsReadyToPerformOperation] = useState(false); @@ -86,9 +87,6 @@ const LinearSearchComponent: React.FC = ({ speedRange, ueeRandomKey } */ const handleLinearSearch = async (): Promise => { try { - // Set the button to loading state to disable further interactions - setButtonLoading(true); - // Reset the array to its initial state and get the reset data const resetData = await resetAllAtInitialState(); @@ -111,36 +109,36 @@ const LinearSearchComponent: React.FC = ({ speedRange, ueeRandomKey } // eslint-disable-next-line no-console console.error(error); } - } finally { - // Reset the button loading state after search completion or error - setButtonLoading(false); } }; return ( <>
    -
    - -
    - - +
    +
    + +
    + + +
    +
    +
    +
    diff --git a/src/app/data/mockData.ts b/src/app/data/mockData.ts index b45c803..03bc001 100644 --- a/src/app/data/mockData.ts +++ b/src/app/data/mockData.ts @@ -313,3 +313,21 @@ export const dijkstraColorsPlate: StatusColorsDataProps[] = [ 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', + }, +]; From 03ccad50f60bd39a696084abdf5b1461ea059339 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Mon, 30 Sep 2024 12:01:01 +0600 Subject: [PATCH 36/65] moved all sorting algorithm within it's folder --- src/app/algorithm/{ => sorting}/bubbleSort.ts | 4 ++-- src/app/algorithm/{ => sorting}/heapSort.ts | 6 +++--- src/app/algorithm/{ => sorting}/mergeSort.ts | 5 ++--- src/app/algorithm/{ => sorting}/quickSort.ts | 4 ++-- src/app/algorithm/{ => sorting}/selectionSort.ts | 4 ++-- src/app/components/sorting/BubbleSortComponent.tsx | 2 +- src/app/components/sorting/HeapSortComponent.tsx | 2 +- src/app/components/sorting/MergeSortComponent.tsx | 2 +- src/app/components/sorting/QuickSortComponent.tsx | 2 +- src/app/components/sorting/SelectionSortComponent.tsx | 2 +- 10 files changed, 16 insertions(+), 17 deletions(-) rename src/app/algorithm/{ => sorting}/bubbleSort.ts (94%) rename src/app/algorithm/{ => sorting}/heapSort.ts (96%) rename src/app/algorithm/{ => sorting}/mergeSort.ts (97%) rename src/app/algorithm/{ => sorting}/quickSort.ts (97%) rename src/app/algorithm/{ => sorting}/selectionSort.ts (96%) 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/components/sorting/BubbleSortComponent.tsx b/src/app/components/sorting/BubbleSortComponent.tsx index a02b1c3..2205bf7 100644 --- a/src/app/components/sorting/BubbleSortComponent.tsx +++ b/src/app/components/sorting/BubbleSortComponent.tsx @@ -1,4 +1,4 @@ -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'; diff --git a/src/app/components/sorting/HeapSortComponent.tsx b/src/app/components/sorting/HeapSortComponent.tsx index fdd42d9..ba6cfc2 100644 --- a/src/app/components/sorting/HeapSortComponent.tsx +++ b/src/app/components/sorting/HeapSortComponent.tsx @@ -1,5 +1,5 @@ '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'; diff --git a/src/app/components/sorting/MergeSortComponent.tsx b/src/app/components/sorting/MergeSortComponent.tsx index f6bcd2a..69c13a3 100644 --- a/src/app/components/sorting/MergeSortComponent.tsx +++ b/src/app/components/sorting/MergeSortComponent.tsx @@ -1,6 +1,6 @@ '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'; diff --git a/src/app/components/sorting/QuickSortComponent.tsx b/src/app/components/sorting/QuickSortComponent.tsx index 3644e88..34f8f29 100644 --- a/src/app/components/sorting/QuickSortComponent.tsx +++ b/src/app/components/sorting/QuickSortComponent.tsx @@ -1,4 +1,4 @@ -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'; diff --git a/src/app/components/sorting/SelectionSortComponent.tsx b/src/app/components/sorting/SelectionSortComponent.tsx index 6c6c649..d21c080 100644 --- a/src/app/components/sorting/SelectionSortComponent.tsx +++ b/src/app/components/sorting/SelectionSortComponent.tsx @@ -1,4 +1,4 @@ -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'; From 96df54bf85dcca71d442cf298fecdb12013d6a74 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Mon, 30 Sep 2024 12:07:20 +0600 Subject: [PATCH 37/65] move path-finding algo withing it's folder --- .../algorithm/{ => path-finding}/noOfValidIslands.ts | 10 +++++----- src/app/algorithm/{ => path-finding}/uniquePath.ts | 11 +++++------ src/app/components/pathFind/NoOfIslands.tsx | 2 +- src/app/components/pathFind/UniquePath.tsx | 2 +- 4 files changed, 12 insertions(+), 13 deletions(-) rename src/app/algorithm/{ => path-finding}/noOfValidIslands.ts (94%) rename src/app/algorithm/{ => path-finding}/uniquePath.ts (95%) diff --git a/src/app/algorithm/noOfValidIslands.ts b/src/app/algorithm/path-finding/noOfValidIslands.ts similarity index 94% rename from src/app/algorithm/noOfValidIslands.ts rename to src/app/algorithm/path-finding/noOfValidIslands.ts index 1a8709a..7d03415 100644 --- a/src/app/algorithm/noOfValidIslands.ts +++ b/src/app/algorithm/path-finding/noOfValidIslands.ts @@ -1,9 +1,9 @@ +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'; -import { islandColorsPlate } from '../data/mockData'; let counter = 0; 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/components/pathFind/NoOfIslands.tsx b/src/app/components/pathFind/NoOfIslands.tsx index 92995b2..aff1d71 100644 --- a/src/app/components/pathFind/NoOfIslands.tsx +++ b/src/app/components/pathFind/NoOfIslands.tsx @@ -1,6 +1,6 @@ 'use client'; -import { findNoOfIslands } from '@/app/algorithm/noOfValidIslands'; +import { findNoOfIslands } from '@/app/algorithm/path-finding/noOfValidIslands'; import { UNIQUE_PATH_GRID_SIZE } from '@/app/constant'; import { noOfIslandsSortColorsData } from '@/app/data/mockData'; import { createGridWithUniquePath } from '@/app/data/PathFindingGridData'; diff --git a/src/app/components/pathFind/UniquePath.tsx b/src/app/components/pathFind/UniquePath.tsx index 483d44b..fe10ab7 100644 --- a/src/app/components/pathFind/UniquePath.tsx +++ b/src/app/components/pathFind/UniquePath.tsx @@ -1,6 +1,6 @@ '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'; From 035117d87057c572bbabc0193593850cbe3ee15a Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Mon, 30 Sep 2024 14:28:56 +0600 Subject: [PATCH 38/65] gitup header icon, did some improvement --- src/app/components/layouts/header/HeaderComponent.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/components/layouts/header/HeaderComponent.tsx b/src/app/components/layouts/header/HeaderComponent.tsx index 1842fa9..1abb0cf 100644 --- a/src/app/components/layouts/header/HeaderComponent.tsx +++ b/src/app/components/layouts/header/HeaderComponent.tsx @@ -137,11 +137,11 @@ const HeaderComponent = () => { -
  • +
  • { viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg' - className='h-[95%] w-[95%]' + className='h-[100%] w-[100%] lg:h-[93%] lg:w-[93%]' > Date: Mon, 30 Sep 2024 15:19:37 +0600 Subject: [PATCH 39/65] binary search component , visualization functionality is implemetned, enhanced some UI --- .../searching/BinarySearchTreeComponent.tsx | 73 +++++++++++++++++-- .../searching/SearchingComponent.tsx | 2 +- 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/src/app/components/searching/BinarySearchTreeComponent.tsx b/src/app/components/searching/BinarySearchTreeComponent.tsx index 4b81645..6ae80e9 100644 --- a/src/app/components/searching/BinarySearchTreeComponent.tsx +++ b/src/app/components/searching/BinarySearchTreeComponent.tsx @@ -22,6 +22,7 @@ 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(() => { @@ -54,18 +55,78 @@ const BinarySearchTreeComponent: React.FC = ({ speedRange }) => { // 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} +

    +
    +
    <> diff --git a/src/app/components/searching/SearchingComponent.tsx b/src/app/components/searching/SearchingComponent.tsx index 1da62b5..f3901c0 100644 --- a/src/app/components/searching/SearchingComponent.tsx +++ b/src/app/components/searching/SearchingComponent.tsx @@ -12,7 +12,7 @@ import LinearSearchComponent from './LinearSearchComponent'; const SearchingComponent = () => { // define a component local memory - const [activeRootBtnType, setActiveRootBtnType] = useState('linear-search'); + const [activeRootBtnType, setActiveRootBtnType] = useState('bst'); const [randomKey, setRandomKey] = useState('1'); const [speedRange, setSpeedRange] = useState(200); From fd92e813cd90d7da56c10d12c3d1d7385c775805 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Mon, 30 Sep 2024 16:12:03 +0600 Subject: [PATCH 40/65] updated logo of header component --- .../layouts/header/HeaderComponent.tsx | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/app/components/layouts/header/HeaderComponent.tsx b/src/app/components/layouts/header/HeaderComponent.tsx index 1abb0cf..82b6fa1 100644 --- a/src/app/components/layouts/header/HeaderComponent.tsx +++ b/src/app/components/layouts/header/HeaderComponent.tsx @@ -51,7 +51,32 @@ const HeaderComponent = () => { // className='bg-custom-gradient bg-clip-text pr-4 text-4xl font-bold tracking-wider text-transparent md:pr-0' className='m-0 flex items-center p-0 pr-4 text-center text-3xl font-semibold tracking-wider text-theme-secondary sm:text-4xl md:pr-0 dark:text-white' > - HOME + + + + + + +
      Date: Mon, 30 Sep 2024 16:58:27 +0600 Subject: [PATCH 41/65] logo animation is added --- .../layouts/header/HeaderComponent.tsx | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/app/components/layouts/header/HeaderComponent.tsx b/src/app/components/layouts/header/HeaderComponent.tsx index 82b6fa1..14e5195 100644 --- a/src/app/components/layouts/header/HeaderComponent.tsx +++ b/src/app/components/layouts/header/HeaderComponent.tsx @@ -48,7 +48,6 @@ const HeaderComponent = () => {
      { d='M29.9998 24.4233C29.9998 25.1469 29.4502 25.7312 28.7738 25.7312L16.5182 29.6527C15.8418 29.6527 15.2939 29.0685 15.2939 28.3449V15.2743C15.2939 14.5508 15.8418 13.9665 16.5182 13.9665L28.7738 10.0449C29.4502 10.0449 29.9998 10.6293 29.9998 11.3527V24.4233Z' fill='#42446E' className='transition-colors duration-300 ease-in-out group-hover:fill-[#4db5ff]' - /> + > + + + > + + + > + + From 7613b3ef3d07a06e1c928c75bb57c69dc2d675a1 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Tue, 1 Oct 2024 10:43:43 +0600 Subject: [PATCH 42/65] graph source, destination logic issue updated --- .../shortest-path/dijkstraShortestPath.ts | 10 ++++++++- .../layouts/header/HeaderComponent.tsx | 21 +++---------------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/app/algorithm/shortest-path/dijkstraShortestPath.ts b/src/app/algorithm/shortest-path/dijkstraShortestPath.ts index fd710e6..cdb17e6 100644 --- a/src/app/algorithm/shortest-path/dijkstraShortestPath.ts +++ b/src/app/algorithm/shortest-path/dijkstraShortestPath.ts @@ -36,7 +36,15 @@ export const findShortestPathUsingDijkstra = async ( distances[source] = 0; // Visualize the current node being processed - setNodes((prev) => prev.map((node) => (node.id === source ? { ...node, isCurrentNode: true } : node))); + setNodes((prev) => + prev.map((node) => + node.id === source + ? { ...node, isCurrentNode: true, isSource: true } + : node.id === destination + ? { ...node, isDestination: true } + : node + ) + ); // Animation delay await Sleep(speedRange); // Reset isCurrentNode after processing diff --git a/src/app/components/layouts/header/HeaderComponent.tsx b/src/app/components/layouts/header/HeaderComponent.tsx index 14e5195..d309aeb 100644 --- a/src/app/components/layouts/header/HeaderComponent.tsx +++ b/src/app/components/layouts/header/HeaderComponent.tsx @@ -64,34 +64,19 @@ const HeaderComponent = () => { fill='#42446E' className='transition-colors duration-300 ease-in-out group-hover:fill-[#4db5ff]' > - + - + - + From 99cbd554a9528471074b5cf3654258127474a95e Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Tue, 1 Oct 2024 17:36:20 +0600 Subject: [PATCH 43/65] a linkedlist cycle graph is created --- next.config.mjs | 4 +- .../components/linked-list/DetectCycle.tsx | 227 ++++++++++++++++++ .../linked-list/LinkedListComponent.tsx | 8 +- .../data-structure/LinkedList/LinkedList.ts | 41 +++- 4 files changed, 276 insertions(+), 4 deletions(-) create mode 100644 src/app/components/linked-list/DetectCycle.tsx diff --git a/next.config.mjs b/next.config.mjs index 4678774..d9abcf0 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,4 +1,6 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + reactStrictMode: false, // Disable Strict Mode +}; export default nextConfig; diff --git a/src/app/components/linked-list/DetectCycle.tsx b/src/app/components/linked-list/DetectCycle.tsx new file mode 100644 index 0000000..cc5d8fb --- /dev/null +++ b/src/app/components/linked-list/DetectCycle.tsx @@ -0,0 +1,227 @@ +'use client'; + +import { LinkedList } from '@/app/data-structure/LinkedList/LinkedList'; +import { mergeTwoListColorsPlate } 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'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const DetectCycle: React.FC = ({ speedRange, updateComponentWithKey }) => { + // define component local state + const [lists, setLists] = useState(); + const [rootLists, setRootLists] = useState(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [btnLoading, setBtnLoading] = useState(false); + useEffect(() => { + insertIntoList(); + + return () => { + clearAllTimeouts(); + }; + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [updateComponentWithKey]); + + const insertIntoList = () => { + const data = [ + { x: 130, y: 20, node: 1 }, + { x: 180, y: 30, node: 2 }, + { x: 190, y: 60, node: 3 }, + { x: 160, y: 90, node: 4 }, + { x: 120, y: 80, node: 5 }, + { x: 110, y: 50, node: 6 }, + ]; + const Obj = new LinkedList([], 0); + for (let i = 0; i < data?.length; i++) { + Obj.insertIntoListWithGivenPositionXY(data[i].x, data[i].y, data[i].node); + } + if (Obj.head) { + setLists(Obj.head); + setRootLists(JSON.parse(JSON.stringify(Obj.head))); + } + }; + + const createACycle = () => { + const head = createACycleMethod(3, 6, resetVisitedNodes(JSON.parse(JSON.stringify(rootLists)))); + setLists(head); + }; + + return ( + <> +
      +
      +
      + +
      + +
      +

      I am from detect cycle

      + + + {lists ? ( +
      + + + +
      + ) : null} +
      + + ); +}; + +const radius = 6; // Circle radius + +const RenderNodeRecursively: React.FC<{ node: ITreeNode | null }> = ({ node }) => { + // Base case: If node is null or already visited, stop rendering + if (!node || node.isVisited) return null; + node.isVisited = true; + + 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, + }; +}; + +const resetVisitedNodes = (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; + // Move to the next node + currentNode = currentNode.next as ITreeNode; + } + return head; +}; + +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 +}; + +export default DetectCycle; diff --git a/src/app/components/linked-list/LinkedListComponent.tsx b/src/app/components/linked-list/LinkedListComponent.tsx index f6e4749..c731cd6 100644 --- a/src/app/components/linked-list/LinkedListComponent.tsx +++ b/src/app/components/linked-list/LinkedListComponent.tsx @@ -5,10 +5,11 @@ 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('reverse-linked-list'); + const [activeRootBtnType, setActiveRootBtnType] = useState('cycle'); //reverse-linked-list const [randomKey, setRandomKey] = useState('1'); const [speedRange, setSpeedRange] = useState(200); @@ -72,6 +73,9 @@ const LinkedListComponent = () => { +
  • diff --git a/src/app/data-structure/LinkedList/LinkedList.ts b/src/app/data-structure/LinkedList/LinkedList.ts index df92be5..467836c 100644 --- a/src/app/data-structure/LinkedList/LinkedList.ts +++ b/src/app/data-structure/LinkedList/LinkedList.ts @@ -25,8 +25,8 @@ export class LinkedList implements ITree { * @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) { - this.head = null; + constructor(arr: (number | null)[], initialize_cx: number = 20, head: ITreeNode | null = null) { + this.head = head; this.arr = arr; this.initialize_cx = initialize_cx; } @@ -61,4 +61,41 @@ export class LinkedList implements ITree { 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; + } + } } From 07ab531ce491b0c98b8ef6ee41bc7a9544c3e41f Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Thu, 3 Oct 2024 16:08:16 +0600 Subject: [PATCH 44/65] linked list cycle detection approach with visualization technique is integrated --- next.config.mjs | 2 +- src/app/algorithm/linked-list/detectCycle.ts | 226 +++++++++++++++ .../components/linked-list/DetectCycle.tsx | 262 +++++++++++++----- src/app/data-structure/Tree/Node.ts | 10 + src/app/data/linkedListData.ts | 13 + src/app/types/TreeTypeProps.ts | 31 +++ 6 files changed, 466 insertions(+), 78 deletions(-) create mode 100644 src/app/algorithm/linked-list/detectCycle.ts create mode 100644 src/app/data/linkedListData.ts diff --git a/next.config.mjs b/next.config.mjs index d9abcf0..29f1128 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -3,4 +3,4 @@ const nextConfig = { reactStrictMode: false, // Disable Strict Mode }; -export default nextConfig; +export default process.env.NODE_ENV === 'production' ? {} : nextConfig; 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/components/linked-list/DetectCycle.tsx b/src/app/components/linked-list/DetectCycle.tsx index cc5d8fb..93a312b 100644 --- a/src/app/components/linked-list/DetectCycle.tsx +++ b/src/app/components/linked-list/DetectCycle.tsx @@ -1,8 +1,18 @@ '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 { mergeTwoListColorsPlate } from '@/app/data/mockData'; -import { clearAllTimeouts } from '@/app/lib/sleepMethod'; +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'; @@ -12,9 +22,10 @@ import React, { useEffect, useState } from 'react'; const DetectCycle: React.FC = ({ speedRange, updateComponentWithKey }) => { // define component local state const [lists, setLists] = useState(); - const [rootLists, setRootLists] = useState(); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [btnLoading, setBtnLoading] = useState(false); + const [rootVisitedNodes, setRootVisitedNodes] = useState>(new Map()); + useEffect(() => { insertIntoList(); @@ -25,30 +36,151 @@ const DetectCycle: React.FC = ({ speedRange, updateComponentWithKey } // eslint-disable-next-line react-hooks/exhaustive-deps }, [updateComponentWithKey]); + /** + * Inserts nodes into the linked list from a predefined data array. + */ const insertIntoList = () => { - const data = [ - { x: 130, y: 20, node: 1 }, - { x: 180, y: 30, node: 2 }, - { x: 190, y: 60, node: 3 }, - { x: 160, y: 90, node: 4 }, - { x: 120, y: 80, node: 5 }, - { x: 110, y: 50, node: 6 }, - ]; - const Obj = new LinkedList([], 0); - for (let i = 0; i < data?.length; i++) { - Obj.insertIntoListWithGivenPositionXY(data[i].x, data[i].y, data[i].node); - } - if (Obj.head) { - setLists(Obj.head); - setRootLists(JSON.parse(JSON.stringify(Obj.head))); + 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); } }; - const createACycle = () => { - const head = createACycleMethod(3, 6, resetVisitedNodes(JSON.parse(JSON.stringify(rootLists)))); + /** + * Creates a cycle in the linked list. + */ + const createACycle = (rootLists: ITreeNode) => { + resetVisitedMap(); + const head = createACycleMethod(2, 7, resetNodes(JSON.parse(JSON.stringify(rootLists)))); setLists(head); }; + const handleIsCyclePresent = async () => { + try { + 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); + } + } + }; + + /** + * 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 ( <>
    @@ -63,17 +195,17 @@ const DetectCycle: React.FC = ({ speedRange, updateComponentWithKey } Revisualize
    -

    I am from detect cycle

    + - {lists ? (
    - +
    ) : null} @@ -82,12 +214,34 @@ const DetectCycle: React.FC = ({ speedRange, updateComponentWithKey } ); }; -const radius = 6; // Circle radius +const radius = 5; // Circle radius -const RenderNodeRecursively: React.FC<{ node: ITreeNode | null }> = ({ node }) => { +const RenderNodeRecursively: React.FC<{ node: ITreeNode | null; visited: Map }> = ({ + node, + visited, +}) => { // Base case: If node is null or already visited, stop rendering - if (!node || node.isVisited) return null; - node.isVisited = true; + 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 ( <> @@ -140,8 +294,8 @@ const RenderNodeRecursively: React.FC<{ node: ITreeNode | null }> = ({ node }) = cx={node.cx!} cy={node.cy!} r={radius} - fill={node.isCurrent ? 'red' : node.isTarget ? 'green' : 'white'} - stroke='black' + fill={cycle_fill_color} + stroke={node.isCycle || node.isCycleStartPoint || node.isCycleEndPoint ? 'white' : 'black'} strokeWidth='0.3' > @@ -152,14 +306,14 @@ const RenderNodeRecursively: React.FC<{ node: ITreeNode | null }> = ({ node }) = dy={2} textAnchor='middle' className='text-center text-[4px]' - fill={node.isInsertedPosition || node.isCurrent || node.isTarget ? 'white' : 'black'} + fill={text_fill_color} > {node?.value} {/* Recursively render the next node */} - {node.next ? : null} + {node.next ? : null} ); }; @@ -178,50 +332,4 @@ const calculateOffsetLine = (x1: number, y1: number, x2: number, y2: number, rad }; }; -const resetVisitedNodes = (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; - // Move to the next node - currentNode = currentNode.next as ITreeNode; - } - return head; -}; - -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 -}; - export default DetectCycle; diff --git a/src/app/data-structure/Tree/Node.ts b/src/app/data-structure/Tree/Node.ts index 68e8f1e..e8aa25a 100644 --- a/src/app/data-structure/Tree/Node.ts +++ b/src/app/data-structure/Tree/Node.ts @@ -32,6 +32,11 @@ export class TreeNode implements ITreeNode { 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, @@ -51,5 +56,10 @@ export class TreeNode implements ITreeNode { 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/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/types/TreeTypeProps.ts b/src/app/types/TreeTypeProps.ts index 43fdf29..f7d6850 100644 --- a/src/app/types/TreeTypeProps.ts +++ b/src/app/types/TreeTypeProps.ts @@ -31,6 +31,11 @@ export interface ITreeNode { next: ITreeNode | null; isCycle: boolean; isInsertedPosition: boolean; + destination: { x: number | null; y: number | null }; + slowPointer: boolean; + firstPointer: boolean; + isCycleStartPoint: boolean; + isCycleEndPoint: boolean; } /** @@ -57,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; +} From b2ed421780a5bc385af7509a622c8a39bcee15e2 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Thu, 3 Oct 2024 17:42:30 +0600 Subject: [PATCH 45/65] detect cycle visualization is implemented, improved some code quality --- next.config.mjs | 6 +-- .../components/linked-list/DetectCycle.tsx | 42 ++++++++++++++----- .../linked-list/LinkedListComponent.tsx | 14 ++++--- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/next.config.mjs b/next.config.mjs index 29f1128..4678774 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,6 +1,4 @@ /** @type {import('next').NextConfig} */ -const nextConfig = { - reactStrictMode: false, // Disable Strict Mode -}; +const nextConfig = {}; -export default process.env.NODE_ENV === 'production' ? {} : nextConfig; +export default nextConfig; diff --git a/src/app/components/linked-list/DetectCycle.tsx b/src/app/components/linked-list/DetectCycle.tsx index 93a312b..e004d13 100644 --- a/src/app/components/linked-list/DetectCycle.tsx +++ b/src/app/components/linked-list/DetectCycle.tsx @@ -18,13 +18,26 @@ 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, updateComponentWithKey }) => { +const DetectCycle: React.FC = ({ speedRange }) => { // define component local state const [lists, setLists] = useState(); // eslint-disable-next-line @typescript-eslint/no-unused-vars 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(); @@ -32,9 +45,16 @@ const DetectCycle: React.FC = ({ speedRange, updateComponentWithKey } return () => { clearAllTimeouts(); }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useEffect(() => { + if (isPerformOperation) { + handleIsCyclePresent(); + setIsPerformOperation(false); + } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [updateComponentWithKey]); + }, [isPerformOperation]); /** * Inserts nodes into the linked list from a predefined data array. @@ -51,6 +71,7 @@ const DetectCycle: React.FC = ({ speedRange, updateComponentWithKey } const head = linkedList.head; setLists(head); createACycle(head); + setIsPerformOperation(true); } }; @@ -59,7 +80,7 @@ const DetectCycle: React.FC = ({ speedRange, updateComponentWithKey } */ const createACycle = (rootLists: ITreeNode) => { resetVisitedMap(); - const head = createACycleMethod(2, 7, resetNodes(JSON.parse(JSON.stringify(rootLists)))); + const head = createACycleMethod(cycleNode.start, 7, resetNodes(JSON.parse(JSON.stringify(rootLists)))); setLists(head); }; @@ -191,24 +212,23 @@ const DetectCycle: React.FC = ({ speedRange, updateComponentWithKey }
    - - {lists ? (
    - +
    - ) : null} + ) : ( +
    +

    Loading...

    +
    + )}
    ); diff --git a/src/app/components/linked-list/LinkedListComponent.tsx b/src/app/components/linked-list/LinkedListComponent.tsx index c731cd6..b0c1e83 100644 --- a/src/app/components/linked-list/LinkedListComponent.tsx +++ b/src/app/components/linked-list/LinkedListComponent.tsx @@ -7,9 +7,11 @@ import ReverseLinkedList from './ReverseLinkedList'; import MergeTwoSortedList from './MergeTwoSortedList'; import DetectCycle from './DetectCycle'; +const IS_PROD = process.env.NODE_ENV === 'production'; + const LinkedListComponent = () => { // define a component local memory - const [activeRootBtnType, setActiveRootBtnType] = useState('cycle'); //reverse-linked-list + const [activeRootBtnType, setActiveRootBtnType] = useState(IS_PROD ? 'reverse-linked-list' : 'cycle'); //reverse-linked-list const [randomKey, setRandomKey] = useState('1'); const [speedRange, setSpeedRange] = useState(200); @@ -73,9 +75,11 @@ const LinkedListComponent = () => { - + {IS_PROD ? null : ( + + )}
    From 446379f5d7ada972cd53ac5b5452eeeff1053abd Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Thu, 3 Oct 2024 17:45:38 +0600 Subject: [PATCH 46/65] make visualization button section as a absolute untill mid --- src/app/components/linked-list/DetectCycle.tsx | 2 +- src/app/components/linked-list/LinkedListComponent.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/components/linked-list/DetectCycle.tsx b/src/app/components/linked-list/DetectCycle.tsx index e004d13..146839c 100644 --- a/src/app/components/linked-list/DetectCycle.tsx +++ b/src/app/components/linked-list/DetectCycle.tsx @@ -205,7 +205,7 @@ const DetectCycle: React.FC = ({ speedRange }) => { return ( <>
    -
    +
    diff --git a/src/app/components/linked-list/LinkedListComponent.tsx b/src/app/components/linked-list/LinkedListComponent.tsx index b0c1e83..163517d 100644 --- a/src/app/components/linked-list/LinkedListComponent.tsx +++ b/src/app/components/linked-list/LinkedListComponent.tsx @@ -40,7 +40,7 @@ const LinkedListComponent = () => { }; return ( -
    +
    From 9f4de4d687d0bdf654f97834f41bdebe93757e94 Mon Sep 17 00:00:00 2001 From: alaminpu1007 Date: Thu, 3 Oct 2024 20:03:36 +0600 Subject: [PATCH 47/65] linked list, detect cycle visualization functionality is added --- package-lock.json | 4 ++-- .../components/linked-list/LinkedListComponent.tsx | 12 ++++-------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6d97d42..990ebad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "algorithm-visualizer", - "version": "1.0.0", + "version": "1.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "algorithm-visualizer", - "version": "1.0.0", + "version": "1.2.0", "dependencies": { "next": "14.2.5", "react": "^18", diff --git a/src/app/components/linked-list/LinkedListComponent.tsx b/src/app/components/linked-list/LinkedListComponent.tsx index 163517d..a5351d3 100644 --- a/src/app/components/linked-list/LinkedListComponent.tsx +++ b/src/app/components/linked-list/LinkedListComponent.tsx @@ -7,11 +7,9 @@ import ReverseLinkedList from './ReverseLinkedList'; import MergeTwoSortedList from './MergeTwoSortedList'; import DetectCycle from './DetectCycle'; -const IS_PROD = process.env.NODE_ENV === 'production'; - const LinkedListComponent = () => { // define a component local memory - const [activeRootBtnType, setActiveRootBtnType] = useState(IS_PROD ? 'reverse-linked-list' : 'cycle'); //reverse-linked-list + const [activeRootBtnType, setActiveRootBtnType] = useState('merge-two-linked-list'); //reverse-linked-list const [randomKey, setRandomKey] = useState('1'); const [speedRange, setSpeedRange] = useState(200); @@ -75,11 +73,9 @@ const LinkedListComponent = () => { - {IS_PROD ? null : ( - - )} +
    +
    +
    +
    +

    Speed: {speedRange} (0 to 1500)

    + +
    +
    + +
    +
    +
    +
    + +
    + {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-orange-600 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/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 19b4b62..c9bf983 100644 --- a/src/app/globals.scss +++ b/src/app/globals.scss @@ -63,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/sudoku-solver/page.tsx b/src/app/sudoku-solver/page.tsx new file mode 100644 index 0000000..60e42f1 --- /dev/null +++ b/src/app/sudoku-solver/page.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import SudoKuSolverComponent from '../components/sudoku-solver/SudoKuSolverComponent'; + +const page = () => { + return ( + <> + + + ); +}; + +export default page; diff --git a/src/app/types/sudokyProps.ts b/src/app/types/sudokyProps.ts new file mode 100644 index 0000000..e36359c --- /dev/null +++ b/src/app/types/sudokyProps.ts @@ -0,0 +1,17 @@ +export interface SudoKuBoardProps { + id: number | string; + value: string; + isValid: boolean; + isActive: boolean; + isCurrent: boolean; + isTarget: boolean; + isValidRowItem: boolean; + isValidColItem: boolean; + isValidSubGridItem: boolean; + isInvalid: boolean; +} + +export interface GridInputProps { + value: string; + id: string | number; +} From cac10cbe6ecca7b322c9225b6c6abce35995a921 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Mon, 7 Oct 2024 14:34:21 +0600 Subject: [PATCH 53/65] split code into some reusable component, improved some functionality, optimize sudoku solver approach --- .../algorithm/sudoku-solver/sudokuSolver.ts | 278 +++++++++ .../components/sudoku-solver/SudoKuSolver.tsx | 70 +++ .../sudoku-solver/SudoKuSolverComponent.tsx | 534 ++++++------------ src/app/data/mockData.ts | 38 ++ src/app/sudoku-solver/page.tsx | 4 +- src/app/types/sudokyProps.ts | 24 + 6 files changed, 583 insertions(+), 365 deletions(-) create mode 100644 src/app/algorithm/sudoku-solver/sudokuSolver.ts create mode 100644 src/app/components/sudoku-solver/SudoKuSolver.tsx diff --git a/src/app/algorithm/sudoku-solver/sudokuSolver.ts b/src/app/algorithm/sudoku-solver/sudokuSolver.ts new file mode 100644 index 0000000..b379823 --- /dev/null +++ b/src/app/algorithm/sudoku-solver/sudokuSolver.ts @@ -0,0 +1,278 @@ +import { Sleep } from '@/app/lib/sleepMethod'; +import { GridInputProps, 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 === '#' || value === '') continue; + + // 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/sudoku-solver/SudoKuSolver.tsx b/src/app/components/sudoku-solver/SudoKuSolver.tsx new file mode 100644 index 0000000..0220014 --- /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 index 62f2a9b..513f3da 100644 --- a/src/app/components/sudoku-solver/SudoKuSolverComponent.tsx +++ b/src/app/components/sudoku-solver/SudoKuSolverComponent.tsx @@ -1,410 +1,218 @@ '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 { Sleep } from '@/app/lib/sleepMethod'; +import { clearAllTimeouts } from '@/app/lib/sleepMethod'; import { GridInputProps, SudoKuBoardProps } from '@/app/types/sudokyProps'; -import React, { ReactNode, useState } from 'react'; +import StatusColorsPlate from '@/app/utils/StatusColorsPlate'; +import React, { ReactNode, useEffect, useState } from 'react'; import { toast } from 'react-toastify'; -const SudoKuSolverComponent = () => { - const [board, setBoard] = useState(JSON.parse(JSON.stringify(SUDOKU_BOARD_DATA))); - const [inputsData, setInputsData] = useState(JSON.parse(JSON.stringify(SUDOKU_BOARD_DATA))); - const [speedRange, setSpeedRange] = useState(200); +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); + + // Trigger for component mount + useEffect(() => { + clearAllTimeouts(); + const tempBoard = JSON.parse(JSON.stringify(SUDOKU_BOARD_DATA)); + setBoard(tempBoard); + setInputsData(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(); + 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`); } - // Validate the input to only allow numbers between 1 and 9 + // 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 number are allowed 1-9.`); + toast.error(`Only numbers 1-9 are allowed.`); } - setInputsData((prv) => { - const tempInputData = [...prv]; - tempInputData[rowIndex][colIndex].value = validValue ? value : ''; - return tempInputData; - }); - setBoard((prv) => { - const tempInputData = [...prv]; - tempInputData[rowIndex][colIndex].value = validValue ? value : ''; - return tempInputData; + // Update the `inputsData` state with the valid value or clear it if invalid + setInputsData((prevData) => { + const updatedInputData = [...prevData]; + updatedInputData[rowIndex][colIndex].value = validValue ? value : ''; // Set the value or clear + return updatedInputData; }); - }; - - const isValidSudoku = (board: SudoKuBoardProps[][]): boolean => { - const rows = new Set(); - const cols = new Set(); - const grids = new Set(); - - for (let i = 0; i < 9; i++) { - for (let j = 0; j < 9; j++) { - const value = board[i][j].value; - if (value === '#' || value === '') { - continue; // Skip empty cells - } - - // Check row - const rowKey = `row-${i}-${value}`; - if (rows.has(rowKey)) return false; - rows.add(rowKey); - // Check column - const colKey = `col-${j}-${value}`; - if (cols.has(colKey)) return false; - cols.add(colKey); - - // Check 3x3 subgrid - const gridIndex = Math.floor(i / 3) * 3 + Math.floor(j / 3); - const gridKey = `grid-${gridIndex}-${value}`; - if (grids.has(gridKey)) return false; - grids.add(gridKey); - } - } - return true; // If no duplicates are found + // 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 + return updatedBoard; + }); }; - const Solve = async (board: SudoKuBoardProps[][]): Promise => { - // before perform to check valid sudoku, check this is already a valid grid or not - if (!isValidSudoku(board)) { - toast.error(`The Sudoku grid is invalid!`); - return false; - } - - const n: number = board.length; - const m: number = board[0].length; - - for (let i = 0; i < n; i++) { - for (let j = 0; j < m; j++) { - // active current item - setBoard(() => { - const tempData: SudoKuBoardProps[][] = [...board]; - tempData[i][j].isCurrent = true; - return tempData; - }); - - await Sleep(speedRange); - - setBoard(() => { - const tempData: SudoKuBoardProps[][] = [...board]; - tempData[i][j].isCurrent = false; - return tempData; - }); - - if (board[i][j].value === '#') { - for (let c = 1; c <= 9; c++) { - if (await isValid(board, i, j, String(c))) { - board[i][j].value = String(c); - - setBoard(() => { - const tempData: SudoKuBoardProps[][] = [...board]; - tempData[i][j].isValid = true; - return tempData; - }); - - if (await Solve(board)) { - return true; - } else { - setBoard(() => { - const tempData: SudoKuBoardProps[][] = [...board]; - tempData[i][j].isValid = false; - tempData[i][j].isInvalid = true; - return tempData; - }); - await Sleep(speedRange); - setBoard(() => { - const tempData: SudoKuBoardProps[][] = [...board]; - tempData[i][j].isInvalid = false; - return tempData; - }); - - board[i][j].value = '#'; - } - } - } - - return false; - } - } - } - toast.success(`Sudoku solved successfully!`); - return true; + /** + * Initiate board with it's default value + * + * @param {SudoKuBoardProps[][]} board + * @returns {*} + */ + 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 subgrid validity flag + isInvalid: false, // Reset the invalid state of the cell + })) + ); }; + /** + * 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); - const tempBoard: SudoKuBoardProps[][] = JSON.parse(JSON.stringify(board)).map((row: SudoKuBoardProps[]) => - row.map((cell) => ({ - ...cell, - isValid: false, - isActive: false, - isCurrent: false, - isTarget: false, - isValidRowItem: false, - isValidColItem: false, - isValidSubGridItem: false, - isInvalid: false, - })) - ); - await Solve(tempBoard); + // 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); } }; - const isValid = async (board: SudoKuBoardProps[][], row: number, col: number, c: string): Promise => { - try { - const tempUpdates: SudoKuBoardProps[][] = board.map((r) => r.map((cell) => ({ ...cell }))); - - setBoard(() => { - const tempData: SudoKuBoardProps[][] = tempUpdates.map((r) => r.map((cell) => ({ ...cell }))); - tempData[row][col].value = String(c); - return tempData; - }); - setInputsData(() => { - const tempData: SudoKuBoardProps[][] = tempUpdates.map((r) => r.map((cell) => ({ ...cell }))); - tempData[row][col].value = String(c); - return tempData; - }); - await Sleep(speedRange); - - // Check the column - for (let i = 0; i < 9; i++) { - // Highlight the current column cell being checked - tempUpdates[i][col].isValidColItem = true; - setBoard([...tempUpdates]); - // setInputsData([...tempUpdates]); - await Sleep(speedRange); // Delay for visualization - - // Check for duplicates in the column - if (board[i][col].value === String(c)) { - tempUpdates[i][col].isInvalid = true; - setBoard([...tempUpdates]); - await Sleep(speedRange); - tempUpdates[i][col].isInvalid = false; - setBoard([...tempUpdates]); - - return false; - } - } - - // Check the row - for (let j = 0; j < 9; j++) { - // Highlight the current row cell being checked - tempUpdates[row][j].isValidRowItem = true; - setBoard([...tempUpdates]); - // setInputsData([...tempUpdates]); - await Sleep(speedRange); // Delay for visualization - - // Check for duplicates in the row - if (board[row][j].value === String(c)) { - tempUpdates[row][j].isInvalid = true; - setBoard([...tempUpdates]); - await Sleep(speedRange); - tempUpdates[row][j].isInvalid = false; - setBoard([...tempUpdates]); - return false; - } - } - - // Check the 3x3 subgrid - 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; - - // Highlight the current subgrid cell being checked - tempUpdates[subGridRow][subGridCol].isValidSubGridItem = true; - setBoard([...tempUpdates]); - // setInputsData([...tempUpdates]); - await Sleep(speedRange); // Delay for visualization - - // Check for duplicates in the subgrid - if (board[subGridRow][subGridCol].value === c) { - tempUpdates[subGridRow][subGridCol].isInvalid = true; - setBoard([...tempUpdates]); - await Sleep(speedRange); - tempUpdates[subGridRow][subGridCol].isInvalid = false; - setBoard([...tempUpdates]); - return false; - } - } - } - // Reset all valid flags before returning - setBoard((prevBoard) => { - return prevBoard.map((rowItem) => - rowItem.map((cell) => { - return { - ...cell, - isValidColItem: false, - isValidRowItem: false, - isValidSubGridItem: false, - }; - }) - ); - }); - setInputsData((prevBoard) => - prevBoard.map((rowItem) => - rowItem.map((cell) => { - return { - ...cell, - isValidColItem: false, - isValidRowItem: false, - isValidSubGridItem: false, - }; - }) - ) - ); - await Sleep(speedRange); - return true; - } catch (error) { - if (process.env.NODE_ENV === 'development') { - // eslint-disable-next-line no-console - console.log(error); - } - return false; - } - }; - - /** - * input type range method - * - * @param {*} e - */ - const inputRangeMethod = (e: React.ChangeEvent) => { - setSpeedRange(Number(e.target.value)); - }; - return ( <> -
    -
    - -
    -
    -
    -

    Speed: {speedRange} (0 to 1500)

    - -
    -
    - +
    +
    + +
    +
    +
    + {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-orange-600 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} + /> +
    + ); + })}
    -
    + ))}
    -
    - -
    - {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-orange-600 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...

    -
    - )} -
    + ) : ( +
    +

    Loading...

    +
    + )}
    ); diff --git a/src/app/data/mockData.ts b/src/app/data/mockData.ts index ecfabfd..dff00f5 100644 --- a/src/app/data/mockData.ts +++ b/src/app/data/mockData.ts @@ -349,3 +349,41 @@ export const detectCycleFromGivenLinkedList: StatusColorsDataProps[] = [ 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-orange-600', + }, + { + 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/sudoku-solver/page.tsx b/src/app/sudoku-solver/page.tsx index 60e42f1..742a946 100644 --- a/src/app/sudoku-solver/page.tsx +++ b/src/app/sudoku-solver/page.tsx @@ -1,10 +1,10 @@ import React from 'react'; -import SudoKuSolverComponent from '../components/sudoku-solver/SudoKuSolverComponent'; +import SudoKuSolver from '../components/sudoku-solver/SudoKuSolver'; const page = () => { return ( <> - + ); }; diff --git a/src/app/types/sudokyProps.ts b/src/app/types/sudokyProps.ts index e36359c..e79f70b 100644 --- a/src/app/types/sudokyProps.ts +++ b/src/app/types/sudokyProps.ts @@ -1,3 +1,19 @@ +/** + * 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; @@ -11,6 +27,14 @@ export interface SudoKuBoardProps { isInvalid: boolean; } +/** + * Interface representing the properties for grid input fields in the Sudoku board. + * + * @interface GridInputProps + * + * @property {string} value - The current value of the input (typically a number from 1-9 or an empty string). + * @property {string | number} id - A unique identifier for the input field. + */ export interface GridInputProps { value: string; id: string | number; From 8db36cec394e873799d73658585b9096a6443b95 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Mon, 7 Oct 2024 15:00:13 +0600 Subject: [PATCH 54/65] a dynamic grid approach is integrated to create sudoku board --- .../sudoku-solver/SudoKuSolverComponent.tsx | 16 +++++-- src/app/lib/sudokuHelperMethod.ts | 47 +++++++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 src/app/lib/sudokuHelperMethod.ts diff --git a/src/app/components/sudoku-solver/SudoKuSolverComponent.tsx b/src/app/components/sudoku-solver/SudoKuSolverComponent.tsx index 513f3da..19ec8e2 100644 --- a/src/app/components/sudoku-solver/SudoKuSolverComponent.tsx +++ b/src/app/components/sudoku-solver/SudoKuSolverComponent.tsx @@ -4,6 +4,7 @@ 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 } from '@/app/lib/sudokuHelperMethod'; import { GridInputProps, SudoKuBoardProps } from '@/app/types/sudokyProps'; import StatusColorsPlate from '@/app/utils/StatusColorsPlate'; import React, { ReactNode, useEffect, useState } from 'react'; @@ -19,7 +20,7 @@ const SudoKuSolverComponent: React.FC<{ speedRange: number }> = ({ speedRange }) // Trigger for component mount useEffect(() => { clearAllTimeouts(); - const tempBoard = JSON.parse(JSON.stringify(SUDOKU_BOARD_DATA)); + const tempBoard = addRandomHashesToBoard(JSON.parse(JSON.stringify(SUDOKU_BOARD_DATA)), 4); setBoard(tempBoard); setInputsData(tempBoard); setIsPerformOperation(true); @@ -131,12 +132,19 @@ const SudoKuSolverComponent: React.FC<{ speedRange: number }> = ({ speedRange }) return ( <> -
    -
    +
    +
    +
    -
    +
    {board?.length ? (
    {board.map((row, rowIndex) => ( diff --git a/src/app/lib/sudokuHelperMethod.ts b/src/app/lib/sudokuHelperMethod.ts new file mode 100644 index 0000000..729dd13 --- /dev/null +++ b/src/app/lib/sudokuHelperMethod.ts @@ -0,0 +1,47 @@ +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; +}; From 9590d0a78a96b9fc4781f38654c91798019766c2 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Mon, 7 Oct 2024 15:27:23 +0600 Subject: [PATCH 55/65] create user own board functionality is implemented --- .../algorithm/sudoku-solver/sudokuSolver.ts | 6 +++--- .../components/sudoku-solver/SudoKuSolver.tsx | 4 ++-- .../sudoku-solver/SudoKuSolverComponent.tsx | 20 ++++++++++--------- src/app/data/mockData.ts | 2 +- src/app/types/sudokyProps.ts | 13 ------------ 5 files changed, 17 insertions(+), 28 deletions(-) diff --git a/src/app/algorithm/sudoku-solver/sudokuSolver.ts b/src/app/algorithm/sudoku-solver/sudokuSolver.ts index b379823..1ef16b3 100644 --- a/src/app/algorithm/sudoku-solver/sudokuSolver.ts +++ b/src/app/algorithm/sudoku-solver/sudokuSolver.ts @@ -1,5 +1,5 @@ import { Sleep } from '@/app/lib/sleepMethod'; -import { GridInputProps, SudoKuBoardProps } from '@/app/types/sudokyProps'; +import { SudoKuBoardProps } from '@/app/types/sudokyProps'; import React from 'react'; import { toast } from 'react-toastify'; @@ -57,7 +57,7 @@ const isValidSudoku = (board: SudoKuBoardProps[][]): boolean => { export const Solve = async ( board: SudoKuBoardProps[][], setBoard: React.Dispatch>, - setInputsData: React.Dispatch>, + setInputsData: React.Dispatch>, speedRange: number ): Promise => { // Before attempting to solve, check if the provided board is already valid @@ -156,7 +156,7 @@ const isValid = async ( col: number, c: string, setBoard: React.Dispatch>, - setInputsData: React.Dispatch>, + setInputsData: React.Dispatch>, speedRange: number ): Promise => { try { diff --git a/src/app/components/sudoku-solver/SudoKuSolver.tsx b/src/app/components/sudoku-solver/SudoKuSolver.tsx index 0220014..2945da2 100644 --- a/src/app/components/sudoku-solver/SudoKuSolver.tsx +++ b/src/app/components/sudoku-solver/SudoKuSolver.tsx @@ -34,8 +34,8 @@ const SudoKuSolver = () => { return (
    -
    -
    +
    +

    Speed: {speedRange} (0 to 1500)

    diff --git a/src/app/components/sudoku-solver/SudoKuSolverComponent.tsx b/src/app/components/sudoku-solver/SudoKuSolverComponent.tsx index 19ec8e2..4a436ca 100644 --- a/src/app/components/sudoku-solver/SudoKuSolverComponent.tsx +++ b/src/app/components/sudoku-solver/SudoKuSolverComponent.tsx @@ -5,7 +5,7 @@ import { sudokuColorsPlate } from '@/app/data/mockData'; import { SUDOKU_BOARD_DATA } from '@/app/data/sudokuData'; import { clearAllTimeouts } from '@/app/lib/sleepMethod'; import { addRandomHashesToBoard } from '@/app/lib/sudokuHelperMethod'; -import { GridInputProps, SudoKuBoardProps } from '@/app/types/sudokyProps'; +import { SudoKuBoardProps } from '@/app/types/sudokyProps'; import StatusColorsPlate from '@/app/utils/StatusColorsPlate'; import React, { ReactNode, useEffect, useState } from 'react'; import { toast } from 'react-toastify'; @@ -13,7 +13,7 @@ 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 [inputsData, setInputsData] = useState([]); const [btnLoading, setBtnLoading] = useState(false); const [isPerformOperation, setIsPerformOperation] = useState(false); @@ -61,16 +61,18 @@ const SudoKuSolverComponent: React.FC<{ speedRange: number }> = ({ speedRange }) } // Update the `inputsData` state with the valid value or clear it if invalid - setInputsData((prevData) => { - const updatedInputData = [...prevData]; - updatedInputData[rowIndex][colIndex].value = validValue ? value : ''; // Set the value or clear - return updatedInputData; + 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; }); }; @@ -144,7 +146,7 @@ const SudoKuSolverComponent: React.FC<{ speedRange: number }> = ({ speedRange }) Visualize
    -
    +
    {board?.length ? (
    {board.map((row, rowIndex) => ( @@ -179,7 +181,7 @@ const SudoKuSolverComponent: React.FC<{ speedRange: number }> = ({ speedRange }) } // for valid row check if (board[rowIndex][colIndex].isValidRowItem) { - BG_COLOR = `bg-orange-600 text-white`; + BG_COLOR = `bg-[#D895DA] text-white`; } // for valid col check if (board[rowIndex][colIndex].isValidColItem) { @@ -196,7 +198,7 @@ const SudoKuSolverComponent: React.FC<{ speedRange: number }> = ({ speedRange }) return (
    Date: Mon, 7 Oct 2024 15:40:10 +0600 Subject: [PATCH 56/65] updated projects Readme, changelog, also updated projects versions minor --- CHANGELOG.md | 20 +++++++++++++++++++- README.md | 3 +++ package.json | 2 +- src/app/data/dashboardSchemaData.ts | 11 +++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d9608f..f0fb98b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,24 @@ # Changelog -## 1.3.0 (Oct 26, 2024) +## 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. diff --git a/README.md b/README.md index f2e9bd5..3f0ae6a 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,14 @@ A Next.js 14 project for visualizing famous algorithms using React, TypeScript, - **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 ### Prerequisites diff --git a/package.json b/package.json index 7a95887..4fa5415 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "algorithm-visualizer", - "version": "1.3.0", + "version": "1.4.1", "private": true, "scripts": { "dev": "next dev", diff --git a/src/app/data/dashboardSchemaData.ts b/src/app/data/dashboardSchemaData.ts index 10a8151..7573faf 100644 --- a/src/app/data/dashboardSchemaData.ts +++ b/src/app/data/dashboardSchemaData.ts @@ -6,6 +6,17 @@ import { uid } from '../lib/uidGenerator'; import { ProjectSchema } from '../types/commonProps'; export const projectsData: ProjectSchema[] = [ + { + id: uid(), + name: 'Sudoku Solver', + navigate: '/sudoku-solver', + lists: [ + { + id: uid(), + name: 'DFS', + }, + ], + }, { id: uid(), name: 'Tree', From 2b01489199bf918dc21fc440972763301310575c Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Mon, 7 Oct 2024 16:16:01 +0600 Subject: [PATCH 57/65] enhanced some UI in footer & home pages --- CHANGELOG.md | 4 ++++ package.json | 2 +- src/app/components/home/HomeComponent.tsx | 4 ++-- .../layouts/footer/FooterComponent.tsx | 2 +- src/app/data/dashboardSchemaData.ts | 19 ++++++++++++------- src/app/types/commonProps.ts | 2 +- 6 files changed, 21 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0fb98b..c784066 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.4.2 (Oct 07, 2024) + +Improved some UI in footer component & Home component. + ## 1.4.0 (Oct 07, 2024) ### Added diff --git a/package.json b/package.json index 4fa5415..69c560b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "algorithm-visualizer", - "version": "1.4.1", + "version": "1.4.2", "private": true, "scripts": { "dev": "next dev", diff --git a/src/app/components/home/HomeComponent.tsx b/src/app/components/home/HomeComponent.tsx index 803d29b..777a5b4 100644 --- a/src/app/components/home/HomeComponent.tsx +++ b/src/app/components/home/HomeComponent.tsx @@ -23,8 +23,8 @@ const HomeComponent = (): ReactNode => {

    {item.name}

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

    ( alamin66.sit@gmail.com - + Date: Mon, 7 Oct 2024 17:03:13 +0600 Subject: [PATCH 58/65] sudoku solved stop visualization functionality is integrated --- CHANGELOG.md | 4 ++ package.json | 2 +- .../sudoku-solver/SudoKuSolverComponent.tsx | 58 +++++++++---------- src/app/lib/sudokuHelperMethod.ts | 22 +++++++ 4 files changed, 55 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c784066..415e812 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 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. diff --git a/package.json b/package.json index 69c560b..e8660bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "algorithm-visualizer", - "version": "1.4.2", + "version": "1.4.3", "private": true, "scripts": { "dev": "next dev", diff --git a/src/app/components/sudoku-solver/SudoKuSolverComponent.tsx b/src/app/components/sudoku-solver/SudoKuSolverComponent.tsx index 4a436ca..66c5761 100644 --- a/src/app/components/sudoku-solver/SudoKuSolverComponent.tsx +++ b/src/app/components/sudoku-solver/SudoKuSolverComponent.tsx @@ -4,7 +4,7 @@ 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 } from '@/app/lib/sudokuHelperMethod'; +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'; @@ -16,6 +16,7 @@ const SudoKuSolverComponent: React.FC<{ speedRange: number }> = ({ speedRange }) const [inputsData, setInputsData] = useState([]); const [btnLoading, setBtnLoading] = useState(false); const [isPerformOperation, setIsPerformOperation] = useState(false); + const [rootData, setRootData] = useState([]); // Trigger for component mount useEffect(() => { @@ -23,6 +24,7 @@ const SudoKuSolverComponent: React.FC<{ speedRange: number }> = ({ speedRange }) 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(); @@ -77,28 +79,6 @@ const SudoKuSolverComponent: React.FC<{ speedRange: number }> = ({ speedRange }) }); }; - /** - * Initiate board with it's default value - * - * @param {SudoKuBoardProps[][]} board - * @returns {*} - */ - 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 subgrid validity flag - isInvalid: false, // Reset the invalid state of the cell - })) - ); - }; - /** * Handles the execution of the Sudoku solver algorithm. * @@ -132,19 +112,37 @@ const SudoKuSolverComponent: React.FC<{ speedRange: number }> = ({ speedRange }) } }; + /** Prevent to perform sudoku solver */ + const handleStopSudokuMethod = () => { + clearAllTimeouts(); + const tempBoard = JSON.parse(JSON.stringify(rootData)); + setBoard([...tempBoard]); + setInputsData([...tempBoard]); + setBtnLoading(false); + }; + return ( <>

    - +
    + + +
    {board?.length ? ( diff --git a/src/app/lib/sudokuHelperMethod.ts b/src/app/lib/sudokuHelperMethod.ts index 729dd13..9c44b48 100644 --- a/src/app/lib/sudokuHelperMethod.ts +++ b/src/app/lib/sudokuHelperMethod.ts @@ -45,3 +45,25 @@ export const addRandomHashesToBoard = (tempBoard: SudoKuBoardProps[][], maxHashe 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 + })) + ); +}; From 5b77a00279935b24edc197802023dcca46d5e801 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Wed, 9 Oct 2024 09:36:40 +0600 Subject: [PATCH 59/65] sudoku solver input validation updated --- CHANGELOG.md | 4 ++++ package.json | 2 +- src/app/algorithm/sudoku-solver/sudokuSolver.ts | 7 ++++++- src/app/components/sudoku-solver/SudoKuSolverComponent.tsx | 6 +++++- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 415e812..305349c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 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. diff --git a/package.json b/package.json index e8660bb..aaa1803 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "algorithm-visualizer", - "version": "1.4.3", + "version": "1.4.4", "private": true, "scripts": { "dev": "next dev", diff --git a/src/app/algorithm/sudoku-solver/sudokuSolver.ts b/src/app/algorithm/sudoku-solver/sudokuSolver.ts index 1ef16b3..ca40dac 100644 --- a/src/app/algorithm/sudoku-solver/sudokuSolver.ts +++ b/src/app/algorithm/sudoku-solver/sudokuSolver.ts @@ -22,7 +22,12 @@ const isValidSudoku = (board: SudoKuBoardProps[][]): boolean => { const value = board[i][j].value; // Skip empty cells - if (value === '#' || value === '') continue; + 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}`; diff --git a/src/app/components/sudoku-solver/SudoKuSolverComponent.tsx b/src/app/components/sudoku-solver/SudoKuSolverComponent.tsx index 66c5761..d5a4feb 100644 --- a/src/app/components/sudoku-solver/SudoKuSolverComponent.tsx +++ b/src/app/components/sudoku-solver/SudoKuSolverComponent.tsx @@ -54,12 +54,16 @@ const SudoKuSolverComponent: React.FC<{ speedRange: number }> = ({ speedRange }) 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.`); + toast.error(`Only numbers 1-9 & # are allowed.`); } // Update the `inputsData` state with the valid value or clear it if invalid From 3c88a9453e7de56bac13ad4e766b04785d2824f5 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Thu, 10 Oct 2024 17:27:02 +0600 Subject: [PATCH 60/65] shortest path new component is introduced, bellman ford to get shortest path visualization approach is implemented --- package.json | 2 +- .../shortest-path/bellmanFordShortestPath.ts | 262 +++++++++++++++++ .../shortest-path/dijkstraShortestPath.ts | 154 +++++----- src/app/components/pathFind/PathFind.tsx | 14 +- ...DijkstraComponent.tsx => ShortestPath.tsx} | 165 ++++++++--- src/app/data/shortestPathData.ts | 266 +++++++----------- src/app/types/sortingProps.ts | 15 + 7 files changed, 587 insertions(+), 291 deletions(-) create mode 100644 src/app/algorithm/shortest-path/bellmanFordShortestPath.ts rename src/app/components/pathFind/{DijkstraComponent.tsx => ShortestPath.tsx} (58%) diff --git a/package.json b/package.json index aaa1803..483b4ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "algorithm-visualizer", - "version": "1.4.4", + "version": "1.5.5", "private": true, "scripts": { "dev": "next dev", diff --git a/src/app/algorithm/shortest-path/bellmanFordShortestPath.ts b/src/app/algorithm/shortest-path/bellmanFordShortestPath.ts new file mode 100644 index 0000000..1a05452 --- /dev/null +++ b/src/app/algorithm/shortest-path/bellmanFordShortestPath.ts @@ -0,0 +1,262 @@ +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 = handleResetData(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)) + ); +}; + +/** + * 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 handleResetData = (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/algorithm/shortest-path/dijkstraShortestPath.ts b/src/app/algorithm/shortest-path/dijkstraShortestPath.ts index cdb17e6..cf4d913 100644 --- a/src/app/algorithm/shortest-path/dijkstraShortestPath.ts +++ b/src/app/algorithm/shortest-path/dijkstraShortestPath.ts @@ -1,7 +1,8 @@ import React from 'react'; import { MinHeap } from '../../data-structure/minHeap/MinHeap'; import { Sleep } from '../../lib/sleepMethod'; -import { IGraphEdge, IGraphNode } from '../../types/shortestPathProps'; +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. @@ -22,96 +23,105 @@ export const findShortestPathUsingDijkstra = async ( destination: number, nodeSizes: number, speedRange: number, - setNodes: React.Dispatch>, + setNodes: React.Dispatch>, edges: IGraphEdge[], setShortestPathEdges: React.Dispatch> ): Promise => { - // initialized a minHeap - const pq = new MinHeap(); - const distances = Array(nodeSizes).fill(Infinity); - const predecessors = Array(nodeSizes).fill(null); // To track the shortest path + 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; + // 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 } - : 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))); + // 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()!; + while (!pq.isEmpty()) { + const { node: currentNodeId } = pq.extractMin()!; - // If we reached the destination, break the loop - if (currentNodeId === destination) { - break; - } + // 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 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); + // 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 } : node)); - }); + // 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); + // 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)); - }); + // 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; + // 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 + // 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 === currentNode ? { ...node, isShortestPath: true, isDestination: false } : node)) + prev.map((node) => (node.id === source ? { ...node, isShortestPath: true, isSource: false } : node)) ); - await Sleep(speedRange); - currentNode = predecessors[currentNode]; + } catch (error) { + if (process.env.NODE_ENV === 'development') { + // eslint-disable-next-line no-console + console.log(error); + } } - setNodes((prev) => - prev.map((node) => (node.id === source ? { ...node, isShortestPath: true, isSource: false } : node)) - ); }; diff --git a/src/app/components/pathFind/PathFind.tsx b/src/app/components/pathFind/PathFind.tsx index c3cde0a..a5ae080 100644 --- a/src/app/components/pathFind/PathFind.tsx +++ b/src/app/components/pathFind/PathFind.tsx @@ -6,11 +6,11 @@ import UniquePath from './UniquePath'; import { gridRowColSize } from '@/app/lib/helpers'; import { clearAllTimeouts } from '@/app/lib/sleepMethod'; import NoOfIslands from './NoOfIslands'; -import DijkstraComponent from './DijkstraComponent'; +import ShortestPath from './ShortestPath'; const PathFind = () => { // define local state - const [buttonType, setButtonType] = useState('dijkstra'); + const [buttonType, setButtonType] = useState('shortest-path'); //dijkstra const [randomKey, setRandomKey] = useState('1'); const [speedRange, setSpeedRange] = useState(100); const [gridSize, setGridSize] = useState<{ rowSize: number; colSize: number }>({ rowSize: 8, colSize: 10 }); @@ -82,7 +82,7 @@ const PathFind = () => { />
    - {buttonType !== 'dijkstra' ? ( + {buttonType !== 'shortest-path' ? (

    Row

    @@ -125,8 +125,8 @@ const PathFind = () => { value={buttonType} className='text-md cursor-pointer rounded-sm border-[1px] border-theme-primary px-[5px] py-[4px] outline-none transition-all duration-200 hover:border-theme-btn-secondary max-[410px]:w-[60%]' > -
    ); diff --git a/src/app/components/pathFind/DijkstraComponent.tsx b/src/app/components/pathFind/ShortestPath.tsx similarity index 58% rename from src/app/components/pathFind/DijkstraComponent.tsx rename to src/app/components/pathFind/ShortestPath.tsx index d371aac..449e98e 100644 --- a/src/app/components/pathFind/DijkstraComponent.tsx +++ b/src/app/components/pathFind/ShortestPath.tsx @@ -1,10 +1,15 @@ /* eslint-disable indent */ 'use client'; +import { + findShortestPathUsingBellmanFord, + handleResetData, +} from '@/app/algorithm/shortest-path/bellmanFordShortestPath'; import { findShortestPathUsingDijkstra } from '@/app/algorithm/shortest-path/dijkstraShortestPath'; import { dijkstraColorsPlate } from '@/app/data/mockData'; import { generateEdges, generateEdgesForASearch, graphData } from '@/app/data/shortestPathData'; 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'; @@ -13,12 +18,13 @@ interface PageProps { useRandomKey: string; speedRange: number; } + // initialized a list of possible ending or destination node for graph-2 -const graphTwoDestinationNodes: number[] = [16, 11, 4, 5, 15]; +const graphTwoDestinationNodes: number[] = [13, 11, 4, 5, 12]; -const DijkstraComponent: React.FC = ({ speedRange, useRandomKey }) => { +const ShortestPath: React.FC = ({ speedRange, useRandomKey }) => { // define component memory - const [nodes, setNodes] = useState([]); + const [nodes, setNodes] = useState([]); const [edges, setEdges] = useState([]); const [shortestPathEdges, setShortestPathEdges] = useState([]); const [isReadyToPerformOperation, setIsReadyToPerformOperation] = useState(false); @@ -28,6 +34,7 @@ const DijkstraComponent: React.FC = ({ speedRange, useRandomKey }) => nodeSizes: -1, }); const [btnLoading, setBtnLoading] = useState(false); + const [variationOfShortestPath, setVariationOfShortestPath] = useState('dijkstra'); // Trigger for component mount as well as dependency changes useEffect(() => { @@ -38,12 +45,8 @@ const DijkstraComponent: React.FC = ({ speedRange, useRandomKey }) => const tempNodes = JSON.parse(JSON.stringify(getGraph.nodes)); const sourceNode = getGraph.source; - const destinationNode = - graphRandomPosition === 1 - ? graphTwoDestinationNodes[ - Math.floor(Math.random() * graphTwoDestinationNodes?.length + 1) % graphTwoDestinationNodes?.length - ] - : getGraph.destination; + 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) => { @@ -78,8 +81,7 @@ const DijkstraComponent: React.FC = ({ speedRange, useRandomKey }) => // Trigger for component mount as well as dependency changes useEffect(() => { if (isReadyToPerformOperation) { - const { source, destination, nodeSizes } = initialNodes; - handleDijkstra({ source, destination, nodeSizes }); + handleReVisualized(variationOfShortestPath); setIsReadyToPerformOperation(false); } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -119,48 +121,107 @@ const DijkstraComponent: React.FC = ({ speedRange, useRandomKey }) => } }; - const handleReVisualized = async () => { + /** + * 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); + } + }; + + /** + * 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((prv) => { - return prv.map((i) => { - return { - ...i, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }; - }); - }); + setNodes(handleResetData(nodes)); setShortestPathEdges([]); - await handleDijkstra({ source, destination, nodeSizes }); + if (type === 'dijkstra') { + await handleDijkstra({ source, destination, nodeSizes }); + } else { + await handleBellmanFord({ 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); @@ -182,7 +243,7 @@ const DijkstraComponent: React.FC = ({ speedRange, useRandomKey }) => y1={sourceNode.y} x2={targetNode.x} y2={targetNode.y} - strokeWidth={'0.5'} + strokeWidth={'0.4'} stroke={isShortestPathEdge ? 'green' : 'black'} /> {/* Draw the weight */} @@ -225,16 +286,30 @@ const DijkstraComponent: React.FC = ({ speedRange, useRandomKey }) => {/* Node Label */} {node.id} + + {`(`} + {node.distance === Number.MAX_SAFE_INTEGER ? '∞' : node.distance} + {`)`} + ))} @@ -248,4 +323,4 @@ const DijkstraComponent: React.FC = ({ speedRange, useRandomKey }) => ); }; -export default DijkstraComponent; +export default ShortestPath; diff --git a/src/app/data/shortestPathData.ts b/src/app/data/shortestPathData.ts index 46132d0..62257b3 100644 --- a/src/app/data/shortestPathData.ts +++ b/src/app/data/shortestPathData.ts @@ -11,22 +11,40 @@ import { IGraphEdge } from '../types/shortestPathProps'; */ export const generateEdges = (): IGraphEdge[] => { - return [ + 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(), weightPosition: { x: -1, y: -1 } }, + { 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(), 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(), weightPosition: { x: -1, y: 85 } }, + { 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(), 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; }; /** @@ -38,7 +56,7 @@ export const generateEdges = (): IGraphEdge[] => { * containing the source node, target node, and weight. */ export const generateEdgesForASearch = (): IGraphEdge[] => { - return [ + 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 } }, @@ -49,7 +67,7 @@ export const generateEdgesForASearch = (): IGraphEdge[] => { { 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: 15, target: 6, weight: generateRandomWeight(), weightPosition: { x: -1, y: 148 } }, + { 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 } }, @@ -57,15 +75,46 @@ export const generateEdgesForASearch = (): IGraphEdge[] => { { 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: 15, 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: 16, target: 11, weight: generateRandomWeight(), weightPosition: { x: -1, y: 58 } }, + { 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: [ @@ -73,101 +122,56 @@ export const graphData = [ id: 0, x: 50, y: 60, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Closer to the top-left + ...nodeProperties, + }, { id: 1, x: 100, y: 20, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Top-middle-left + ...nodeProperties, + }, { id: 2, x: 200, y: 20, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Top-middle-right + ...nodeProperties, + }, { id: 3, x: 300, y: 20, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Top-right + ...nodeProperties, + }, { id: 4, x: 350, y: 60, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Middle-right + ...nodeProperties, + }, { id: 5, x: 300, y: 100, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Middle-right (above node 6) + ...nodeProperties, + }, { id: 6, x: 200, y: 100, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Middle (below node 2) + ...nodeProperties, + }, { id: 7, x: 100, y: 100, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Middle-left + ...nodeProperties, + }, { id: 8, x: 200, y: 60, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Center + ...nodeProperties, + }, ], edges: [...generateEdges()], source: 0, @@ -180,160 +184,90 @@ export const graphData = [ id: 0, x: 50, y: 60, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Closer to the top-left + ...nodeProperties, + }, { id: 1, x: 100, y: 20, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Top-middle-left + ...nodeProperties, + }, { id: 2, x: 100, y: 60, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Top-middle-right + ...nodeProperties, + }, { id: 3, x: 300, y: 20, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Top-right + ...nodeProperties, + }, { id: 4, x: 350, y: 60, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, + ...nodeProperties, }, { id: 5, x: 300, y: 100, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Middle-right (above node 6) + ...nodeProperties, + }, { id: 6, x: 200, y: 150, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, - }, // Middle (below node 2) + ...nodeProperties, + }, { id: 7, x: 70, y: 100, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, + ...nodeProperties, }, { id: 8, x: 200, y: 20, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, + ...nodeProperties, }, { id: 9, x: 130, y: 100, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, + ...nodeProperties, }, { id: 10, x: 100, y: 150, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, + ...nodeProperties, }, { id: 11, x: 250, y: 60, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, + ...nodeProperties, }, { - id: 15, + id: 12, x: 350, y: 150, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, + ...nodeProperties, }, { - id: 16, + id: 13, x: 180, y: 60, - isVisited: false, - isCurrentNode: false, - isShortestPath: false, - isInvalidPath: false, - isDestination: false, - isSource: false, + ...nodeProperties, }, ], edges: [...generateEdgesForASearch()], source: 0, - destination: 16, - nodeSizes: 17, + destination: 13, + nodeSizes: 14, }, ]; 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; +} From 11df6b2cc02bf54ea76aa7d1239cfa07379604d9 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Thu, 10 Oct 2024 17:32:15 +0600 Subject: [PATCH 61/65] update Readme, changelog file --- CHANGELOG.md | 6 ++++++ README.md | 17 +++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 305349c..936fbcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 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. diff --git a/README.md b/README.md index 3f0ae6a..3aa11a6 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,20 @@ 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 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. @@ -49,9 +58,9 @@ npm install ``` 4. Start the development server: - `bash -npm run dev -` + ``` + npm run dev + ``` The application will be available at http://localhost:3000. ### Usage From ca409519ec96a83ec8d6c2ba30f2487f8d601719 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Tue, 15 Oct 2024 12:19:05 +0600 Subject: [PATCH 62/65] shortest path bellman ford algorithm visualization is implemented --- .../shortest-path/bellmanFordShortestPath.ts | 29 +-- .../algorithm/shortest-path/floydWarshall.ts | 191 ++++++++++++++++++ src/app/components/pathFind/ShortestPath.tsx | 67 +++++- src/app/data/dashboardSchemaData.ts | 4 + src/app/data/shortestPathData.ts | 2 +- src/app/lib/graph.ts | 26 +++ 6 files changed, 282 insertions(+), 37 deletions(-) create mode 100644 src/app/algorithm/shortest-path/floydWarshall.ts create mode 100644 src/app/lib/graph.ts diff --git a/src/app/algorithm/shortest-path/bellmanFordShortestPath.ts b/src/app/algorithm/shortest-path/bellmanFordShortestPath.ts index 1a05452..600b246 100644 --- a/src/app/algorithm/shortest-path/bellmanFordShortestPath.ts +++ b/src/app/algorithm/shortest-path/bellmanFordShortestPath.ts @@ -1,3 +1,4 @@ +import { handleResetDataForShortestPath } from '@/app/lib/graph'; import { Sleep } from '@/app/lib/sleepMethod'; import { IGraphEdge } from '@/app/types/shortestPathProps'; import { GraphNodesProps } from '@/app/types/sortingProps'; @@ -32,7 +33,7 @@ export const findShortestPathUsingBellmanFord = async ( ): Promise => { try { // Initialize node distances and predecessors - const tempNodes = handleResetData(nodes); + const tempNodes = handleResetDataForShortestPath(nodes); const predecessors = new Array(nodeSizes).fill(null); // Set initial nodes and add visualization delay @@ -74,7 +75,6 @@ export const findShortestPathUsingBellmanFord = async ( break; } } - setNodes([...tempNodes]); // Check for negative weight cycles @@ -235,28 +235,3 @@ const backtrackShortestPath = async ( prev.map((node) => (node.id === source ? { ...node, isShortestPath: true, isSource: false } : node)) ); }; - -/** - * 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 handleResetData = (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/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/components/pathFind/ShortestPath.tsx b/src/app/components/pathFind/ShortestPath.tsx index 449e98e..038a959 100644 --- a/src/app/components/pathFind/ShortestPath.tsx +++ b/src/app/components/pathFind/ShortestPath.tsx @@ -1,12 +1,11 @@ /* eslint-disable indent */ 'use client'; -import { - findShortestPathUsingBellmanFord, - handleResetData, -} from '@/app/algorithm/shortest-path/bellmanFordShortestPath'; +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'; @@ -157,6 +156,46 @@ const ShortestPath: React.FC = ({ speedRange, useRandomKey }) => { } }; + /** + * 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 @@ -171,12 +210,15 @@ const ShortestPath: React.FC = ({ speedRange, useRandomKey }) => { const handleReVisualized = async (type: string) => { const { source, destination, nodeSizes } = initialNodes; // initialized all at initial state - setNodes(handleResetData(nodes)); + setNodes(handleResetDataForShortestPath(nodes)); setShortestPathEdges([]); + if (type === 'dijkstra') { await handleDijkstra({ source, destination, nodeSizes }); - } else { + } else if (type === 'bellman-ford') { await handleBellmanFord({ source, destination, nodeSizes }); + } else { + await handleFloydWarshall({ source, destination, nodeSizes }); } }; @@ -201,21 +243,28 @@ const ShortestPath: React.FC = ({ speedRange, useRandomKey }) => {
    -
    +
    +
    diff --git a/src/app/data/dashboardSchemaData.ts b/src/app/data/dashboardSchemaData.ts index 1728b56..21ddee1 100644 --- a/src/app/data/dashboardSchemaData.ts +++ b/src/app/data/dashboardSchemaData.ts @@ -78,6 +78,10 @@ export const projectsData: ProjectSchema[] = [ id: uid(), name: 'Dijkstra', }, + { + id: uid(), + name: 'Bellman Ford', + }, { id: uid(), name: 'Rat in maze', diff --git a/src/app/data/shortestPathData.ts b/src/app/data/shortestPathData.ts index 62257b3..dc384a1 100644 --- a/src/app/data/shortestPathData.ts +++ b/src/app/data/shortestPathData.ts @@ -176,7 +176,7 @@ export const graphData = [ edges: [...generateEdges()], source: 0, destination: 4, - nodeSizes: 10, + nodeSizes: 9, }, { nodes: [ 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, + })); +}; From 9c4727467756753ec48e15f687b792432ba4f23b Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Tue, 15 Oct 2024 12:21:57 +0600 Subject: [PATCH 63/65] updated, application version, updated changelog, readme file as well --- CHANGELOG.md | 6 ++++++ README.md | 16 +++++++--------- package.json | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 936fbcc..447ad92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # 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. diff --git a/README.md b/README.md index 3aa11a6..5a9627d 100644 --- a/README.md +++ b/README.md @@ -8,16 +8,12 @@ A Next.js 14 project for visualizing famous algorithms using React, TypeScript, 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 and Dijkstra's algorithms for shortest path calculations. + 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. @@ -58,10 +54,12 @@ npm install ``` 4. Start the development server: - ``` - npm run dev - ``` - The application will be available at http://localhost:3000. + +```sh {"id":"01JA7DBZ9NEQ04ND8EJW745P39"} +npm run dev +``` + +The application will be available at http://localhost:3000. ### Usage diff --git a/package.json b/package.json index 483b4ac..94e4198 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "algorithm-visualizer", - "version": "1.5.5", + "version": "1.6.0", "private": true, "scripts": { "dev": "next dev", From 97603749e324c0d73f043c0f5473e34b89e9b4a9 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Tue, 15 Oct 2024 12:25:03 +0600 Subject: [PATCH 64/65] add floyd warshall algorithm tag in home component --- src/app/data/dashboardSchemaData.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/data/dashboardSchemaData.ts b/src/app/data/dashboardSchemaData.ts index 21ddee1..6aff1d1 100644 --- a/src/app/data/dashboardSchemaData.ts +++ b/src/app/data/dashboardSchemaData.ts @@ -82,6 +82,10 @@ export const projectsData: ProjectSchema[] = [ id: uid(), name: 'Bellman Ford', }, + { + id: uid(), + name: 'Floyd Warshall', + }, { id: uid(), name: 'Rat in maze', From 21845e972dd8e2378cbcd16accc5ae8cdd37acb2 Mon Sep 17 00:00:00 2001 From: AlaminPu1007 Date: Tue, 15 Oct 2024 14:40:29 +0600 Subject: [PATCH 65/65] pathfinding, some UI enhancement improved --- src/app/components/pathFind/NoOfIslands.tsx | 4 +- src/app/components/pathFind/PathFind.tsx | 112 ++++++++++--------- src/app/components/pathFind/ShortestPath.tsx | 4 +- src/app/components/pathFind/UniquePath.tsx | 4 +- 4 files changed, 63 insertions(+), 61 deletions(-) diff --git a/src/app/components/pathFind/NoOfIslands.tsx b/src/app/components/pathFind/NoOfIslands.tsx index aff1d71..87c4104 100644 --- a/src/app/components/pathFind/NoOfIslands.tsx +++ b/src/app/components/pathFind/NoOfIslands.tsx @@ -86,7 +86,7 @@ const NoOfIslands: React.FC = ({ useRandomKey, speedRange, }; return ( -
    + <>
    @@ -142,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 a5ae080..d01c346 100644 --- a/src/app/components/pathFind/PathFind.tsx +++ b/src/app/components/pathFind/PathFind.tsx @@ -10,7 +10,7 @@ import ShortestPath from './ShortestPath'; const PathFind = () => { // define local state - const [buttonType, setButtonType] = useState('shortest-path'); //dijkstra + const [buttonType, setButtonType] = useState('unique-path'); //dijkstra const [randomKey, setRandomKey] = useState('1'); const [speedRange, setSpeedRange] = useState(100); const [gridSize, setGridSize] = useState<{ rowSize: number; colSize: number }>({ rowSize: 8, colSize: 10 }); @@ -66,58 +66,60 @@ const PathFind = () => { }; return ( -
    -
    -
    -
    -

    Speed: {speedRange} (0 to 1500)

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

    Row

    - -
    -
    -

    Col

    - +
    +
    +
    +
    +
    +

    Speed: {speedRange} (0 to 1500)

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

    Row

    + +
    +
    +

    Col

    + +
    -
    - ) : null} + ) : null} +

    Select type