Skip to content

Commit 90e6ffd

Browse files
authored
Merge pull request #6 from Ricardocanul7/main
Add bucket sort algorithm
2 parents 299ac13 + cfdd204 commit 90e6ffd

File tree

5 files changed

+673
-1
lines changed

5 files changed

+673
-1
lines changed

src/components/ConceptVisualizer.tsx

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type {
1010
SlidingWindowState,
1111
MemoTableState,
1212
CoinChangeState,
13+
BucketsState,
1314
} from '@lib/types'
1415

1516
interface ConceptVisualizerProps {
@@ -41,6 +42,8 @@ export default function ConceptVisualizer({ step }: ConceptVisualizerProps) {
4142
return <MemoTableViz state={concept} />
4243
case 'coinChange':
4344
return <CoinChangeViz state={concept} />
45+
case 'buckets':
46+
return <BucketsViz state={concept} />
4447
default:
4548
return null
4649
}
@@ -1164,3 +1167,249 @@ function CoinChangeViz({ state }: { state: CoinChangeState }) {
11641167
</div>
11651168
)
11661169
}
1170+
1171+
// ════════════════════════════════════════════════════════════════
1172+
// BUCKETS — Visualization for Bucket Sort
1173+
// ════════════════════════════════════════════════════════════════
1174+
1175+
function BucketsViz({ state }: { state: BucketsState }) {
1176+
const {
1177+
array,
1178+
buckets,
1179+
range,
1180+
min,
1181+
max,
1182+
bucketSize,
1183+
currentElementIndex,
1184+
activeBucketIndex,
1185+
innerHighlights,
1186+
phase,
1187+
operation,
1188+
} = state
1189+
1190+
return (
1191+
<div className="flex-1 flex flex-col items-center justify-center gap-6 w-full py-4 scale-90 md:scale-100">
1192+
<div className="text-neutral-500 font-mono text-[11px] uppercase tracking-widest">
1193+
Bucket Sort Visualization
1194+
</div>
1195+
1196+
{operation && (
1197+
<div className="font-mono text-xs px-3 py-1 rounded-full bg-white/5 border border-white/10 text-neutral-300">
1198+
{operation}
1199+
</div>
1200+
)}
1201+
1202+
{/* Min/Max Status & Calculation during Initializing */}
1203+
{phase === 'initializing' && (min !== undefined || max !== undefined) && (
1204+
<div className="flex flex-col items-center gap-4 animate-in fade-in slide-in-from-top-2 duration-500">
1205+
<div className="flex gap-4">
1206+
<div className="flex flex-col items-center px-4 py-2 bg-rose-500/10 border border-rose-500/20 rounded-lg">
1207+
<span className="text-[9px] font-mono text-rose-400 uppercase font-bold">Current Min</span>
1208+
<span className="text-xl font-mono font-bold text-rose-500">{min ?? '—'}</span>
1209+
</div>
1210+
<div className="flex flex-col items-center px-4 py-2 bg-blue-500/10 border border-blue-500/20 rounded-lg">
1211+
<span className="text-[9px] font-mono text-blue-400 uppercase font-bold">Current Max</span>
1212+
<span className="text-xl font-mono font-bold text-blue-500">{max ?? '—'}</span>
1213+
</div>
1214+
</div>
1215+
1216+
{/* Bucket Calculation Formula */}
1217+
{buckets.length > 0 && (
1218+
<div className="bg-neutral-900/50 border border-white/10 rounded-xl px-6 py-4 flex flex-col items-center gap-2 shadow-2xl">
1219+
<div className="text-[10px] font-mono text-neutral-500 uppercase tracking-widest">Bucket Count Calculation</div>
1220+
<div className="flex items-center gap-3 font-mono">
1221+
<div className="flex flex-col items-center">
1222+
<div className="text-xs text-neutral-400 mb-1">floor((max - min) / size) + 1</div>
1223+
<div className="flex items-center text-lg">
1224+
<span className="text-neutral-500"></span>
1225+
<div className="flex flex-col items-center px-2">
1226+
<div className="border-b border-white/20 px-2 pb-0.5 mb-0.5">
1227+
<span className="text-blue-400">{max}</span> - <span className="text-rose-400">{min}</span>
1228+
</div>
1229+
<div className="pt-0.5 text-amber-400 text-sm">{bucketSize}</div>
1230+
</div>
1231+
<span className="text-neutral-500"></span>
1232+
<span className="mx-2 text-white">+ 1</span>
1233+
<span className="mx-2 text-neutral-400">=</span>
1234+
<span className="text-2xl font-bold text-green-400">{buckets.length}</span>
1235+
</div>
1236+
</div>
1237+
</div>
1238+
<div className="text-[10px] text-neutral-500 italic mt-1">
1239+
Each bucket covers a range of {bucketSize} values
1240+
</div>
1241+
</div>
1242+
)}
1243+
</div>
1244+
)}
1245+
1246+
{/* Main Array Section */}
1247+
<div className="flex flex-col items-center gap-3 w-full max-w-2xl px-4 py-3 bg-white/2 rounded-xl border border-white/5">
1248+
<div className="flex items-center justify-between w-full mb-1">
1249+
<span className="text-[10px] font-mono text-neutral-500 uppercase tracking-wider">
1250+
{phase === 'collecting' ? 'Result Array' : 'Input Array'}
1251+
</span>
1252+
{phase === 'distributing' && currentElementIndex !== undefined && (
1253+
<span className="text-[10px] font-mono text-blue-400">
1254+
Processing: <span className="font-bold">{array[currentElementIndex]}</span>
1255+
</span>
1256+
)}
1257+
{phase === 'initializing' && (
1258+
<span className="text-[10px] font-mono text-amber-400">
1259+
{buckets.length > 0 ? 'Creating Buckets...' : 'Scanning Range...'}
1260+
</span>
1261+
)}
1262+
</div>
1263+
<div className="flex flex-wrap justify-center gap-1.5">
1264+
{array.map((val, i) => {
1265+
const isProcessing = i === currentElementIndex
1266+
const isCollected = phase === 'collecting' && i < currentElementIndex!
1267+
return (
1268+
<div
1269+
key={i}
1270+
className="relative group h-10 w-10"
1271+
>
1272+
<div
1273+
className="absolute inset-0 rounded border flex items-center justify-center font-mono text-sm transition-all duration-500"
1274+
style={{
1275+
backgroundColor: isProcessing
1276+
? 'rgba(59,130,246,0.25)'
1277+
: isCollected
1278+
? 'rgba(74,222,128,0.1)'
1279+
: 'rgba(255,255,255,0.03)',
1280+
borderColor: isProcessing
1281+
? '#3b82f6'
1282+
: isCollected
1283+
? '#4ade8050'
1284+
: 'rgba(255,255,255,0.1)',
1285+
color: isProcessing ? '#fff' : isCollected ? '#4ade80' : '#888',
1286+
transform: isProcessing ? 'translateY(-4px)' : 'none',
1287+
boxShadow: isProcessing ? '0 4px 12px rgba(59,130,246,0.3)' : 'none',
1288+
opacity: isCollected ? 0.6 : 1,
1289+
}}
1290+
>
1291+
{val}
1292+
</div>
1293+
{isProcessing && (
1294+
<div className="absolute -top-6 left-1/2 -translate-x-1/2 text-blue-400 animate-bounce">
1295+
<svg width="10" height="10" viewBox="0 0 10 10" fill="currentColor">
1296+
<path d="M5 10L0 0H10L5 10Z" />
1297+
</svg>
1298+
</div>
1299+
)}
1300+
</div>
1301+
)
1302+
})}
1303+
</div>
1304+
</div>
1305+
1306+
{/* Buckets Section */}
1307+
<div className="flex flex-wrap justify-center gap-x-4 gap-y-8 mt-4 w-full px-4">
1308+
{buckets.map((bucket, bIdx) => {
1309+
const isActive = bIdx === activeBucketIndex
1310+
const rangeStart = range ? range.min + bIdx * (bucketSize ?? 1) : 0
1311+
const rangeEnd = range ? rangeStart + (bucketSize ?? 1) - 1 : 0
1312+
1313+
return (
1314+
<div key={bIdx} className="flex flex-col items-center min-w-[80px]">
1315+
{/* Range label */}
1316+
<div
1317+
className="mb-1.5 px-2 py-0.5 rounded-full text-[9px] font-mono transition-colors duration-300"
1318+
style={{
1319+
backgroundColor: isActive ? 'rgba(59,130,246,0.15)' : 'rgba(255,255,255,0.03)',
1320+
color: isActive ? '#60a5fa' : '#555',
1321+
}}
1322+
>
1323+
{rangeStart}{rangeEnd}
1324+
</div>
1325+
1326+
{/* Bucket container */}
1327+
<div className="relative group">
1328+
<div
1329+
className="w-20 min-h-[120px] rounded-t-xl border-x-2 border-t-2 flex flex-col items-center p-2 gap-1.5 transition-all duration-500"
1330+
style={{
1331+
backgroundColor: isActive ? 'rgba(59,130,246,0.08)' : 'rgba(255,255,255,0.02)',
1332+
borderColor: isActive ? 'rgba(59,130,246,0.5)' : 'rgba(255,255,255,0.08)',
1333+
boxShadow: isActive ? '0 -8px 20px rgba(59,130,246,0.15)' : 'none',
1334+
}}
1335+
>
1336+
{bucket.map((val, vIdx) => {
1337+
const highlight = isActive ? innerHighlights?.[vIdx] : undefined
1338+
const getHighlightStyles = () => {
1339+
switch (highlight) {
1340+
case 'comparing': return { bg: 'rgba(59,130,246,0.3)', border: '#3b82f6', text: '#fff' }
1341+
case 'active': return { bg: 'rgba(234,179,8,0.3)', border: '#eab308', text: '#fff' }
1342+
case 'current': return { bg: 'rgba(168,85,247,0.3)', border: '#a855f7', text: '#fff' }
1343+
case 'found': return { bg: 'rgba(74,222,128,0.2)', border: '#4ade80', text: '#4ade80' }
1344+
default: return { bg: 'rgba(38,38,38,1)', border: 'rgba(255,255,255,0.1)', text: '#60a5fa' }
1345+
}
1346+
}
1347+
const styles = getHighlightStyles()
1348+
1349+
return (
1350+
<div
1351+
key={vIdx}
1352+
className="w-full h-8 flex items-center justify-center font-mono text-xs font-bold rounded shadow-sm transition-all duration-300 hover:scale-105"
1353+
style={{
1354+
backgroundColor: styles.bg,
1355+
borderColor: styles.border,
1356+
borderWidth: '1px',
1357+
color: styles.text,
1358+
animation: phase === 'distributing' && isActive && vIdx === bucket.length - 1 ? 'pop 0.3s ease-out' : 'none',
1359+
transform: highlight ? 'scale(1.05)' : 'none',
1360+
zIndex: highlight ? 10 : 1,
1361+
}}
1362+
>
1363+
{val}
1364+
</div>
1365+
)
1366+
})}
1367+
1368+
{/* Empty state dots */}
1369+
{bucket.length === 0 && (
1370+
<div className="flex-1 flex flex-col items-center justify-center gap-2 opacity-20">
1371+
<div className="w-1 h-1 rounded-full bg-white" />
1372+
<div className="w-1 h-1 rounded-full bg-white" />
1373+
<div className="w-1 h-1 rounded-full bg-white" />
1374+
</div>
1375+
)}
1376+
</div>
1377+
1378+
{/* Bucket Bottom */}
1379+
<div
1380+
className="w-20 h-3 rounded-b-xl border-x-2 border-b-2 transition-all duration-500"
1381+
style={{
1382+
backgroundColor: isActive ? 'rgba(59,130,246,0.2)' : 'rgba(255,255,255,0.05)',
1383+
borderColor: isActive ? 'rgba(59,130,246,0.5)' : 'rgba(255,255,255,0.08)',
1384+
}}
1385+
/>
1386+
1387+
{/* Active indicator */}
1388+
{isActive && (
1389+
<div className="absolute -bottom-6 left-1/2 -translate-x-1/2 whitespace-nowrap">
1390+
<span className="text-[10px] font-bold text-blue-400 uppercase tracking-tighter animate-pulse">
1391+
Active
1392+
</span>
1393+
</div>
1394+
)}
1395+
</div>
1396+
1397+
{/* Bucket Index */}
1398+
<div className="mt-7 text-[10px] font-mono text-neutral-500 font-bold">
1399+
Bucket {bIdx}
1400+
</div>
1401+
</div>
1402+
)
1403+
})}
1404+
</div>
1405+
1406+
<style>{`
1407+
@keyframes pop {
1408+
0% { transform: scale(0.8); opacity: 0; }
1409+
70% { transform: scale(1.1); }
1410+
100% { transform: scale(1); opacity: 1; }
1411+
}
1412+
`}</style>
1413+
</div>
1414+
)
1415+
}

src/i18n/translations.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,6 +724,28 @@ Properties:
724724
- Adaptive
725725
726726
Shell Sort is faster than Insertion Sort for larger arrays because it moves elements closer to their final position earlier. Performance depends heavily on the gap sequence chosen.`,
727+
'bucket-sort': `Bucket Sort
728+
729+
Bucket Sort is a distribution-based sorting algorithm that works by partitioning an array into a number of buckets. Each bucket is then sorted individually using another sorting algorithm or recursively applying the bucket sort.
730+
731+
How it works:
732+
1. Find the range (min/max) to determine bucket indices
733+
2. Create empty buckets based on a fixed size (e.g., 10 or 20)
734+
3. Distribute elements into buckets: index = floor((value - min) / size)
735+
4. Sort each non-empty bucket using Insertion Sort
736+
5. Collect elements from sorted buckets back into the main array
737+
738+
Time Complexity:
739+
Best: O(n + k) — uniform distribution
740+
Average: O(n + k)
741+
Worst: O(n²) — all elements fall into one bucket
742+
743+
Space Complexity: O(n + k) — extra space for buckets
744+
745+
Properties:
746+
- Stable sort (if underlying sort is stable)
747+
- Not in-place
748+
- Data-distribution dependent`,
727749

728750
'jump-search': `Jump Search
729751
@@ -1634,6 +1656,28 @@ Propiedades:
16341656
- Adaptativo
16351657
16361658
Shell Sort es más rápido que Insertion Sort para arreglos grandes porque mueve elementos más cerca de su posición final antes. El rendimiento depende mucho de la secuencia de brechas elegida.`,
1659+
'bucket-sort': `Bucket Sort (Ordenamiento por Cubetas)
1660+
1661+
Bucket Sort es un algoritmo de ordenamiento basado en la distribución que funciona particionando un arreglo en varias cubetas. Cada cubeta se ordena individualmente usando otro algoritmo de ordenamiento o aplicando recursivamente el mismo algoritmo.
1662+
1663+
Cómo funciona:
1664+
1. Encontrar el rango (mín/máx) para determinar los índices de las cubetas
1665+
2. Crear cubetas vacías basadas en un tamaño fijo (por ejemplo, 10 o 20)
1666+
3. Distribuir elementos en las cubetas: índice = suelo((valor - mín) / tamaño)
1667+
4. Ordenar cada cubeta no vacía usando Insertion Sort
1668+
5. Recolectar elementos de las cubetas ordenadas de vuelta al arreglo principal
1669+
1670+
Complejidad Temporal:
1671+
Mejor: O(n + k) — distribución uniforme
1672+
Promedio: O(n + k)
1673+
Peor: O(n²) — todos los elementos caen en una sola cubeta
1674+
1675+
Complejidad Espacial: O(n + k) — espacio extra para cubetas
1676+
1677+
Propiedades:
1678+
- Ordenamiento estable (si el ordenamiento subyacente lo es)
1679+
- No es in-place
1680+
- Dependiente de la distribución de los datos`,
16371681

16381682
'jump-search': `Jump Search (Búsqueda por Saltos)
16391683

src/lib/algorithms/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
countingSort,
3030
radixSort,
3131
shellSort,
32+
bucketSort,
3233
} from '@lib/algorithms/sorting'
3334

3435
import {
@@ -86,6 +87,7 @@ export const algorithms: Algorithm[] = [
8687
countingSort,
8788
radixSort,
8889
shellSort,
90+
bucketSort,
8991
// Searching
9092
binarySearch,
9193
linearSearch,

0 commit comments

Comments
 (0)