Skip to content

Commit ad89f0c

Browse files
authored
Merge pull request #11 from dcq-31/feat/add-sieve-of-eratosthenes
feat: Add Sieve of Eratosthenes algorithm in new Math category
2 parents 0d12425 + 14b4a86 commit ad89f0c

5 files changed

Lines changed: 257 additions & 0 deletions

File tree

src/components/ComplexityChart.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ type ComplexityKey =
55
| 'O(log n)'
66
| 'O(√n)'
77
| 'O(n)'
8+
| 'O(n log log n)'
89
| 'O(n log n)'
910
| 'O(n²)'
1011
| 'O(2^n)'
@@ -15,6 +16,7 @@ const COMPLEXITY_FNS: Record<ComplexityKey, (n: number) => number> = {
1516
'O(log n)': (n) => Math.log2(Math.max(1, n)),
1617
'O(√n)': (n) => Math.sqrt(n),
1718
'O(n)': (n) => n,
19+
'O(n log log n)': (n) => n * Math.log2(Math.max(2, Math.log2(Math.max(2, n)))),
1820
'O(n log n)': (n) => n * Math.log2(Math.max(1, n)),
1921
'O(n²)': (n) => n * n,
2022
'O(2^n)': (n) => Math.pow(2, n),
@@ -37,6 +39,7 @@ function normalizeToKey(raw: string): ComplexityKey | null {
3739
if (/2\^|k\^/.test(s)) return 'O(2^n)'
3840
if (/n|sqrt/.test(s)) return 'O(√n)'
3941
if (/n²|n\^2|v²/.test(s)) return 'O(n²)'
42+
if (/nloglogn|n\*loglogn/.test(s)) return 'O(n log log n)'
4043
if (/nlogn|n\*logn|\(v\+e\)log|elog|n\^1\.25/.test(s)) return 'O(n log n)'
4144
if (/loglog/.test(s)) return 'O(log n)'
4245
if (/log/.test(s)) return 'O(log n)'

src/components/Sidebar.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,22 @@ const categoryIcons: Record<string, React.ReactNode> = {
139139
/>
140140
</svg>
141141
),
142+
Math: (
143+
<svg
144+
className="w-3.5 h-3.5"
145+
fill="none"
146+
viewBox="0 0 24 24"
147+
stroke="currentColor"
148+
strokeWidth={2}
149+
aria-hidden="true"
150+
>
151+
<path
152+
strokeLinecap="round"
153+
strokeLinejoin="round"
154+
d="M5 5h14M9 5c0 4-1.5 10-3 14M15 5c0 4 1.5 10 3 14"
155+
/>
156+
</svg>
157+
),
142158
}
143159

144160
const categoryColors: Record<string, { icon: string; badge: string; line: string; active: string }> = {
@@ -190,6 +206,12 @@ const categoryColors: Record<string, { icon: string; badge: string; line: string
190206
line: 'border-indigo-500/20',
191207
active: 'border-l-indigo-400',
192208
},
209+
Math: {
210+
icon: 'text-fuchsia-400',
211+
badge: 'bg-fuchsia-500/10 text-fuchsia-400/70',
212+
line: 'border-fuchsia-500/20',
213+
active: 'border-l-fuchsia-400',
214+
},
193215
}
194216

195217
const defaultCategoryColor = {

src/i18n/translations.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ export const translations: Record<Locale, Translations> = {
144144
'Dynamic Programming': 'Dynamic Programming',
145145
Backtracking: 'Backtracking',
146146
'Divide and Conquer': 'Divide and Conquer',
147+
Math: 'Math',
147148
},
148149

149150
algorithmDescriptions: {
@@ -1021,6 +1022,32 @@ Properties:
10211022
- Demonstrates the power of recursion
10221023
10231024
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).`,
1025+
1026+
'sieve-of-eratosthenes': `Sieve of Eratosthenes
1027+
1028+
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.
1029+
1030+
How it works:
1031+
1. Create a boolean array marking 2..n as potentially prime
1032+
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
1033+
3. Numbers that remain marked after the loop are the primes ≤ n
1034+
1035+
Why start crossing from i²?
1036+
All smaller multiples of i (2i, 3i, …, (i−1)i) have already been crossed by a smaller prime.
1037+
1038+
Time Complexity:
1039+
Best: O(n log log n)
1040+
Average: O(n log log n)
1041+
Worst: O(n log log n)
1042+
1043+
Space Complexity: O(n)
1044+
1045+
Properties:
1046+
- Deterministic, no randomness
1047+
- Cache-friendly when n fits in memory
1048+
- Foundational for number theory and cryptography preprocessing
1049+
1050+
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.`,
10241051
},
10251052
},
10261053

@@ -1089,6 +1116,7 @@ The puzzle was invented by mathematician Édouard Lucas in 1883. Legend says mon
10891116
'Dynamic Programming': 'Programación Dinámica',
10901117
Backtracking: 'Backtracking',
10911118
'Divide and Conquer': 'Divide y Vencerás',
1119+
Math: 'Matemáticas',
10921120
},
10931121

10941122
algorithmDescriptions: {
@@ -1966,6 +1994,32 @@ Propiedades:
19661994
- Demuestra el poder de la recursión
19671995
19681996
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).`,
1997+
1998+
'sieve-of-eratosthenes': `Criba de Eratóstenes
1999+
2000+
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.
2001+
2002+
Cómo funciona:
2003+
1. Crea un arreglo booleano marcando 2..n como potencialmente primos
2004+
2. Para cada i desde 2 hasta √n, si i sigue marcado como primo, marca todos sus múltiplos (empezando desde i²) como compuestos
2005+
3. Los números que permanezcan marcados al terminar el bucle son los primos ≤ n
2006+
2007+
¿Por qué empezar a tachar desde i²?
2008+
Todos los múltiplos menores de i (2i, 3i, …, (i−1)i) ya fueron tachados por un primo más pequeño.
2009+
2010+
Complejidad Temporal:
2011+
Mejor: O(n log log n)
2012+
Promedio: O(n log log n)
2013+
Peor: O(n log log n)
2014+
2015+
Complejidad Espacial: O(n)
2016+
2017+
Propiedades:
2018+
- Determinista, sin aleatoriedad
2019+
- Eficiente en caché cuando n cabe en memoria
2020+
- Fundamento para teoría de números y preprocesamiento criptográfico
2021+
2022+
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.`,
19692023
},
19702024
},
19712025
}

src/lib/algorithms/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ import {
6161

6262
import { towerOfHanoi } from '@lib/algorithms/divide-and-conquer'
6363

64+
import { sieveOfEratosthenes } from '@lib/algorithms/math'
65+
6466
export const algorithms: Algorithm[] = [
6567
// Concepts
6668
bigONotation,
@@ -109,6 +111,8 @@ export const algorithms: Algorithm[] = [
109111
mazePathfinding,
110112
// Divide and Conquer
111113
towerOfHanoi,
114+
// Math
115+
sieveOfEratosthenes,
112116
]
113117

114118
export const categories: Category[] = [
@@ -126,4 +130,5 @@ export const categories: Category[] = [
126130
name: 'Divide and Conquer',
127131
algorithms: algorithms.filter((a) => a.category === 'Divide and Conquer'),
128132
},
133+
{ name: 'Math', algorithms: algorithms.filter((a) => a.category === 'Math') },
129134
]

src/lib/algorithms/math.ts

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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

Comments
 (0)