|
| 1 | +import type { Algorithm, Step, HighlightType } from '@lib/types' |
| 2 | +import { d } from '@lib/algorithms/shared' |
| 3 | + |
| 4 | +const sieveOfEratosthenes: Algorithm = { |
| 5 | + id: 'sieve-of-eratosthenes', |
| 6 | + name: 'Sieve of Eratosthenes', |
| 7 | + category: 'Math', |
| 8 | + difficulty: 'intermediate', |
| 9 | + visualization: 'matrix', |
| 10 | + code: `function sieveOfEratosthenes(n) { |
| 11 | + const isPrime = new Array(n + 1).fill(true); |
| 12 | + isPrime[0] = isPrime[1] = false; |
| 13 | +
|
| 14 | + for (let i = 2; i * i <= n; i++) { |
| 15 | + if (isPrime[i]) { |
| 16 | + for (let j = i * i; j <= n; j += i) { |
| 17 | + isPrime[j] = false; |
| 18 | + } |
| 19 | + } |
| 20 | + } |
| 21 | +
|
| 22 | + return isPrime |
| 23 | + .map((p, i) => p ? i : null) |
| 24 | + .filter(x => x !== null); |
| 25 | +} |
| 26 | +
|
| 27 | +sieveOfEratosthenes(30);`, |
| 28 | + description: `Sieve of Eratosthenes |
| 29 | +
|
| 30 | +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. |
| 31 | +
|
| 32 | +How it works: |
| 33 | +1. Create a boolean array marking 2..n as potentially prime |
| 34 | +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 |
| 35 | +3. Numbers that remain marked after the loop are the primes ≤ n |
| 36 | +
|
| 37 | +Why start crossing from i²? |
| 38 | + All smaller multiples of i (2i, 3i, …, (i−1)i) have already been crossed by a smaller prime. |
| 39 | +
|
| 40 | +Time Complexity: |
| 41 | + Best: O(n log log n) |
| 42 | + Average: O(n log log n) |
| 43 | + Worst: O(n log log n) |
| 44 | +
|
| 45 | +Space Complexity: O(n) |
| 46 | +
|
| 47 | +Properties: |
| 48 | + - Deterministic, no randomness |
| 49 | + - Cache-friendly when n fits in memory |
| 50 | + - Foundational for number theory and cryptography preprocessing`, |
| 51 | + |
| 52 | + generateSteps(locale = 'en') { |
| 53 | + const N = 30 |
| 54 | + const COLS = 6 |
| 55 | + const ROWS = Math.ceil(N / COLS) |
| 56 | + |
| 57 | + const values: (number | string)[][] = Array.from({ length: ROWS }, (_, r) => |
| 58 | + Array.from({ length: COLS }, (_, c) => r * COLS + c + 1), |
| 59 | + ) |
| 60 | + |
| 61 | + const cellOf = (v: number): [number, number] => [Math.floor((v - 1) / COLS), (v - 1) % COLS] |
| 62 | + |
| 63 | + const steps: Step[] = [] |
| 64 | + const composite = new Set<number>() |
| 65 | + |
| 66 | + const buildHighlights = ( |
| 67 | + currentPrime: number | null, |
| 68 | + currentMultiple: number | null, |
| 69 | + ): Record<string, HighlightType> => { |
| 70 | + const h: Record<string, HighlightType> = {} |
| 71 | + h['0,0'] = 'sorted' |
| 72 | + for (const c of composite) { |
| 73 | + const [r, col] = cellOf(c) |
| 74 | + h[`${r},${col}`] = 'placed' |
| 75 | + } |
| 76 | + if (currentPrime != null) { |
| 77 | + const [r, col] = cellOf(currentPrime) |
| 78 | + h[`${r},${col}`] = 'current' |
| 79 | + } |
| 80 | + if (currentMultiple != null) { |
| 81 | + const [r, col] = cellOf(currentMultiple) |
| 82 | + h[`${r},${col}`] = 'checking' |
| 83 | + } |
| 84 | + return h |
| 85 | + } |
| 86 | + |
| 87 | + steps.push({ |
| 88 | + matrix: { |
| 89 | + rows: ROWS, |
| 90 | + cols: COLS, |
| 91 | + values, |
| 92 | + highlights: buildHighlights(null, null), |
| 93 | + }, |
| 94 | + description: d( |
| 95 | + locale, |
| 96 | + `Initialize: assume every number from 2 to ${N} is prime. 1 is excluded by definition.`, |
| 97 | + `Inicializar: asumimos que todo número de 2 a ${N} es primo. 1 se excluye por definición.`, |
| 98 | + ), |
| 99 | + codeLine: 2, |
| 100 | + variables: { n: N, primes: 0 }, |
| 101 | + }) |
| 102 | + |
| 103 | + const limit = Math.floor(Math.sqrt(N)) |
| 104 | + for (let i = 2; i <= limit; i++) { |
| 105 | + if (composite.has(i)) continue |
| 106 | + |
| 107 | + steps.push({ |
| 108 | + matrix: { |
| 109 | + rows: ROWS, |
| 110 | + cols: COLS, |
| 111 | + values, |
| 112 | + highlights: buildHighlights(i, null), |
| 113 | + }, |
| 114 | + description: d( |
| 115 | + locale, |
| 116 | + `${i} is still marked prime. Cross out its multiples starting from ${i}² = ${i * i}.`, |
| 117 | + `${i} sigue marcado como primo. Tachar sus múltiplos empezando en ${i}² = ${i * i}.`, |
| 118 | + ), |
| 119 | + codeLine: 5, |
| 120 | + variables: { i, 'i*i': i * i }, |
| 121 | + }) |
| 122 | + |
| 123 | + for (let j = i * i; j <= N; j += i) { |
| 124 | + steps.push({ |
| 125 | + matrix: { |
| 126 | + rows: ROWS, |
| 127 | + cols: COLS, |
| 128 | + values, |
| 129 | + highlights: buildHighlights(i, j), |
| 130 | + }, |
| 131 | + description: d( |
| 132 | + locale, |
| 133 | + `Mark ${j} as composite (multiple of ${i}).`, |
| 134 | + `Marcar ${j} como compuesto (múltiplo de ${i}).`, |
| 135 | + ), |
| 136 | + codeLine: 7, |
| 137 | + variables: { i, j }, |
| 138 | + }) |
| 139 | + composite.add(j) |
| 140 | + } |
| 141 | + } |
| 142 | + |
| 143 | + const primes: number[] = [] |
| 144 | + for (let k = 2; k <= N; k++) if (!composite.has(k)) primes.push(k) |
| 145 | + |
| 146 | + const finalHighlights: Record<string, HighlightType> = { '0,0': 'sorted' } |
| 147 | + for (let k = 2; k <= N; k++) { |
| 148 | + const [r, col] = cellOf(k) |
| 149 | + finalHighlights[`${r},${col}`] = composite.has(k) ? 'placed' : 'pivot' |
| 150 | + } |
| 151 | + |
| 152 | + steps.push({ |
| 153 | + matrix: { |
| 154 | + rows: ROWS, |
| 155 | + cols: COLS, |
| 156 | + values, |
| 157 | + highlights: finalHighlights, |
| 158 | + }, |
| 159 | + description: d( |
| 160 | + locale, |
| 161 | + `Done. Primes ≤ ${N}: ${primes.join(', ')} (${primes.length} primes).`, |
| 162 | + `Listo. Primos ≤ ${N}: ${primes.join(', ')} (${primes.length} primos).`, |
| 163 | + ), |
| 164 | + codeLine: 12, |
| 165 | + variables: { count: primes.length, primes: primes.join(',') }, |
| 166 | + consoleOutput: [`[${primes.join(', ')}]`], |
| 167 | + }) |
| 168 | + |
| 169 | + return steps |
| 170 | + }, |
| 171 | +} |
| 172 | + |
| 173 | +export { sieveOfEratosthenes } |
0 commit comments