diff --git a/src/App.tsx b/src/App.tsx index ea227a0..8c8f166 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -60,6 +60,7 @@ import NeighborSumAvoidancePage from './pages/NeighborSumAvoidancePage'; import BurnsidesLemmaPage from './pages/BurnsidesLemmaPage'; import CubeColoringPage from './pages/CubeColoringPage'; import PresentsPuzzlePage from './pages/PresentsPuzzlePage'; +import FerrersRogersRamanujanPage from './pages/FerrersRogersRamanujanPage'; const queryClient = new QueryClient(); @@ -89,6 +90,7 @@ const App = () => ( } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/components/FerrersRogersRamanujan.tsx b/src/components/FerrersRogersRamanujan.tsx new file mode 100644 index 0000000..99b9c7c --- /dev/null +++ b/src/components/FerrersRogersRamanujan.tsx @@ -0,0 +1,285 @@ +import { useState, useEffect } from "react"; +import { Card } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Slider } from "@/components/ui/slider"; +import { Input } from "@/components/ui/input"; +import { toast } from "sonner"; + +const FerrersRogersRamanujan = () => { + const [n, setN] = useState(14); + const [partitionSliders, setPartitionSliders] = useState([]); + const [committedPartition, setCommittedPartition] = useState([]); + const [rrPartition, setRRPartition] = useState([]); + const [animatingHook, setAnimatingHook] = useState<{ row: number; col: number } | null>(null); + const [animationStep, setAnimationStep] = useState(0); + + useEffect(() => { + // Initialize sliders with all zeros + setPartitionSliders(new Array(n).fill(0)); + setCommittedPartition([]); + setRRPartition([]); + }, [n]); + + const currentSum = partitionSliders.reduce((sum, val) => sum + val, 0); + const isValid = currentSum === n; + + const handleCommit = () => { + if (!isValid) { + toast.error(`Sum must equal ${n}. Current sum: ${currentSum}`); + return; + } + // Filter out zeros and sort in descending order + const partition = partitionSliders.filter(v => v > 0).sort((a, b) => b - a); + setCommittedPartition(partition); + setRRPartition([]); + setAnimatingHook(null); + setAnimationStep(0); + toast.success("Partition committed!"); + }; + + const handleReset = () => { + setPartitionSliders(new Array(n).fill(0)); + setCommittedPartition([]); + setRRPartition([]); + setAnimatingHook(null); + setAnimationStep(0); + }; + + const generateRRPartition = () => { + if (committedPartition.length === 0) { + toast.error("Please commit a partition first"); + return; + } + + setRRPartition([]); + setAnimationStep(0); + + // Start animation + animateHooks(); + }; + + const animateHooks = () => { + const hooks: number[] = []; + const partition = committedPartition; + + // We need to traverse the L-shaped hooks + // Start from position (0, 0) and go around the perimeter + let step = 0; + const maxRow = partition.length; + const maxCol = partition[0] || 0; + + const processNextHook = () => { + // Determine which hook to process + // We traverse diagonally: (0,0), (1,0), (0,1), (2,0), (1,1), (0,2), ... + let row = 0, col = 0; + let diag = 0; + let found = false; + + // Find the step-th valid hook + let hookCount = 0; + for (let d = 0; d < maxRow + maxCol; d++) { + for (let r = 0; r <= d; r++) { + const c = d - r; + if (r < partition.length && c < partition[r]) { + if (hookCount === step) { + row = r; + col = c; + found = true; + break; + } + hookCount++; + } + } + if (found) break; + } + + if (!found) { + // Animation complete + setAnimatingHook(null); + return; + } + + setAnimatingHook({ row, col }); + + // Calculate hook size: boxes to the right + boxes below + 1 (the box itself) + const boxesRight = partition[row] - col - 1; + let boxesBelow = 0; + for (let r = row + 1; r < partition.length; r++) { + if (partition[r] > col) { + boxesBelow++; + } else { + break; + } + } + const hookSize = boxesRight + boxesBelow + 1; + + hooks.push(hookSize); + setRRPartition([...hooks]); + + setTimeout(() => { + setAnimationStep(step + 1); + }, 500); + }; + + const interval = setInterval(() => { + processNextHook(); + if (animatingHook === null && step > 0) { + clearInterval(interval); + } + }, 500); + + processNextHook(); + }; + + const renderFerrersDiagram = (partition: number[], highlightHook?: { row: number; col: number }) => { + if (partition.length === 0) return null; + + const boxSize = 30; + const gap = 2; + + return ( +
+ {partition.map((count, rowIdx) => ( +
+ {Array.from({ length: count }).map((_, colIdx) => { + const isHook = highlightHook && + ((rowIdx === highlightHook.row && colIdx >= highlightHook.col) || + (colIdx === highlightHook.col && rowIdx >= highlightHook.row)); + + return ( +
+ ); + })} +
+ ))} +
+ ); + }; + + return ( + +
+
+

Ferrers Diagram & Rogers-Ramanujan Partition

+

+ Create a partition of a number and visualize its transformation using L-shaped hooks. +

+
+ + {/* Number Input */} +
+ + setN(Math.max(1, Math.min(30, parseInt(e.target.value) || 1)))} + className="w-32" + disabled={committedPartition.length > 0} + /> +
+ + {/* Partition Sliders */} +
+
+

Create Partition

+
+ Sum: {currentSum} / {n} +
+
+ +
+ {partitionSliders.map((value, idx) => ( +
+
+ + {value} +
+ { + const newSliders = [...partitionSliders]; + newSliders[idx] = newVal; + setPartitionSliders(newSliders); + }} + max={n} + step={1} + disabled={committedPartition.length > 0} + /> +
+ ))} +
+ +
+ + +
+
+ + {/* Display Partitions */} + {committedPartition.length > 0 && ( +
+
+

Your Partition

+

+ {committedPartition.join(" + ")} = {n} +

+ {renderFerrersDiagram(committedPartition, animatingHook || undefined)} +
+ + + + {/* RR Partition Display */} + {rrPartition.length > 0 && ( +
+

Rogers-Ramanujan Partition

+

+ {rrPartition.join(" + ")} = {n} (parts differ by ≥ 2) +

+ {renderFerrersDiagram(rrPartition)} +
+ )} +
+ )} + + {/* Explanation */} +
+

+ Ferrers Diagram: A visual representation of an integer partition using boxes arranged in rows. +

+

+ Rogers-Ramanujan Partition: Traverse the L-shaped hooks of the Ferrers diagram. + Each hook consists of all boxes to the right and below (including the corner box). + The hook sizes form a new partition where parts differ by at least 2. +

+
+
+
+ ); +}; + +export default FerrersRogersRamanujan; diff --git a/src/pages/FerrersRogersRamanujanPage.tsx b/src/pages/FerrersRogersRamanujanPage.tsx new file mode 100644 index 0000000..c343f8e --- /dev/null +++ b/src/pages/FerrersRogersRamanujanPage.tsx @@ -0,0 +1,28 @@ +import Layout from "@/components/Layout"; +import FerrersRogersRamanujan from "@/components/FerrersRogersRamanujan"; +import SocialShare from "@/components/SocialShare"; + +const FerrersRogersRamanujanPage = () => { + return ( + +
+
+

Ferrers Diagram & Rogers-Ramanujan Partition

+

+ Explore integer partitions through visual Ferrers diagrams and discover the elegant Rogers-Ramanujan transformation. +

+
+ + + + +
+
+ ); +}; + +export default FerrersRogersRamanujanPage; diff --git a/src/pages/theme-pages/discrete-math/Counting.tsx b/src/pages/theme-pages/discrete-math/Counting.tsx index ed45dd5..65bb32a 100644 --- a/src/pages/theme-pages/discrete-math/Counting.tsx +++ b/src/pages/theme-pages/discrete-math/Counting.tsx @@ -1,11 +1,38 @@ -import ComingSoon from "@/components/ComingSoon"; +import Layout from "@/components/Layout"; +import InteractiveCard from "@/components/InteractiveCard"; +import { Grid } from "lucide-react"; const Counting = () => { + const interactives = [ + { + id: "ferrers-rogers-ramanujan", + title: "Ferrers Diagram & Rogers-Ramanujan Partition", + description: "Visualize integer partitions as Ferrers diagrams and discover the Rogers-Ramanujan transformation using L-shaped hooks.", + tags: ["Partitions", "Visualization", "Combinatorics"], + path: "/themes/discrete-math/counting/ferrers-rogers-ramanujan", + icon: Grid, + } + ]; + return ( - + +
+
+
+

Counting

+

+ Explore combinatorics, permutations, combinations, and advanced counting principles through visual tools and interactive exercises. +

+
+ +
+ {interactives.map((interactive) => ( + + ))} +
+
+
+
); };