diff --git a/src/components/ComplexityChart.tsx b/src/components/ComplexityChart.tsx index e9273fc..0a4bd49 100644 --- a/src/components/ComplexityChart.tsx +++ b/src/components/ComplexityChart.tsx @@ -5,6 +5,7 @@ type ComplexityKey = | 'O(log n)' | 'O(√n)' | 'O(n)' + | 'O(n log log n)' | 'O(n log n)' | 'O(n²)' | 'O(2^n)' @@ -15,6 +16,7 @@ const COMPLEXITY_FNS: Record number> = { 'O(log n)': (n) => Math.log2(Math.max(1, n)), 'O(√n)': (n) => Math.sqrt(n), 'O(n)': (n) => n, + 'O(n log log n)': (n) => n * Math.log2(Math.max(2, Math.log2(Math.max(2, n)))), 'O(n log n)': (n) => n * Math.log2(Math.max(1, n)), 'O(n²)': (n) => n * n, 'O(2^n)': (n) => Math.pow(2, n), @@ -37,6 +39,7 @@ function normalizeToKey(raw: string): ComplexityKey | null { if (/2\^|k\^/.test(s)) return 'O(2^n)' if (/√n|sqrt/.test(s)) return 'O(√n)' if (/n²|n\^2|v²/.test(s)) return 'O(n²)' + if (/nloglogn|n\*loglogn/.test(s)) return 'O(n log log n)' if (/nlogn|n\*logn|\(v\+e\)log|elog|n\^1\.25/.test(s)) return 'O(n log n)' if (/loglog/.test(s)) return 'O(log n)' if (/log/.test(s)) return 'O(log n)' diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 497f1f6..10f377b 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -139,6 +139,22 @@ const categoryIcons: Record = { /> ), + Math: ( + + ), } const categoryColors: Record = { @@ -190,6 +206,12 @@ const categoryColors: Record = { 'Dynamic Programming': 'Dynamic Programming', Backtracking: 'Backtracking', 'Divide and Conquer': 'Divide and Conquer', + Math: 'Math', }, algorithmDescriptions: { @@ -993,6 +994,32 @@ Properties: - Demonstrates the power of recursion The puzzle was invented by mathematician Édouard Lucas in 1883. Legend says monks in a temple are moving 64 golden disks — completing the puzzle would mark the end of the world (requiring 18,446,744,073,709,551,615 moves).`, + + 'sieve-of-eratosthenes': `Sieve of Eratosthenes + +The Sieve of Eratosthenes is a classic algorithm for finding all prime numbers up to a limit n. It works by iteratively marking the multiples of each prime, starting from 2. + +How it works: +1. Create a boolean array marking 2..n as potentially prime +2. For each i from 2 up to √n, if i is still marked prime, mark every multiple of i (starting from i²) as composite +3. Numbers that remain marked after the loop are the primes ≤ n + +Why start crossing from i²? + All smaller multiples of i (2i, 3i, …, (i−1)i) have already been crossed by a smaller prime. + +Time Complexity: + Best: O(n log log n) + Average: O(n log log n) + Worst: O(n log log n) + +Space Complexity: O(n) + +Properties: + - Deterministic, no randomness + - Cache-friendly when n fits in memory + - Foundational for number theory and cryptography preprocessing + +Named after the Greek mathematician Eratosthenes of Cyrene (~276–194 BCE), this sieve remains one of the most efficient ways to find all small primes and is the basis for many factorization preprocessing steps.`, }, }, @@ -1048,6 +1075,7 @@ The puzzle was invented by mathematician Édouard Lucas in 1883. Legend says mon 'Dynamic Programming': 'Programación Dinámica', Backtracking: 'Backtracking', 'Divide and Conquer': 'Divide y Vencerás', + Math: 'Matemáticas', }, algorithmDescriptions: { @@ -1925,6 +1953,32 @@ Propiedades: - Demuestra el poder de la recursión El rompecabezas fue inventado por el matemático Édouard Lucas en 1883. La leyenda dice que monjes en un templo están moviendo 64 discos dorados — completar el rompecabezas marcaría el fin del mundo (requiriendo 18.446.744.073.709.551.615 movimientos).`, + + 'sieve-of-eratosthenes': `Criba de Eratóstenes + +La Criba de Eratóstenes es un algoritmo clásico para encontrar todos los números primos hasta un límite n. Funciona marcando iterativamente los múltiplos de cada primo, empezando por 2. + +Cómo funciona: +1. Crea un arreglo booleano marcando 2..n como potencialmente primos +2. Para cada i desde 2 hasta √n, si i sigue marcado como primo, marca todos sus múltiplos (empezando desde i²) como compuestos +3. Los números que permanezcan marcados al terminar el bucle son los primos ≤ n + +¿Por qué empezar a tachar desde i²? + Todos los múltiplos menores de i (2i, 3i, …, (i−1)i) ya fueron tachados por un primo más pequeño. + +Complejidad Temporal: + Mejor: O(n log log n) + Promedio: O(n log log n) + Peor: O(n log log n) + +Complejidad Espacial: O(n) + +Propiedades: + - Determinista, sin aleatoriedad + - Eficiente en caché cuando n cabe en memoria + - Fundamento para teoría de números y preprocesamiento criptográfico + +Lleva el nombre del matemático griego Eratóstenes de Cirene (~276–194 a.C.). Esta criba sigue siendo una de las formas más eficientes de encontrar todos los primos pequeños y es la base de muchos pasos de preprocesamiento para factorización.`, }, }, } diff --git a/src/lib/algorithms/index.ts b/src/lib/algorithms/index.ts index 8f15f85..0985581 100644 --- a/src/lib/algorithms/index.ts +++ b/src/lib/algorithms/index.ts @@ -61,6 +61,8 @@ import { import { towerOfHanoi } from '@lib/algorithms/divide-and-conquer' +import { sieveOfEratosthenes } from '@lib/algorithms/math' + export const algorithms: Algorithm[] = [ // Concepts bigONotation, @@ -109,6 +111,8 @@ export const algorithms: Algorithm[] = [ mazePathfinding, // Divide and Conquer towerOfHanoi, + // Math + sieveOfEratosthenes, ] export const categories: Category[] = [ @@ -126,4 +130,5 @@ export const categories: Category[] = [ name: 'Divide and Conquer', algorithms: algorithms.filter((a) => a.category === 'Divide and Conquer'), }, + { name: 'Math', algorithms: algorithms.filter((a) => a.category === 'Math') }, ] diff --git a/src/lib/algorithms/math.ts b/src/lib/algorithms/math.ts new file mode 100644 index 0000000..a0c945c --- /dev/null +++ b/src/lib/algorithms/math.ts @@ -0,0 +1,173 @@ +import type { Algorithm, Step, HighlightType } from '@lib/types' +import { d } from '@lib/algorithms/shared' + +const sieveOfEratosthenes: Algorithm = { + id: 'sieve-of-eratosthenes', + name: 'Sieve of Eratosthenes', + category: 'Math', + difficulty: 'intermediate', + visualization: 'matrix', + code: `function sieveOfEratosthenes(n) { + const isPrime = new Array(n + 1).fill(true); + isPrime[0] = isPrime[1] = false; + + for (let i = 2; i * i <= n; i++) { + if (isPrime[i]) { + for (let j = i * i; j <= n; j += i) { + isPrime[j] = false; + } + } + } + + return isPrime + .map((p, i) => p ? i : null) + .filter(x => x !== null); +} + +sieveOfEratosthenes(30);`, + description: `Sieve of Eratosthenes + +The Sieve of Eratosthenes is a classic algorithm for finding all prime numbers up to a limit n. It works by iteratively marking the multiples of each prime, starting from 2. + +How it works: +1. Create a boolean array marking 2..n as potentially prime +2. For each i from 2 up to √n, if i is still marked prime, mark every multiple of i (starting from i²) as composite +3. Numbers that remain marked after the loop are the primes ≤ n + +Why start crossing from i²? + All smaller multiples of i (2i, 3i, …, (i−1)i) have already been crossed by a smaller prime. + +Time Complexity: + Best: O(n log log n) + Average: O(n log log n) + Worst: O(n log log n) + +Space Complexity: O(n) + +Properties: + - Deterministic, no randomness + - Cache-friendly when n fits in memory + - Foundational for number theory and cryptography preprocessing`, + + generateSteps(locale = 'en') { + const N = 30 + const COLS = 6 + const ROWS = Math.ceil(N / COLS) + + const values: (number | string)[][] = Array.from({ length: ROWS }, (_, r) => + Array.from({ length: COLS }, (_, c) => r * COLS + c + 1), + ) + + const cellOf = (v: number): [number, number] => [Math.floor((v - 1) / COLS), (v - 1) % COLS] + + const steps: Step[] = [] + const composite = new Set() + + const buildHighlights = ( + currentPrime: number | null, + currentMultiple: number | null, + ): Record => { + const h: Record = {} + h['0,0'] = 'sorted' + for (const c of composite) { + const [r, col] = cellOf(c) + h[`${r},${col}`] = 'placed' + } + if (currentPrime != null) { + const [r, col] = cellOf(currentPrime) + h[`${r},${col}`] = 'current' + } + if (currentMultiple != null) { + const [r, col] = cellOf(currentMultiple) + h[`${r},${col}`] = 'checking' + } + return h + } + + steps.push({ + matrix: { + rows: ROWS, + cols: COLS, + values, + highlights: buildHighlights(null, null), + }, + description: d( + locale, + `Initialize: assume every number from 2 to ${N} is prime. 1 is excluded by definition.`, + `Inicializar: asumimos que todo número de 2 a ${N} es primo. 1 se excluye por definición.`, + ), + codeLine: 2, + variables: { n: N, primes: 0 }, + }) + + const limit = Math.floor(Math.sqrt(N)) + for (let i = 2; i <= limit; i++) { + if (composite.has(i)) continue + + steps.push({ + matrix: { + rows: ROWS, + cols: COLS, + values, + highlights: buildHighlights(i, null), + }, + description: d( + locale, + `${i} is still marked prime. Cross out its multiples starting from ${i}² = ${i * i}.`, + `${i} sigue marcado como primo. Tachar sus múltiplos empezando en ${i}² = ${i * i}.`, + ), + codeLine: 5, + variables: { i, 'i*i': i * i }, + }) + + for (let j = i * i; j <= N; j += i) { + steps.push({ + matrix: { + rows: ROWS, + cols: COLS, + values, + highlights: buildHighlights(i, j), + }, + description: d( + locale, + `Mark ${j} as composite (multiple of ${i}).`, + `Marcar ${j} como compuesto (múltiplo de ${i}).`, + ), + codeLine: 7, + variables: { i, j }, + }) + composite.add(j) + } + } + + const primes: number[] = [] + for (let k = 2; k <= N; k++) if (!composite.has(k)) primes.push(k) + + const finalHighlights: Record = { '0,0': 'sorted' } + for (let k = 2; k <= N; k++) { + const [r, col] = cellOf(k) + finalHighlights[`${r},${col}`] = composite.has(k) ? 'placed' : 'pivot' + } + + steps.push({ + matrix: { + rows: ROWS, + cols: COLS, + values, + highlights: finalHighlights, + }, + description: d( + locale, + `Done. Primes ≤ ${N}: ${primes.join(', ')} (${primes.length} primes).`, + `Listo. Primos ≤ ${N}: ${primes.join(', ')} (${primes.length} primos).`, + ), + codeLine: 12, + variables: { count: primes.length, primes: primes.join(',') }, + consoleOutput: [`[${primes.join(', ')}]`], + }) + + return steps + }, +} + +export { sieveOfEratosthenes }