Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/components/ComplexityChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)'
Expand All @@ -15,6 +16,7 @@ const COMPLEXITY_FNS: Record<ComplexityKey, (n: number) => 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),
Expand All @@ -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)'
Expand Down
22 changes: 22 additions & 0 deletions src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,22 @@ const categoryIcons: Record<string, React.ReactNode> = {
/>
</svg>
),
Math: (
<svg
className="w-3.5 h-3.5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M5 5h14M9 5c0 4-1.5 10-3 14M15 5c0 4 1.5 10 3 14"
/>
</svg>
),
}

const categoryColors: Record<string, { icon: string; badge: string; line: string; active: string }> = {
Expand Down Expand Up @@ -190,6 +206,12 @@ const categoryColors: Record<string, { icon: string; badge: string; line: string
line: 'border-indigo-500/20',
active: 'border-l-indigo-400',
},
Math: {
icon: 'text-fuchsia-400',
badge: 'bg-fuchsia-500/10 text-fuchsia-400/70',
line: 'border-fuchsia-500/20',
active: 'border-l-fuchsia-400',
},
}

const defaultCategoryColor = {
Expand Down
54 changes: 54 additions & 0 deletions src/i18n/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ export const translations: Record<Locale, Translations> = {
'Dynamic Programming': 'Dynamic Programming',
Backtracking: 'Backtracking',
'Divide and Conquer': 'Divide and Conquer',
Math: 'Math',
},

algorithmDescriptions: {
Expand Down Expand Up @@ -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.`,
},
},

Expand Down Expand Up @@ -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: {
Expand Down Expand Up @@ -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.`,
},
},
}
Expand Down
5 changes: 5 additions & 0 deletions src/lib/algorithms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -109,6 +111,8 @@ export const algorithms: Algorithm[] = [
mazePathfinding,
// Divide and Conquer
towerOfHanoi,
// Math
sieveOfEratosthenes,
]

export const categories: Category[] = [
Expand All @@ -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') },
]
173 changes: 173 additions & 0 deletions src/lib/algorithms/math.ts
Original file line number Diff line number Diff line change
@@ -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<number>()

const buildHighlights = (
currentPrime: number | null,
currentMultiple: number | null,
): Record<string, HighlightType> => {
const h: Record<string, HighlightType> = {}
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<string, HighlightType> = { '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 }