From f528d832c5479639b71d86691ec98c64daa84242 Mon Sep 17 00:00:00 2001 From: Kai Chappell Date: Sun, 24 Aug 2025 16:09:24 +0100 Subject: [PATCH] feat(viz): sprint 1 - array visualisations --- frontend/src/app/globals.css | 102 ++- frontend/src/app/patterns/[slug]/page.tsx | 20 +- .../algorithms/prefix-sum.tsx | 103 +++ .../algorithms/two-pointers.tsx | 14 +- .../core/explanation-panel.tsx | 50 +- .../visualizations-new/core/step-controls.tsx | 190 ++--- .../core/visualization-container.tsx | 57 +- .../components/visualizations-new/index.ts | 1 + .../src/content/algorithms/binary-search.ts | 773 +++++++++++++++++ frontend/src/content/algorithms/prefix-sum.ts | 807 ++++++++++++++++++ .../src/content/algorithms/sliding-window.ts | 732 ++++++++++++++++ frontend/src/lib/visualizations/types.ts | 20 +- .../lib/visualizations/use-visualization.ts | 20 +- 13 files changed, 2702 insertions(+), 187 deletions(-) create mode 100644 frontend/src/components/visualizations-new/algorithms/prefix-sum.tsx create mode 100644 frontend/src/content/algorithms/binary-search.ts create mode 100644 frontend/src/content/algorithms/prefix-sum.ts create mode 100644 frontend/src/content/algorithms/sliding-window.ts diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 75cc62e..5a5c997 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -1,19 +1,71 @@ @import "tailwindcss"; +/* Register theme tokens with Tailwind so utility classes work */ +@theme { + --color-background: var(--background); + --color-background-subtle: var(--background-subtle); + --color-surface: var(--surface); + --color-surface-variant: var(--surface-variant); + --color-surface-subtle: var(--surface-subtle); + --color-border: var(--border); + --color-border-focus: var(--border-focus); + --color-border-disabled: var(--border-disabled); + --color-foreground: var(--foreground); + --color-foreground-muted: var(--foreground-muted); + --color-primary: var(--primary); + --color-primary-hover: var(--primary-hover); + --color-primary-muted: var(--primary-muted); + --color-accent: var(--accent); + + /* Semantic status colors */ + --color-success: var(--success); + --color-warning: var(--warning); + --color-error: var(--error); + --color-info: var(--info); + + /* Visualization pointer colors */ + --color-viz-pointer-left: var(--viz-pointer-left); + --color-viz-pointer-right: var(--viz-pointer-right); + --color-viz-pointer-mid: var(--viz-pointer-mid); + --color-viz-pointer-default: var(--viz-pointer-default); + --color-viz-success: var(--viz-success); + --color-viz-compare: var(--viz-compare); + --color-viz-highlight: var(--viz-highlight); + + --font-sans: var(--font-inter); + --font-mono: var(--font-jetbrains-mono); +} + :root { --background: #ffffff; + --background-subtle: #f5f5f5; --foreground: #171717; + --foreground-muted: #5a5a5a; + --surface: #ffffff; + --surface-variant: #f5f5f5; + --surface-subtle: #fafafa; --card: #ffffff; --card-foreground: #171717; --primary: #3b82f6; --primary-foreground: #ffffff; + --primary-hover: #2563eb; + --primary-muted: #dbeafe; --secondary: #f3f4f6; --secondary-foreground: #171717; --muted: #f3f4f6; --muted-foreground: #5f6368; + --accent: #0891b2; --border: #e5e7eb; + --border-focus: var(--primary); + --border-disabled: #d4d4d4; --ring: #3b82f6; + /* Semantic status colors */ + --success: #059669; + --warning: #d97706; + --error: #dc2626; + --info: #2563eb; + /* Difficulty colors */ --difficulty-easy: #16a34a; --difficulty-easy-bg: #dcfce7; @@ -70,23 +122,54 @@ --viz-visited: #9ca3af; --viz-swapping: #8b5cf6; --viz-transition: 300ms; + + /* Visualization pointer colors */ + --viz-pointer-left: #2563eb; + --viz-pointer-right: #ea580c; + --viz-pointer-mid: #9333ea; + --viz-pointer-default: #4b5563; + --viz-success: #16a34a; + --viz-compare: #eab308; + --viz-highlight: #4f46e5; + --viz-dimmed-opacity: 0.3; + + /* Visualization timing */ + --viz-duration-fast: 200ms; + --viz-duration-normal: 400ms; + --viz-duration-slow: 600ms; } @media (prefers-color-scheme: dark) { :root { --background: #0a0a0a; - --foreground: #ededed; + --background-subtle: #121212; + --foreground: #e5e5e5; + --foreground-muted: #b0b0b0; + --surface: #141414; + --surface-variant: #1e1e1e; + --surface-subtle: #0f0f0f; --card: #171717; --card-foreground: #ededed; - --primary: #3b82f6; + --primary: #6366f1; --primary-foreground: #ffffff; + --primary-hover: #818cf8; + --primary-muted: #1e1b4b; --secondary: #262626; --secondary-foreground: #ededed; --muted: #262626; --muted-foreground: #a3a3a3; + --accent: #22d3ee; --border: #262626; + --border-focus: var(--primary); + --border-disabled: #404040; --ring: #3b82f6; + /* Semantic status colors */ + --success: #10b981; + --warning: #f59e0b; + --error: #ef4444; + --info: #3b82f6; + /* Difficulty colors (dark mode) */ --difficulty-easy: #4ade80; --difficulty-easy-bg: rgba(34, 197, 94, 0.2); @@ -142,6 +225,21 @@ --viz-found: #22c55e; --viz-visited: #6b7280; --viz-swapping: #8b5cf6; + + /* Visualization pointer colors (dark mode) */ + --viz-pointer-left: #3b82f6; + --viz-pointer-right: #f97316; + --viz-pointer-mid: #a855f7; + --viz-pointer-default: #6b7280; + --viz-success: #22c55e; + --viz-compare: #fbbf24; + --viz-highlight: #6366f1; + --viz-dimmed-opacity: 0.3; + + /* Visualization timing */ + --viz-duration-fast: 200ms; + --viz-duration-normal: 400ms; + --viz-duration-slow: 600ms; } } diff --git a/frontend/src/app/patterns/[slug]/page.tsx b/frontend/src/app/patterns/[slug]/page.tsx index 6f00380..ddd2bad 100644 --- a/frontend/src/app/patterns/[slug]/page.tsx +++ b/frontend/src/app/patterns/[slug]/page.tsx @@ -14,8 +14,11 @@ import { RelatedPatterns, } from "@/components/patterns"; import { PatternVisualization } from "@/components/visualization"; -import { TwoPointersVisualization } from "@/components/visualizations-new"; +import { TwoPointersVisualization, PrefixSumVisualization } 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"; +import { prefixSumAlgorithm } from "@/content/algorithms/prefix-sum"; interface PageProps { params: Promise<{ slug: string }>; @@ -109,14 +112,13 @@ export default async function PatternDetailPage({ params }: PageProps) { {/* Interactive Visualization */} {slug === "two-pointers" ? ( - - - Interactive Visualization - - - - - + + ) : slug === "sliding-window" ? ( + + ) : slug === "binary-search" ? ( + + ) : slug === "prefix-sum" ? ( + ) : pattern.visualization_examples && pattern.visualization_examples.length > 0 ? ( diff --git a/frontend/src/components/visualizations-new/algorithms/prefix-sum.tsx b/frontend/src/components/visualizations-new/algorithms/prefix-sum.tsx new file mode 100644 index 0000000..c032f0b --- /dev/null +++ b/frontend/src/components/visualizations-new/algorithms/prefix-sum.tsx @@ -0,0 +1,103 @@ +'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 { CalculationBubble } from '../primitives/calculation-bubble'; + +interface PrefixSumVisualizationProps { + algorithm: AlgorithmDefinition; + className?: string; +} + +export function PrefixSumVisualization({ + algorithm, + className, +}: PrefixSumVisualizationProps) { + const { + currentStep, + currentStepIndex, + totalSteps, + playback, + controls, + currentPhase, + progress, + } = useVisualization(algorithm); + + const { dataState } = currentStep; + const originalArray = dataState.arrays[0]; + const prefixArray = dataState.arrays[1] ?? null; + const calculation = dataState.calculations[0] ?? null; + + // Filter pointers for each array + // Pointers for original array: those with index < original array length + // Pointers for prefix array: shown on prefix array if it exists + const originalPointers = prefixArray + ? dataState.pointers.filter( + (p) => + p.name === 'num' || + p.name === 'curr' || + p.name === 'i' || + p.name === 'j' + ) + : dataState.pointers; + + const prefixPointers = prefixArray + ? dataState.pointers.filter( + (p) => + p.name === 'prefix[i]' || + p.name === 'prefix[j+1]' || + (p.name !== 'num' && + p.name !== 'curr' && + p.name !== 'i' && + p.name !== 'j') + ) + : []; + + return ( + +
+ {/* Fixed height area for calculation bubble */} +
+ +
+ + {/* Original array */} + {originalArray && ( + + )} + + {/* Prefix sum array (appears during execution) */} + {prefixArray && ( + + )} +
+
+ ); +} diff --git a/frontend/src/components/visualizations-new/algorithms/two-pointers.tsx b/frontend/src/components/visualizations-new/algorithms/two-pointers.tsx index 63e2684..021b682 100644 --- a/frontend/src/components/visualizations-new/algorithms/two-pointers.tsx +++ b/frontend/src/components/visualizations-new/algorithms/two-pointers.tsx @@ -1,10 +1,10 @@ -"use client"; +'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 { CalculationBubble } from "../primitives/calculation-bubble"; +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 { CalculationBubble } from '../primitives/calculation-bubble'; interface TwoPointersVisualizationProps { algorithm: AlgorithmDefinition; @@ -59,7 +59,7 @@ export function TwoPointersVisualization({ )} diff --git a/frontend/src/components/visualizations-new/core/explanation-panel.tsx b/frontend/src/components/visualizations-new/core/explanation-panel.tsx index b4ecae1..b706738 100644 --- a/frontend/src/components/visualizations-new/core/explanation-panel.tsx +++ b/frontend/src/components/visualizations-new/core/explanation-panel.tsx @@ -1,8 +1,8 @@ -"use client"; +'use client'; -import { AnimatePresence, motion } from "framer-motion"; -import { cn } from "@/lib/utils"; -import type { DecisionCallout, VisualizationPhase } from "@/lib/visualizations/types"; +import { AnimatePresence, motion } from 'framer-motion'; +import { cn } from '@/lib/utils'; +import type { DecisionCallout, VisualizationPhase } from '@/lib/visualizations/types'; interface ExplanationPanelProps { explanation: string; @@ -12,19 +12,19 @@ interface ExplanationPanelProps { } const PHASE_LABELS: Record = { - problem: "Problem", - intuition: "Intuition", - pattern: "Pattern", - code: "Code", - execution: "Execution", + problem: 'Problem', + intuition: 'Intuition', + pattern: 'Pattern', + code: 'Code', + execution: 'Execution', }; const PHASE_COLORS: Record = { - problem: "bg-red-500/20 text-red-500", - intuition: "bg-amber-500/20 text-amber-500", - pattern: "bg-blue-500/20 text-blue-500", - code: "bg-[var(--primary)]/20 text-[var(--primary)]", - execution: "bg-green-500/20 text-green-500", + problem: 'bg-error/20 text-error', + intuition: 'bg-warning/20 text-warning', + pattern: 'bg-info/20 text-info', + code: 'bg-primary/20 text-primary', + execution: 'bg-success/20 text-success', }; export function ExplanationPanel({ @@ -36,14 +36,14 @@ export function ExplanationPanel({ return (
@@ -59,7 +59,7 @@ export function ExplanationPanel({ exit={{ opacity: 0, y: -10 }} transition={{ duration: 0.2 }} > -

+

{explanation}

@@ -67,23 +67,23 @@ export function ExplanationPanel({ -
+
Decision Point
- Q: - {decision.question} + Q: + {decision.question}
- A: - {decision.answer} + A: + {decision.answer}
- - {decision.action} + + {decision.action}
diff --git a/frontend/src/components/visualizations-new/core/step-controls.tsx b/frontend/src/components/visualizations-new/core/step-controls.tsx index 5f1b4d7..4092ded 100644 --- a/frontend/src/components/visualizations-new/core/step-controls.tsx +++ b/frontend/src/components/visualizations-new/core/step-controls.tsx @@ -1,10 +1,10 @@ -"use client"; +'use client'; -import { cn } from "@/lib/utils"; +import { cn } from '@/lib/utils'; import type { PlaybackSpeed, VisualizationControls, -} from "@/lib/visualizations/types"; +} from '@/lib/visualizations/types'; interface StepControlsProps { currentStepIndex: number; @@ -33,107 +33,81 @@ export function StepControls({ return (
-
-
- +
+ - + - + - + - -
- -
- - Step {currentStepIndex + 1} of {totalSteps} - - - -
+
-
+
+ +
+ + {currentStepIndex + 1}/{totalSteps} + + + +
); } diff --git a/frontend/src/components/visualizations-new/core/visualization-container.tsx b/frontend/src/components/visualizations-new/core/visualization-container.tsx index 5605c60..6c6a4b5 100644 --- a/frontend/src/components/visualizations-new/core/visualization-container.tsx +++ b/frontend/src/components/visualizations-new/core/visualization-container.tsx @@ -1,7 +1,7 @@ -"use client"; +'use client'; -import { cn } from "@/lib/utils"; -import type { ReactNode } from "react"; +import { cn } from '@/lib/utils'; +import type { ReactNode } from 'react'; import type { AlgorithmCode, AlgorithmPattern, @@ -10,11 +10,11 @@ import type { VariableState, VisualizationControls, VisualizationPhase, -} from "@/lib/visualizations/types"; -import { CodePanel } from "./code-panel"; -import { ExplanationPanel } from "./explanation-panel"; -import { StepControls } from "./step-controls"; -import { VariableInspector } from "./variable-inspector"; +} from '@/lib/visualizations/types'; +import { CodePanel } from './code-panel'; +import { ExplanationPanel } from './explanation-panel'; +import { StepControls } from './step-controls'; +import { VariableInspector } from './variable-inspector'; interface VisualizationContainerProps { title: string; @@ -58,44 +58,44 @@ export function VisualizationContainer({ return (
-
-

{title}

- +
+

{title}

+ {pattern.name}
-
-
+
+
-
-
- {children} -
- +
+ {children}
+ +
); diff --git a/frontend/src/components/visualizations-new/index.ts b/frontend/src/components/visualizations-new/index.ts index 7a61b43..25831ab 100644 --- a/frontend/src/components/visualizations-new/index.ts +++ b/frontend/src/components/visualizations-new/index.ts @@ -14,4 +14,5 @@ export { CalculationBubble } from "./primitives/calculation-bubble"; export { ArrayView } from "./data-structures/array-view"; // Algorithm visualizations +export { PrefixSumVisualization } from "./algorithms/prefix-sum"; export { TwoPointersVisualization } from "./algorithms/two-pointers"; diff --git a/frontend/src/content/algorithms/binary-search.ts b/frontend/src/content/algorithms/binary-search.ts new file mode 100644 index 0000000..e585fbe --- /dev/null +++ b/frontend/src/content/algorithms/binary-search.ts @@ -0,0 +1,773 @@ +import type { AlgorithmDefinition } from '@/lib/visualizations/types'; + +export const binarySearchAlgorithm: AlgorithmDefinition = { + id: 'binary-search', + title: 'Binary Search', + slug: 'binary-search', + pattern: { + name: 'Binary Search', + description: + 'Repeatedly divide the search space in half by comparing to the middle element.', + }, + problemStatement: + 'Given a sorted array of integers, find the index of a target value. Return -1 if not found.', + intuition: + 'In a sorted array, we can eliminate half of the remaining elements with each comparison. Compare the target to the middle element: if smaller, search left half; if larger, search right half.', + code: { + language: 'python', + code: `def binary_search(nums: list[int], target: int) -> int: + left = 0 + right = len(nums) - 1 + + while left <= right: + mid = (left + right) // 2 + + if nums[mid] == target: + return mid + elif nums[mid] < target: + left = mid + 1 + else: + right = mid - 1 + + return -1`, + }, + initialExample: { + input: { nums: [1, 3, 5, 7, 9, 11, 13], target: 9 }, + expected: 4, + }, + steps: [ + // Phase 1: Problem (2 steps) + { + id: 'problem-1', + phase: 'problem', + explanation: + 'We have a sorted array [1, 3, 5, 7, 9, 11, 13] and need to find the index of target value 9.', + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 1, index: 0, state: 'normal' }, + { value: 3, index: 1, state: 'normal' }, + { value: 5, index: 2, state: 'normal' }, + { value: 7, index: 3, state: 'normal' }, + { value: 9, index: 4, state: 'normal' }, + { value: 11, index: 5, state: 'normal' }, + { value: 13, index: 6, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [{ id: 'target', name: 'target', value: 9 }], + calculations: [], + }, + }, + { + id: 'problem-2', + phase: 'problem', + explanation: + 'A linear search would check each element, giving O(n) time. Can we use the sorted property to do better?', + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 1, index: 0, state: 'comparing' }, + { value: 3, index: 1, state: 'normal' }, + { value: 5, index: 2, state: 'normal' }, + { value: 7, index: 3, state: 'normal' }, + { value: 9, index: 4, state: 'normal' }, + { value: 11, index: 5, state: 'normal' }, + { value: 13, index: 6, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [{ id: 'target', name: 'target', value: 9 }], + calculations: [], + }, + }, + + // Phase 2: Intuition (3 steps) + { + id: 'intuition-1', + phase: 'intuition', + explanation: + 'Key insight: If we check the middle element, we can eliminate half the array with one comparison.', + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 1, index: 0, state: 'normal' }, + { value: 3, index: 1, state: 'normal' }, + { value: 5, index: 2, state: 'normal' }, + { value: 7, index: 3, state: 'highlighted' }, + { value: 9, index: 4, state: 'normal' }, + { value: 11, index: 5, state: 'normal' }, + { value: 13, index: 6, state: 'normal' }, + ], + }, + ], + pointers: [ + { id: 'mid', name: 'mid', index: 3, color: 'mid' }, + ], + variables: [{ id: 'target', name: 'target', value: 9 }], + calculations: [], + }, + }, + { + id: 'intuition-2', + phase: 'intuition', + explanation: + 'Middle element is 7. Since 9 > 7, the target must be in the right half. We can ignore the left half entirely!', + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 1, index: 0, state: 'dimmed' }, + { value: 3, index: 1, state: 'dimmed' }, + { value: 5, index: 2, state: 'dimmed' }, + { value: 7, index: 3, state: 'dimmed' }, + { value: 9, index: 4, state: 'normal' }, + { value: 11, index: 5, state: 'normal' }, + { value: 13, index: 6, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [{ id: 'target', name: 'target', value: 9 }], + calculations: [], + }, + }, + { + id: 'intuition-3', + phase: 'intuition', + explanation: + 'Repeat this process on the remaining half. Each step halves the search space, giving O(log n) time complexity.', + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 1, index: 0, state: 'dimmed' }, + { value: 3, index: 1, state: 'dimmed' }, + { value: 5, index: 2, state: 'dimmed' }, + { value: 7, index: 3, state: 'dimmed' }, + { value: 9, index: 4, state: 'normal' }, + { value: 11, index: 5, state: 'highlighted' }, + { value: 13, index: 6, state: 'normal' }, + ], + }, + ], + pointers: [ + { id: 'mid', name: 'mid', index: 5, color: 'mid' }, + ], + variables: [{ id: 'target', name: 'target', value: 9 }], + calculations: [], + }, + }, + + // Phase 3: Code walkthrough (5 steps) + { + id: 'code-1', + phase: 'code', + explanation: + 'Initialize two pointers: left at 0, right at the last index.', + codeLine: 2, + codeHighlightLines: [2, 3], + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 1, index: 0, state: 'normal' }, + { value: 3, index: 1, state: 'normal' }, + { value: 5, index: 2, state: 'normal' }, + { value: 7, index: 3, state: 'normal' }, + { value: 9, index: 4, state: 'normal' }, + { value: 11, index: 5, state: 'normal' }, + { value: 13, index: 6, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [{ id: 'target', name: 'target', value: 9 }], + calculations: [], + }, + }, + { + id: 'code-2', + phase: 'code', + explanation: + 'Loop while the search space is valid (left <= right).', + codeLine: 5, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 1, index: 0, state: 'normal' }, + { value: 3, index: 1, state: 'normal' }, + { value: 5, index: 2, state: 'normal' }, + { value: 7, index: 3, state: 'normal' }, + { value: 9, index: 4, state: 'normal' }, + { value: 11, index: 5, state: 'normal' }, + { value: 13, index: 6, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [{ id: 'target', name: 'target', value: 9 }], + calculations: [], + }, + }, + { + id: 'code-3', + phase: 'code', + explanation: + 'Calculate the middle index: mid = (left + right) // 2.', + codeLine: 6, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 1, index: 0, state: 'normal' }, + { value: 3, index: 1, state: 'normal' }, + { value: 5, index: 2, state: 'normal' }, + { value: 7, index: 3, state: 'normal' }, + { value: 9, index: 4, state: 'normal' }, + { value: 11, index: 5, state: 'normal' }, + { value: 13, index: 6, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [{ id: 'target', name: 'target', value: 9 }], + calculations: [], + }, + }, + { + id: 'code-4', + phase: 'code', + explanation: + 'If nums[mid] equals target, we found it! Otherwise, narrow the search space.', + codeLine: 8, + codeHighlightLines: [8, 9, 10, 11, 12, 13], + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 1, index: 0, state: 'normal' }, + { value: 3, index: 1, state: 'normal' }, + { value: 5, index: 2, state: 'normal' }, + { value: 7, index: 3, state: 'normal' }, + { value: 9, index: 4, state: 'normal' }, + { value: 11, index: 5, state: 'normal' }, + { value: 13, index: 6, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [{ id: 'target', name: 'target', value: 9 }], + calculations: [], + }, + }, + { + id: 'code-5', + phase: 'code', + explanation: + 'If target not found after search space is exhausted, return -1.', + codeLine: 15, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 1, index: 0, state: 'normal' }, + { value: 3, index: 1, state: 'normal' }, + { value: 5, index: 2, state: 'normal' }, + { value: 7, index: 3, state: 'normal' }, + { value: 9, index: 4, state: 'normal' }, + { value: 11, index: 5, state: 'normal' }, + { value: 13, index: 6, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [{ id: 'target', name: 'target', value: 9 }], + calculations: [], + }, + }, + + // Phase 4: Execution (~15 steps) + { + id: 'exec-1', + phase: 'execution', + explanation: 'Initialize left = 0, right = 6 (len(nums) - 1).', + codeLine: 2, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 1, index: 0, state: 'highlighted' }, + { value: 3, index: 1, state: 'normal' }, + { value: 5, index: 2, state: 'normal' }, + { value: 7, index: 3, state: 'normal' }, + { value: 9, index: 4, state: 'normal' }, + { value: 11, index: 5, state: 'normal' }, + { value: 13, index: 6, state: 'highlighted' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 0, color: 'left', showValue: true }, + { id: 'right', name: 'right', index: 6, color: 'right', showValue: true }, + ], + variables: [ + { id: 'target', name: 'target', value: 9 }, + { id: 'left', name: 'left', value: 0 }, + { id: 'right', name: 'right', value: 6 }, + ], + calculations: [], + }, + }, + { + id: 'exec-2', + phase: 'execution', + explanation: 'Check loop condition: left (0) <= right (6)? Yes, enter loop.', + codeLine: 5, + decision: { + question: 'Is left <= right?', + answer: '0 <= 6 = true', + action: 'Enter the while loop', + }, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 1, index: 0, state: 'highlighted' }, + { value: 3, index: 1, state: 'normal' }, + { value: 5, index: 2, state: 'normal' }, + { value: 7, index: 3, state: 'normal' }, + { value: 9, index: 4, state: 'normal' }, + { value: 11, index: 5, state: 'normal' }, + { value: 13, index: 6, state: 'highlighted' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 0, color: 'left', showValue: true }, + { id: 'right', name: 'right', index: 6, color: 'right', showValue: true }, + ], + variables: [ + { id: 'target', name: 'target', value: 9 }, + { id: 'left', name: 'left', value: 0 }, + { id: 'right', name: 'right', value: 6 }, + ], + calculations: [], + }, + }, + { + id: 'exec-3', + phase: 'execution', + explanation: 'Calculate mid = (0 + 6) // 2 = 3. Middle element is 7.', + codeLine: 6, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 1, index: 0, state: 'highlighted' }, + { value: 3, index: 1, state: 'normal' }, + { value: 5, index: 2, state: 'normal' }, + { value: 7, index: 3, state: 'comparing' }, + { value: 9, index: 4, state: 'normal' }, + { value: 11, index: 5, state: 'normal' }, + { value: 13, index: 6, state: 'highlighted' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 0, color: 'left', showValue: true }, + { id: 'mid', name: 'mid', index: 3, color: 'mid', showValue: true }, + { id: 'right', name: 'right', index: 6, color: 'right', showValue: true }, + ], + variables: [ + { id: 'target', name: 'target', value: 9 }, + { id: 'left', name: 'left', value: 0 }, + { id: 'right', name: 'right', value: 6 }, + { id: 'mid', name: 'mid', value: 3, derivation: '(0 + 6) // 2' }, + ], + calculations: [ + { id: 'calc-1', expression: '(0 + 6) // 2', result: '3', position: 'above' }, + ], + }, + }, + { + id: 'exec-4', + phase: 'execution', + explanation: 'Compare: nums[3] = 7 vs target = 9. 7 < 9, so target is in right half.', + codeLine: 10, + decision: { + question: 'Is nums[mid] == target?', + answer: '7 != 9, and 7 < 9', + action: 'Search the right half (left = mid + 1)', + }, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 1, index: 0, state: 'highlighted' }, + { value: 3, index: 1, state: 'normal' }, + { value: 5, index: 2, state: 'normal' }, + { value: 7, index: 3, state: 'comparing' }, + { value: 9, index: 4, state: 'normal' }, + { value: 11, index: 5, state: 'normal' }, + { value: 13, index: 6, state: 'highlighted' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 0, color: 'left', showValue: true }, + { id: 'mid', name: 'mid', index: 3, color: 'mid', showValue: true }, + { id: 'right', name: 'right', index: 6, color: 'right', showValue: true }, + ], + variables: [ + { id: 'target', name: 'target', value: 9 }, + { id: 'left', name: 'left', value: 0 }, + { id: 'right', name: 'right', value: 6 }, + { id: 'mid', name: 'mid', value: 3 }, + ], + calculations: [ + { id: 'calc-1', expression: '7 < 9', result: 'true', position: 'above' }, + ], + }, + }, + { + id: 'exec-5', + phase: 'execution', + explanation: 'Update left = mid + 1 = 4. The left half is eliminated.', + codeLine: 11, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 1, index: 0, state: 'dimmed' }, + { value: 3, index: 1, state: 'dimmed' }, + { value: 5, index: 2, state: 'dimmed' }, + { value: 7, index: 3, state: 'dimmed' }, + { value: 9, index: 4, state: 'highlighted' }, + { value: 11, index: 5, state: 'normal' }, + { value: 13, index: 6, state: 'highlighted' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 4, color: 'left', showValue: true }, + { id: 'right', name: 'right', index: 6, color: 'right', showValue: true }, + ], + variables: [ + { id: 'target', name: 'target', value: 9 }, + { id: 'left', name: 'left', value: 4, previousValue: 0 }, + { id: 'right', name: 'right', value: 6 }, + ], + calculations: [], + }, + }, + { + id: 'exec-6', + phase: 'execution', + explanation: 'Check loop condition: left (4) <= right (6)? Yes, continue.', + codeLine: 5, + decision: { + question: 'Is left <= right?', + answer: '4 <= 6 = true', + action: 'Continue the while loop', + }, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 1, index: 0, state: 'dimmed' }, + { value: 3, index: 1, state: 'dimmed' }, + { value: 5, index: 2, state: 'dimmed' }, + { value: 7, index: 3, state: 'dimmed' }, + { value: 9, index: 4, state: 'highlighted' }, + { value: 11, index: 5, state: 'normal' }, + { value: 13, index: 6, state: 'highlighted' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 4, color: 'left', showValue: true }, + { id: 'right', name: 'right', index: 6, color: 'right', showValue: true }, + ], + variables: [ + { id: 'target', name: 'target', value: 9 }, + { id: 'left', name: 'left', value: 4 }, + { id: 'right', name: 'right', value: 6 }, + ], + calculations: [], + }, + }, + { + id: 'exec-7', + phase: 'execution', + explanation: 'Calculate mid = (4 + 6) // 2 = 5. Middle element is 11.', + codeLine: 6, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 1, index: 0, state: 'dimmed' }, + { value: 3, index: 1, state: 'dimmed' }, + { value: 5, index: 2, state: 'dimmed' }, + { value: 7, index: 3, state: 'dimmed' }, + { value: 9, index: 4, state: 'highlighted' }, + { value: 11, index: 5, state: 'comparing' }, + { value: 13, index: 6, state: 'highlighted' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 4, color: 'left', showValue: true }, + { id: 'mid', name: 'mid', index: 5, color: 'mid', showValue: true }, + { id: 'right', name: 'right', index: 6, color: 'right', showValue: true }, + ], + variables: [ + { id: 'target', name: 'target', value: 9 }, + { id: 'left', name: 'left', value: 4 }, + { id: 'right', name: 'right', value: 6 }, + { id: 'mid', name: 'mid', value: 5, previousValue: 3, derivation: '(4 + 6) // 2' }, + ], + calculations: [ + { id: 'calc-2', expression: '(4 + 6) // 2', result: '5', position: 'above' }, + ], + }, + }, + { + id: 'exec-8', + phase: 'execution', + explanation: 'Compare: nums[5] = 11 vs target = 9. 11 > 9, so target is in left half.', + codeLine: 12, + decision: { + question: 'Is nums[mid] == target?', + answer: '11 != 9, and 11 > 9', + action: 'Search the left half (right = mid - 1)', + }, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 1, index: 0, state: 'dimmed' }, + { value: 3, index: 1, state: 'dimmed' }, + { value: 5, index: 2, state: 'dimmed' }, + { value: 7, index: 3, state: 'dimmed' }, + { value: 9, index: 4, state: 'highlighted' }, + { value: 11, index: 5, state: 'comparing' }, + { value: 13, index: 6, state: 'highlighted' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 4, color: 'left', showValue: true }, + { id: 'mid', name: 'mid', index: 5, color: 'mid', showValue: true }, + { id: 'right', name: 'right', index: 6, color: 'right', showValue: true }, + ], + variables: [ + { id: 'target', name: 'target', value: 9 }, + { id: 'left', name: 'left', value: 4 }, + { id: 'right', name: 'right', value: 6 }, + { id: 'mid', name: 'mid', value: 5 }, + ], + calculations: [ + { id: 'calc-2', expression: '11 > 9', result: 'true', position: 'above' }, + ], + }, + }, + { + id: 'exec-9', + phase: 'execution', + explanation: 'Update right = mid - 1 = 4. The right portion is eliminated.', + codeLine: 13, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 1, index: 0, state: 'dimmed' }, + { value: 3, index: 1, state: 'dimmed' }, + { value: 5, index: 2, state: 'dimmed' }, + { value: 7, index: 3, state: 'dimmed' }, + { value: 9, index: 4, state: 'highlighted' }, + { value: 11, index: 5, state: 'dimmed' }, + { value: 13, index: 6, state: 'dimmed' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 4, color: 'left', showValue: true }, + { id: 'right', name: 'right', index: 4, color: 'right', showValue: true }, + ], + variables: [ + { id: 'target', name: 'target', value: 9 }, + { id: 'left', name: 'left', value: 4 }, + { id: 'right', name: 'right', value: 4, previousValue: 6 }, + ], + calculations: [], + }, + }, + { + id: 'exec-10', + phase: 'execution', + explanation: 'Check loop condition: left (4) <= right (4)? Yes, continue.', + codeLine: 5, + decision: { + question: 'Is left <= right?', + answer: '4 <= 4 = true', + action: 'Continue the while loop', + }, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 1, index: 0, state: 'dimmed' }, + { value: 3, index: 1, state: 'dimmed' }, + { value: 5, index: 2, state: 'dimmed' }, + { value: 7, index: 3, state: 'dimmed' }, + { value: 9, index: 4, state: 'highlighted' }, + { value: 11, index: 5, state: 'dimmed' }, + { value: 13, index: 6, state: 'dimmed' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 4, color: 'left', showValue: true }, + { id: 'right', name: 'right', index: 4, color: 'right', showValue: true }, + ], + variables: [ + { id: 'target', name: 'target', value: 9 }, + { id: 'left', name: 'left', value: 4 }, + { id: 'right', name: 'right', value: 4 }, + ], + calculations: [], + }, + }, + { + id: 'exec-11', + phase: 'execution', + explanation: 'Calculate mid = (4 + 4) // 2 = 4. Middle element is 9.', + codeLine: 6, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 1, index: 0, state: 'dimmed' }, + { value: 3, index: 1, state: 'dimmed' }, + { value: 5, index: 2, state: 'dimmed' }, + { value: 7, index: 3, state: 'dimmed' }, + { value: 9, index: 4, state: 'comparing' }, + { value: 11, index: 5, state: 'dimmed' }, + { value: 13, index: 6, state: 'dimmed' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 4, color: 'left', showValue: true }, + { id: 'mid', name: 'mid', index: 4, color: 'mid', showValue: true }, + { id: 'right', name: 'right', index: 4, color: 'right', showValue: true }, + ], + variables: [ + { id: 'target', name: 'target', value: 9 }, + { id: 'left', name: 'left', value: 4 }, + { id: 'right', name: 'right', value: 4 }, + { id: 'mid', name: 'mid', value: 4, previousValue: 5, derivation: '(4 + 4) // 2' }, + ], + calculations: [ + { id: 'calc-3', expression: '(4 + 4) // 2', result: '4', position: 'above' }, + ], + }, + }, + { + id: 'exec-12', + phase: 'execution', + explanation: 'Compare: nums[4] = 9 vs target = 9. Found it!', + codeLine: 8, + decision: { + question: 'Is nums[mid] == target?', + answer: '9 == 9 = true', + action: 'Return mid = 4', + }, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 1, index: 0, state: 'dimmed' }, + { value: 3, index: 1, state: 'dimmed' }, + { value: 5, index: 2, state: 'dimmed' }, + { value: 7, index: 3, state: 'dimmed' }, + { value: 9, index: 4, state: 'success' }, + { value: 11, index: 5, state: 'dimmed' }, + { value: 13, index: 6, state: 'dimmed' }, + ], + }, + ], + pointers: [ + { id: 'mid', name: 'mid', index: 4, color: 'mid', showValue: true }, + ], + variables: [ + { id: 'target', name: 'target', value: 9 }, + { id: 'mid', name: 'mid', value: 4 }, + ], + calculations: [ + { id: 'calc-3', expression: '9 == 9', result: 'true', position: 'above' }, + ], + }, + }, + { + id: 'exec-13', + phase: 'execution', + explanation: + 'Return 4. Found target 9 at index 4 in just 3 comparisons! O(log n) time complexity.', + codeLine: 9, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 1, index: 0, state: 'dimmed' }, + { value: 3, index: 1, state: 'dimmed' }, + { value: 5, index: 2, state: 'dimmed' }, + { value: 7, index: 3, state: 'dimmed' }, + { value: 9, index: 4, state: 'success' }, + { value: 11, index: 5, state: 'dimmed' }, + { value: 13, index: 6, state: 'dimmed' }, + ], + }, + ], + pointers: [ + { id: 'mid', name: 'mid', index: 4, color: 'mid', showValue: true }, + ], + variables: [ + { id: 'target', name: 'target', value: 9 }, + { id: 'result', name: 'result', value: 4 }, + ], + calculations: [], + }, + }, + ], +}; diff --git a/frontend/src/content/algorithms/prefix-sum.ts b/frontend/src/content/algorithms/prefix-sum.ts new file mode 100644 index 0000000..b594e56 --- /dev/null +++ b/frontend/src/content/algorithms/prefix-sum.ts @@ -0,0 +1,807 @@ +import type { AlgorithmDefinition } from '@/lib/visualizations/types'; + +export const prefixSumAlgorithm: AlgorithmDefinition = { + id: 'prefix-sum', + title: 'Prefix Sum - Range Query', + slug: 'prefix-sum', + pattern: { + name: 'Prefix Sum', + description: + 'Precompute cumulative sums to answer range sum queries in O(1) time.', + }, + problemStatement: + 'Given an array, answer multiple range sum queries efficiently. Each query asks for the sum of elements from index i to j.', + intuition: + 'Build a prefix sum array where prefix[k] = sum of all elements from 0 to k-1. Then any range sum from i to j is simply prefix[j+1] - prefix[i].', + code: { + language: 'python', + code: `def build_prefix(nums: list[int]) -> list[int]: + prefix = [0] + for num in nums: + prefix.append(prefix[-1] + num) + return prefix + +def range_sum(prefix: list[int], i: int, j: int) -> int: + return prefix[j + 1] - prefix[i]`, + }, + initialExample: { + input: { nums: [3, 1, 4, 1, 5, 9], query: { i: 1, j: 4 } }, + expected: 11, + }, + steps: [ + // Phase 1: Problem (2 steps) + { + id: 'problem-1', + phase: 'problem', + explanation: + 'We have an array [3, 1, 4, 1, 5, 9] and need to efficiently answer range sum queries like "sum from index 1 to 4".', + dataState: { + arrays: [ + { + id: 'nums', + label: 'Original Array', + elements: [ + { value: 3, index: 0, state: 'normal' }, + { value: 1, index: 1, state: 'normal' }, + { value: 4, index: 2, state: 'normal' }, + { value: 1, index: 3, state: 'normal' }, + { value: 5, index: 4, state: 'normal' }, + { value: 9, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [], + calculations: [], + }, + }, + { + id: 'problem-2', + phase: 'problem', + explanation: + 'Naively summing each range takes O(n) per query. With many queries, this becomes expensive. Can we precompute something?', + dataState: { + arrays: [ + { + id: 'nums', + label: 'Original Array', + elements: [ + { value: 3, index: 0, state: 'normal' }, + { value: 1, index: 1, state: 'highlighted' }, + { value: 4, index: 2, state: 'highlighted' }, + { value: 1, index: 3, state: 'highlighted' }, + { value: 5, index: 4, state: 'highlighted' }, + { value: 9, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [ + { id: 'i', name: 'i', index: 1, color: 'left' }, + { id: 'j', name: 'j', index: 4, color: 'right' }, + ], + variables: [ + { id: 'i', name: 'i', value: 1 }, + { id: 'j', name: 'j', value: 4 }, + ], + calculations: [], + }, + }, + + // Phase 2: Intuition (3 steps) + { + id: 'intuition-1', + phase: 'intuition', + explanation: + 'Key insight: Build a prefix sum array where each element is the cumulative sum of all elements up to that point.', + dataState: { + arrays: [ + { + id: 'nums', + label: 'Original Array', + elements: [ + { value: 3, index: 0, state: 'normal' }, + { value: 1, index: 1, state: 'normal' }, + { value: 4, index: 2, state: 'normal' }, + { value: 1, index: 3, state: 'normal' }, + { value: 5, index: 4, state: 'normal' }, + { value: 9, index: 5, state: 'normal' }, + ], + }, + { + id: 'prefix', + label: 'Prefix Sum Array', + elements: [ + { value: 0, index: 0, state: 'normal' }, + { value: 3, index: 1, state: 'normal' }, + { value: 4, index: 2, state: 'normal' }, + { value: 8, index: 3, state: 'normal' }, + { value: 9, index: 4, state: 'normal' }, + { value: 14, index: 5, state: 'normal' }, + { value: 23, index: 6, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [], + calculations: [], + }, + }, + { + id: 'intuition-2', + phase: 'intuition', + explanation: + 'prefix[k] represents the sum of elements from index 0 to k-1. The extra 0 at the start simplifies our formula.', + dataState: { + arrays: [ + { + id: 'nums', + label: 'Original Array', + elements: [ + { value: 3, index: 0, state: 'highlighted' }, + { value: 1, index: 1, state: 'highlighted' }, + { value: 4, index: 2, state: 'highlighted' }, + { value: 1, index: 3, state: 'normal' }, + { value: 5, index: 4, state: 'normal' }, + { value: 9, index: 5, state: 'normal' }, + ], + }, + { + id: 'prefix', + label: 'Prefix Sum Array', + elements: [ + { value: 0, index: 0, state: 'normal' }, + { value: 3, index: 1, state: 'normal' }, + { value: 4, index: 2, state: 'normal' }, + { value: 8, index: 3, state: 'highlighted' }, + { value: 9, index: 4, state: 'normal' }, + { value: 14, index: 5, state: 'normal' }, + { value: 23, index: 6, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [], + calculations: [ + { id: 'calc-1', expression: '3 + 1 + 4', result: '8', position: 'above' }, + ], + }, + }, + { + id: 'intuition-3', + phase: 'intuition', + explanation: + 'Range sum from i to j = prefix[j+1] - prefix[i]. This is O(1) per query after O(n) preprocessing!', + dataState: { + arrays: [ + { + id: 'nums', + label: 'Original Array', + elements: [ + { value: 3, index: 0, state: 'normal' }, + { value: 1, index: 1, state: 'highlighted' }, + { value: 4, index: 2, state: 'highlighted' }, + { value: 1, index: 3, state: 'highlighted' }, + { value: 5, index: 4, state: 'highlighted' }, + { value: 9, index: 5, state: 'normal' }, + ], + }, + { + id: 'prefix', + label: 'Prefix Sum Array', + elements: [ + { value: 0, index: 0, state: 'normal' }, + { value: 3, index: 1, state: 'comparing' }, + { value: 4, index: 2, state: 'normal' }, + { value: 8, index: 3, state: 'normal' }, + { value: 9, index: 4, state: 'normal' }, + { value: 14, index: 5, state: 'comparing' }, + { value: 23, index: 6, state: 'normal' }, + ], + }, + ], + pointers: [ + { id: 'i', name: 'i', index: 1, color: 'left' }, + { id: 'j+1', name: 'j+1', index: 5, color: 'right' }, + ], + variables: [ + { id: 'i', name: 'i', value: 1 }, + { id: 'j', name: 'j', value: 4 }, + ], + calculations: [ + { id: 'calc-1', expression: '14 - 3', result: '11', position: 'above' }, + ], + }, + }, + + // Phase 3: Code walkthrough (4 steps) + { + id: 'code-1', + phase: 'code', + explanation: + 'Start with prefix = [0]. This handles the edge case of summing from index 0.', + codeLine: 2, + dataState: { + arrays: [ + { + id: 'nums', + label: 'Original Array', + elements: [ + { value: 3, index: 0, state: 'normal' }, + { value: 1, index: 1, state: 'normal' }, + { value: 4, index: 2, state: 'normal' }, + { value: 1, index: 3, state: 'normal' }, + { value: 5, index: 4, state: 'normal' }, + { value: 9, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [], + calculations: [], + }, + }, + { + id: 'code-2', + phase: 'code', + explanation: + 'For each element, append prefix[-1] + num. This builds the cumulative sum.', + codeLine: 3, + codeHighlightLines: [3, 4], + dataState: { + arrays: [ + { + id: 'nums', + label: 'Original Array', + elements: [ + { value: 3, index: 0, state: 'normal' }, + { value: 1, index: 1, state: 'normal' }, + { value: 4, index: 2, state: 'normal' }, + { value: 1, index: 3, state: 'normal' }, + { value: 5, index: 4, state: 'normal' }, + { value: 9, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [], + calculations: [], + }, + }, + { + id: 'code-3', + phase: 'code', + explanation: + 'The range_sum function computes prefix[j+1] - prefix[i] in O(1).', + codeLine: 8, + dataState: { + arrays: [ + { + id: 'nums', + label: 'Original Array', + elements: [ + { value: 3, index: 0, state: 'normal' }, + { value: 1, index: 1, state: 'normal' }, + { value: 4, index: 2, state: 'normal' }, + { value: 1, index: 3, state: 'normal' }, + { value: 5, index: 4, state: 'normal' }, + { value: 9, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [], + calculations: [], + }, + }, + { + id: 'code-4', + phase: 'code', + explanation: + 'Why j+1? Because prefix[k] is the sum of elements 0 through k-1. So prefix[j+1] includes element j.', + codeLine: 8, + dataState: { + arrays: [ + { + id: 'nums', + label: 'Original Array', + elements: [ + { value: 3, index: 0, state: 'normal' }, + { value: 1, index: 1, state: 'normal' }, + { value: 4, index: 2, state: 'normal' }, + { value: 1, index: 3, state: 'normal' }, + { value: 5, index: 4, state: 'normal' }, + { value: 9, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [], + calculations: [], + }, + }, + + // Phase 4: Execution - Building prefix array (10 steps) + { + id: 'exec-1', + phase: 'execution', + explanation: 'Initialize prefix = [0].', + codeLine: 2, + dataState: { + arrays: [ + { + id: 'nums', + label: 'Original Array', + elements: [ + { value: 3, index: 0, state: 'normal' }, + { value: 1, index: 1, state: 'normal' }, + { value: 4, index: 2, state: 'normal' }, + { value: 1, index: 3, state: 'normal' }, + { value: 5, index: 4, state: 'normal' }, + { value: 9, index: 5, state: 'normal' }, + ], + }, + { + id: 'prefix', + label: 'Prefix Sum Array', + elements: [ + { value: 0, index: 0, state: 'highlighted' }, + ], + }, + ], + pointers: [], + variables: [], + calculations: [], + }, + }, + { + id: 'exec-2', + phase: 'execution', + explanation: 'Process nums[0] = 3. Append prefix[-1] + 3 = 0 + 3 = 3.', + codeLine: 4, + dataState: { + arrays: [ + { + id: 'nums', + label: 'Original Array', + elements: [ + { value: 3, index: 0, state: 'comparing' }, + { value: 1, index: 1, state: 'normal' }, + { value: 4, index: 2, state: 'normal' }, + { value: 1, index: 3, state: 'normal' }, + { value: 5, index: 4, state: 'normal' }, + { value: 9, index: 5, state: 'normal' }, + ], + }, + { + id: 'prefix', + label: 'Prefix Sum Array', + elements: [ + { value: 0, index: 0, state: 'normal' }, + { value: 3, index: 1, state: 'highlighted' }, + ], + }, + ], + pointers: [ + { id: 'curr', name: 'num', index: 0, color: 'mid' }, + ], + variables: [], + calculations: [ + { id: 'calc-1', expression: '0 + 3', result: '3', position: 'above' }, + ], + }, + }, + { + id: 'exec-3', + phase: 'execution', + explanation: 'Process nums[1] = 1. Append prefix[-1] + 1 = 3 + 1 = 4.', + codeLine: 4, + dataState: { + arrays: [ + { + id: 'nums', + label: 'Original Array', + elements: [ + { value: 3, index: 0, state: 'dimmed' }, + { value: 1, index: 1, state: 'comparing' }, + { value: 4, index: 2, state: 'normal' }, + { value: 1, index: 3, state: 'normal' }, + { value: 5, index: 4, state: 'normal' }, + { value: 9, index: 5, state: 'normal' }, + ], + }, + { + id: 'prefix', + label: 'Prefix Sum Array', + elements: [ + { value: 0, index: 0, state: 'normal' }, + { value: 3, index: 1, state: 'normal' }, + { value: 4, index: 2, state: 'highlighted' }, + ], + }, + ], + pointers: [ + { id: 'curr', name: 'num', index: 1, color: 'mid' }, + ], + variables: [], + calculations: [ + { id: 'calc-2', expression: '3 + 1', result: '4', position: 'above' }, + ], + }, + }, + { + id: 'exec-4', + phase: 'execution', + explanation: 'Process nums[2] = 4. Append prefix[-1] + 4 = 4 + 4 = 8.', + codeLine: 4, + dataState: { + arrays: [ + { + id: 'nums', + label: 'Original Array', + elements: [ + { value: 3, index: 0, state: 'dimmed' }, + { value: 1, index: 1, state: 'dimmed' }, + { value: 4, index: 2, state: 'comparing' }, + { value: 1, index: 3, state: 'normal' }, + { value: 5, index: 4, state: 'normal' }, + { value: 9, index: 5, state: 'normal' }, + ], + }, + { + id: 'prefix', + label: 'Prefix Sum Array', + elements: [ + { value: 0, index: 0, state: 'normal' }, + { value: 3, index: 1, state: 'normal' }, + { value: 4, index: 2, state: 'normal' }, + { value: 8, index: 3, state: 'highlighted' }, + ], + }, + ], + pointers: [ + { id: 'curr', name: 'num', index: 2, color: 'mid' }, + ], + variables: [], + calculations: [ + { id: 'calc-3', expression: '4 + 4', result: '8', position: 'above' }, + ], + }, + }, + { + id: 'exec-5', + phase: 'execution', + explanation: 'Process nums[3] = 1. Append prefix[-1] + 1 = 8 + 1 = 9.', + codeLine: 4, + dataState: { + arrays: [ + { + id: 'nums', + label: 'Original Array', + elements: [ + { value: 3, index: 0, state: 'dimmed' }, + { value: 1, index: 1, state: 'dimmed' }, + { value: 4, index: 2, state: 'dimmed' }, + { value: 1, index: 3, state: 'comparing' }, + { value: 5, index: 4, state: 'normal' }, + { value: 9, index: 5, state: 'normal' }, + ], + }, + { + id: 'prefix', + label: 'Prefix Sum Array', + elements: [ + { value: 0, index: 0, state: 'normal' }, + { value: 3, index: 1, state: 'normal' }, + { value: 4, index: 2, state: 'normal' }, + { value: 8, index: 3, state: 'normal' }, + { value: 9, index: 4, state: 'highlighted' }, + ], + }, + ], + pointers: [ + { id: 'curr', name: 'num', index: 3, color: 'mid' }, + ], + variables: [], + calculations: [ + { id: 'calc-4', expression: '8 + 1', result: '9', position: 'above' }, + ], + }, + }, + { + id: 'exec-6', + phase: 'execution', + explanation: 'Process nums[4] = 5. Append prefix[-1] + 5 = 9 + 5 = 14.', + codeLine: 4, + dataState: { + arrays: [ + { + id: 'nums', + label: 'Original Array', + elements: [ + { value: 3, index: 0, state: 'dimmed' }, + { value: 1, index: 1, state: 'dimmed' }, + { value: 4, index: 2, state: 'dimmed' }, + { value: 1, index: 3, state: 'dimmed' }, + { value: 5, index: 4, state: 'comparing' }, + { value: 9, index: 5, state: 'normal' }, + ], + }, + { + id: 'prefix', + label: 'Prefix Sum Array', + elements: [ + { value: 0, index: 0, state: 'normal' }, + { value: 3, index: 1, state: 'normal' }, + { value: 4, index: 2, state: 'normal' }, + { value: 8, index: 3, state: 'normal' }, + { value: 9, index: 4, state: 'normal' }, + { value: 14, index: 5, state: 'highlighted' }, + ], + }, + ], + pointers: [ + { id: 'curr', name: 'num', index: 4, color: 'mid' }, + ], + variables: [], + calculations: [ + { id: 'calc-5', expression: '9 + 5', result: '14', position: 'above' }, + ], + }, + }, + { + id: 'exec-7', + phase: 'execution', + explanation: 'Process nums[5] = 9. Append prefix[-1] + 9 = 14 + 9 = 23.', + codeLine: 4, + dataState: { + arrays: [ + { + id: 'nums', + label: 'Original Array', + elements: [ + { value: 3, index: 0, state: 'dimmed' }, + { value: 1, index: 1, state: 'dimmed' }, + { value: 4, index: 2, state: 'dimmed' }, + { value: 1, index: 3, state: 'dimmed' }, + { value: 5, index: 4, state: 'dimmed' }, + { value: 9, index: 5, state: 'comparing' }, + ], + }, + { + id: 'prefix', + label: 'Prefix Sum Array', + elements: [ + { value: 0, index: 0, state: 'normal' }, + { value: 3, index: 1, state: 'normal' }, + { value: 4, index: 2, state: 'normal' }, + { value: 8, index: 3, state: 'normal' }, + { value: 9, index: 4, state: 'normal' }, + { value: 14, index: 5, state: 'normal' }, + { value: 23, index: 6, state: 'highlighted' }, + ], + }, + ], + pointers: [ + { id: 'curr', name: 'num', index: 5, color: 'mid' }, + ], + variables: [], + calculations: [ + { id: 'calc-6', expression: '14 + 9', result: '23', position: 'above' }, + ], + }, + }, + { + id: 'exec-8', + phase: 'execution', + explanation: 'Prefix array complete! Now we can answer range queries in O(1).', + codeLine: 5, + dataState: { + arrays: [ + { + id: 'nums', + label: 'Original Array', + elements: [ + { value: 3, index: 0, state: 'normal' }, + { value: 1, index: 1, state: 'normal' }, + { value: 4, index: 2, state: 'normal' }, + { value: 1, index: 3, state: 'normal' }, + { value: 5, index: 4, state: 'normal' }, + { value: 9, index: 5, state: 'normal' }, + ], + }, + { + id: 'prefix', + label: 'Prefix Sum Array', + elements: [ + { value: 0, index: 0, state: 'success' }, + { value: 3, index: 1, state: 'success' }, + { value: 4, index: 2, state: 'success' }, + { value: 8, index: 3, state: 'success' }, + { value: 9, index: 4, state: 'success' }, + { value: 14, index: 5, state: 'success' }, + { value: 23, index: 6, state: 'success' }, + ], + }, + ], + pointers: [], + variables: [], + calculations: [], + }, + }, + // Range query execution + { + id: 'exec-9', + phase: 'execution', + explanation: 'Query: sum from index 1 to 4. We need elements [1, 4, 1, 5].', + codeLine: 8, + dataState: { + arrays: [ + { + id: 'nums', + label: 'Original Array', + elements: [ + { value: 3, index: 0, state: 'normal' }, + { value: 1, index: 1, state: 'highlighted' }, + { value: 4, index: 2, state: 'highlighted' }, + { value: 1, index: 3, state: 'highlighted' }, + { value: 5, index: 4, state: 'highlighted' }, + { value: 9, index: 5, state: 'normal' }, + ], + }, + { + id: 'prefix', + label: 'Prefix Sum Array', + elements: [ + { value: 0, index: 0, state: 'normal' }, + { value: 3, index: 1, state: 'normal' }, + { value: 4, index: 2, state: 'normal' }, + { value: 8, index: 3, state: 'normal' }, + { value: 9, index: 4, state: 'normal' }, + { value: 14, index: 5, state: 'normal' }, + { value: 23, index: 6, state: 'normal' }, + ], + }, + ], + pointers: [ + { id: 'i', name: 'i', index: 1, color: 'left' }, + { id: 'j', name: 'j', index: 4, color: 'right' }, + ], + variables: [ + { id: 'i', name: 'i', value: 1 }, + { id: 'j', name: 'j', value: 4 }, + ], + calculations: [], + }, + }, + { + id: 'exec-10', + phase: 'execution', + explanation: 'Use formula: prefix[j+1] - prefix[i] = prefix[5] - prefix[1].', + codeLine: 8, + dataState: { + arrays: [ + { + id: 'nums', + label: 'Original Array', + elements: [ + { value: 3, index: 0, state: 'normal' }, + { value: 1, index: 1, state: 'highlighted' }, + { value: 4, index: 2, state: 'highlighted' }, + { value: 1, index: 3, state: 'highlighted' }, + { value: 5, index: 4, state: 'highlighted' }, + { value: 9, index: 5, state: 'normal' }, + ], + }, + { + id: 'prefix', + label: 'Prefix Sum Array', + elements: [ + { value: 0, index: 0, state: 'normal' }, + { value: 3, index: 1, state: 'comparing' }, + { value: 4, index: 2, state: 'normal' }, + { value: 8, index: 3, state: 'normal' }, + { value: 9, index: 4, state: 'normal' }, + { value: 14, index: 5, state: 'comparing' }, + { value: 23, index: 6, state: 'normal' }, + ], + }, + ], + pointers: [ + { id: 'prefix[i]', name: 'prefix[i]', index: 1, color: 'left' }, + { id: 'prefix[j+1]', name: 'prefix[j+1]', index: 5, color: 'right' }, + ], + variables: [ + { id: 'i', name: 'i', value: 1 }, + { id: 'j', name: 'j', value: 4 }, + ], + calculations: [], + }, + }, + { + id: 'exec-11', + phase: 'execution', + explanation: 'Calculate: prefix[5] - prefix[1] = 14 - 3 = 11.', + codeLine: 8, + dataState: { + arrays: [ + { + id: 'nums', + label: 'Original Array', + elements: [ + { value: 3, index: 0, state: 'normal' }, + { value: 1, index: 1, state: 'success' }, + { value: 4, index: 2, state: 'success' }, + { value: 1, index: 3, state: 'success' }, + { value: 5, index: 4, state: 'success' }, + { value: 9, index: 5, state: 'normal' }, + ], + }, + { + id: 'prefix', + label: 'Prefix Sum Array', + elements: [ + { value: 0, index: 0, state: 'normal' }, + { value: 3, index: 1, state: 'comparing' }, + { value: 4, index: 2, state: 'normal' }, + { value: 8, index: 3, state: 'normal' }, + { value: 9, index: 4, state: 'normal' }, + { value: 14, index: 5, state: 'comparing' }, + { value: 23, index: 6, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [ + { id: 'i', name: 'i', value: 1 }, + { id: 'j', name: 'j', value: 4 }, + { id: 'result', name: 'result', value: 11, derivation: '14 - 3' }, + ], + calculations: [ + { id: 'calc-7', expression: '14 - 3', result: '11', position: 'above' }, + ], + }, + }, + { + id: 'exec-12', + phase: 'execution', + explanation: 'Verify: 1 + 4 + 1 + 5 = 11. Correct! O(n) preprocessing enables O(1) queries.', + codeLine: 8, + dataState: { + arrays: [ + { + id: 'nums', + label: 'Original Array', + elements: [ + { value: 3, index: 0, state: 'dimmed' }, + { value: 1, index: 1, state: 'success' }, + { value: 4, index: 2, state: 'success' }, + { value: 1, index: 3, state: 'success' }, + { value: 5, index: 4, state: 'success' }, + { value: 9, index: 5, state: 'dimmed' }, + ], + }, + { + id: 'prefix', + label: 'Prefix Sum Array', + elements: [ + { value: 0, index: 0, state: 'dimmed' }, + { value: 3, index: 1, state: 'success' }, + { value: 4, index: 2, state: 'dimmed' }, + { value: 8, index: 3, state: 'dimmed' }, + { value: 9, index: 4, state: 'dimmed' }, + { value: 14, index: 5, state: 'success' }, + { value: 23, index: 6, state: 'dimmed' }, + ], + }, + ], + pointers: [], + variables: [ + { id: 'result', name: 'result', value: 11 }, + ], + calculations: [ + { id: 'calc-8', expression: '1 + 4 + 1 + 5', result: '11', position: 'above' }, + ], + }, + }, + ], +}; diff --git a/frontend/src/content/algorithms/sliding-window.ts b/frontend/src/content/algorithms/sliding-window.ts new file mode 100644 index 0000000..6504fb4 --- /dev/null +++ b/frontend/src/content/algorithms/sliding-window.ts @@ -0,0 +1,732 @@ +import type { AlgorithmDefinition } from '@/lib/visualizations/types'; + +export const slidingWindowAlgorithm: AlgorithmDefinition = { + id: 'max-sum-subarray', + title: 'Maximum Sum Subarray of Size K', + slug: 'max-sum-subarray', + pattern: { + name: 'Sliding Window', + description: + 'Maintain a window of elements that slides through the array, updating state incrementally.', + }, + problemStatement: + 'Given an array of integers and a window size k, find the maximum sum of any contiguous subarray of size k.', + intuition: + 'Instead of recalculating the sum for each window from scratch (O(n*k)), we can slide the window by subtracting the element leaving and adding the element entering (O(n)).', + code: { + language: 'python', + code: `def max_sum_subarray(nums: list[int], k: int) -> int: + window_sum = sum(nums[:k]) + max_sum = window_sum + + for i in range(k, len(nums)): + window_sum += nums[i] - nums[i - k] + max_sum = max(max_sum, window_sum) + + return max_sum`, + }, + initialExample: { + input: { nums: [2, 1, 5, 1, 3, 2], k: 3 }, + expected: 9, + }, + steps: [ + // Phase 1: Problem (2 steps) + { + id: 'problem-1', + phase: 'problem', + explanation: + 'We have an array [2, 1, 5, 1, 3, 2] and need to find the maximum sum of any 3 consecutive elements.', + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 2, index: 0, state: 'normal' }, + { value: 1, index: 1, state: 'normal' }, + { value: 5, index: 2, state: 'normal' }, + { value: 1, index: 3, state: 'normal' }, + { value: 3, index: 4, state: 'normal' }, + { value: 2, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [{ id: 'k', name: 'k', value: 3 }], + calculations: [], + }, + }, + { + id: 'problem-2', + phase: 'problem', + explanation: + 'A brute force approach would sum each window separately, giving O(n*k) time. Can we avoid redundant calculations?', + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 2, index: 0, state: 'highlighted' }, + { value: 1, index: 1, state: 'highlighted' }, + { value: 5, index: 2, state: 'highlighted' }, + { value: 1, index: 3, state: 'normal' }, + { value: 3, index: 4, state: 'normal' }, + { value: 2, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [{ id: 'k', name: 'k', value: 3 }], + calculations: [], + }, + }, + + // Phase 2: Intuition (3 steps) + { + id: 'intuition-1', + phase: 'intuition', + explanation: + 'Key insight: When sliding the window one position right, we only need to subtract the leaving element and add the entering element.', + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 2, index: 0, state: 'highlighted' }, + { value: 1, index: 1, state: 'highlighted' }, + { value: 5, index: 2, state: 'highlighted' }, + { value: 1, index: 3, state: 'normal' }, + { value: 3, index: 4, state: 'normal' }, + { value: 2, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 0, color: 'left' }, + { id: 'right', name: 'right', index: 2, color: 'right' }, + ], + variables: [{ id: 'k', name: 'k', value: 3 }], + calculations: [], + }, + }, + { + id: 'intuition-2', + phase: 'intuition', + explanation: + 'Window slides: element at left exits (subtract), element after right enters (add). This gives O(1) per slide!', + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 2, index: 0, state: 'dimmed' }, + { value: 1, index: 1, state: 'highlighted' }, + { value: 5, index: 2, state: 'highlighted' }, + { value: 1, index: 3, state: 'highlighted' }, + { value: 3, index: 4, state: 'normal' }, + { value: 2, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 1, color: 'left' }, + { id: 'right', name: 'right', index: 3, color: 'right' }, + ], + variables: [{ id: 'k', name: 'k', value: 3 }], + calculations: [], + }, + }, + { + id: 'intuition-3', + phase: 'intuition', + explanation: + 'Track the maximum sum seen so far as the window slides through the entire array.', + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 2, index: 0, state: 'normal' }, + { value: 1, index: 1, state: 'normal' }, + { value: 5, index: 2, state: 'normal' }, + { value: 1, index: 3, state: 'normal' }, + { value: 3, index: 4, state: 'normal' }, + { value: 2, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [ + { id: 'k', name: 'k', value: 3 }, + { id: 'max_sum', name: 'max_sum', value: '?' }, + ], + calculations: [], + }, + }, + + // Phase 3: Code walkthrough (4 steps) + { + id: 'code-1', + phase: 'code', + explanation: + 'First, calculate the sum of the initial window (first k elements).', + codeLine: 2, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 2, index: 0, state: 'normal' }, + { value: 1, index: 1, state: 'normal' }, + { value: 5, index: 2, state: 'normal' }, + { value: 1, index: 3, state: 'normal' }, + { value: 3, index: 4, state: 'normal' }, + { value: 2, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [{ id: 'k', name: 'k', value: 3 }], + calculations: [], + }, + }, + { + id: 'code-2', + phase: 'code', + explanation: + 'Initialize max_sum with the initial window sum. This is our best answer so far.', + codeLine: 3, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 2, index: 0, state: 'normal' }, + { value: 1, index: 1, state: 'normal' }, + { value: 5, index: 2, state: 'normal' }, + { value: 1, index: 3, state: 'normal' }, + { value: 3, index: 4, state: 'normal' }, + { value: 2, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [{ id: 'k', name: 'k', value: 3 }], + calculations: [], + }, + }, + { + id: 'code-3', + phase: 'code', + explanation: + 'Slide the window: add the new element nums[i], subtract the leaving element nums[i-k].', + codeLine: 6, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 2, index: 0, state: 'normal' }, + { value: 1, index: 1, state: 'normal' }, + { value: 5, index: 2, state: 'normal' }, + { value: 1, index: 3, state: 'normal' }, + { value: 3, index: 4, state: 'normal' }, + { value: 2, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [{ id: 'k', name: 'k', value: 3 }], + calculations: [], + }, + }, + { + id: 'code-4', + phase: 'code', + explanation: + 'Update max_sum if the current window sum is larger.', + codeLine: 7, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 2, index: 0, state: 'normal' }, + { value: 1, index: 1, state: 'normal' }, + { value: 5, index: 2, state: 'normal' }, + { value: 1, index: 3, state: 'normal' }, + { value: 3, index: 4, state: 'normal' }, + { value: 2, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [{ id: 'k', name: 'k', value: 3 }], + calculations: [], + }, + }, + + // Phase 4: Execution (~15 steps) + { + id: 'exec-1', + phase: 'execution', + explanation: + 'Calculate the initial window sum: nums[0] + nums[1] + nums[2] = 2 + 1 + 5 = 8.', + codeLine: 2, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 2, index: 0, state: 'highlighted' }, + { value: 1, index: 1, state: 'highlighted' }, + { value: 5, index: 2, state: 'highlighted' }, + { value: 1, index: 3, state: 'normal' }, + { value: 3, index: 4, state: 'normal' }, + { value: 2, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 0, color: 'left' }, + { id: 'right', name: 'right', index: 2, color: 'right' }, + ], + variables: [ + { id: 'k', name: 'k', value: 3 }, + { id: 'window_sum', name: 'window_sum', value: 8, derivation: '2 + 1 + 5' }, + ], + calculations: [ + { id: 'calc-1', expression: '2 + 1 + 5', result: '8', position: 'above' }, + ], + }, + }, + { + id: 'exec-2', + phase: 'execution', + explanation: 'Initialize max_sum = window_sum = 8.', + codeLine: 3, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 2, index: 0, state: 'highlighted' }, + { value: 1, index: 1, state: 'highlighted' }, + { value: 5, index: 2, state: 'highlighted' }, + { value: 1, index: 3, state: 'normal' }, + { value: 3, index: 4, state: 'normal' }, + { value: 2, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 0, color: 'left' }, + { id: 'right', name: 'right', index: 2, color: 'right' }, + ], + variables: [ + { id: 'k', name: 'k', value: 3 }, + { id: 'window_sum', name: 'window_sum', value: 8 }, + { id: 'max_sum', name: 'max_sum', value: 8 }, + ], + calculations: [], + }, + }, + { + id: 'exec-3', + phase: 'execution', + explanation: 'Start sliding: i = 3 (k). Loop condition: i < len(nums) = 6? Yes.', + codeLine: 5, + decision: { + question: 'Is i < len(nums)?', + answer: '3 < 6 = true', + action: 'Enter the for loop with i = 3', + }, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 2, index: 0, state: 'highlighted' }, + { value: 1, index: 1, state: 'highlighted' }, + { value: 5, index: 2, state: 'highlighted' }, + { value: 1, index: 3, state: 'comparing' }, + { value: 3, index: 4, state: 'normal' }, + { value: 2, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 0, color: 'left' }, + { id: 'right', name: 'right', index: 2, color: 'right' }, + { id: 'i', name: 'i', index: 3, color: 'mid' }, + ], + variables: [ + { id: 'k', name: 'k', value: 3 }, + { id: 'window_sum', name: 'window_sum', value: 8 }, + { id: 'max_sum', name: 'max_sum', value: 8 }, + { id: 'i', name: 'i', value: 3 }, + ], + calculations: [], + }, + }, + { + id: 'exec-4', + phase: 'execution', + explanation: 'Slide window: add nums[3]=1, subtract nums[0]=2. New sum: 8 + 1 - 2 = 7.', + codeLine: 6, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 2, index: 0, state: 'dimmed' }, + { value: 1, index: 1, state: 'highlighted' }, + { value: 5, index: 2, state: 'highlighted' }, + { value: 1, index: 3, state: 'highlighted' }, + { value: 3, index: 4, state: 'normal' }, + { value: 2, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 1, color: 'left' }, + { id: 'right', name: 'right', index: 3, color: 'right' }, + ], + variables: [ + { id: 'k', name: 'k', value: 3 }, + { id: 'window_sum', name: 'window_sum', value: 7, previousValue: 8, derivation: '8 + 1 - 2' }, + { id: 'max_sum', name: 'max_sum', value: 8 }, + { id: 'i', name: 'i', value: 3 }, + ], + calculations: [ + { id: 'calc-2', expression: '8 + 1 - 2', result: '7', position: 'above' }, + ], + }, + }, + { + id: 'exec-5', + phase: 'execution', + explanation: 'Compare: window_sum (7) vs max_sum (8). 7 < 8, so max_sum stays 8.', + codeLine: 7, + decision: { + question: 'Is window_sum > max_sum?', + answer: '7 > 8 = false', + action: 'Keep max_sum = 8', + }, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 2, index: 0, state: 'dimmed' }, + { value: 1, index: 1, state: 'highlighted' }, + { value: 5, index: 2, state: 'highlighted' }, + { value: 1, index: 3, state: 'highlighted' }, + { value: 3, index: 4, state: 'normal' }, + { value: 2, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 1, color: 'left' }, + { id: 'right', name: 'right', index: 3, color: 'right' }, + ], + variables: [ + { id: 'k', name: 'k', value: 3 }, + { id: 'window_sum', name: 'window_sum', value: 7 }, + { id: 'max_sum', name: 'max_sum', value: 8 }, + { id: 'i', name: 'i', value: 3 }, + ], + calculations: [ + { id: 'calc-2', expression: '7 > 8', result: 'false', position: 'above' }, + ], + }, + }, + { + id: 'exec-6', + phase: 'execution', + explanation: 'Next iteration: i = 4. Loop condition: 4 < 6? Yes.', + codeLine: 5, + decision: { + question: 'Is i < len(nums)?', + answer: '4 < 6 = true', + action: 'Continue for loop with i = 4', + }, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 2, index: 0, state: 'dimmed' }, + { value: 1, index: 1, state: 'highlighted' }, + { value: 5, index: 2, state: 'highlighted' }, + { value: 1, index: 3, state: 'highlighted' }, + { value: 3, index: 4, state: 'comparing' }, + { value: 2, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 1, color: 'left' }, + { id: 'right', name: 'right', index: 3, color: 'right' }, + { id: 'i', name: 'i', index: 4, color: 'mid' }, + ], + variables: [ + { id: 'k', name: 'k', value: 3 }, + { id: 'window_sum', name: 'window_sum', value: 7 }, + { id: 'max_sum', name: 'max_sum', value: 8 }, + { id: 'i', name: 'i', value: 4, previousValue: 3 }, + ], + calculations: [], + }, + }, + { + id: 'exec-7', + phase: 'execution', + explanation: 'Slide window: add nums[4]=3, subtract nums[1]=1. New sum: 7 + 3 - 1 = 9.', + codeLine: 6, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 2, index: 0, state: 'dimmed' }, + { value: 1, index: 1, state: 'dimmed' }, + { value: 5, index: 2, state: 'highlighted' }, + { value: 1, index: 3, state: 'highlighted' }, + { value: 3, index: 4, state: 'highlighted' }, + { value: 2, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 2, color: 'left' }, + { id: 'right', name: 'right', index: 4, color: 'right' }, + ], + variables: [ + { id: 'k', name: 'k', value: 3 }, + { id: 'window_sum', name: 'window_sum', value: 9, previousValue: 7, derivation: '7 + 3 - 1' }, + { id: 'max_sum', name: 'max_sum', value: 8 }, + { id: 'i', name: 'i', value: 4 }, + ], + calculations: [ + { id: 'calc-3', expression: '7 + 3 - 1', result: '9', position: 'above' }, + ], + }, + }, + { + id: 'exec-8', + phase: 'execution', + explanation: 'Compare: window_sum (9) > max_sum (8)! Update max_sum to 9.', + codeLine: 7, + decision: { + question: 'Is window_sum > max_sum?', + answer: '9 > 8 = true', + action: 'Update max_sum = 9', + }, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 2, index: 0, state: 'dimmed' }, + { value: 1, index: 1, state: 'dimmed' }, + { value: 5, index: 2, state: 'success' }, + { value: 1, index: 3, state: 'success' }, + { value: 3, index: 4, state: 'success' }, + { value: 2, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 2, color: 'left' }, + { id: 'right', name: 'right', index: 4, color: 'right' }, + ], + variables: [ + { id: 'k', name: 'k', value: 3 }, + { id: 'window_sum', name: 'window_sum', value: 9 }, + { id: 'max_sum', name: 'max_sum', value: 9, previousValue: 8 }, + { id: 'i', name: 'i', value: 4 }, + ], + calculations: [ + { id: 'calc-3', expression: '9 > 8', result: 'true', position: 'above' }, + ], + }, + }, + { + id: 'exec-9', + phase: 'execution', + explanation: 'Next iteration: i = 5. Loop condition: 5 < 6? Yes.', + codeLine: 5, + decision: { + question: 'Is i < len(nums)?', + answer: '5 < 6 = true', + action: 'Continue for loop with i = 5', + }, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 2, index: 0, state: 'dimmed' }, + { value: 1, index: 1, state: 'dimmed' }, + { value: 5, index: 2, state: 'highlighted' }, + { value: 1, index: 3, state: 'highlighted' }, + { value: 3, index: 4, state: 'highlighted' }, + { value: 2, index: 5, state: 'comparing' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 2, color: 'left' }, + { id: 'right', name: 'right', index: 4, color: 'right' }, + { id: 'i', name: 'i', index: 5, color: 'mid' }, + ], + variables: [ + { id: 'k', name: 'k', value: 3 }, + { id: 'window_sum', name: 'window_sum', value: 9 }, + { id: 'max_sum', name: 'max_sum', value: 9 }, + { id: 'i', name: 'i', value: 5, previousValue: 4 }, + ], + calculations: [], + }, + }, + { + id: 'exec-10', + phase: 'execution', + explanation: 'Slide window: add nums[5]=2, subtract nums[2]=5. New sum: 9 + 2 - 5 = 6.', + codeLine: 6, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 2, index: 0, state: 'dimmed' }, + { value: 1, index: 1, state: 'dimmed' }, + { value: 5, index: 2, state: 'dimmed' }, + { value: 1, index: 3, state: 'highlighted' }, + { value: 3, index: 4, state: 'highlighted' }, + { value: 2, index: 5, state: 'highlighted' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 3, color: 'left' }, + { id: 'right', name: 'right', index: 5, color: 'right' }, + ], + variables: [ + { id: 'k', name: 'k', value: 3 }, + { id: 'window_sum', name: 'window_sum', value: 6, previousValue: 9, derivation: '9 + 2 - 5' }, + { id: 'max_sum', name: 'max_sum', value: 9 }, + { id: 'i', name: 'i', value: 5 }, + ], + calculations: [ + { id: 'calc-4', expression: '9 + 2 - 5', result: '6', position: 'above' }, + ], + }, + }, + { + id: 'exec-11', + phase: 'execution', + explanation: 'Compare: window_sum (6) vs max_sum (9). 6 < 9, so max_sum stays 9.', + codeLine: 7, + decision: { + question: 'Is window_sum > max_sum?', + answer: '6 > 9 = false', + action: 'Keep max_sum = 9', + }, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 2, index: 0, state: 'dimmed' }, + { value: 1, index: 1, state: 'dimmed' }, + { value: 5, index: 2, state: 'dimmed' }, + { value: 1, index: 3, state: 'highlighted' }, + { value: 3, index: 4, state: 'highlighted' }, + { value: 2, index: 5, state: 'highlighted' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 3, color: 'left' }, + { id: 'right', name: 'right', index: 5, color: 'right' }, + ], + variables: [ + { id: 'k', name: 'k', value: 3 }, + { id: 'window_sum', name: 'window_sum', value: 6 }, + { id: 'max_sum', name: 'max_sum', value: 9 }, + { id: 'i', name: 'i', value: 5 }, + ], + calculations: [ + { id: 'calc-4', expression: '6 > 9', result: 'false', position: 'above' }, + ], + }, + }, + { + id: 'exec-12', + phase: 'execution', + explanation: 'Next iteration: i = 6. Loop condition: 6 < 6? No, exit loop.', + codeLine: 5, + decision: { + question: 'Is i < len(nums)?', + answer: '6 < 6 = false', + action: 'Exit the for loop', + }, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 2, index: 0, state: 'dimmed' }, + { value: 1, index: 1, state: 'dimmed' }, + { value: 5, index: 2, state: 'dimmed' }, + { value: 1, index: 3, state: 'normal' }, + { value: 3, index: 4, state: 'normal' }, + { value: 2, index: 5, state: 'normal' }, + ], + }, + ], + pointers: [], + variables: [ + { id: 'k', name: 'k', value: 3 }, + { id: 'window_sum', name: 'window_sum', value: 6 }, + { id: 'max_sum', name: 'max_sum', value: 9 }, + { id: 'i', name: 'i', value: 6, previousValue: 5 }, + ], + calculations: [], + }, + }, + { + id: 'exec-13', + phase: 'execution', + explanation: + 'Return max_sum = 9. The maximum sum window is [5, 1, 3] at indices 2-4. Achieved in O(n) time!', + codeLine: 9, + dataState: { + arrays: [ + { + id: 'nums', + elements: [ + { value: 2, index: 0, state: 'dimmed' }, + { value: 1, index: 1, state: 'dimmed' }, + { value: 5, index: 2, state: 'success' }, + { value: 1, index: 3, state: 'success' }, + { value: 3, index: 4, state: 'success' }, + { value: 2, index: 5, state: 'dimmed' }, + ], + }, + ], + pointers: [ + { id: 'left', name: 'left', index: 2, color: 'left' }, + { id: 'right', name: 'right', index: 4, color: 'right' }, + ], + variables: [ + { id: 'k', name: 'k', value: 3 }, + { id: 'result', name: 'result', value: 9 }, + ], + calculations: [], + }, + }, + ], +}; diff --git a/frontend/src/lib/visualizations/types.ts b/frontend/src/lib/visualizations/types.ts index 2f36b08..a8eafec 100644 --- a/frontend/src/lib/visualizations/types.ts +++ b/frontend/src/lib/visualizations/types.ts @@ -9,17 +9,17 @@ /** Phases of algorithm explanation */ export type VisualizationPhase = - | "problem" - | "intuition" - | "pattern" - | "code" - | "execution"; + | 'problem' + | 'intuition' + | 'pattern' + | 'code' + | 'execution'; /** State of an individual array element */ export interface ArrayElementState { value: number; index: number; - state: "normal" | "highlighted" | "dimmed" | "success" | "comparing"; + state: 'normal' | 'highlighted' | 'dimmed' | 'success' | 'comparing'; } /** Complete array state */ @@ -34,7 +34,7 @@ export interface PointerState { id: string; name: string; index: number; - color: "left" | "right" | "mid" | "default"; + color: 'left' | 'right' | 'mid' | 'default'; showValue?: boolean; } @@ -59,13 +59,13 @@ export interface CalculationState { id: string; expression: string; result: string; - position: "above" | "below" | "inline"; + position: 'above' | 'below' | 'inline'; anchorElementId?: string; } /** Animation descriptor for transitions */ export interface Animation { - type: "move" | "highlight" | "fade" | "appear" | "disappear" | "calculate"; + type: 'move' | 'highlight' | 'fade' | 'appear' | 'disappear' | 'calculate'; targetId: string; duration?: number; delay?: number; @@ -93,7 +93,7 @@ export interface VisualizationStep { /** Code definition for the algorithm */ export interface AlgorithmCode { - language: "typescript" | "python" | "javascript"; + language: 'typescript' | 'python' | 'javascript'; code: string; } diff --git a/frontend/src/lib/visualizations/use-visualization.ts b/frontend/src/lib/visualizations/use-visualization.ts index 4965316..077d047 100644 --- a/frontend/src/lib/visualizations/use-visualization.ts +++ b/frontend/src/lib/visualizations/use-visualization.ts @@ -1,13 +1,13 @@ -"use client"; +'use client'; -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from 'react'; import type { AlgorithmDefinition, PlaybackSpeed, PlaybackState, UseVisualizationReturn, VisualizationControls, -} from "./types"; +} from './types'; const SPEED_MULTIPLIERS: Record = { 0.5: 2, @@ -142,31 +142,31 @@ export function useVisualization( } switch (e.key) { - case " ": + case ' ': e.preventDefault(); togglePlay(); break; - case "ArrowLeft": + case 'ArrowLeft': e.preventDefault(); stepBackward(); break; - case "ArrowRight": + case 'ArrowRight': e.preventDefault(); stepForward(); break; - case "Home": + case 'Home': e.preventDefault(); goToFirst(); break; - case "End": + case 'End': e.preventDefault(); goToLast(); break; } }; - window.addEventListener("keydown", handleKeyDown); - return () => window.removeEventListener("keydown", handleKeyDown); + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); }, [togglePlay, stepBackward, stepForward, goToFirst, goToLast]); const currentStep = algorithm.steps[playback.currentStepIndex];