diff --git a/frontend/src/app/patterns/[slug]/page.tsx b/frontend/src/app/patterns/[slug]/page.tsx index 4f34d29..32bdb84 100644 --- a/frontend/src/app/patterns/[slug]/page.tsx +++ b/frontend/src/app/patterns/[slug]/page.tsx @@ -14,7 +14,7 @@ import { RelatedPatterns, } from "@/components/patterns"; import { PatternVisualization } from "@/components/visualization"; -import { TwoPointersVisualization, PrefixSumVisualization, LinkedListVisualization, MonotonicStackVisualization, TreeTraversalVisualization, BFSVisualization, DFSVisualization } from "@/components/visualizations-new"; +import { TwoPointersVisualization, PrefixSumVisualization, LinkedListVisualization, MonotonicStackVisualization, TreeTraversalVisualization, BFSVisualization, DFSVisualization, CoinChangeVisualization } from "@/components/visualizations-new"; import { twoSumAlgorithm } from "@/content/algorithms/two-sum"; import { slidingWindowAlgorithm } from "@/content/algorithms/sliding-window"; import { binarySearchAlgorithm } from "@/content/algorithms/binary-search"; @@ -25,6 +25,7 @@ import { monotonicStackAlgorithm } from "@/content/algorithms/monotonic-stack"; import { treeTraversalAlgorithm } from "@/content/algorithms/tree-traversal"; import { bfsAlgorithm } from "@/content/algorithms/bfs"; import { dfsAlgorithm } from "@/content/algorithms/dfs"; +import { coinChangeAlgorithm } from "@/content/algorithms/coin-change"; interface PageProps { params: Promise<{ slug: string }>; @@ -137,6 +138,8 @@ export default async function PatternDetailPage({ params }: PageProps) { ) : slug === "dfs" ? ( + ) : slug === "dynamic-programming" ? ( + ) : pattern.visualization_examples && pattern.visualization_examples.length > 0 ? ( diff --git a/frontend/src/components/visualizations-new/algorithms/coin-change.tsx b/frontend/src/components/visualizations-new/algorithms/coin-change.tsx new file mode 100644 index 0000000..c61fb1b --- /dev/null +++ b/frontend/src/components/visualizations-new/algorithms/coin-change.tsx @@ -0,0 +1,81 @@ +'use client'; + +import { useVisualization } from '@/lib/visualizations/use-visualization'; +import type { AlgorithmDefinition } from '@/lib/visualizations/types'; +import { VisualizationContainer } from '../core/visualization-container'; +import { ArrayView } from '../data-structures/array-view'; +import { GridView } from '../data-structures/grid-view'; +import { CalculationBubble } from '../primitives/calculation-bubble'; + +interface CoinChangeVisualizationProps { + algorithm: AlgorithmDefinition; + className?: string; +} + +export function CoinChangeVisualization({ + algorithm, + className, +}: CoinChangeVisualizationProps) { + const { + currentStep, + currentStepIndex, + totalSteps, + playback, + controls, + currentPhase, + progress, + } = useVisualization(algorithm); + + const { dataState } = currentStep; + const coinsArray = dataState.arrays.find((a) => a.id === 'coins') ?? null; + const dpGrid = dataState.grids?.[0] ?? null; + const calculation = dataState.calculations[0] ?? null; + const gridPointers = dataState.gridPointers ?? []; + + return ( + +
+ {/* Calculation bubble */} +
+ +
+ + {/* Coins array */} + {coinsArray && ( + + )} + + {/* DP Table grid */} + {dpGrid && ( + + )} +
+
+ ); +} diff --git a/frontend/src/components/visualizations-new/data-structures/grid-view.tsx b/frontend/src/components/visualizations-new/data-structures/grid-view.tsx new file mode 100644 index 0000000..127f9f0 --- /dev/null +++ b/frontend/src/components/visualizations-new/data-structures/grid-view.tsx @@ -0,0 +1,149 @@ +"use client"; + +import { cn } from "@/lib/utils"; +import type { GridState, GridPointerState } from "@/lib/visualizations/types"; +import { GridCell } from "../primitives/grid-cell"; + +interface GridViewProps { + grid: GridState; + pointers?: GridPointerState[]; + cellSize?: "sm" | "md" | "lg"; + showColLabels?: boolean; + showRowLabels?: boolean; + className?: string; +} + +const CELL_WIDTHS = { + sm: 40, + md: 56, + lg: 72, +} as const; + +const CELL_GAPS = { + sm: 2, + md: 4, + lg: 6, +} as const; + +const POINTER_COLORS = { + current: "border-blue-500 text-blue-500", + dependency: "border-amber-500 text-amber-500", + result: "border-green-500 text-green-500", + default: "border-[var(--primary)] text-[var(--primary)]", +} as const; + +export function GridView({ + grid, + pointers = [], + cellSize = "sm", + showColLabels = true, + showRowLabels = false, + className, +}: GridViewProps) { + const cellWidth = CELL_WIDTHS[cellSize]; + const gap = CELL_GAPS[cellSize]; + + // For 1D grids (single row), flatten for easier rendering + const is1D = grid.cells.length === 1; + + // Calculate pointer positions for highlighting + const pointerPositions = new Map(); + for (const pointer of pointers) { + const key = `${pointer.row}-${pointer.col}`; + pointerPositions.set(key, pointer); + } + + return ( +
+ {grid.label && ( + + {grid.label} + + )} + + {/* Column labels */} + {showColLabels && grid.colLabels && ( +
+ {grid.colLabels.map((label, idx) => ( +
+ {label} +
+ ))} +
+ )} + + {/* Grid rows */} + {is1D ? ( + // 1D grid (single row) +
+
+ {grid.cells[0].map((cell, colIdx) => { + const pointer = pointerPositions.get(`0-${colIdx}`); + return ( +
+ {pointer && ( +
+ {pointer.name} +
+ )} + +
+ ); + })} +
+
+ ) : ( + // 2D grid +
+ {grid.cells.map((row, rowIdx) => ( +
+ {/* Row label */} + {showRowLabels && grid.rowLabels && ( +
+ {grid.rowLabels[rowIdx]} +
+ )} + {/* Row cells */} + {row.map((cell, colIdx) => { + const pointer = pointerPositions.get(`${rowIdx}-${colIdx}`); + return ( +
+ {pointer && ( +
+ {pointer.name} +
+ )} + +
+ ); + })} +
+ ))} +
+ )} +
+ ); +} diff --git a/frontend/src/components/visualizations-new/index.ts b/frontend/src/components/visualizations-new/index.ts index 9e7e61e..ec1c71b 100644 --- a/frontend/src/components/visualizations-new/index.ts +++ b/frontend/src/components/visualizations-new/index.ts @@ -14,6 +14,7 @@ export { LinkedListPointer } from "./primitives/linked-list-pointer"; export { StackElement } from "./primitives/stack-element"; export { QueueElement } from "./primitives/queue-element"; export { TreeNode } from "./primitives/tree-node"; +export { GridCell } from "./primitives/grid-cell"; // Data structures export { ArrayView } from "./data-structures/array-view"; @@ -21,6 +22,7 @@ export { LinkedListView } from "./data-structures/linked-list-view"; export { StackView } from "./data-structures/stack-view"; export { QueueView } from "./data-structures/queue-view"; export { BinaryTreeView } from "./data-structures/binary-tree-view"; +export { GridView } from "./data-structures/grid-view"; // Algorithm visualizations export { MonotonicStackVisualization } from "./algorithms/monotonic-stack"; @@ -30,3 +32,4 @@ export { BFSVisualization } from "./algorithms/bfs"; export { DFSVisualization } from "./algorithms/dfs"; export { TwoPointersVisualization } from "./algorithms/two-pointers"; export { LinkedListVisualization } from "./algorithms/linked-list"; +export { CoinChangeVisualization } from "./algorithms/coin-change"; diff --git a/frontend/src/components/visualizations-new/primitives/grid-cell.tsx b/frontend/src/components/visualizations-new/primitives/grid-cell.tsx new file mode 100644 index 0000000..b814e05 --- /dev/null +++ b/frontend/src/components/visualizations-new/primitives/grid-cell.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { motion } from "framer-motion"; +import { cn } from "@/lib/utils"; +import type { GridCellState } from "@/lib/visualizations/types"; + +interface GridCellProps { + cell: GridCellState; + size?: "sm" | "md" | "lg"; + className?: string; +} + +const SIZE_CLASSES = { + sm: "w-10 h-10 text-sm", + md: "w-14 h-14 text-base", + lg: "w-18 h-18 text-lg", +} as const; + +const STATE_CLASSES = { + normal: "bg-[var(--muted)] border-[var(--border)] text-[var(--foreground)]", + highlighted: "bg-[var(--primary)]/20 border-[var(--primary)] text-[var(--primary)]", + dimmed: "bg-[var(--muted)]/50 border-[var(--border)]/50 text-[var(--muted-foreground)] opacity-30", + success: "bg-green-500/20 border-green-500 text-green-500", + comparing: "bg-amber-500/20 border-amber-500 text-amber-500", + computing: "bg-blue-500/20 border-blue-500 text-blue-500", +} as const; + +export function GridCell({ + cell, + size = "md", + className, +}: GridCellProps) { + const isComputing = cell.state === "computing"; + + return ( + + {cell.value === Infinity ? "∞" : cell.value} + + ); +} diff --git a/frontend/src/content/algorithms/coin-change.ts b/frontend/src/content/algorithms/coin-change.ts new file mode 100644 index 0000000..6916b19 --- /dev/null +++ b/frontend/src/content/algorithms/coin-change.ts @@ -0,0 +1,1111 @@ +import type { AlgorithmDefinition } from '@/lib/visualizations/types'; + +// Helper to create a DP cell +function dpCell( + col: number, + value: number | string, + state: 'normal' | 'highlighted' | 'comparing' | 'computing' | 'success' | 'dimmed' = 'normal' +) { + return { + id: `dp-${col}`, + value, + row: 0, + col, + state, + }; +} + +// Helper to create coins array element +function coinElement( + index: number, + value: number, + state: 'normal' | 'highlighted' | 'dimmed' | 'success' | 'comparing' = 'normal' +) { + return { value, index, state }; +} + +export const coinChangeAlgorithm: AlgorithmDefinition = { + id: 'coin-change', + title: 'Coin Change - Dynamic Programming', + slug: 'coin-change', + pattern: { + name: 'Dynamic Programming', + description: + 'Break down problems into overlapping subproblems and build solutions from optimal substructure.', + }, + problemStatement: + 'Given coins of different denominations and a total amount, find the minimum number of coins needed to make that amount. If it cannot be made, return -1.', + intuition: + 'For each amount from 1 to target, we compute the minimum coins needed by checking all coin denominations. dp[i] = min coins needed for amount i. For each coin, if coin <= i, we can use it: dp[i] = min(dp[i], dp[i-coin] + 1).', + code: { + language: 'python', + code: `def coin_change(coins: list[int], amount: int) -> int: + # Initialize dp with infinity; dp[0] = 0 + dp = [float('inf')] * (amount + 1) + dp[0] = 0 + + # Build solution for each amount + for i in range(1, amount + 1): + for coin in coins: + if i - coin >= 0: + dp[i] = min(dp[i], dp[i - coin] + 1) + + return dp[amount] if dp[amount] != float('inf') else -1`, + }, + initialExample: { + input: { coins: [1, 2, 5], amount: 11 }, + expected: 3, + }, + steps: [ + // Phase 1: Problem (2 steps) + { + id: 'problem-1', + phase: 'problem', + explanation: + 'Given coins [1, 2, 5] and amount 11, find the minimum number of coins to make exactly 11.', + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'normal'), + coinElement(1, 2, 'normal'), + coinElement(2, 5, 'normal'), + ], + }, + ], + pointers: [], + variables: [ + { id: 'amount', name: 'amount', value: 11 }, + ], + calculations: [], + }, + }, + { + id: 'problem-2', + phase: 'problem', + explanation: + 'A brute-force approach tries all combinations - exponential time O(S^n). Can we do better by reusing solutions to smaller amounts?', + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'normal'), + coinElement(1, 2, 'normal'), + coinElement(2, 5, 'normal'), + ], + }, + ], + pointers: [], + variables: [ + { id: 'amount', name: 'amount', value: 11 }, + ], + calculations: [], + }, + }, + + // Phase 2: Intuition (4 steps) + { + id: 'intuition-1', + phase: 'intuition', + explanation: + 'Key insight: If we know the minimum coins for amount 10, we can make 11 with one more coin of value 1. This is optimal substructure.', + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'highlighted'), + coinElement(1, 2, 'normal'), + coinElement(2, 5, 'normal'), + ], + }, + ], + pointers: [], + variables: [ + { id: 'amount', name: 'amount', value: 11 }, + ], + calculations: [ + { id: 'calc-1', expression: 'dp[11] = dp[10] + 1', result: '?', position: 'above' }, + ], + }, + }, + { + id: 'intuition-2', + phase: 'intuition', + explanation: + 'We can also make 11 from amount 9 (using coin 2) or amount 6 (using coin 5). We take the minimum of all options.', + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'comparing'), + coinElement(1, 2, 'comparing'), + coinElement(2, 5, 'comparing'), + ], + }, + ], + pointers: [], + variables: [ + { id: 'amount', name: 'amount', value: 11 }, + ], + calculations: [ + { id: 'calc-1', expression: 'min(dp[10]+1, dp[9]+1, dp[6]+1)', result: '?', position: 'above' }, + ], + }, + }, + { + id: 'intuition-3', + phase: 'intuition', + explanation: + 'Build solutions bottom-up: start from amount 0, compute minimum coins for each amount 1, 2, 3, ... up to target.', + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'normal'), + coinElement(1, 2, 'normal'), + coinElement(2, 5, 'normal'), + ], + }, + ], + pointers: [], + variables: [], + calculations: [], + grids: [ + { + id: 'dp', + label: 'DP Table (min coins for each amount)', + colLabels: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + cells: [[ + dpCell(0, '?'), + dpCell(1, '?'), + dpCell(2, '?'), + dpCell(3, '?'), + dpCell(4, '?'), + dpCell(5, '?'), + dpCell(6, '?'), + dpCell(7, '?'), + dpCell(8, '?'), + dpCell(9, '?'), + dpCell(10, '?'), + dpCell(11, '?'), + ]], + }, + ], + }, + }, + { + id: 'intuition-4', + phase: 'intuition', + explanation: + 'State transition: dp[i] = min(dp[i], dp[i - coin] + 1) for each valid coin. Base case: dp[0] = 0 (zero coins for amount zero).', + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'normal'), + coinElement(1, 2, 'normal'), + coinElement(2, 5, 'normal'), + ], + }, + ], + pointers: [], + variables: [], + calculations: [ + { id: 'calc-1', expression: 'dp[i] = min(dp[i], dp[i-coin] + 1)', result: '', position: 'above' }, + ], + grids: [ + { + id: 'dp', + label: 'DP Table', + colLabels: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + cells: [[ + dpCell(0, 0, 'success'), + dpCell(1, '?'), + dpCell(2, '?'), + dpCell(3, '?'), + dpCell(4, '?'), + dpCell(5, '?'), + dpCell(6, '?'), + dpCell(7, '?'), + dpCell(8, '?'), + dpCell(9, '?'), + dpCell(10, '?'), + dpCell(11, '?'), + ]], + }, + ], + }, + }, + + // Phase 3: Pattern (2 steps) + { + id: 'pattern-1', + phase: 'pattern', + explanation: + 'Dynamic Programming pattern: Define state (dp[i] = min coins for amount i), identify transition (try each coin), set base case (dp[0] = 0).', + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'normal'), + coinElement(1, 2, 'normal'), + coinElement(2, 5, 'normal'), + ], + }, + ], + pointers: [], + variables: [ + { id: 'state', name: 'State', value: 'dp[i] = min coins' }, + ], + calculations: [], + }, + }, + { + id: 'pattern-2', + phase: 'pattern', + explanation: + 'Time: O(amount * coins), Space: O(amount). Each amount computed once, checking all coins.', + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'normal'), + coinElement(1, 2, 'normal'), + coinElement(2, 5, 'normal'), + ], + }, + ], + pointers: [], + variables: [ + { id: 'time', name: 'Time', value: 'O(amount * coins)' }, + { id: 'space', name: 'Space', value: 'O(amount)' }, + ], + calculations: [], + }, + }, + + // Phase 4: Code walkthrough (4 steps) + { + id: 'code-1', + phase: 'code', + explanation: + 'Initialize dp array with infinity (impossible). dp[0] = 0 because we need 0 coins for amount 0.', + codeLine: 3, + codeHighlightLines: [2, 3, 4], + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'normal'), + coinElement(1, 2, 'normal'), + coinElement(2, 5, 'normal'), + ], + }, + ], + pointers: [], + variables: [], + calculations: [], + grids: [ + { + id: 'dp', + label: 'DP Table', + colLabels: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + cells: [[ + dpCell(0, 0, 'highlighted'), + dpCell(1, '∞'), + dpCell(2, '∞'), + dpCell(3, '∞'), + dpCell(4, '∞'), + dpCell(5, '∞'), + dpCell(6, '∞'), + dpCell(7, '∞'), + dpCell(8, '∞'), + dpCell(9, '∞'), + dpCell(10, '∞'), + dpCell(11, '∞'), + ]], + }, + ], + }, + }, + { + id: 'code-2', + phase: 'code', + explanation: + 'Outer loop: iterate through amounts 1 to target. For each amount, we compute the minimum coins.', + codeLine: 7, + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'normal'), + coinElement(1, 2, 'normal'), + coinElement(2, 5, 'normal'), + ], + }, + ], + pointers: [], + variables: [ + { id: 'i', name: 'i', value: '1 to amount' }, + ], + calculations: [], + }, + }, + { + id: 'code-3', + phase: 'code', + explanation: + 'Inner loop: try each coin. If coin fits (i - coin >= 0), update dp[i] with minimum.', + codeLine: 8, + codeHighlightLines: [8, 9, 10], + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'comparing'), + coinElement(1, 2, 'comparing'), + coinElement(2, 5, 'comparing'), + ], + }, + ], + pointers: [], + variables: [], + calculations: [ + { id: 'calc-1', expression: 'dp[i] = min(dp[i], dp[i-coin] + 1)', result: '', position: 'above' }, + ], + }, + }, + { + id: 'code-4', + phase: 'code', + explanation: + 'Return dp[amount] if reachable, else -1. Infinity means the amount cannot be made with given coins.', + codeLine: 12, + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'normal'), + coinElement(1, 2, 'normal'), + coinElement(2, 5, 'normal'), + ], + }, + ], + pointers: [], + variables: [], + calculations: [], + }, + }, + + // Phase 5: Execution (~25 steps) + // dp[0] = 0 (base case) + { + id: 'exec-1', + phase: 'execution', + explanation: 'Initialize: dp[0] = 0 (base case), all others = infinity.', + codeLine: 4, + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'normal'), + coinElement(1, 2, 'normal'), + coinElement(2, 5, 'normal'), + ], + }, + ], + pointers: [], + variables: [ + { id: 'i', name: 'i', value: '-' }, + ], + calculations: [], + grids: [ + { + id: 'dp', + label: 'DP Table', + colLabels: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + cells: [[ + dpCell(0, 0, 'success'), + dpCell(1, '∞'), + dpCell(2, '∞'), + dpCell(3, '∞'), + dpCell(4, '∞'), + dpCell(5, '∞'), + dpCell(6, '∞'), + dpCell(7, '∞'), + dpCell(8, '∞'), + dpCell(9, '∞'), + dpCell(10, '∞'), + dpCell(11, '∞'), + ]], + }, + ], + }, + }, + + // Compute dp[1] + { + id: 'exec-2', + phase: 'execution', + explanation: 'Compute dp[1]: Try coin 1. dp[1] = min(∞, dp[0] + 1) = min(∞, 1) = 1.', + codeLine: 10, + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'highlighted'), + coinElement(1, 2, 'dimmed'), + coinElement(2, 5, 'dimmed'), + ], + }, + ], + pointers: [], + variables: [ + { id: 'i', name: 'i', value: 1 }, + { id: 'coin', name: 'coin', value: 1 }, + ], + calculations: [ + { id: 'calc-1', expression: 'min(∞, dp[0]+1)', result: '1', position: 'above' }, + ], + grids: [ + { + id: 'dp', + label: 'DP Table', + colLabels: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + cells: [[ + dpCell(0, 0, 'comparing'), + dpCell(1, 1, 'computing'), + dpCell(2, '∞'), + dpCell(3, '∞'), + dpCell(4, '∞'), + dpCell(5, '∞'), + dpCell(6, '∞'), + dpCell(7, '∞'), + dpCell(8, '∞'), + dpCell(9, '∞'), + dpCell(10, '∞'), + dpCell(11, '∞'), + ]], + }, + ], + gridPointers: [ + { id: 'ptr-i', name: 'i', row: 0, col: 1, color: 'current' }, + { id: 'ptr-dep', name: 'i-1', row: 0, col: 0, color: 'dependency' }, + ], + }, + }, + + // Compute dp[2] + { + id: 'exec-3', + phase: 'execution', + explanation: 'Compute dp[2]: Try coin 1 -> dp[1]+1=2. Try coin 2 -> dp[0]+1=1. dp[2] = min(2, 1) = 1.', + codeLine: 10, + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'comparing'), + coinElement(1, 2, 'highlighted'), + coinElement(2, 5, 'dimmed'), + ], + }, + ], + pointers: [], + variables: [ + { id: 'i', name: 'i', value: 2 }, + { id: 'coin', name: 'coin', value: 2 }, + ], + calculations: [ + { id: 'calc-1', expression: 'min(dp[1]+1, dp[0]+1)', result: '1', position: 'above' }, + ], + grids: [ + { + id: 'dp', + label: 'DP Table', + colLabels: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + cells: [[ + dpCell(0, 0, 'comparing'), + dpCell(1, 1, 'success'), + dpCell(2, 1, 'computing'), + dpCell(3, '∞'), + dpCell(4, '∞'), + dpCell(5, '∞'), + dpCell(6, '∞'), + dpCell(7, '∞'), + dpCell(8, '∞'), + dpCell(9, '∞'), + dpCell(10, '∞'), + dpCell(11, '∞'), + ]], + }, + ], + gridPointers: [ + { id: 'ptr-i', name: 'i', row: 0, col: 2, color: 'current' }, + ], + }, + }, + + // Compute dp[3] + { + id: 'exec-4', + phase: 'execution', + explanation: 'Compute dp[3]: coin 1 -> dp[2]+1=2, coin 2 -> dp[1]+1=2. dp[3] = 2.', + codeLine: 10, + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'comparing'), + coinElement(1, 2, 'comparing'), + coinElement(2, 5, 'dimmed'), + ], + }, + ], + pointers: [], + variables: [ + { id: 'i', name: 'i', value: 3 }, + ], + calculations: [ + { id: 'calc-1', expression: 'min(dp[2]+1, dp[1]+1)', result: '2', position: 'above' }, + ], + grids: [ + { + id: 'dp', + label: 'DP Table', + colLabels: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + cells: [[ + dpCell(0, 0, 'normal'), + dpCell(1, 1, 'comparing'), + dpCell(2, 1, 'comparing'), + dpCell(3, 2, 'computing'), + dpCell(4, '∞'), + dpCell(5, '∞'), + dpCell(6, '∞'), + dpCell(7, '∞'), + dpCell(8, '∞'), + dpCell(9, '∞'), + dpCell(10, '∞'), + dpCell(11, '∞'), + ]], + }, + ], + gridPointers: [ + { id: 'ptr-i', name: 'i', row: 0, col: 3, color: 'current' }, + ], + }, + }, + + // Compute dp[4] + { + id: 'exec-5', + phase: 'execution', + explanation: 'Compute dp[4]: coin 1 -> dp[3]+1=3, coin 2 -> dp[2]+1=2. dp[4] = 2.', + codeLine: 10, + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'comparing'), + coinElement(1, 2, 'highlighted'), + coinElement(2, 5, 'dimmed'), + ], + }, + ], + pointers: [], + variables: [ + { id: 'i', name: 'i', value: 4 }, + ], + calculations: [ + { id: 'calc-1', expression: 'min(dp[3]+1, dp[2]+1)', result: '2', position: 'above' }, + ], + grids: [ + { + id: 'dp', + label: 'DP Table', + colLabels: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + cells: [[ + dpCell(0, 0, 'normal'), + dpCell(1, 1, 'normal'), + dpCell(2, 1, 'comparing'), + dpCell(3, 2, 'success'), + dpCell(4, 2, 'computing'), + dpCell(5, '∞'), + dpCell(6, '∞'), + dpCell(7, '∞'), + dpCell(8, '∞'), + dpCell(9, '∞'), + dpCell(10, '∞'), + dpCell(11, '∞'), + ]], + }, + ], + gridPointers: [ + { id: 'ptr-i', name: 'i', row: 0, col: 4, color: 'current' }, + ], + }, + }, + + // Compute dp[5] - First time using coin 5! + { + id: 'exec-6', + phase: 'execution', + explanation: 'Compute dp[5]: coin 1 -> 3, coin 2 -> 3, coin 5 -> dp[0]+1=1. dp[5] = 1!', + codeLine: 10, + decision: { + question: 'Which coin gives minimum?', + answer: 'Coin 5: dp[0]+1 = 1', + action: 'Use one coin of 5', + }, + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'comparing'), + coinElement(1, 2, 'comparing'), + coinElement(2, 5, 'highlighted'), + ], + }, + ], + pointers: [], + variables: [ + { id: 'i', name: 'i', value: 5 }, + ], + calculations: [ + { id: 'calc-1', expression: 'min(3, 3, dp[0]+1)', result: '1', position: 'above' }, + ], + grids: [ + { + id: 'dp', + label: 'DP Table', + colLabels: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + cells: [[ + dpCell(0, 0, 'comparing'), + dpCell(1, 1, 'normal'), + dpCell(2, 1, 'normal'), + dpCell(3, 2, 'normal'), + dpCell(4, 2, 'success'), + dpCell(5, 1, 'computing'), + dpCell(6, '∞'), + dpCell(7, '∞'), + dpCell(8, '∞'), + dpCell(9, '∞'), + dpCell(10, '∞'), + dpCell(11, '∞'), + ]], + }, + ], + gridPointers: [ + { id: 'ptr-i', name: 'i', row: 0, col: 5, color: 'current' }, + { id: 'ptr-dep', name: 'i-5', row: 0, col: 0, color: 'dependency' }, + ], + }, + }, + + // Compute dp[6] + { + id: 'exec-7', + phase: 'execution', + explanation: 'Compute dp[6]: coin 1 -> dp[5]+1=2, coin 2 -> dp[4]+1=3, coin 5 -> dp[1]+1=2. dp[6] = 2.', + codeLine: 10, + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'highlighted'), + coinElement(1, 2, 'comparing'), + coinElement(2, 5, 'comparing'), + ], + }, + ], + pointers: [], + variables: [ + { id: 'i', name: 'i', value: 6 }, + ], + calculations: [ + { id: 'calc-1', expression: 'min(dp[5]+1, dp[4]+1, dp[1]+1)', result: '2', position: 'above' }, + ], + grids: [ + { + id: 'dp', + label: 'DP Table', + colLabels: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + cells: [[ + dpCell(0, 0, 'normal'), + dpCell(1, 1, 'comparing'), + dpCell(2, 1, 'normal'), + dpCell(3, 2, 'normal'), + dpCell(4, 2, 'comparing'), + dpCell(5, 1, 'comparing'), + dpCell(6, 2, 'computing'), + dpCell(7, '∞'), + dpCell(8, '∞'), + dpCell(9, '∞'), + dpCell(10, '∞'), + dpCell(11, '∞'), + ]], + }, + ], + gridPointers: [ + { id: 'ptr-i', name: 'i', row: 0, col: 6, color: 'current' }, + ], + }, + }, + + // Compute dp[7] + { + id: 'exec-8', + phase: 'execution', + explanation: 'Compute dp[7]: coin 1 -> 3, coin 2 -> dp[5]+1=2, coin 5 -> dp[2]+1=2. dp[7] = 2.', + codeLine: 10, + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'comparing'), + coinElement(1, 2, 'highlighted'), + coinElement(2, 5, 'highlighted'), + ], + }, + ], + pointers: [], + variables: [ + { id: 'i', name: 'i', value: 7 }, + ], + calculations: [ + { id: 'calc-1', expression: 'min(3, dp[5]+1, dp[2]+1)', result: '2', position: 'above' }, + ], + grids: [ + { + id: 'dp', + label: 'DP Table', + colLabels: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + cells: [[ + dpCell(0, 0, 'normal'), + dpCell(1, 1, 'normal'), + dpCell(2, 1, 'comparing'), + dpCell(3, 2, 'normal'), + dpCell(4, 2, 'normal'), + dpCell(5, 1, 'comparing'), + dpCell(6, 2, 'success'), + dpCell(7, 2, 'computing'), + dpCell(8, '∞'), + dpCell(9, '∞'), + dpCell(10, '∞'), + dpCell(11, '∞'), + ]], + }, + ], + gridPointers: [ + { id: 'ptr-i', name: 'i', row: 0, col: 7, color: 'current' }, + ], + }, + }, + + // Compute dp[8] + { + id: 'exec-9', + phase: 'execution', + explanation: 'Compute dp[8]: coin 1 -> 3, coin 2 -> 3, coin 5 -> dp[3]+1=3. dp[8] = 3.', + codeLine: 10, + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'comparing'), + coinElement(1, 2, 'comparing'), + coinElement(2, 5, 'comparing'), + ], + }, + ], + pointers: [], + variables: [ + { id: 'i', name: 'i', value: 8 }, + ], + calculations: [ + { id: 'calc-1', expression: 'min(dp[7]+1, dp[6]+1, dp[3]+1)', result: '3', position: 'above' }, + ], + grids: [ + { + id: 'dp', + label: 'DP Table', + colLabels: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + cells: [[ + dpCell(0, 0, 'normal'), + dpCell(1, 1, 'normal'), + dpCell(2, 1, 'normal'), + dpCell(3, 2, 'comparing'), + dpCell(4, 2, 'normal'), + dpCell(5, 1, 'normal'), + dpCell(6, 2, 'comparing'), + dpCell(7, 2, 'comparing'), + dpCell(8, 3, 'computing'), + dpCell(9, '∞'), + dpCell(10, '∞'), + dpCell(11, '∞'), + ]], + }, + ], + gridPointers: [ + { id: 'ptr-i', name: 'i', row: 0, col: 8, color: 'current' }, + ], + }, + }, + + // Compute dp[9] + { + id: 'exec-10', + phase: 'execution', + explanation: 'Compute dp[9]: coin 1 -> 4, coin 2 -> dp[7]+1=3, coin 5 -> dp[4]+1=3. dp[9] = 3.', + codeLine: 10, + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'comparing'), + coinElement(1, 2, 'highlighted'), + coinElement(2, 5, 'highlighted'), + ], + }, + ], + pointers: [], + variables: [ + { id: 'i', name: 'i', value: 9 }, + ], + calculations: [ + { id: 'calc-1', expression: 'min(4, dp[7]+1, dp[4]+1)', result: '3', position: 'above' }, + ], + grids: [ + { + id: 'dp', + label: 'DP Table', + colLabels: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + cells: [[ + dpCell(0, 0, 'normal'), + dpCell(1, 1, 'normal'), + dpCell(2, 1, 'normal'), + dpCell(3, 2, 'normal'), + dpCell(4, 2, 'comparing'), + dpCell(5, 1, 'normal'), + dpCell(6, 2, 'normal'), + dpCell(7, 2, 'comparing'), + dpCell(8, 3, 'success'), + dpCell(9, 3, 'computing'), + dpCell(10, '∞'), + dpCell(11, '∞'), + ]], + }, + ], + gridPointers: [ + { id: 'ptr-i', name: 'i', row: 0, col: 9, color: 'current' }, + ], + }, + }, + + // Compute dp[10] + { + id: 'exec-11', + phase: 'execution', + explanation: 'Compute dp[10]: coin 1 -> 4, coin 2 -> 4, coin 5 -> dp[5]+1=2. dp[10] = 2!', + codeLine: 10, + decision: { + question: 'Which coin gives minimum?', + answer: 'Coin 5: dp[5]+1 = 2', + action: 'Two coins of 5', + }, + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'comparing'), + coinElement(1, 2, 'comparing'), + coinElement(2, 5, 'highlighted'), + ], + }, + ], + pointers: [], + variables: [ + { id: 'i', name: 'i', value: 10 }, + ], + calculations: [ + { id: 'calc-1', expression: 'min(4, 4, dp[5]+1)', result: '2', position: 'above' }, + ], + grids: [ + { + id: 'dp', + label: 'DP Table', + colLabels: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + cells: [[ + dpCell(0, 0, 'normal'), + dpCell(1, 1, 'normal'), + dpCell(2, 1, 'normal'), + dpCell(3, 2, 'normal'), + dpCell(4, 2, 'normal'), + dpCell(5, 1, 'comparing'), + dpCell(6, 2, 'normal'), + dpCell(7, 2, 'normal'), + dpCell(8, 3, 'normal'), + dpCell(9, 3, 'success'), + dpCell(10, 2, 'computing'), + dpCell(11, '∞'), + ]], + }, + ], + gridPointers: [ + { id: 'ptr-i', name: 'i', row: 0, col: 10, color: 'current' }, + { id: 'ptr-dep', name: 'i-5', row: 0, col: 5, color: 'dependency' }, + ], + }, + }, + + // Compute dp[11] - The answer! + { + id: 'exec-12', + phase: 'execution', + explanation: 'Compute dp[11]: coin 1 -> dp[10]+1=3, coin 2 -> dp[9]+1=4, coin 5 -> dp[6]+1=3. dp[11] = 3.', + codeLine: 10, + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'highlighted'), + coinElement(1, 2, 'comparing'), + coinElement(2, 5, 'highlighted'), + ], + }, + ], + pointers: [], + variables: [ + { id: 'i', name: 'i', value: 11 }, + ], + calculations: [ + { id: 'calc-1', expression: 'min(dp[10]+1, dp[9]+1, dp[6]+1)', result: '3', position: 'above' }, + ], + grids: [ + { + id: 'dp', + label: 'DP Table', + colLabels: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + cells: [[ + dpCell(0, 0, 'normal'), + dpCell(1, 1, 'normal'), + dpCell(2, 1, 'normal'), + dpCell(3, 2, 'normal'), + dpCell(4, 2, 'normal'), + dpCell(5, 1, 'normal'), + dpCell(6, 2, 'comparing'), + dpCell(7, 2, 'normal'), + dpCell(8, 3, 'normal'), + dpCell(9, 3, 'comparing'), + dpCell(10, 2, 'comparing'), + dpCell(11, 3, 'computing'), + ]], + }, + ], + gridPointers: [ + { id: 'ptr-i', name: 'i', row: 0, col: 11, color: 'current' }, + ], + }, + }, + + // Final result + { + id: 'exec-13', + phase: 'execution', + explanation: 'Complete! dp[11] = 3. We need minimum 3 coins: 5 + 5 + 1 = 11.', + codeLine: 12, + dataState: { + arrays: [ + { + id: 'coins', + label: 'Coins', + elements: [ + coinElement(0, 1, 'success'), + coinElement(1, 2, 'dimmed'), + coinElement(2, 5, 'success'), + ], + }, + ], + pointers: [], + variables: [ + { id: 'result', name: 'result', value: 3, derivation: '5 + 5 + 1 = 11' }, + ], + calculations: [ + { id: 'calc-1', expression: '5 + 5 + 1', result: '11', position: 'above' }, + ], + grids: [ + { + id: 'dp', + label: 'DP Table', + colLabels: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], + cells: [[ + dpCell(0, 0, 'success'), + dpCell(1, 1, 'success'), + dpCell(2, 1, 'success'), + dpCell(3, 2, 'success'), + dpCell(4, 2, 'success'), + dpCell(5, 1, 'success'), + dpCell(6, 2, 'success'), + dpCell(7, 2, 'success'), + dpCell(8, 3, 'success'), + dpCell(9, 3, 'success'), + dpCell(10, 2, 'success'), + dpCell(11, 3, 'highlighted'), + ]], + }, + ], + gridPointers: [ + { id: 'ptr-result', name: 'answer', row: 0, col: 11, color: 'result' }, + ], + }, + }, + ], +}; diff --git a/frontend/src/lib/visualizations/types.ts b/frontend/src/lib/visualizations/types.ts index 2d114a5..389c3e5 100644 --- a/frontend/src/lib/visualizations/types.ts +++ b/frontend/src/lib/visualizations/types.ts @@ -86,6 +86,9 @@ export interface DataState { trees?: BinaryTreeState[]; // Queue support queues?: QueueState[]; + // Grid support (for DP visualizations) + grids?: GridState[]; + gridPointers?: GridPointerState[]; } /** Single step in the visualization */ @@ -246,3 +249,34 @@ export interface QueueState { elements: QueueElementState[]; label?: string; } + +// ============================================ +// Grid Types (for DP visualizations) +// ============================================ + +/** State of a grid cell */ +export interface GridCellState { + id: string; + value: number | string; + row: number; + col: number; + state: 'normal' | 'highlighted' | 'comparing' | 'computing' | 'success' | 'dimmed'; +} + +/** Complete grid state */ +export interface GridState { + id: string; + cells: GridCellState[][]; // 2D array: cells[row][col] + colLabels?: (string | number)[]; + rowLabels?: (string | number)[]; + label?: string; +} + +/** Pointer for grid visualization */ +export interface GridPointerState { + id: string; + name: string; + row: number; + col: number; + color: 'current' | 'dependency' | 'result' | 'default'; +}