diff --git a/package-lock.json b/package-lock.json index 210d3cd..c952e7e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -74,6 +74,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -2453,6 +2454,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "devOptional": true, + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -2462,6 +2464,7 @@ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz", "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "devOptional": true, + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -2497,6 +2500,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2649,6 +2653,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -2812,7 +2817,8 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "peer": true }, "node_modules/d3-color": { "version": "3.1.0", @@ -2871,6 +2877,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -3088,6 +3095,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.38.0.tgz", "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4054,6 +4062,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "peer": true, "engines": { "node": ">=12" }, @@ -4079,6 +4088,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -4116,6 +4126,7 @@ "version": "19.2.0", "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -4124,6 +4135,7 @@ "version": "19.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -4152,6 +4164,7 @@ "version": "9.2.0", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -4302,7 +4315,8 @@ "node_modules/redux": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "peer": true }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -4661,6 +4675,7 @@ "version": "7.1.12", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz", "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", diff --git a/src/algorithms/dynamic-programming/rodCutting.js b/src/algorithms/dynamic-programming/rodCutting.js new file mode 100644 index 0000000..2c89616 --- /dev/null +++ b/src/algorithms/dynamic-programming/rodCutting.js @@ -0,0 +1,151 @@ +export function rodCuttingTopDown(prices) { + const n = prices.length; + const memo = new Array(n + 1).fill(null); + const steps = []; + + function solve(k) { + steps.push({ + array: [...memo], + currentIndex: k, + readingIndices: [], + priceIndex: null, + message: `Calling solve(k = ${k}).` + }); + + if (k === 0) { + steps.push({ + array: [...memo], + currentIndex: k, + readingIndices: [], + priceIndex: null, + message: `Base case: solve(0) = 0.` + }); + return 0; + } + + if (memo[k] !== null) { + steps.push({ + array: [...memo], + currentIndex: k, + readingIndices: [], + priceIndex: null, + message: `Memo hit: solve(${k}) is already computed as ${memo[k]}.` + }); + return memo[k]; + } + + let currentMaxProfit = -1; + + for (let j = 1; j <= k; j++) { + steps.push({ + array: [...memo], + currentIndex: k, + readingIndices: [], + priceIndex: j - 1, + message: `... for k = ${k}, trying cut j = ${j}. Need to find solve(${k - j}).` + }); + + const remainingProfit = solve(k - j); + let profit = prices[j - 1] + remainingProfit; + + steps.push({ + array: [...memo], + currentIndex: k, + readingIndices: [k - j], + priceIndex: j - 1, + message: `... cut j = ${j} gives profit = prices[${j - 1}] (${prices[j-1]}) + solve(${k - j}) (${remainingProfit}) = ${profit}.` + }); + + if (profit > currentMaxProfit) { + currentMaxProfit = profit; + } + } + + memo[k] = currentMaxProfit; + + steps.push({ + array: [...memo], + currentIndex: k, + readingIndices: [], + priceIndex: null, + message: `Computed solve(${k}) = ${currentMaxProfit}. Storing in memo[${k}].` + }); + + return currentMaxProfit; + } + + const result = solve(n); + + steps.push({ + array: [...memo], + currentIndex: n, + readingIndices: [], + priceIndex: null, + message: `Computation complete. Max profit for a rod of length ${n} is ${result}.` + }); + + return { steps, result }; +} + + +export function rodCuttingBottomUp(prices) { + const n = prices.length; + const dp = new Array(n + 1).fill(0); + const steps = []; + + steps.push({ + array: [...dp], + currentIndex: 0, + readingIndices: [], + priceIndex: null, + message: `Base case: Max profit for length 0 is 0. dp[0] = 0.` + }); + + for (let i = 1; i <= n; i++) { + let currentMaxProfit = -1; + + steps.push({ + array: [...dp], + currentIndex: i, + readingIndices: [], + priceIndex: null, + message: `Calculating max profit for length i = ${i}.` + }); + + for (let j = 1; j <= i; j++) { + let profit = prices[j - 1] + dp[i - j]; + + steps.push({ + array: [...dp], + currentIndex: i, + readingIndices: [i - j], + priceIndex: j - 1, + message: `... trying cut j = ${j}. Profit = prices[${j - 1}] (${prices[j-1]}) + dp[${i - j}] (${dp[i-j]}) = ${profit}.` + }); + + if (profit > currentMaxProfit) { + currentMaxProfit = profit; + } + } + + dp[i] = currentMaxProfit; + + steps.push({ + array: [...dp], + currentIndex: i, + readingIndices: [], + priceIndex: null, + message: `Max profit for length ${i} is ${currentMaxProfit}. Storing in dp[${i}].` + }); + } + + steps.push({ + array: [...dp], + currentIndex: n, + readingIndices: [], + priceIndex: null, + message: `Computation complete. Max profit for a rod of length ${n} is ${dp[n]}.` + }); + + return { steps, result: dp[n] }; +} \ No newline at end of file diff --git a/src/components/dynamic-programming/RodCuttingVisualizer.jsx b/src/components/dynamic-programming/RodCuttingVisualizer.jsx new file mode 100644 index 0000000..b634415 --- /dev/null +++ b/src/components/dynamic-programming/RodCuttingVisualizer.jsx @@ -0,0 +1,306 @@ +import React, { useState, useMemo, useEffect, useRef } from "react"; +import { rodCuttingTopDown, rodCuttingBottomUp } from "@/algorithms/dynamic-programming/rodCutting"; + +const PricesArray = ({ prices, currentIndex }) => { + return ( +
+ Given a rod of length n and an array of prices for rods of length 1 to n, + determine the maximum value obtainable by cutting up the rod and selling the pieces. +
++ Step {currentStep + 1} / {steps.length} +
+Current Action
++ {currentState.message || 'Starting computation...'} +
++ 🎉 Final Result: Max Profit for length {n} = {finalResult} +
+
+ {JSON.stringify(currentState, null, 2)}
+
+ Welcome to the Rod Cutting DP Visualizer!
+Enter a list of Prices and select an Algorithm above.
+Click Re-Visualize to begin.
+