diff --git a/algorithms-js/AGENTS.md b/AGENTS.md
similarity index 82%
rename from algorithms-js/AGENTS.md
rename to AGENTS.md
index 29074220..8c50b661 100644
--- a/algorithms-js/AGENTS.md
+++ b/AGENTS.md
@@ -105,11 +105,22 @@ algorithms-js/
## π Development Workflow
### **Testing Approach**
+- **Algorithm Testing Lab**: Use `http://localhost:8080/test-algorithm.html` for comprehensive testing
- Test algorithm functionality first (core logic)
- Verify UI/visualization works correctly
+- Test step-by-step animations and tracking
- Check responsive design on multiple screen sizes
- Test dark/light mode switching
- Validate in browser console (no 404s or errors)
+- Use external browser (not terminal web viewers) for JavaScript execution
+
+### **Algorithm Testing Workflow**
+1. **Dependencies**: Test if all scripts load correctly
+2. **Configuration**: Verify config objects are exported properly
+3. **Core Functions**: Test algorithm logic and basic functionality
+4. **Step Tracking**: Verify animation step generation works
+5. **Demo Simulation**: Test the full demo interface functionality
+6. **Cross-browser Testing**: Test in Chrome, Firefox, Safari
### **Code Quality**
- Write clear, self-documenting code
@@ -124,6 +135,15 @@ algorithms-js/
## π Common Issues & Solutions
+### **JavaScript Execution Issues**
+- **Warp Terminal Browser**: Warp's built-in web rendering may not execute JavaScript properly
+- **Symptom**: "You need to enable JavaScript to run this app" message
+- **Solutions**:
+ - Use external browser (Chrome, Firefox, Safari) for testing demos
+ - Use the permanent test page: `http://localhost:8080/test-algorithm.html`
+ - Test individual components using Node.js for core functionality
+- **Testing Strategy**: Always verify demos in a full browser, not just terminal web viewers
+
### **404 Errors**
- Often caused by missing `-core.js` files
- Ensure config files point to correct paths
@@ -181,6 +201,13 @@ algorithms-js/
## π‘ Tips for AI Assistants
+### **Reading GitHub Issues**
+- **Use curl for reading GitHub issues**: When you need to read a GitHub issue, use curl instead of gh CLI
+- **Command format**: `curl -s "https://api.github.com/repos/sachinlala/SimplifyLearning/issues/ISSUE_NUMBER"`
+- **Get specific fields**: `curl -s "https://api.github.com/repos/sachinlala/SimplifyLearning/issues/ISSUE_NUMBER" | jq '{title: .title, body: .body, state: .state}'`
+- **List all issues**: `curl -s "https://api.github.com/repos/sachinlala/SimplifyLearning/issues"`
+- This approach is more reliable than gh CLI for programmatic access
+
### **π¨ CRITICAL: Workflow Requirements**
- **NEVER push directly to `master` branch - use feature branches only**
- **NEVER merge PRs without explicit approval from maintainer**
diff --git a/algorithms-js/assets/css/styles.css b/algorithms-js/assets/css/styles.css
index bd5e072b..fb0c7c16 100644
--- a/algorithms-js/assets/css/styles.css
+++ b/algorithms-js/assets/css/styles.css
@@ -2035,6 +2035,825 @@ body.dark-mode .demo-icon:hover {
box-shadow: 0 4px 8px rgba(88, 166, 255, 0.4);
}
+/* ===== WIGGLE SORT VISUALIZATION STYLES ===== */
+/* Valley (even positions) - blue colors */
+.viz-cell.valley {
+ background: rgba(33, 150, 243, 0.8) !important;
+ border-color: #1976d2 !important;
+ color: white !important;
+}
+
+/* Peak (odd positions) - red colors */
+.viz-cell.peak {
+ background: rgba(244, 67, 54, 0.8) !important;
+ border-color: #c62828 !important;
+ color: white !important;
+}
+
+/* Comparing elements - yellow */
+.viz-cell.comparing {
+ background: rgba(255, 193, 7, 0.9) !important;
+ border-color: #f57c00 !important;
+ color: #333 !important;
+ animation: pulse 0.8s ease-in-out infinite alternate;
+}
+
+/* Swapping elements - green */
+.viz-cell.swapping {
+ background: rgba(76, 175, 80, 0.9) !important;
+ border-color: #388e3c !important;
+ color: white !important;
+ animation: bounce 0.6s ease-in-out;
+}
+
+/* Dark mode overrides for wiggle sort */
+body.dark-mode .viz-cell.valley {
+ background: rgba(30, 136, 229, 0.9) !important;
+ border-color: #0d47a1 !important;
+ color: #fff !important;
+}
+
+body.dark-mode .viz-cell.peak {
+ background: rgba(229, 57, 53, 0.9) !important;
+ border-color: #b71c1c !important;
+ color: #fff !important;
+}
+
+body.dark-mode .viz-cell.comparing {
+ background: rgba(255, 193, 7, 0.85) !important;
+ border-color: #f57c00 !important;
+ color: #333 !important;
+}
+
+body.dark-mode .viz-cell.swapping {
+ background: rgba(76, 175, 80, 0.85) !important;
+ border-color: #2e7d32 !important;
+ color: #fff !important;
+}
+
+/* Animations for wiggle sort */
+@keyframes pulse {
+ 0% { transform: scale(1); }
+ 100% { transform: scale(1.1); }
+}
+
+@keyframes bounce {
+ 0%, 20%, 60%, 100% { transform: translateY(0); }
+ 40% { transform: translateY(-8px); }
+ 80% { transform: translateY(-4px); }
+}
+
+/* ===== DUTCH FLAG SORT VISUALIZATION STYLES ===== */
+/* Red partition (first group) */
+.viz-cell.red-partition {
+ background: rgba(244, 67, 54, 0.8) !important;
+ border-color: #c62828 !important;
+ color: white !important;
+}
+
+/* White/Other partition (middle group) */
+.viz-cell.white-partition {
+ background: rgba(238, 238, 238, 0.9) !important;
+ border-color: #bdbdbd !important;
+ color: #333 !important;
+}
+
+/* Blue partition (last group) */
+.viz-cell.blue-partition {
+ background: rgba(33, 150, 243, 0.8) !important;
+ border-color: #1976d2 !important;
+ color: white !important;
+}
+
+/* Dark mode overrides for dutch flag sort */
+body.dark-mode .viz-cell.red-partition {
+ background: rgba(229, 57, 53, 0.9) !important;
+ border-color: #b71c1c !important;
+ color: #fff !important;
+}
+
+body.dark-mode .viz-cell.white-partition {
+ background: rgba(189, 189, 189, 0.3) !important;
+ border-color: #757575 !important;
+ color: #fff !important;
+}
+
+body.dark-mode .viz-cell.blue-partition {
+ background: rgba(30, 136, 229, 0.9) !important;
+ border-color: #0d47a1 !important;
+ color: #fff !important;
+}
+
+/* ===== BUCKET SORT VISUALIZATION STYLES ===== */
+/* Bucket info and legend display */
+.bucket-info {
+ margin: 10px 0;
+ padding: 8px;
+ background: rgba(0, 123, 255, 0.05);
+ border: 1px solid rgba(0, 123, 255, 0.2);
+ border-radius: 6px;
+}
+
+.bucket-legend {
+ font-size: 13px;
+ text-align: center;
+ line-height: 1.4;
+}
+
+.bucket-color {
+ display: inline-block;
+ margin: 3px 5px;
+ padding: 6px 10px;
+ border-radius: 6px;
+ font-size: 12px;
+ font-weight: bold;
+ color: white;
+ text-shadow: 0 1px 2px rgba(0,0,0,0.7);
+ border: 2px solid rgba(255,255,255,0.3);
+ box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+}
+
+/* Bucket color definitions */
+.bucket-color.bucket-0 { background-color: #ff6b6b !important; }
+.bucket-color.bucket-1 { background-color: #4ecdc4 !important; }
+.bucket-color.bucket-2 { background-color: #45b7d1 !important; }
+.bucket-color.bucket-3 { background-color: #f9ca24 !important; }
+.bucket-color.bucket-4 { background-color: #f0932b !important; }
+.bucket-color.bucket-5 { background-color: #eb4d4b !important; }
+.bucket-color.bucket-6 { background-color: #6c5ce7 !important; }
+.bucket-color.bucket-7 { background-color: #74b9ff !important; }
+.bucket-color.bucket-8 { background-color: #00b894 !important; }
+.bucket-color.bucket-9 { background-color: #fdcb6e !important; }
+
+
+/* Animation states for bucket sort */
+.viz-cell.distributing {
+ animation: bucket-distribute 1s ease-in-out;
+ transform: scale(1.1);
+ z-index: 10;
+}
+
+@keyframes bucket-distribute {
+ 0% { transform: scale(1); }
+ 50% { transform: scale(1.2) rotate(5deg); }
+ 100% { transform: scale(1.1); }
+}
+
+.viz-cell.bucket-sorting {
+ animation: bucket-sort-pulse 1.2s ease-in-out infinite alternate;
+ box-shadow: 0 0 8px rgba(255, 152, 0, 0.6);
+}
+
+@keyframes bucket-sort-pulse {
+ from { opacity: 1; transform: scale(1); }
+ to { opacity: 0.8; transform: scale(1.05); }
+}
+
+.viz-cell.collected {
+ background: #e8f5e8 !important;
+ border-color: #4caf50 !important;
+ animation: bucket-collect 0.8s ease-out;
+}
+
+@keyframes bucket-collect {
+ 0% { transform: translateY(-10px); opacity: 0.7; }
+ 100% { transform: translateY(0); opacity: 1; }
+}
+
+/* Ensure cells have relative positioning for bucket indicators */
+.viz-cell {
+ position: relative;
+}
+
+.viz-cell.complete {
+ background: #c8e6c9 !important;
+ border-color: #4caf50 !important;
+ animation: completion-glow 1s ease-out;
+}
+
+@keyframes completion-glow {
+ 0% { box-shadow: 0 0 0 rgba(76, 175, 80, 0.4); }
+ 50% { box-shadow: 0 0 20px rgba(76, 175, 80, 0.6); }
+ 100% { box-shadow: 0 0 0 rgba(76, 175, 80, 0.4); }
+}
+
+/* Dark mode overrides for bucket sort */
+body.dark-mode .bucket-info {
+ background: rgba(66, 165, 245, 0.1);
+ border-color: rgba(66, 165, 245, 0.3);
+ color: #e3f2fd;
+}
+
+
+body.dark-mode .viz-cell.collected {
+ background: rgba(76, 175, 80, 0.3) !important;
+ border-color: #81c784 !important;
+}
+
+body.dark-mode .viz-cell.complete {
+ background: rgba(165, 214, 167, 0.4) !important;
+ border-color: #81c784 !important;
+}
+
+/* Mobile responsive adjustments for bucket sort */
+@media (max-width: 768px) {
+ .bucket-legend {
+ font-size: 12px;
+ }
+
+ .bucket-color {
+ margin: 1px 2px;
+ padding: 2px 4px;
+ font-size: 10px;
+ }
+
+}
+
+/* ===== RADIX SORT VISUALIZATION STYLES ===== */
+/* Radix sort specific cell styling */
+.viz-cell.radix-cell {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ min-height: 60px;
+ font-size: 14px;
+ font-weight: bold;
+}
+
+/* Digit display within cells */
+.digit-display {
+ position: absolute;
+ bottom: -20px;
+ left: 50%;
+ transform: translateX(-50%);
+ background: #ff9800;
+ color: white;
+ padding: 2px 6px;
+ border-radius: 4px;
+ font-size: 10px;
+ font-weight: bold;
+ min-width: 16px;
+ text-align: center;
+ opacity: 0;
+ transition: all 0.3s ease;
+}
+
+.digit-display.digit-highlight {
+ opacity: 1;
+ animation: digit-pulse 1.5s ease-in-out;
+ background: #f44336 !important;
+ color: white !important;
+ box-shadow: 0 0 8px rgba(244, 67, 54, 0.6);
+}
+
+@keyframes digit-pulse {
+ 0%, 100% {
+ transform: translateX(-50%) scale(1);
+ box-shadow: 0 0 8px rgba(244, 67, 54, 0.6);
+ }
+ 50% {
+ transform: translateX(-50%) scale(1.3);
+ box-shadow: 0 0 15px rgba(244, 67, 54, 0.9);
+ }
+}
+
+/* Current digit position display */
+.digit-info {
+ margin: 15px 0;
+ padding: 10px;
+ background: rgba(33, 150, 243, 0.1);
+ border: 2px solid #2196f3;
+ border-radius: 8px;
+ text-align: center;
+}
+
+.digit-legend {
+ font-size: 16px;
+ color: #2196f3;
+}
+
+#current-digit-pos {
+ font-weight: bold;
+ color: #1976d2;
+ font-size: 18px;
+}
+
+/* Radix sort color guide */
+.radix-color-guide {
+ margin: 15px 0;
+ padding: 16px 20px;
+ background: rgba(33, 150, 243, 0.06);
+ border: 1px solid rgba(33, 150, 243, 0.2);
+ border-radius: 8px;
+ text-align: center;
+ overflow: visible;
+}
+
+.radix-color-header {
+ margin-bottom: 8px;
+ color: #1976d2;
+ font-size: 14px;
+}
+
+.radix-color-row {
+ text-align: center !important;
+ line-height: 1 !important;
+ white-space: nowrap !important;
+ margin: 8px 0 !important;
+ padding: 0 !important;
+}
+
+.radix-color-row * {
+ display: inline-block !important;
+}
+
+.radix-color-circle {
+ cursor: help !important;
+ transition: all 0.2s ease !important;
+ display: inline-block !important;
+ margin: 0 4px !important;
+ vertical-align: middle !important;
+}
+
+.radix-color-circle:hover {
+ transform: scale(1.15) !important;
+ box-shadow: 0 0 8px rgba(0, 0, 0, 0.3) !important;
+ z-index: 10 !important;
+}
+
+/* Ensure all digit circles are visible */
+.radix-color-circle.digit-0,
+.radix-color-circle.digit-1,
+.radix-color-circle.digit-2,
+.radix-color-circle.digit-3,
+.radix-color-circle.digit-4,
+.radix-color-circle.digit-5,
+.radix-color-circle.digit-6,
+.radix-color-circle.digit-7,
+.radix-color-circle.digit-8,
+.radix-color-circle.digit-9 {
+ opacity: 1 !important;
+ visibility: visible !important;
+}
+
+/* Animation states for radix sort */
+.viz-cell.distributing {
+ animation: radix-distribute 1.0s ease-in-out;
+ z-index: 10;
+ transform-origin: center;
+}
+
+@keyframes radix-distribute {
+ 0% { transform: scale(1) rotate(0deg); }
+ 25% { transform: scale(1.15) rotate(3deg); }
+ 50% { transform: scale(1.25) rotate(-3deg); }
+ 75% { transform: scale(1.15) rotate(2deg); }
+ 100% { transform: scale(1) rotate(0deg); }
+}
+
+.viz-cell.collecting {
+ animation: radix-collect 0.8s ease-out;
+}
+
+@keyframes radix-collect {
+ 0% { transform: translateY(-20px) scale(1.1); opacity: 0.8; }
+ 100% { transform: translateY(0) scale(1); opacity: 1; }
+}
+
+.viz-cell.digit-colored {
+ animation: radix-color-reveal 0.6s ease-out;
+}
+
+@keyframes radix-color-reveal {
+ 0% { transform: scale(0.8); opacity: 0.7; }
+ 50% { transform: scale(1.1); }
+ 100% { transform: scale(1); opacity: 1; }
+}
+
+.viz-cell.pass-complete {
+ animation: radix-pass-done 1.2s ease-in-out;
+}
+
+@keyframes radix-pass-done {
+ 0%, 100% { transform: scale(1); }
+ 50% { transform: scale(1.05); box-shadow: 0 0 15px rgba(76, 175, 80, 0.6); }
+}
+
+.viz-cell.complete {
+ background: #8bc34a !important;
+ color: white !important;
+ border-color: #7cb342 !important;
+ animation: radix-completion 1s ease-out;
+}
+
+@keyframes radix-completion {
+ 0% { box-shadow: 0 0 0 rgba(139, 195, 74, 0.4); }
+ 50% { box-shadow: 0 0 25px rgba(139, 195, 74, 0.8); }
+ 100% { box-shadow: 0 0 0 rgba(139, 195, 74, 0.4); }
+}
+
+/* Dark mode overrides for radix sort */
+body.dark-mode .digit-info {
+ background: rgba(66, 165, 245, 0.2);
+ border-color: #42a5f5;
+}
+
+body.dark-mode .digit-legend {
+ color: #90caf9;
+}
+
+body.dark-mode #current-digit-pos {
+ color: #64b5f6;
+}
+
+body.dark-mode .radix-color-guide {
+ background: rgba(66, 165, 245, 0.1);
+ border-color: rgba(66, 165, 245, 0.3);
+}
+
+body.dark-mode .radix-color-header {
+ color: #90caf9;
+}
+
+body.dark-mode .radix-color-circle {
+ border-color: rgba(255, 255, 255, 0.3) !important;
+}
+
+body.dark-mode .radix-color-circle:hover {
+ box-shadow: 0 0 8px rgba(255, 255, 255, 0.4);
+}
+
+body.dark-mode .digit-display {
+ background: #ffb74d;
+ color: #333;
+}
+
+body.dark-mode .digit-display.digit-highlight {
+ background: #ff5252 !important;
+ color: white !important;
+ box-shadow: 0 0 8px rgba(255, 82, 82, 0.7);
+}
+
+/* Mobile responsive adjustments for radix sort */
+@media (max-width: 768px) {
+ .radix-color-guide {
+ padding: 10px 12px;
+ }
+
+ .radix-color-row {
+ gap: 6px;
+ }
+
+ .radix-color-circle {
+ width: 22px !important;
+ height: 22px !important;
+ font-size: 10px !important;
+ }
+
+ .digit-display {
+ bottom: -18px;
+ font-size: 9px;
+ padding: 1px 4px;
+ }
+
+ .digit-info {
+ margin: 10px 0;
+ padding: 8px;
+ }
+
+ .digit-legend {
+ font-size: 14px;
+ }
+
+ #current-digit-pos {
+ font-size: 16px;
+ }
+}
+
+@media (max-width: 480px) {
+ .radix-color-guide {
+ padding: 8px 10px;
+ }
+
+ .radix-color-header {
+ font-size: 13px;
+ }
+
+ .radix-color-row {
+ gap: 4px;
+ }
+
+ .radix-color-circle {
+ width: 20px !important;
+ height: 20px !important;
+ font-size: 9px !important;
+ }
+
+ .viz-cell.radix-cell {
+ min-height: 50px;
+ font-size: 12px;
+ }
+}
+
+/* ===== COUNTING SORT VISUALIZATION STYLES ===== */
+/* Counting sort section containers */
+.counting-sort-section {
+ margin: 20px 0;
+ padding: 15px;
+ border: 2px solid rgba(76, 175, 80, 0.3);
+ border-radius: 12px;
+ background: rgba(76, 175, 80, 0.05);
+}
+
+.counting-sort-section h4 {
+ margin: 0 0 15px 0;
+ color: #2e7d32;
+ font-size: 16px;
+ text-align: center;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+}
+
+/* Counting array specific styling */
+.counting-array-display {
+ display: flex;
+ justify-content: center;
+ align-items: flex-end;
+ gap: 8px;
+ flex-wrap: wrap;
+ margin: 10px 0;
+ min-height: 80px;
+}
+
+.counting-cell-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ min-width: 40px;
+ transition: all 0.3s ease;
+}
+
+.counting-value-label {
+ background: #e8f5e8;
+ color: #2e7d32;
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 12px;
+ font-weight: bold;
+ margin-bottom: 4px;
+ border: 1px solid #4caf50;
+}
+
+.counting-count-value {
+ background: #4caf50;
+ color: white;
+ padding: 8px 12px;
+ border-radius: 6px;
+ font-size: 14px;
+ font-weight: bold;
+ min-width: 32px;
+ text-align: center;
+ border: 2px solid #45a049;
+ transition: all 0.3s ease;
+ position: relative;
+}
+
+.counting-count-value[data-count="0"] {
+ background: #f5f5f5;
+ color: #999;
+ border-color: #ddd;
+}
+
+.counting-count-value.counting-active {
+ background: #ff9800 !important;
+ color: white !important;
+ border-color: #f57c00 !important;
+ animation: count-increment 0.8s ease-out;
+ transform: scale(1.1);
+}
+
+@keyframes count-increment {
+ 0% { transform: scale(1); }
+ 50% { transform: scale(1.3); background: #ff5722; }
+ 100% { transform: scale(1.1); }
+}
+
+.counting-count-value.cumulative-active {
+ background: #2196f3 !important;
+ color: white !important;
+ border-color: #1976d2 !important;
+ animation: cumulative-update 1s ease-in-out;
+ box-shadow: 0 0 15px rgba(33, 150, 243, 0.6);
+}
+
+@keyframes cumulative-update {
+ 0% { transform: scale(1); }
+ 25% { transform: scale(1.2) rotate(5deg); }
+ 50% { transform: scale(1.1) rotate(-5deg); }
+ 75% { transform: scale(1.15) rotate(2deg); }
+ 100% { transform: scale(1); }
+}
+
+/* Original array cell states */
+.viz-cell.counting-cell {
+ transition: all 0.4s ease;
+}
+
+.viz-cell.finding-max {
+ background: #f44336 !important;
+ color: white !important;
+ border-color: #d32f2f !important;
+ animation: finding-max-pulse 1s ease-in-out;
+ transform: scale(1.1);
+}
+
+@keyframes finding-max-pulse {
+ 0%, 100% { box-shadow: 0 0 0 rgba(244, 67, 54, 0.4); }
+ 50% { box-shadow: 0 0 20px rgba(244, 67, 54, 0.8); }
+}
+
+.viz-cell.being-counted {
+ background: #4caf50 !important;
+ color: white !important;
+ border-color: #45a049 !important;
+ animation: being-counted 1s ease-out;
+ transform: scale(1.05);
+}
+
+@keyframes being-counted {
+ 0% { transform: scale(1) rotate(0deg); }
+ 25% { transform: scale(1.15) rotate(5deg); }
+ 50% { transform: scale(1.1) rotate(-5deg); }
+ 75% { transform: scale(1.05) rotate(2deg); }
+ 100% { transform: scale(1.05) rotate(0deg); }
+}
+
+.viz-cell.being-placed {
+ background: #2196f3 !important;
+ color: white !important;
+ border-color: #1976d2 !important;
+ animation: being-placed 1.2s ease-out;
+}
+
+@keyframes being-placed {
+ 0% { transform: translateY(0) scale(1); }
+ 25% { transform: translateY(-20px) scale(1.1); }
+ 50% { transform: translateY(-10px) scale(1.05); }
+ 100% { transform: translateY(0) scale(1); }
+}
+
+/* Output array cells */
+.viz-cell.output-cell {
+ background: #f5f5f5;
+ color: #999;
+ border: 2px dashed #ddd;
+ transition: all 0.5s ease;
+}
+
+.viz-cell.output-cell.placed {
+ background: #e3f2fd;
+ color: #1976d2;
+ border: 2px solid #2196f3;
+ font-weight: bold;
+}
+
+.viz-cell.output-cell.just-placed {
+ animation: just-placed 1s ease-out;
+}
+
+@keyframes just-placed {
+ 0% {
+ background: #ffeb3b;
+ transform: scale(1.3);
+ box-shadow: 0 0 20px rgba(255, 235, 59, 0.8);
+ }
+ 100% {
+ background: #e3f2fd;
+ transform: scale(1);
+ box-shadow: none;
+ }
+}
+
+.viz-cell.complete {
+ background: #8bc34a !important;
+ color: white !important;
+ border-color: #7cb342 !important;
+ animation: counting-completion 1.5s ease-out;
+}
+
+@keyframes counting-completion {
+ 0% { box-shadow: 0 0 0 rgba(139, 195, 74, 0.4); }
+ 50% { box-shadow: 0 0 30px rgba(139, 195, 74, 0.8); }
+ 100% { box-shadow: 0 0 0 rgba(139, 195, 74, 0.4); }
+}
+
+/* Dark mode overrides for counting sort */
+body.dark-mode .counting-sort-section {
+ border-color: rgba(102, 187, 106, 0.4);
+ background: rgba(102, 187, 106, 0.1);
+}
+
+body.dark-mode .counting-sort-section h4 {
+ color: #a5d6a7;
+}
+
+body.dark-mode .counting-value-label {
+ background: rgba(102, 187, 106, 0.3);
+ color: #c8e6c9;
+ border-color: #66bb6a;
+}
+
+body.dark-mode .counting-count-value {
+ background: #66bb6a;
+ border-color: #4caf50;
+}
+
+body.dark-mode .counting-count-value[data-count="0"] {
+ background: rgba(66, 66, 66, 0.8);
+ color: #999;
+ border-color: #555;
+}
+
+body.dark-mode .viz-cell.output-cell {
+ background: rgba(66, 66, 66, 0.8);
+ color: #999;
+ border-color: #555;
+}
+
+body.dark-mode .viz-cell.output-cell.placed {
+ background: rgba(33, 150, 243, 0.3);
+ color: #90caf9;
+ border-color: #42a5f5;
+}
+
+/* Mobile responsive adjustments for counting sort */
+@media (max-width: 768px) {
+ .counting-sort-section {
+ margin: 15px 0;
+ padding: 12px;
+ }
+
+ .counting-array-display {
+ gap: 6px;
+ }
+
+ .counting-cell-container {
+ min-width: 35px;
+ }
+
+ .counting-value-label {
+ font-size: 10px;
+ padding: 3px 6px;
+ }
+
+ .counting-count-value {
+ font-size: 12px;
+ padding: 6px 8px;
+ min-width: 28px;
+ }
+
+ .viz-cell.counting-cell,
+ .viz-cell.output-cell {
+ min-width: 35px;
+ min-height: 35px;
+ font-size: 12px;
+ }
+}
+
+@media (max-width: 480px) {
+ .counting-sort-section h4 {
+ font-size: 14px;
+ }
+
+ .counting-array-display {
+ gap: 4px;
+ }
+
+ .counting-cell-container {
+ min-width: 30px;
+ }
+
+ .counting-value-label {
+ font-size: 9px;
+ padding: 2px 4px;
+ }
+
+ .counting-count-value {
+ font-size: 11px;
+ padding: 4px 6px;
+ min-width: 24px;
+ }
+
+ .viz-cell.counting-cell,
+ .viz-cell.output-cell {
+ min-width: 30px;
+ min-height: 30px;
+ font-size: 11px;
+ }
+}
+
/* ===== GLOBAL OVERRIDES FOR EXISTING ALGORITHM VISUALIZATIONS ===== */
/* These rules override inline styles in existing algorithm configs to ensure dark mode compatibility */
diff --git a/algorithms-js/src/sort/bucket-sort/bucket-sort-core.js b/algorithms-js/src/sort/bucket-sort/bucket-sort-core.js
index 4d29b703..69761b91 100644
--- a/algorithms-js/src/sort/bucket-sort/bucket-sort-core.js
+++ b/algorithms-js/src/sort/bucket-sort/bucket-sort-core.js
@@ -12,13 +12,9 @@
* @see https://github.com/sachinlala/SimplifyLearning
*/
-// Import utilities for reusable functions
-// Note: SortingUtils is available globally via window.SortingUtils when loaded by universal loader
if (typeof require !== 'undefined') {
- // Node.js environment
const SortingUtils = require('../utils/sorting-utils.js');
}
-// In browser environment, SortingUtils is available via window.SortingUtils
/**
* Core bucket sort algorithm for floating-point numbers in range [0, 1)
@@ -33,7 +29,6 @@ function bucketSort(arr) {
};
}
- // Validate input - should be numbers
for (let i = 0; i < arr.length; i++) {
if (typeof arr[i] !== 'number' || isNaN(arr[i])) {
throw new Error(`Bucket sort requires numbers. Found: ${arr[i]} at index ${i}`);
@@ -46,12 +41,10 @@ function bucketSort(arr) {
let swaps = 0;
let bucketSorts = 0;
- // Determine range of data
const min = Math.min(...sortedArray);
const max = Math.max(...sortedArray);
const range = max - min;
- // Handle edge case where all elements are the same
if (range === 0) {
return {
sortedArray,
diff --git a/algorithms-js/src/sort/bucket-sort/bucket-sort-steps.js b/algorithms-js/src/sort/bucket-sort/bucket-sort-steps.js
index d4a7221e..b4b281d7 100644
--- a/algorithms-js/src/sort/bucket-sort/bucket-sort-steps.js
+++ b/algorithms-js/src/sort/bucket-sort/bucket-sort-steps.js
@@ -184,7 +184,10 @@ function bucketSortWithSteps(arr, bucketCount = null) {
return {
sortedArray: finalArray,
steps: steps,
- metrics: metrics
+ metrics: {
+ ...metrics,
+ buckets: bucketCount
+ }
};
}
@@ -193,6 +196,8 @@ if (typeof window !== 'undefined') {
window.BucketSortSteps = {
bucketSortWithSteps
};
+ // Expose commonly used function in global scope for demo configs
+ window.bucketSortWithSteps = bucketSortWithSteps;
}
// Export for CommonJS compatibility
diff --git a/algorithms-js/src/sort/bucket-sort/bucket-sort.config.js b/algorithms-js/src/sort/bucket-sort/bucket-sort.config.js
index caaa61d6..c585f3c1 100644
--- a/algorithms-js/src/sort/bucket-sort/bucket-sort.config.js
+++ b/algorithms-js/src/sort/bucket-sort/bucket-sort.config.js
@@ -18,7 +18,7 @@ const BucketSortConfig = {
{
id: 'array-input',
type: 'text',
- label: 'Array Elements (decimals 0.0-1.0)',
+ label: 'Array Elements (integers or decimals)',
defaultValue: '0.78, 0.17, 0.39, 0.26, 0.72, 0.94, 0.21, 0.12',
width: '280px'
},
@@ -248,6 +248,27 @@ const BucketSortConfig = {
description: 'Very close values - most will go to same bucket',
expected: [0.1, 0.11, 0.12, 0.13, 0.14, 0.15],
difficulty: 'hard'
+ },
+ {
+ name: 'Integer Array',
+ input: [64, 34, 25, 12, 22, 11, 90],
+ description: 'Integer array - will be normalized for bucket sort',
+ expected: [11, 12, 22, 25, 34, 64, 90],
+ difficulty: 'easy'
+ },
+ {
+ name: 'Small Integers',
+ input: [5, 2, 8, 1, 9, 3],
+ description: 'Small integer values',
+ expected: [1, 2, 3, 5, 8, 9],
+ difficulty: 'easy'
+ },
+ {
+ name: 'Large Range Integers',
+ input: [100, 50, 200, 10, 150, 75],
+ description: 'Integers with large range - tests normalization',
+ expected: [10, 50, 75, 100, 150, 200],
+ difficulty: 'medium'
}
],
@@ -346,7 +367,7 @@ const BucketSortConfig = {
bucketSorts: result.metrics.bucketSorts,
comparisons: result.metrics.comparisons,
swaps: result.metrics.swaps,
- timeComplexity: `O(n + k) where n=${result.sortedArray.length}, k=${result.metrics.buckets}`
+ timeComplexity: 'O(n + k) where n=' + result.sortedArray.length + ', k=' + (result.metrics.buckets || 'unknown')
}
};
}
@@ -409,7 +430,7 @@ const BucketSortConfig = {
return asNumber;
});
} catch (e) {
- showError('Invalid array format. Please use comma-separated decimal numbers (0.0-1.0).');
+ showError('Invalid array format. Please use comma-separated numbers.');
return;
}
@@ -424,14 +445,30 @@ const BucketSortConfig = {
return;
}
- // Validate range
- for (let i = 0; i < arrayInput.length; i++) {
- if (arrayInput[i] < 0 || arrayInput[i] > 1) {
- showError('All elements should be between 0.0 and 1.0 for optimal bucket sort performance');
- return;
+ // Auto-normalize the array if it's not in 0.0-1.0 range
+ const min = Math.min(...arrayInput);
+ const max = Math.max(...arrayInput);
+ const range = max - min;
+
+ let normalizedArray = [...arrayInput];
+ let isNormalized = false;
+
+ // Check if normalization is needed
+ if (min < 0 || max > 1 || range > 1) {
+ if (range === 0) {
+ // All elements are the same
+ normalizedArray = arrayInput.map(() => 0.5);
+ } else {
+ // Normalize to [0,1] range
+ normalizedArray = arrayInput.map(x => (x - min) / range);
+ isNormalized = true;
}
}
+ // Update arrayInput to the normalized version for processing
+ const originalArray = [...arrayInput];
+ arrayInput = normalizedArray;
+
// Parse bucket count
const bucketCount = parseInt(bucketCountStr);
if (isNaN(bucketCount) || bucketCount < 2 || bucketCount > 10) {
@@ -442,40 +479,331 @@ const BucketSortConfig = {
try {
const startTime = performance.now();
- // Execute bucket sort
- const result = window.BucketSortCore ? window.BucketSortCore.bucketSort(arrayInput) : bucketSort(arrayInput);
+ // Execute bucket sort using steps function for animation
+ let result;
+ if (window.BucketSortSteps) {
+ result = window.BucketSortSteps.bucketSortWithSteps(arrayInput, bucketCount);
+ } else if (window.bucketSortWithSteps) {
+ result = window.bucketSortWithSteps(arrayInput, bucketCount);
+ } else if (window.BucketSortCore) {
+ const coreResult = window.BucketSortCore.bucketSort(arrayInput);
+ result = { ...coreResult, steps: [] };
+ } else {
+ result = { sortedArray: [...arrayInput].sort((a, b) => a - b), metrics: { comparisons: 0, swaps: 0, bucketOperations: 0 }, steps: [] };
+ }
const endTime = performance.now();
const executionTime = (endTime - startTime).toFixed(4);
// Show result
- let resultHTML = \`
- Original Array: [\${arrayInput.join(', ')}]
- Sorted Array: [\${result.sortedArray.join(', ')}]
- Buckets Used: \${result.metrics.buckets}
- Buckets Sorted: \${result.metrics.bucketSorts}
- Total Comparisons: \${result.metrics.comparisons}
- Total Swaps: \${result.metrics.swaps}
- Execution Time: \${executionTime} ms
- \`;
+ let resultHTML =
+ 'Original Array: [' + originalArray.join(', ') + ']
';
+
+ if (isNormalized) {
+ resultHTML +=
+ 'Normalized for Processing: [' + arrayInput.map(x => x.toFixed(3)).join(', ') + ']
' +
+ 'Note: Array was normalized to [0,1] range for optimal bucket sort performance
';
+ }
+
+ // Map normalized result back to original scale for display
+ let displaySortedArray;
+ if (isNormalized && range > 0) {
+ displaySortedArray = result.sortedArray.map(x => (x * range + min));
+ } else {
+ displaySortedArray = result.sortedArray;
+ }
+
+ resultHTML +=
+ 'Sorted Array: [' + displaySortedArray.map(x => typeof x === 'number' ? x.toFixed(3) : x).join(', ') + ']
' +
+ 'Buckets Used: ' + bucketCount + '
' +
+ 'Bucket Operations: ' + (result.metrics.bucketOperations || 0) + '
' +
+ 'Total Comparisons: ' + (result.metrics.comparisons || 0) + '
' +
+ 'Total Insertions: ' + (result.metrics.insertions || 0) + '
' +
+ 'Execution Time: ' + executionTime + ' ms';
resultContainer.innerHTML = resultHTML;
+ // Show the visualization section with bucket sort animation
+ if (result.steps && result.steps.length > 0) {
+ showBucketSortVisualization(originalArray, result.steps, bucketCount, isNormalized, min, range);
+ visualizationSection.style.display = 'block';
+ }
+
} catch (error) {
showError(error.message);
}
}
+
+ function showBucketSortVisualization(originalArray, steps, bucketCount, isNormalized = false, minVal = 0, rangeVal = 1) {
+ const arrayViz = document.getElementById('array-visualization');
+ const stepsContainer = document.getElementById('steps-container');
+
+ // Define bucket colors - shared between legend and animation
+ const bucketColors = [
+ '#ff6b6b', '#4ecdc4', '#45b7d1', '#f9ca24',
+ '#f0932b', '#eb4d4b', '#6c5ce7', '#74b9ff',
+ '#00b894', '#fdcb6e'
+ ];
+
+ // Clear previous visualization
+ arrayViz.innerHTML = '';
+ stepsContainer.innerHTML = '';
+
+ // Create array visualization
+ const arrayDiv = document.createElement('div');
+ arrayDiv.className = 'array-visualization';
+ arrayDiv.id = 'bucket-array-display';
+
+ originalArray.forEach((value, index) => {
+ const cell = document.createElement('div');
+ cell.textContent = value;
+ cell.className = 'viz-cell';
+ cell.setAttribute('data-index', index);
+ cell.setAttribute('data-value', value);
+ arrayDiv.appendChild(cell);
+ });
+
+ arrayViz.appendChild(arrayDiv);
+
+ // Create bucket info display (color legend only)
+ const bucketInfoDiv = document.createElement('div');
+ bucketInfoDiv.className = 'bucket-info';
+ bucketInfoDiv.id = 'bucket-info';
+
+ let bucketColorsHTML = '';
+ for (let i = 0; i < bucketCount; i++) {
+ const color = bucketColors[i % bucketColors.length];
+ bucketColorsHTML += 'B' + i + ' ';
+ }
+ bucketInfoDiv.innerHTML = '
Bucket Color Guide:
' + bucketColorsHTML + '
';
+ arrayViz.appendChild(bucketInfoDiv);
+
+ // Add controls with legend
+ const controlsDiv = document.createElement('div');
+ controlsDiv.className = 'viz-controls';
+ controlsDiv.innerHTML =
+ 'Bucket Sort Visualization (' + bucketCount + ' buckets)
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
Color by Bucket | Sort Each Color Group | Collect Results | Complete' +
+ '
' +
+ '
Elements get colored by bucket
' +
+ '
Sort each color group individually
' +
+ '
Collect sorted groups in order
' +
+ '
Final sorted array
' +
+ '
' +
+ '
';
+ arrayViz.appendChild(controlsDiv);
+
+ // Status display
+ const statusDiv = document.createElement('div');
+ statusDiv.id = 'bucket-status';
+ statusDiv.className = 'viz-status';
+ statusDiv.textContent = 'Ready to start bucket sort animation...';
+ arrayViz.appendChild(statusDiv);
+
+ // Animation variables
+ let currentStepIndex = 0;
+ let animationRunning = false;
+ let animationInterval;
+
+ function updateBucketVisualization(step) {
+ const cells = arrayDiv.querySelectorAll('.viz-cell');
+ const statusDiv = document.getElementById('bucket-status');
+
+ // Reset all cell classes
+ cells.forEach(cell => {
+ cell.className = 'viz-cell';
+ });
+
+ // Update array values if changed
+ if (step.array) {
+ step.array.forEach((value, index) => {
+ if (cells[index] && value !== null && value !== undefined) {
+ cells[index].textContent = value;
+ }
+ });
+ }
+
+ // Apply bucket color coding based on step type and phase
+ if (step.type === 'distribute' && step.targetBucket !== undefined) {
+ // Show current element being distributed with preview of target bucket color
+ if (step.currentElementIndex !== undefined && cells[step.currentElementIndex]) {
+ cells[step.currentElementIndex].classList.add('distributing');
+ const targetColor = bucketColors[step.targetBucket % bucketColors.length];
+ cells[step.currentElementIndex].style.backgroundColor = targetColor; // Exact color match with legend
+ cells[step.currentElementIndex].style.borderColor = targetColor;
+ cells[step.currentElementIndex].style.color = '#ffffff';
+ cells[step.currentElementIndex].style.fontWeight = 'bold';
+ cells[step.currentElementIndex].setAttribute('data-bucket', step.targetBucket);
+ }
+ } else if (step.phase === 'distribution-complete' || step.phase === 'bucket-sorting' || step.phase === 'collection') {
+ // Color all elements by their bucket assignment
+ if (step.buckets) {
+ step.buckets.forEach((bucket, bucketIndex) => {
+ bucket.forEach(value => {
+ // Find the cell with this value and color it
+ const matchingCells = Array.from(cells).filter(cell =>
+ parseFloat(cell.textContent) === value || cell.textContent === value.toString()
+ );
+ matchingCells.forEach(cell => {
+ cell.style.backgroundColor = bucketColors[bucketIndex % bucketColors.length]; // Exact color match with legend
+ cell.style.borderColor = bucketColors[bucketIndex % bucketColors.length];
+ cell.style.color = '#ffffff'; // White text for better contrast
+ cell.style.fontWeight = 'bold';
+ cell.setAttribute('data-bucket', bucketIndex);
+ });
+ });
+ });
+ }
+ }
+
+ // Highlight current bucket being sorted
+ if (step.currentBucket !== undefined && step.phase === 'bucket-sorting') {
+ cells.forEach(cell => {
+ if (cell.getAttribute('data-bucket') === step.currentBucket.toString()) {
+ cell.classList.add('bucket-sorting');
+ }
+ });
+ }
+
+ // Show collection phase
+ if (step.type === 'collect') {
+ cells.forEach((cell, index) => {
+ if (index <= step.collectedTo) {
+ cell.classList.add('collected');
+ }
+ });
+ }
+
+ // Final completion state
+ if (step.type === 'complete') {
+ cells.forEach(cell => {
+ cell.classList.add('complete');
+ // Reset to default text styling
+ cell.style.color = '';
+ cell.style.fontWeight = '';
+ });
+ }
+
+ // Update status
+ statusDiv.textContent = step.message;
+
+ // Show step info in container
+ const stepInfo = document.createElement('div');
+ stepInfo.className = step.type === 'complete' ? 'viz-step-info complete' : 'viz-step-info';
+
+ let stepTypeColor = '#007acc';
+ if (step.type === 'complete') stepTypeColor = '#28a745';
+ else if (step.type === 'distribute') stepTypeColor = '#2196f3';
+ else if (step.phase === 'bucket-sorting') stepTypeColor = '#ff9800';
+ else if (step.type === 'collect') stepTypeColor = '#4caf50';
+
+ stepInfo.style.borderLeftColor = stepTypeColor;
+
+ let phaseEmoji = '*';
+ if (step.type === 'distribute') phaseEmoji = '+';
+ else if (step.phase === 'bucket-sorting') phaseEmoji = '~';
+ else if (step.type === 'collect') phaseEmoji = '-';
+ else if (step.type === 'complete') phaseEmoji = '!';
+ else if (step.type === 'initialize') phaseEmoji = '?';
+
+ stepInfo.innerHTML =
+ '' + phaseEmoji + ' Step ' + (currentStepIndex + 1) + ': ' + step.message + '
' +
+ '' +
+ 'Phase: ' + (step.phase || 'processing') + ' | ' +
+ 'Comparisons: ' + (step.metrics.comparisons || 0) + ' | ' +
+ 'Operations: ' + (step.metrics.bucketOperations || 0) +
+ '';
+
+ if (stepsContainer.children.length > 8) {
+ stepsContainer.removeChild(stepsContainer.firstChild);
+ }
+ stepsContainer.appendChild(stepInfo);
+ }
+
+ function startBucketAnimation() {
+ if (animationRunning || currentStepIndex >= steps.length) return;
+
+ animationRunning = true;
+ document.getElementById('start-bucket-animation').disabled = true;
+ document.getElementById('pause-bucket-animation').disabled = false;
+
+ animationInterval = setInterval(() => {
+ if (currentStepIndex >= steps.length) {
+ clearInterval(animationInterval);
+ animationRunning = false;
+ document.getElementById('start-bucket-animation').disabled = false;
+ document.getElementById('pause-bucket-animation').disabled = true;
+ return;
+ }
+
+ updateBucketVisualization(steps[currentStepIndex]);
+ currentStepIndex++;
+ }, 1500); // 1.5 second delay between steps
+ }
+
+ function pauseBucketAnimation() {
+ clearInterval(animationInterval);
+ animationRunning = false;
+ document.getElementById('start-bucket-animation').disabled = false;
+ document.getElementById('pause-bucket-animation').disabled = true;
+ }
+
+ function resetBucketAnimation() {
+ clearInterval(animationInterval);
+ animationRunning = false;
+ currentStepIndex = 0;
+ document.getElementById('start-bucket-animation').disabled = false;
+ document.getElementById('pause-bucket-animation').disabled = true;
+ stepsContainer.innerHTML = '';
+
+ // Reset all cells to default state
+ const cells = arrayDiv.querySelectorAll('.viz-cell');
+ cells.forEach(cell => {
+ cell.className = 'viz-cell';
+ cell.style.backgroundColor = '';
+ cell.style.borderColor = '';
+ cell.style.color = '';
+ cell.style.fontWeight = '';
+ cell.removeAttribute('data-bucket');
+ });
+
+ // Reset visualization to initial state
+ if (steps.length > 0) {
+ updateBucketVisualization(steps[0]);
+ }
+ document.getElementById('bucket-status').textContent = 'Ready to start bucket sort animation...';
+ }
+
+ // Bind control events
+ document.getElementById('start-bucket-animation').addEventListener('click', startBucketAnimation);
+ document.getElementById('pause-bucket-animation').addEventListener('click', pauseBucketAnimation);
+ document.getElementById('reset-bucket-animation').addEventListener('click', resetBucketAnimation);
+
+ // Show initial state
+ if (steps.length > 0) {
+ updateBucketVisualization(steps[0]);
+ }
+ }
`
};
// Export for both Node.js and browser environments
if (typeof module !== 'undefined' && module.exports) {
- module.exports = BucketSortConfig;
+ module.exports = {
+ BUCKET_SORT_CONFIG: BucketSortConfig,
+ BucketSortConfig
+ };
} else if (typeof window !== 'undefined') {
+ // Primary exports
+ window.BUCKET_SORT_CONFIG = BucketSortConfig;
window.BucketSortConfig = BucketSortConfig;
- // Additional exports for universal loader compatibility
- window.BUCKET_SORT_CONFIG = BucketSortConfig;
- window.bucketsortConfig = BucketSortConfig;
- window.bucketsortconfig = BucketSortConfig;
+ // Universal loader compatibility - these are the names the loader looks for
+ window.bucketsortConfig = BucketSortConfig; // algorithmName.replace(/-/g, '') + 'Config'
+ window.bucketsortconfig = BucketSortConfig; // algorithmName.replace(/-/g, '').toLowerCase() + 'Config'
+ window.bucketSortConfig = BucketSortConfig; // toCamelCase(algorithmName) + 'Config'
}
diff --git a/algorithms-js/src/sort/counting-sort/counting-sort-steps.js b/algorithms-js/src/sort/counting-sort/counting-sort-steps.js
index 9c1679a7..277fcec8 100644
--- a/algorithms-js/src/sort/counting-sort/counting-sort-steps.js
+++ b/algorithms-js/src/sort/counting-sort/counting-sort-steps.js
@@ -228,6 +228,12 @@ if (typeof module !== 'undefined' && module.exports) {
window.CountingSortSteps = {
countingSortWithSteps
};
- // Expose commonly used functions in global scope for demo configs
+
+ // Additional exports for universal loader compatibility
window.countingSortWithSteps = countingSortWithSteps;
-}
\ No newline at end of file
+
+ // Alternative naming variants
+ window.COUNTING_SORT_STEPS = {
+ countingSortWithSteps
+ };
+}
diff --git a/algorithms-js/src/sort/counting-sort/counting-sort.config.js b/algorithms-js/src/sort/counting-sort/counting-sort.config.js
index 8538bf74..9710ab96 100644
--- a/algorithms-js/src/sort/counting-sort/counting-sort.config.js
+++ b/algorithms-js/src/sort/counting-sort/counting-sort.config.js
@@ -355,27 +355,332 @@ const CountingSortConfig = {
try {
const startTime = performance.now();
- // Execute counting sort
- const result = window.CountingSortCore ? window.CountingSortCore.countingSort(arrayInput) : countingSort(arrayInput);
+ // Execute counting sort using steps function for animation
+ let result;
+ if (window.CountingSortSteps) {
+ result = window.CountingSortSteps.countingSortWithSteps(arrayInput);
+ } else if (window.countingSortWithSteps) {
+ result = window.countingSortWithSteps(arrayInput);
+ } else if (window.CountingSortCore) {
+ const coreResult = window.CountingSortCore.countingSort(arrayInput);
+ result = { ...coreResult, steps: [] };
+ } else {
+ result = { sortedArray: [...arrayInput].sort((a, b) => a - b), metrics: { comparisons: 0, counts: 0, range: 0 }, steps: [] };
+ }
const endTime = performance.now();
const executionTime = (endTime - startTime).toFixed(4);
// Show result
- let resultHTML = \`
- Original Array: [\${arrayInput.join(', ')}]
- Sorted Array: [\${result.sortedArray.join(', ')}]
- Range (k): \${result.metrics.range} (0 to \${result.metrics.maxValue})
- Space Used: O(n + k) = O(\${arrayInput.length} + \${result.metrics.range})
- Execution Time: \${executionTime} ms
- \`;
+ let resultHTML =
+ 'Original Array: [' + arrayInput.join(', ') + ']
' +
+ 'Sorted Array: [' + result.sortedArray.join(', ') + ']
' +
+ 'Range (k): ' + (result.metrics.range || 'N/A') + '
' +
+ 'Comparisons: ' + (result.metrics.comparisons || 0) + '
' +
+ 'Count Operations: ' + (result.metrics.counts || 0) + '
' +
+ 'Execution Time: ' + executionTime + ' ms';
resultContainer.innerHTML = resultHTML;
+ // Show the visualization section with counting sort animation
+ if (result.steps && result.steps.length > 0) {
+ showCountingSortVisualization(arrayInput, result.steps);
+ visualizationSection.style.display = 'block';
+ }
+
} catch (error) {
showError(error.message);
}
}
+
+ function showCountingSortVisualization(originalArray, steps) {
+ const arrayViz = document.getElementById('array-visualization');
+ const stepsContainer = document.getElementById('steps-container');
+
+ // Clear previous visualization
+ arrayViz.innerHTML = '';
+ stepsContainer.innerHTML = '';
+
+ // Create main containers
+ const originalArrayDiv = document.createElement('div');
+ originalArrayDiv.className = 'counting-sort-section';
+ originalArrayDiv.innerHTML = 'Original Array
';
+
+ const arrayDisplay = document.createElement('div');
+ arrayDisplay.className = 'array-visualization';
+ arrayDisplay.id = 'counting-original-array';
+
+ originalArray.forEach((value, index) => {
+ const cell = document.createElement('div');
+ cell.textContent = value;
+ cell.className = 'viz-cell counting-cell';
+ cell.setAttribute('data-index', index);
+ cell.setAttribute('data-value', value);
+ arrayDisplay.appendChild(cell);
+ });
+
+ originalArrayDiv.appendChild(arrayDisplay);
+ arrayViz.appendChild(originalArrayDiv);
+
+ // Create counting array section
+ const countingSection = document.createElement('div');
+ countingSection.className = 'counting-sort-section';
+ countingSection.innerHTML = 'Counting Array
';
+ countingSection.id = 'counting-array-section';
+ countingSection.style.display = 'none';
+
+ const countingArrayDiv = document.createElement('div');
+ countingArrayDiv.className = 'counting-array-display';
+ countingArrayDiv.id = 'counting-array-display';
+ countingSection.appendChild(countingArrayDiv);
+
+ arrayViz.appendChild(countingSection);
+
+ // Create output array section
+ const outputSection = document.createElement('div');
+ outputSection.className = 'counting-sort-section';
+ outputSection.innerHTML = 'Output Array (Building Result)
';
+ outputSection.id = 'output-array-section';
+ outputSection.style.display = 'none';
+
+ const outputArrayDiv = document.createElement('div');
+ outputArrayDiv.className = 'array-visualization';
+ outputArrayDiv.id = 'counting-output-array';
+ outputSection.appendChild(outputArrayDiv);
+
+ arrayViz.appendChild(outputSection);
+
+ // Add controls with legend
+ const controlsDiv = document.createElement('div');
+ controlsDiv.className = 'viz-controls';
+ controlsDiv.innerHTML =
+ 'Counting Sort Visualization
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
π Find Max | π Count Values | π’ Cumulative Sum | π Place Elements | β
Complete' +
+ '
' +
+ '
π Find the maximum value
' +
+ '
π Count occurrences of each value
' +
+ '
π’ Convert counts to starting positions
' +
+ '
π Place elements in sorted positions
' +
+ '
β
Sorting completed
' +
+ '
' +
+ '
';
+ arrayViz.appendChild(controlsDiv);
+
+ // Status display
+ const statusDiv = document.createElement('div');
+ statusDiv.id = 'counting-status';
+ statusDiv.className = 'viz-status';
+ statusDiv.textContent = 'Ready to start counting sort animation...';
+ arrayViz.appendChild(statusDiv);
+
+ // Animation variables
+ let currentStepIndex = 0;
+ let animationRunning = false;
+ let animationInterval;
+
+ function updateCountingVisualization(step) {
+ const originalCells = arrayDisplay.querySelectorAll('.viz-cell');
+ const statusDiv = document.getElementById('counting-status');
+ const countingSection = document.getElementById('counting-array-section');
+ const outputSection = document.getElementById('output-array-section');
+ const countingDisplay = document.getElementById('counting-array-display');
+ const outputDisplay = document.getElementById('counting-output-array');
+
+ // Reset all original array cell classes
+ originalCells.forEach(cell => {
+ cell.className = 'viz-cell counting-cell';
+ });
+
+ // Handle different phases
+ if (step.phase === 'find-max') {
+ step.highlightIndices.forEach(index => {
+ if (originalCells[index]) {
+ originalCells[index].classList.add('finding-max');
+ }
+ });
+ } else if (step.phase === 'count') {
+ countingSection.style.display = 'block';
+
+ // Create/update counting array display
+ if (step.countArray) {
+ countingDisplay.innerHTML = '';
+ step.countArray.forEach((count, value) => {
+ const countCell = document.createElement('div');
+ countCell.className = 'counting-cell-container';
+
+ const valueLabel = document.createElement('div');
+ valueLabel.className = 'counting-value-label';
+ valueLabel.textContent = value;
+
+ const countValue = document.createElement('div');
+ countValue.className = 'counting-count-value';
+ countValue.textContent = count;
+ countValue.setAttribute('data-count', count);
+
+ if (step.countingValue === value) {
+ countValue.classList.add('counting-active');
+ }
+
+ countCell.appendChild(valueLabel);
+ countCell.appendChild(countValue);
+ countingDisplay.appendChild(countCell);
+ });
+ }
+
+ step.highlightIndices.forEach(index => {
+ if (originalCells[index]) {
+ originalCells[index].classList.add('being-counted');
+ }
+ });
+ } else if (step.phase === 'cumulative') {
+ // Update counting array to show cumulative sums
+ if (step.countArray) {
+ const countCells = countingDisplay.querySelectorAll('.counting-count-value');
+ step.countArray.forEach((count, value) => {
+ if (countCells[value]) {
+ countCells[value].textContent = count;
+ countCells[value].setAttribute('data-count', count);
+
+ if (step.cumulativeIndex === value) {
+ countCells[value].classList.add('cumulative-active');
+ }
+ }
+ });
+ }
+ } else if (step.phase === 'place') {
+ outputSection.style.display = 'block';
+
+ // Create/update output array display
+ if (step.output) {
+ outputDisplay.innerHTML = '';
+ step.output.forEach((value, index) => {
+ const cell = document.createElement('div');
+ cell.className = 'viz-cell output-cell';
+ if (value !== undefined) {
+ cell.textContent = value;
+ cell.classList.add('placed');
+
+ if (step.placingPosition === index) {
+ cell.classList.add('just-placed');
+ }
+ }
+ outputDisplay.appendChild(cell);
+ });
+ }
+
+ step.highlightIndices.forEach(index => {
+ if (originalCells[index]) {
+ originalCells[index].classList.add('being-placed');
+ }
+ });
+ } else if (step.phase === 'complete') {
+ originalCells.forEach(cell => {
+ cell.classList.add('complete');
+ });
+
+ const outputCells = outputDisplay.querySelectorAll('.viz-cell');
+ outputCells.forEach(cell => {
+ cell.classList.add('complete');
+ });
+ }
+
+ // Update status
+ statusDiv.textContent = step.message;
+
+ // Show step info in container
+ const stepInfo = document.createElement('div');
+ stepInfo.className = step.type === 'complete' ? 'viz-step-info complete' : 'viz-step-info';
+
+ let stepTypeColor = '#007acc';
+ if (step.phase === 'find-max') stepTypeColor = '#f44336';
+ else if (step.phase === 'count') stepTypeColor = '#4caf50';
+ else if (step.phase === 'cumulative') stepTypeColor = '#ff9800';
+ else if (step.phase === 'place') stepTypeColor = '#2196f3';
+ else if (step.phase === 'complete') stepTypeColor = '#8bc34a';
+
+ stepInfo.style.borderLeftColor = stepTypeColor;
+
+ let phaseEmoji = 'π';
+ if (step.phase === 'find-max') phaseEmoji = 'π';
+ else if (step.phase === 'count') phaseEmoji = 'π';
+ else if (step.phase === 'cumulative') phaseEmoji = 'π’';
+ else if (step.phase === 'place') phaseEmoji = 'π';
+ else if (step.phase === 'complete') phaseEmoji = 'β
';
+
+ stepInfo.innerHTML =
+ '' + phaseEmoji + ' Step ' + (currentStepIndex + 1) + ': ' + step.message + '
' +
+ '' +
+ 'Phase: ' + (step.phase || 'processing') + ' | ' +
+ 'Comparisons: ' + (step.comparisons || 0) + ' | ' +
+ 'Count Ops: ' + (step.counts || 0) +
+ '';
+
+ if (stepsContainer.children.length > 8) {
+ stepsContainer.removeChild(stepsContainer.firstChild);
+ }
+ stepsContainer.appendChild(stepInfo);
+ }
+
+ function startCountingAnimation() {
+ if (animationRunning || currentStepIndex >= steps.length) return;
+
+ animationRunning = true;
+ document.getElementById('start-counting-animation').disabled = true;
+ document.getElementById('pause-counting-animation').disabled = false;
+
+ animationInterval = setInterval(() => {
+ if (currentStepIndex >= steps.length) {
+ clearInterval(animationInterval);
+ animationRunning = false;
+ document.getElementById('start-counting-animation').disabled = false;
+ document.getElementById('pause-counting-animation').disabled = true;
+ return;
+ }
+
+ updateCountingVisualization(steps[currentStepIndex]);
+ currentStepIndex++;
+ }, 1000); // 1 second delay between steps
+ }
+
+ function pauseCountingAnimation() {
+ clearInterval(animationInterval);
+ animationRunning = false;
+ document.getElementById('start-counting-animation').disabled = false;
+ document.getElementById('pause-counting-animation').disabled = true;
+ }
+
+ function resetCountingAnimation() {
+ clearInterval(animationInterval);
+ animationRunning = false;
+ currentStepIndex = 0;
+ document.getElementById('start-counting-animation').disabled = false;
+ document.getElementById('pause-counting-animation').disabled = true;
+ stepsContainer.innerHTML = '';
+
+ // Reset visualization
+ document.getElementById('counting-array-section').style.display = 'none';
+ document.getElementById('output-array-section').style.display = 'none';
+
+ if (steps.length > 0) {
+ updateCountingVisualization(steps[0]);
+ }
+ document.getElementById('counting-status').textContent = 'Ready to start counting sort animation...';
+ }
+
+ // Bind control events
+ document.getElementById('start-counting-animation').addEventListener('click', startCountingAnimation);
+ document.getElementById('pause-counting-animation').addEventListener('click', pauseCountingAnimation);
+ document.getElementById('reset-counting-animation').addEventListener('click', resetCountingAnimation);
+
+ // Show initial state
+ if (steps.length > 0) {
+ updateCountingVisualization(steps[0]);
+ }
+ }
`
};
diff --git a/algorithms-js/src/sort/dutch-flag-sort/dutch-flag-sort-core.js b/algorithms-js/src/sort/dutch-flag-sort/dutch-flag-sort-core.js
index 69f94800..4125d490 100644
--- a/algorithms-js/src/sort/dutch-flag-sort/dutch-flag-sort-core.js
+++ b/algorithms-js/src/sort/dutch-flag-sort/dutch-flag-sort-core.js
@@ -13,14 +13,10 @@
* @see https://github.com/sachinlala/SimplifyLearning
*/
-// Import utilities for reusable functions
-// Import utilities for reusable functions
-// Note: SortingUtils is available globally via window.SortingUtils when loaded by universal loader
+// SortingUtils is available globally via window.SortingUtils when loaded by universal loader
if (typeof require !== 'undefined') {
- // Node.js environment
const SortingUtils = require('../utils/sorting-utils.js');
}
-// In browser environment, SortingUtils is available via window.SortingUtils
/**
* Core Dutch National Flag sort algorithm
diff --git a/algorithms-js/src/sort/dutch-flag-sort/dutch-flag-sort-steps.js b/algorithms-js/src/sort/dutch-flag-sort/dutch-flag-sort-steps.js
index 2f194b9e..ce72e100 100644
--- a/algorithms-js/src/sort/dutch-flag-sort/dutch-flag-sort-steps.js
+++ b/algorithms-js/src/sort/dutch-flag-sort/dutch-flag-sort-steps.js
@@ -2,36 +2,424 @@
* Dutch Flag Sort - Step Tracking for Visualization
*
* This file contains step-by-step tracking logic for Dutch Flag Sort visualization.
- * Currently placeholder - to be implemented in future animation updates.
+ * Implements detailed step tracking for both 2-way and 3-way partitioning.
*
* @see https://github.com/sachinlala/SimplifyLearning
*/
/**
* Dutch Flag Sort with step-by-step tracking for visualization
- * @param {number[]} arr - Array to be sorted
+ * @param {any[]} arr - Array to be sorted
+ * @param {any} redValue - Red group value
+ * @param {any} whiteValue - White group value (null for 2-way partitioning)
+ * @param {any} blueValue - Blue group value
* @returns {Object} Result with sorted array, steps, and metrics
*/
-function dutchFlagSortWithSteps(arr) {
- // TODO: Implement step-by-step tracking for Dutch Flag Sort visualization
- // This is a placeholder that will be enhanced in future iterations
+function dutchFlagSortWithSteps(arr, redValue, whiteValue, blueValue) {
+ if (!arr || arr.length <= 1) {
+ return {
+ sortedArray: arr || [],
+ steps: [],
+ metrics: { comparisons: 0, swaps: 0, partitions: { red: 0, white: 0, blue: 0 } }
+ };
+ }
+
+ const sortedArray = [...arr];
+ const n = sortedArray.length;
+ const steps = [];
+ let comparisons = 0;
+ let swaps = 0;
+
+ // Handle two-value case (Polish flag) - whiteValue is implicit
+ const isTwoValueSort = (whiteValue === undefined || whiteValue === null);
+ const partitionType = isTwoValueSort ? '2-way' : '3-way';
+ let redCount = 0;
+ let whiteCount = 0;
+ let blueCount = 0;
+
+ // Initial state
+ steps.push({
+ type: 'start',
+ array: [...sortedArray],
+ message: `Starting Dutch Flag Sort (${partitionType} partitioning)`,
+ phase: 'initialization',
+ partitionType,
+ redValue,
+ whiteValue: isTwoValueSort ? 'implicit' : whiteValue,
+ blueValue,
+ comparisons,
+ swaps,
+ partitions: { red: redCount, white: whiteCount, blue: blueCount },
+ highlightIndices: []
+ });
+
+ // Explain the algorithm
+ if (isTwoValueSort) {
+ steps.push({
+ type: 'explanation',
+ array: [...sortedArray],
+ message: `Two-way partitioning: ${JSON.stringify(redValue)} | everything else | ${JSON.stringify(blueValue)}`,
+ phase: 'explanation',
+ partitionType,
+ redValue,
+ whiteValue: 'implicit',
+ blueValue,
+ comparisons,
+ swaps,
+ partitions: { red: redCount, white: whiteCount, blue: blueCount },
+ highlightIndices: []
+ });
+ } else {
+ steps.push({
+ type: 'explanation',
+ array: [...sortedArray],
+ message: `Three-way partitioning: ${JSON.stringify(redValue)} | ${JSON.stringify(whiteValue)} | ${JSON.stringify(blueValue)}`,
+ phase: 'explanation',
+ partitionType,
+ redValue,
+ whiteValue,
+ blueValue,
+ comparisons,
+ swaps,
+ partitions: { red: redCount, white: whiteCount, blue: blueCount },
+ highlightIndices: []
+ });
+ }
+
+ // Initialize pointers
+ let red = 0; // boundary for red section (left)
+ let white = n - 1; // current element being processed (from right)
+ let blue = n - 1; // boundary for blue section (right)
+
+ steps.push({
+ type: 'initialize-pointers',
+ array: [...sortedArray],
+ message: 'Initialize pointers: red=0, white=n-1, blue=n-1. Processing from right to left.',
+ phase: 'initialization',
+ partitionType,
+ pointers: { red, white, blue },
+ redValue,
+ whiteValue: isTwoValueSort ? 'implicit' : whiteValue,
+ blueValue,
+ comparisons,
+ swaps,
+ partitions: { red: redCount, white: whiteCount, blue: blueCount },
+ highlightIndices: [red, white, blue]
+ });
+
+ // Main partitioning loop
+ while (white >= red) {
+ const current = sortedArray[white];
+ comparisons++;
+
+ steps.push({
+ type: 'examine-element',
+ array: [...sortedArray],
+ message: `Examining element ${JSON.stringify(current)} at position ${white}`,
+ phase: 'partitioning',
+ partitionType,
+ currentElement: current,
+ currentIndex: white,
+ pointers: { red, white, blue },
+ redValue,
+ whiteValue: isTwoValueSort ? 'implicit' : whiteValue,
+ blueValue,
+ comparisons,
+ swaps,
+ partitions: { red: redCount, white: whiteCount, blue: blueCount },
+ highlightIndices: [white]
+ });
+
+ if (current === redValue) {
+ // Element belongs in red section
+ steps.push({
+ type: 'identify-red',
+ array: [...sortedArray],
+ message: `${JSON.stringify(current)} belongs in red section`,
+ phase: 'partitioning',
+ partitionType,
+ currentElement: current,
+ targetSection: 'red',
+ pointers: { red, white, blue },
+ redValue,
+ whiteValue: isTwoValueSort ? 'implicit' : whiteValue,
+ blueValue,
+ comparisons,
+ swaps,
+ partitions: { red: redCount, white: whiteCount, blue: blueCount },
+ highlightIndices: [red, white]
+ });
+
+ // Swap with red boundary if needed
+ if (red !== white) {
+ const redElement = sortedArray[red];
+ [sortedArray[red], sortedArray[white]] = [sortedArray[white], sortedArray[red]];
+ swaps++;
+
+ steps.push({
+ type: 'swap-to-red',
+ array: [...sortedArray],
+ message: `Swapped ${JSON.stringify(current)} with ${JSON.stringify(redElement)} to expand red section`,
+ phase: 'partitioning',
+ partitionType,
+ swappedElements: [current, redElement],
+ swappedIndices: [red, white],
+ targetSection: 'red',
+ pointers: { red, white, blue },
+ redValue,
+ whiteValue: isTwoValueSort ? 'implicit' : whiteValue,
+ blueValue,
+ comparisons,
+ swaps,
+ partitions: { red: redCount, white: whiteCount, blue: blueCount },
+ highlightIndices: [red, white]
+ });
+ } else {
+ steps.push({
+ type: 'no-swap-red',
+ array: [...sortedArray],
+ message: `${JSON.stringify(current)} is already in position for red section`,
+ phase: 'partitioning',
+ partitionType,
+ currentElement: current,
+ targetSection: 'red',
+ pointers: { red, white, blue },
+ redValue,
+ whiteValue: isTwoValueSort ? 'implicit' : whiteValue,
+ blueValue,
+ comparisons,
+ swaps,
+ partitions: { red: redCount, white: whiteCount, blue: blueCount },
+ highlightIndices: [red]
+ });
+ }
+
+ redCount++;
+ red++; // Expand red section
+
+ } else if (current === blueValue) {
+ // Element belongs in blue section
+ steps.push({
+ type: 'identify-blue',
+ array: [...sortedArray],
+ message: `${JSON.stringify(current)} belongs in blue section`,
+ phase: 'partitioning',
+ partitionType,
+ currentElement: current,
+ targetSection: 'blue',
+ pointers: { red, white, blue },
+ redValue,
+ whiteValue: isTwoValueSort ? 'implicit' : whiteValue,
+ blueValue,
+ comparisons,
+ swaps,
+ partitions: { red: redCount, white: whiteCount, blue: blueCount },
+ highlightIndices: [white, blue]
+ });
+
+ // Swap with blue boundary if needed
+ if (white !== blue) {
+ const blueElement = sortedArray[blue];
+ [sortedArray[white], sortedArray[blue]] = [sortedArray[blue], sortedArray[white]];
+ swaps++;
+
+ steps.push({
+ type: 'swap-to-blue',
+ array: [...sortedArray],
+ message: `Swapped ${JSON.stringify(current)} with ${JSON.stringify(blueElement)} to expand blue section`,
+ phase: 'partitioning',
+ partitionType,
+ swappedElements: [current, blueElement],
+ swappedIndices: [white, blue],
+ targetSection: 'blue',
+ pointers: { red, white, blue },
+ redValue,
+ whiteValue: isTwoValueSort ? 'implicit' : whiteValue,
+ blueValue,
+ comparisons,
+ swaps,
+ partitions: { red: redCount, white: whiteCount, blue: blueCount },
+ highlightIndices: [white, blue]
+ });
+ } else {
+ steps.push({
+ type: 'no-swap-blue',
+ array: [...sortedArray],
+ message: `${JSON.stringify(current)} is already in position for blue section`,
+ phase: 'partitioning',
+ partitionType,
+ currentElement: current,
+ targetSection: 'blue',
+ pointers: { red, white, blue },
+ redValue,
+ whiteValue: isTwoValueSort ? 'implicit' : whiteValue,
+ blueValue,
+ comparisons,
+ swaps,
+ partitions: { red: redCount, white: whiteCount, blue: blueCount },
+ highlightIndices: [blue]
+ });
+ }
+
+ blueCount++;
+ blue--; // Shrink blue boundary
+ white--; // Move back to check the swapped element
+
+ } else {
+ // Element belongs in white section (middle) or is unknown
+ const isWhiteElement = (isTwoValueSort || current === whiteValue);
+
+ steps.push({
+ type: 'identify-white',
+ array: [...sortedArray],
+ message: isWhiteElement ?
+ `${JSON.stringify(current)} belongs in ${isTwoValueSort ? 'middle' : 'white'} section` :
+ `${JSON.stringify(current)} is unknown, treating as ${isTwoValueSort ? 'middle' : 'white'} element`,
+ phase: 'partitioning',
+ partitionType,
+ currentElement: current,
+ targetSection: 'white',
+ isUnknownElement: !isWhiteElement,
+ pointers: { red, white, blue },
+ redValue,
+ whiteValue: isTwoValueSort ? 'implicit' : whiteValue,
+ blueValue,
+ comparisons,
+ swaps,
+ partitions: { red: redCount, white: whiteCount, blue: blueCount },
+ highlightIndices: [white]
+ });
+
+ whiteCount++;
+ white--; // Element stays in place, move to next
+ }
+
+ // Show updated boundaries after each operation
+ if (steps.length > 0) {
+ const lastStep = steps[steps.length - 1];
+ if (lastStep.type !== 'update-boundaries') {
+ steps.push({
+ type: 'update-boundaries',
+ array: [...sortedArray],
+ message: `Updated boundaries: red=[0,${red-1}], white=[${red},${blue}], blue=[${blue+1},${n-1}]`,
+ phase: 'partitioning',
+ partitionType,
+ pointers: { red, white, blue },
+ sections: {
+ red: { start: 0, end: red - 1, count: redCount },
+ white: { start: red, end: blue, count: whiteCount },
+ blue: { start: blue + 1, end: n - 1, count: blueCount }
+ },
+ redValue,
+ whiteValue: isTwoValueSort ? 'implicit' : whiteValue,
+ blueValue,
+ comparisons,
+ swaps,
+ partitions: { red: redCount, white: whiteCount, blue: blueCount },
+ highlightIndices: []
+ });
+ }
+ }
+ }
+
+ // Final validation and summary
+ steps.push({
+ type: 'complete',
+ array: [...sortedArray],
+ message: `Dutch Flag Sort complete! Partitioned into ${redCount} red, ${whiteCount} ${isTwoValueSort ? 'middle' : 'white'}, and ${blueCount} blue elements in ${swaps} swaps.`,
+ phase: 'complete',
+ partitionType,
+ finalSections: {
+ red: { count: redCount, values: sortedArray.slice(0, red) },
+ white: { count: whiteCount, values: sortedArray.slice(red, blue + 1) },
+ blue: { count: blueCount, values: sortedArray.slice(blue + 1) }
+ },
+ redValue,
+ whiteValue: isTwoValueSort ? 'implicit' : whiteValue,
+ blueValue,
+ comparisons,
+ swaps,
+ partitions: { red: redCount, white: whiteCount, blue: blueCount },
+ highlightIndices: []
+ });
+
return {
- sortedArray: [...arr],
- steps: [],
- metrics: { comparisons: 0, swaps: 0, partitions: 0 }
+ sortedArray,
+ steps,
+ metrics: {
+ comparisons,
+ swaps,
+ partitions: { red: redCount, white: whiteCount, blue: blueCount },
+ redValue,
+ whiteValue: isTwoValueSort ? 'implicit' : whiteValue,
+ blueValue
+ }
};
}
+/**
+ * Three-way Dutch Flag Sort with step tracking
+ * @param {any[]} arr - Array to partition
+ * @param {any} redValue - First group value
+ * @param {any} whiteValue - Second group value
+ * @param {any} blueValue - Third group value
+ * @returns {Object} Result with sorted array, steps, and metrics
+ */
+function dutchFlagSort3WayWithSteps(arr, redValue, whiteValue, blueValue) {
+ return dutchFlagSortWithSteps(arr, redValue, whiteValue, blueValue);
+}
+
+/**
+ * Two-way Dutch Flag Sort with step tracking
+ * @param {any[]} arr - Array to partition
+ * @param {any} firstValue - First group value
+ * @param {any} secondValue - Second group value
+ * @returns {Object} Result with sorted array, steps, and metrics
+ */
+function dutchFlagSort2WayWithSteps(arr, firstValue, secondValue) {
+ return dutchFlagSortWithSteps(arr, firstValue, null, secondValue);
+}
+
+/**
+ * Sort colors with step tracking (classic Dutch flag problem)
+ * @param {string[]} arr - Array of color strings
+ * @returns {Object} Result with sorted array, steps, and metrics
+ */
+function sortColorsWithSteps(arr) {
+ return dutchFlagSortWithSteps(arr, 'red', 'white', 'blue');
+}
+
+/**
+ * Sort 0s, 1s, and 2s with step tracking
+ * @param {number[]} arr - Array of 0s, 1s, and 2s
+ * @returns {Object} Result with sorted array, steps, and metrics
+ */
+function sort012WithSteps(arr) {
+ return dutchFlagSortWithSteps(arr, 0, 1, 2);
+}
+
// Export for both Node.js and browser environments
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
- dutchFlagSortWithSteps
+ dutchFlagSortWithSteps,
+ dutchFlagSort3WayWithSteps,
+ dutchFlagSort2WayWithSteps,
+ sortColorsWithSteps,
+ sort012WithSteps
};
} else if (typeof window !== 'undefined') {
window.DutchFlagSortSteps = {
- dutchFlagSortWithSteps
+ dutchFlagSortWithSteps,
+ dutchFlagSort3WayWithSteps,
+ dutchFlagSort2WayWithSteps,
+ sortColorsWithSteps,
+ sort012WithSteps
};
// Expose commonly used functions in global scope for demo configs
window.dutchFlagSortWithSteps = dutchFlagSortWithSteps;
-}
\ No newline at end of file
+ window.dutchFlagSort3WayWithSteps = dutchFlagSort3WayWithSteps;
+ window.dutchFlagSort2WayWithSteps = dutchFlagSort2WayWithSteps;
+ window.sortColorsWithSteps = sortColorsWithSteps;
+ window.sort012WithSteps = sort012WithSteps;
+}
diff --git a/algorithms-js/src/sort/dutch-flag-sort/dutch-flag-sort.config.js b/algorithms-js/src/sort/dutch-flag-sort/dutch-flag-sort.config.js
index 1b820f2b..7c711a8e 100644
--- a/algorithms-js/src/sort/dutch-flag-sort/dutch-flag-sort.config.js
+++ b/algorithms-js/src/sort/dutch-flag-sort/dutch-flag-sort.config.js
@@ -444,31 +444,229 @@ DUTCH_FLAG_SORT_CONFIG.customDemoFunction = `
try {
const startTime = performance.now();
- // Execute dutch flag sort
- const result = window.DutchFlagSortCore ?
- window.DutchFlagSortCore.dutchFlagSort(arrayInput, redValue, blueValue) :
- dutchFlagSort(arrayInput, redValue, blueValue);
+ // Execute dutch flag sort using steps function for animation
+ let result;
+ if (window.DutchFlagSortSteps) {
+ result = window.DutchFlagSortSteps.dutchFlagSortWithSteps(arrayInput, redValue, null, blueValue);
+ } else if (window.dutchFlagSortWithSteps) {
+ result = window.dutchFlagSortWithSteps(arrayInput, redValue, null, blueValue);
+ } else if (window.DutchFlagSortCore) {
+ const coreResult = window.DutchFlagSortCore.dutchFlagSort(arrayInput, redValue, blueValue);
+ result = { ...coreResult, steps: [] };
+ } else {
+ result = { sortedArray: [...arrayInput].sort(), metrics: { comparisons: 0, swaps: 0 }, steps: [] };
+ }
const endTime = performance.now();
const executionTime = (endTime - startTime).toFixed(4);
// Show result
- let resultHTML = \`
- Original Array: [\${arrayInput.join(', ')}]
- Partitioned Array: [\${result.sortedArray.join(', ')}]
- Red Group (\${redValue}): positions 0 to \${result.metrics.redEnd || 'N/A'}
- White/Other Group: middle section
- Blue Group (\${blueValue}): end section
- Total Swaps: \${result.metrics.swaps || 0}
- Execution Time: \${executionTime} ms
- \`;
+ let resultHTML =
+ 'Original Array: [' + arrayInput.join(', ') + ']
' +
+ 'Partitioned Array: [' + result.sortedArray.join(', ') + ']
' +
+ 'Red Group (' + redValue + '): first section
' +
+ 'White/Other Group: middle section
' +
+ 'Blue Group (' + blueValue + '): end section
' +
+ 'Total Swaps: ' + (result.metrics.swaps || 0) + '
' +
+ 'Execution Time: ' + executionTime + ' ms';
resultContainer.innerHTML = resultHTML;
+ // Show the visualization section with dutch flag animation
+ if (result.steps && result.steps.length > 0) {
+ showDutchFlagVisualization(arrayInput, result.steps, redValue, blueValue);
+ visualizationSection.style.display = 'block';
+ }
+
} catch (error) {
showError(error.message);
}
}
+
+ function showDutchFlagVisualization(originalArray, steps, redValue, blueValue) {
+ const arrayViz = document.getElementById('array-visualization');
+ const stepsContainer = document.getElementById('steps-container');
+
+ // Clear previous visualization
+ arrayViz.innerHTML = '';
+ stepsContainer.innerHTML = '';
+
+ // Create array visualization
+ const arrayDiv = document.createElement('div');
+ arrayDiv.className = 'array-visualization';
+ arrayDiv.id = 'dutch-flag-array-display';
+
+ originalArray.forEach((value, index) => {
+ const cell = document.createElement('div');
+ cell.textContent = value;
+ cell.className = 'viz-cell';
+ cell.setAttribute('data-index', index);
+ cell.setAttribute('data-value', value);
+ arrayDiv.appendChild(cell);
+ });
+
+ arrayViz.appendChild(arrayDiv);
+
+ // Add controls with legend
+ const controlsDiv = document.createElement('div');
+ controlsDiv.className = 'viz-controls';
+ controlsDiv.innerHTML =
+ 'Dutch Flag Sort Visualization
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
π΄ Red (' + redValue + ') | βͺ White/Other | π΅ Blue (' + blueValue + ') | π‘ Comparing | π’ Swapping' +
+ '
' +
+ '
π΄ Red (' + redValue + ')
' +
+ '
βͺ White/Other
' +
+ '
π΅ Blue (' + blueValue + ')
' +
+ '
π‘ Comparing
' +
+ '
π’ Swapping
' +
+ '
' +
+ '
';
+ arrayViz.appendChild(controlsDiv);
+
+ // Status display
+ const statusDiv = document.createElement('div');
+ statusDiv.id = 'dutch-flag-status';
+ statusDiv.className = 'viz-status';
+ statusDiv.textContent = 'Ready to start dutch flag sort animation...';
+ arrayViz.appendChild(statusDiv);
+
+ // Animation variables
+ let currentStepIndex = 0;
+ let animationRunning = false;
+ let animationInterval;
+
+ function updateDutchFlagVisualization(step) {
+ const cells = arrayDiv.querySelectorAll('.viz-cell');
+ const statusDiv = document.getElementById('dutch-flag-status');
+
+ // Reset all cell classes
+ cells.forEach(cell => {
+ cell.className = 'viz-cell';
+ });
+
+ // Update array values
+ step.array.forEach((value, index) => {
+ if (cells[index]) {
+ cells[index].textContent = value;
+ }
+ });
+
+ // Color cells based on their partition group
+ step.array.forEach((value, index) => {
+ if (cells[index]) {
+ if (value === redValue) {
+ cells[index].classList.add('red-partition');
+ } else if (value === blueValue) {
+ cells[index].classList.add('blue-partition');
+ } else {
+ cells[index].classList.add('white-partition');
+ }
+ }
+ });
+
+ // Highlight current indices being processed
+ if (step.highlightIndices) {
+ step.highlightIndices.forEach(index => {
+ if (cells[index]) {
+ cells[index].classList.add('comparing');
+ }
+ });
+ }
+
+ // Highlight swapped indices
+ if (step.swappedIndices) {
+ step.swappedIndices.forEach(index => {
+ if (cells[index]) {
+ cells[index].classList.add('swapping');
+ }
+ });
+ }
+
+ // Update status
+ statusDiv.textContent = step.message;
+
+ // Show step info in container
+ const stepInfo = document.createElement('div');
+ stepInfo.className = step.type === 'complete' ? 'viz-step-info complete' : 'viz-step-info';
+
+ let stepTypeColor = '#007acc';
+ if (step.type === 'complete') stepTypeColor = '#28a745';
+ else if (step.type === 'swap') stepTypeColor = '#dc3545';
+ else if (step.type === 'compare') stepTypeColor = '#ffc107';
+
+ stepInfo.style.borderLeftColor = stepTypeColor;
+
+ stepInfo.innerHTML =
+ 'Step ' + (currentStepIndex + 1) + ': ' + step.message + '
' +
+ '' +
+ 'Phase: ' + (step.phase || 'processing') + ' | ' +
+ 'Comparisons: ' + (step.comparisons || 0) + ' | ' +
+ 'Swaps: ' + (step.swaps || 0) +
+ '';
+
+ if (stepsContainer.children.length > 8) {
+ stepsContainer.removeChild(stepsContainer.firstChild);
+ }
+ stepsContainer.appendChild(stepInfo);
+ }
+
+ function startDutchFlagAnimation() {
+ if (animationRunning || currentStepIndex >= steps.length) return;
+
+ animationRunning = true;
+ document.getElementById('start-dutch-animation').disabled = true;
+ document.getElementById('pause-dutch-animation').disabled = false;
+
+ animationInterval = setInterval(() => {
+ if (currentStepIndex >= steps.length) {
+ clearInterval(animationInterval);
+ animationRunning = false;
+ document.getElementById('start-dutch-animation').disabled = false;
+ document.getElementById('pause-dutch-animation').disabled = true;
+ return;
+ }
+
+ updateDutchFlagVisualization(steps[currentStepIndex]);
+ currentStepIndex++;
+ }, 1200); // 1.2 second delay between steps
+ }
+
+ function pauseDutchFlagAnimation() {
+ clearInterval(animationInterval);
+ animationRunning = false;
+ document.getElementById('start-dutch-animation').disabled = false;
+ document.getElementById('pause-dutch-animation').disabled = true;
+ }
+
+ function resetDutchFlagAnimation() {
+ clearInterval(animationInterval);
+ animationRunning = false;
+ currentStepIndex = 0;
+ document.getElementById('start-dutch-animation').disabled = false;
+ document.getElementById('pause-dutch-animation').disabled = true;
+ stepsContainer.innerHTML = '';
+
+ // Reset visualization
+ if (steps.length > 0) {
+ updateDutchFlagVisualization(steps[0]);
+ }
+ document.getElementById('dutch-flag-status').textContent = 'Ready to start dutch flag sort animation...';
+ }
+
+ // Bind control events
+ document.getElementById('start-dutch-animation').addEventListener('click', startDutchFlagAnimation);
+ document.getElementById('pause-dutch-animation').addEventListener('click', pauseDutchFlagAnimation);
+ document.getElementById('reset-dutch-animation').addEventListener('click', resetDutchFlagAnimation);
+
+ // Show initial state
+ if (steps.length > 0) {
+ updateDutchFlagVisualization(steps[0]);
+ }
+ }
`;
// Export for both Node.js and browser environments
diff --git a/algorithms-js/src/sort/radix-sort/radix-sort-steps.js b/algorithms-js/src/sort/radix-sort/radix-sort-steps.js
index 15bcb5dd..1acd2ef4 100644
--- a/algorithms-js/src/sort/radix-sort/radix-sort-steps.js
+++ b/algorithms-js/src/sort/radix-sort/radix-sort-steps.js
@@ -189,6 +189,18 @@ if (typeof window !== 'undefined') {
getDigit,
getMaxDigits
};
+
+ // Additional exports for universal loader compatibility
+ window.radixSortWithSteps = radixSortWithSteps;
+ window.getDigit = getDigit;
+ window.getMaxDigits = getMaxDigits;
+
+ // Alternative naming variants
+ window.RADIX_SORT_STEPS = {
+ radixSortWithSteps,
+ getDigit,
+ getMaxDigits
+ };
}
// Export for CommonJS compatibility
diff --git a/algorithms-js/src/sort/radix-sort/radix-sort.config.js b/algorithms-js/src/sort/radix-sort/radix-sort.config.js
index 338335ac..0af16ec1 100644
--- a/algorithms-js/src/sort/radix-sort/radix-sort.config.js
+++ b/algorithms-js/src/sort/radix-sort/radix-sort.config.js
@@ -370,28 +370,362 @@ const RadixSortConfig = {
try {
const startTime = performance.now();
- // Execute radix sort
- const result = window.RadixSortCore ? window.RadixSortCore.radixSort(arrayInput) : radixSort(arrayInput);
+ // Execute radix sort using steps function for animation
+ let result;
+ if (window.RadixSortSteps) {
+ result = window.RadixSortSteps.radixSortWithSteps(arrayInput);
+ } else if (window.radixSortWithSteps) {
+ result = window.radixSortWithSteps(arrayInput);
+ } else if (window.RadixSortCore) {
+ const coreResult = window.RadixSortCore.radixSort(arrayInput);
+ result = { ...coreResult, steps: [] };
+ } else {
+ result = { sortedArray: [...arrayInput].sort((a, b) => a - b), metrics: { passes: 0, bucketOperations: 0, maxDigits: 0 }, steps: [] };
+ }
const endTime = performance.now();
const executionTime = (endTime - startTime).toFixed(4);
// Show result
- let resultHTML = \`
- Original Array: [\${arrayInput.join(', ')}]
- Sorted Array: [\${result.sortedArray.join(', ')}]
- Max Digits (d): \${result.metrics.maxDigits}
- Passes: \${result.metrics.passes}
- Total Operations: \${result.metrics.totalOperations}
- Execution Time: \${executionTime} ms
- \`;
+ let resultHTML =
+ 'Original Array: [' + arrayInput.join(', ') + ']
' +
+ 'Sorted Array: [' + result.sortedArray.join(', ') + ']
' +
+ 'Max Digits (d): ' + (result.metrics.maxDigits || 0) + '
' +
+ 'Passes: ' + (result.metrics.passes || 0) + '
' +
+ 'Bucket Operations: ' + (result.metrics.bucketOperations || 0) + '
' +
+ 'Execution Time: ' + executionTime + ' ms';
resultContainer.innerHTML = resultHTML;
+ // Show the visualization section with radix sort animation
+ if (result.steps && result.steps.length > 0) {
+ showRadixSortVisualization(arrayInput, result.steps);
+ visualizationSection.style.display = 'block';
+ }
+
} catch (error) {
showError(error.message);
}
}
+
+ function showRadixSortVisualization(originalArray, steps) {
+ const arrayViz = document.getElementById('array-visualization');
+ const stepsContainer = document.getElementById('steps-container');
+
+ // Clear previous visualization
+ arrayViz.innerHTML = '';
+ stepsContainer.innerHTML = '';
+
+ // Define digit bucket colors (10 distinct colors for digits 0-9)
+ const digitColors = [
+ '#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#feca57',
+ '#ff9ff3', '#54a0ff', '#5f27cd', '#00d2d3', '#ff9f43'
+ ];
+
+ // Create array visualization
+ const arrayDiv = document.createElement('div');
+ arrayDiv.className = 'array-visualization';
+ arrayDiv.id = 'radix-array-display';
+
+ originalArray.forEach((value, index) => {
+ const cell = document.createElement('div');
+ cell.textContent = value;
+ cell.className = 'viz-cell radix-cell';
+ cell.setAttribute('data-index', index);
+ cell.setAttribute('data-value', value);
+
+ // Add digit display container
+ const digitDisplay = document.createElement('div');
+ digitDisplay.className = 'digit-display';
+ digitDisplay.textContent = '';
+ cell.appendChild(digitDisplay);
+
+ arrayDiv.appendChild(cell);
+ });
+
+ arrayViz.appendChild(arrayDiv);
+
+ // Create digit position display
+ const digitInfoDiv = document.createElement('div');
+ digitInfoDiv.className = 'digit-info';
+ digitInfoDiv.id = 'digit-info';
+ digitInfoDiv.innerHTML = 'Current Digit Position: Ready to start...
';
+ arrayViz.appendChild(digitInfoDiv);
+
+ // Create compact color legend for digit assignments
+ const colorGuide = document.createElement('div');
+ colorGuide.className = 'radix-color-guide';
+
+ const colorHeader = document.createElement('div');
+ colorHeader.className = 'radix-color-header';
+ colorHeader.innerHTML = 'Digit Colors:';
+ colorGuide.appendChild(colorHeader);
+
+ const colorRow = document.createElement('div');
+ colorRow.className = 'radix-color-row';
+ colorRow.style.textAlign = 'center';
+ colorRow.style.lineHeight = '1';
+ colorRow.style.whiteSpace = 'nowrap';
+ colorRow.style.margin = '8px 0';
+
+ for (let digit = 0; digit <= 9; digit++) {
+ // Create color circle with digit inside - directly append to row
+ const colorCircle = document.createElement('span');
+ colorCircle.className = 'radix-color-circle digit-' + digit;
+ colorCircle.title = 'Digit ' + digit + ' - ' + digitColors[digit];
+ colorCircle.style.backgroundColor = digitColors[digit];
+ colorCircle.style.color = 'white';
+ colorCircle.style.fontWeight = 'bold';
+ colorCircle.style.fontSize = '11px';
+ colorCircle.style.display = 'inline-block';
+ colorCircle.style.textAlign = 'center';
+ colorCircle.style.lineHeight = '24px';
+ colorCircle.style.width = '24px';
+ colorCircle.style.height = '24px';
+ colorCircle.style.borderRadius = '50%';
+ colorCircle.style.border = '2px solid rgba(0,0,0,0.2)';
+ colorCircle.style.textShadow = '0 1px 1px rgba(0,0,0,0.5)';
+ colorCircle.style.margin = '0 4px';
+ colorCircle.style.verticalAlign = 'middle';
+ colorCircle.textContent = digit;
+
+ colorRow.appendChild(colorCircle);
+ }
+
+ colorGuide.appendChild(colorRow);
+ arrayViz.appendChild(colorGuide);
+
+ // Add controls with legend
+ const controlsDiv = document.createElement('div');
+ controlsDiv.className = 'viz-controls';
+ controlsDiv.innerHTML =
+ 'Radix Sort Visualization
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
β Extract | π¨ Color by Digit | π¦ Distribute | π Collect | β
Complete' +
+ '
' +
+ '
β Extract digit from number
' +
+ '
π¨ Color elements by their digit
' +
+ '
π¦ Distribute to digit groups
' +
+ '
π Collect back in digit order
' +
+ '
β
Pass completed
' +
+ '
' +
+ '
';
+ arrayViz.appendChild(controlsDiv);
+
+ // Status display
+ const statusDiv = document.createElement('div');
+ statusDiv.id = 'radix-status';
+ statusDiv.className = 'viz-status';
+ statusDiv.textContent = 'Ready to start radix sort animation...';
+ arrayViz.appendChild(statusDiv);
+
+ // Animation variables
+ let currentStepIndex = 0;
+ let animationRunning = false;
+ let animationInterval;
+
+ function updateRadixVisualization(step) {
+ const cells = arrayDiv.querySelectorAll('.viz-cell');
+ const statusDiv = document.getElementById('radix-status');
+ const digitPosSpan = document.getElementById('current-digit-pos');
+
+ // Reset all cell classes and styles
+ cells.forEach(cell => {
+ cell.className = 'viz-cell radix-cell';
+ cell.style.backgroundColor = '';
+ cell.style.color = '';
+ cell.style.borderColor = '';
+ const digitDisplay = cell.querySelector('.digit-display');
+ if (digitDisplay) {
+ digitDisplay.textContent = '';
+ digitDisplay.classList.remove('digit-highlight');
+ }
+ });
+
+ // Update array values if changed
+ if (step.array) {
+ step.array.forEach((value, index) => {
+ if (cells[index]) {
+ cells[index].textContent = value;
+ cells[index].setAttribute('data-value', value);
+ }
+ });
+ }
+
+ // Update digit position display
+ if (step.digitPosition !== undefined) {
+ const positionName = ['Ones', 'Tens', 'Hundreds', 'Thousands'][step.digitPosition] || 'Position ' + (step.digitPosition + 1);
+ digitPosSpan.textContent = positionName + ' (' + (step.digitPosition + 1) + ')';
+ }
+
+ // Handle different step types with color coding
+ if (step.type === 'distribute') {
+ // Highlight current element being distributed
+ if (step.currentElementIndex !== undefined && cells[step.currentElementIndex]) {
+ cells[step.currentElementIndex].classList.add('distributing');
+
+ // Show extracted digit
+ const digitDisplay = cells[step.currentElementIndex].querySelector('.digit-display');
+ if (digitDisplay && step.extractedDigit !== undefined) {
+ digitDisplay.textContent = step.extractedDigit;
+ digitDisplay.classList.add('digit-highlight');
+ }
+
+ // Apply color based on extracted digit
+ if (step.extractedDigit !== undefined) {
+ const digitColor = digitColors[step.extractedDigit];
+ cells[step.currentElementIndex].style.backgroundColor = digitColor;
+ cells[step.currentElementIndex].style.color = 'white';
+ cells[step.currentElementIndex].style.borderColor = digitColor;
+ }
+ }
+ } else if (step.type === 'collect') {
+ // Highlight element being collected
+ if (step.collectedTo !== undefined && cells[step.collectedTo]) {
+ cells[step.collectedTo].classList.add('collecting');
+ }
+ } else if (step.type === 'distribution-complete') {
+ // Color all elements by their current digit
+ step.array.forEach((value, index) => {
+ if (cells[index] && step.digitPosition !== undefined) {
+ const digit = Math.floor(value / Math.pow(10, step.digitPosition)) % 10;
+ const digitColor = digitColors[digit];
+ cells[index].style.backgroundColor = digitColor;
+ cells[index].style.color = 'white';
+ cells[index].style.borderColor = digitColor;
+ cells[index].classList.add('digit-colored');
+ }
+ });
+ } else if (step.type === 'pass-complete') {
+ // Show completion of pass with gentle pulse
+ cells.forEach(cell => {
+ cell.classList.add('pass-complete');
+ });
+ }
+
+ // Final completion state
+ if (step.type === 'complete') {
+ cells.forEach(cell => {
+ cell.className = 'viz-cell radix-cell complete';
+ cell.style.backgroundColor = '';
+ cell.style.color = '';
+ cell.style.borderColor = '';
+ const digitDisplay = cell.querySelector('.digit-display');
+ if (digitDisplay) {
+ digitDisplay.textContent = '';
+ digitDisplay.classList.remove('digit-highlight');
+ }
+ });
+ digitPosSpan.textContent = 'Sorting Complete!';
+ }
+
+ // Update status
+ statusDiv.textContent = step.message;
+
+ // Show step info in container
+ const stepInfo = document.createElement('div');
+ stepInfo.className = step.type === 'complete' ? 'viz-step-info complete' : 'viz-step-info';
+
+ let stepTypeColor = '#007acc';
+ if (step.type === 'complete') stepTypeColor = '#28a745';
+ else if (step.type === 'distribute') stepTypeColor = '#ff9800';
+ else if (step.type === 'collect') stepTypeColor = '#4caf50';
+ else if (step.phase === 'digit-processing') stepTypeColor = '#2196f3';
+
+ stepInfo.style.borderLeftColor = stepTypeColor;
+
+ let phaseEmoji = '*';
+ if (step.type === 'distribute') phaseEmoji = '+';
+ else if (step.type === 'collect') phaseEmoji = '-';
+ else if (step.type === 'complete') phaseEmoji = '!';
+ else if (step.type === 'pass-start') phaseEmoji = '>';
+
+ stepInfo.innerHTML =
+ '' + phaseEmoji + ' Step ' + (currentStepIndex + 1) + ': ' + step.message + '
' +
+ '' +
+ 'Phase: ' + (step.phase || 'processing') + ' | ' +
+ 'Pass: ' + (step.passNumber || 'N/A') + ' | ' +
+ 'Operations: ' + (step.metrics.bucketOperations || 0) +
+ '';
+
+ if (stepsContainer.children.length > 8) {
+ stepsContainer.removeChild(stepsContainer.firstChild);
+ }
+ stepsContainer.appendChild(stepInfo);
+ }
+
+ function startRadixAnimation() {
+ if (animationRunning || currentStepIndex >= steps.length) return;
+
+ animationRunning = true;
+ document.getElementById('start-radix-animation').disabled = true;
+ document.getElementById('pause-radix-animation').disabled = false;
+
+ animationInterval = setInterval(() => {
+ if (currentStepIndex >= steps.length) {
+ clearInterval(animationInterval);
+ animationRunning = false;
+ document.getElementById('start-radix-animation').disabled = false;
+ document.getElementById('pause-radix-animation').disabled = true;
+ return;
+ }
+
+ updateRadixVisualization(steps[currentStepIndex]);
+ currentStepIndex++;
+ }, 1200); // 1.2 second delay between steps
+ }
+
+ function pauseRadixAnimation() {
+ clearInterval(animationInterval);
+ animationRunning = false;
+ document.getElementById('start-radix-animation').disabled = false;
+ document.getElementById('pause-radix-animation').disabled = true;
+ }
+
+ function resetRadixAnimation() {
+ clearInterval(animationInterval);
+ animationRunning = false;
+ currentStepIndex = 0;
+ document.getElementById('start-radix-animation').disabled = false;
+ document.getElementById('pause-radix-animation').disabled = true;
+ stepsContainer.innerHTML = '';
+
+ // Reset visualization
+ document.getElementById('current-digit-pos').textContent = 'Ready to start...';
+
+ // Reset all cells to default state
+ const cells = arrayDiv.querySelectorAll('.viz-cell');
+ cells.forEach(cell => {
+ cell.className = 'viz-cell radix-cell';
+ cell.style.backgroundColor = '';
+ cell.style.color = '';
+ cell.style.borderColor = '';
+ const digitDisplay = cell.querySelector('.digit-display');
+ if (digitDisplay) {
+ digitDisplay.textContent = '';
+ digitDisplay.classList.remove('digit-highlight');
+ }
+ });
+
+ if (steps.length > 0) {
+ updateRadixVisualization(steps[0]);
+ }
+ document.getElementById('radix-status').textContent = 'Ready to start radix sort animation...';
+ }
+
+ // Bind control events
+ document.getElementById('start-radix-animation').addEventListener('click', startRadixAnimation);
+ document.getElementById('pause-radix-animation').addEventListener('click', pauseRadixAnimation);
+ document.getElementById('reset-radix-animation').addEventListener('click', resetRadixAnimation);
+
+ // Show initial state
+ if (steps.length > 0) {
+ updateRadixVisualization(steps[0]);
+ }
+ }
`
};
diff --git a/algorithms-js/src/sort/wiggle-sort/wiggle-sort-core.js b/algorithms-js/src/sort/wiggle-sort/wiggle-sort-core.js
index cdea0406..4b900e02 100644
--- a/algorithms-js/src/sort/wiggle-sort/wiggle-sort-core.js
+++ b/algorithms-js/src/sort/wiggle-sort/wiggle-sort-core.js
@@ -14,14 +14,10 @@
* @see https://github.com/sachinlala/SimplifyLearning
*/
-// Import utilities for reusable functions
-// Import utilities for reusable functions
-// Note: SortingUtils is available globally via window.SortingUtils when loaded by universal loader
+// SortingUtils is available globally via window.SortingUtils when loaded by universal loader
if (typeof require !== 'undefined') {
- // Node.js environment
const SortingUtils = require('../utils/sorting-utils.js');
}
-// In browser environment, SortingUtils is available via window.SortingUtils
/**
* Wiggle Sort I - In-place O(n) algorithm
@@ -42,18 +38,15 @@ function wiggleSortI(arr) {
let comparisons = 0;
let swaps = 0;
- // In-place wiggle sort algorithm
for (let i = 0; i < n - 1; i++) {
comparisons++;
if (i % 2 === 0) {
- // Even index: should be less than next (valley)
if (sortedArray[i] > sortedArray[i + 1]) {
SortingUtils.swap(sortedArray, i, i + 1);
swaps++;
}
} else {
- // Odd index: should be greater than next (peak)
if (sortedArray[i] < sortedArray[i + 1]) {
SortingUtils.swap(sortedArray, i, i + 1);
swaps++;
diff --git a/algorithms-js/src/sort/wiggle-sort/wiggle-sort-steps.js b/algorithms-js/src/sort/wiggle-sort/wiggle-sort-steps.js
index 0e6c1a4e..0ccfa6e8 100644
--- a/algorithms-js/src/sort/wiggle-sort/wiggle-sort-steps.js
+++ b/algorithms-js/src/sort/wiggle-sort/wiggle-sort-steps.js
@@ -2,36 +2,464 @@
* Wiggle Sort - Step Tracking for Visualization
*
* This file contains step-by-step tracking logic for Wiggle Sort visualization.
- * Currently placeholder - to be implemented in future animation updates.
+ * Implements detailed step tracking for both Wiggle Sort I and II variants.
*
* @see https://github.com/sachinlala/SimplifyLearning
*/
/**
- * Wiggle Sort with step-by-step tracking for visualization
+ * Wiggle Sort I with step-by-step tracking for visualization
+ * In-place O(n) algorithm that creates wiggling pattern
* @param {number[]} arr - Array to be sorted
* @returns {Object} Result with sorted array, steps, and metrics
*/
-function wiggleSortWithSteps(arr) {
- // TODO: Implement step-by-step tracking for Wiggle Sort visualization
- // This is a placeholder that will be enhanced in future iterations
+function wiggleSortIWithSteps(arr) {
+ if (!arr || arr.length <= 1) {
+ return {
+ sortedArray: arr || [],
+ steps: [],
+ metrics: { comparisons: 0, swaps: 0, wiggleOperations: 0 }
+ };
+ }
+
+ const sortedArray = [...arr];
+ const n = sortedArray.length;
+ const steps = [];
+ let comparisons = 0;
+ let swaps = 0;
+ let wiggleOperations = 0;
+
+ // Initial state
+ steps.push({
+ type: 'start',
+ array: [...sortedArray],
+ message: 'Starting Wiggle Sort I - creating wiggling pattern in-place',
+ phase: 'initialization',
+ variant: 'I',
+ comparisons,
+ swaps,
+ wiggleOperations,
+ highlightIndices: []
+ });
+
+ // Pattern explanation
+ steps.push({
+ type: 'pattern-explanation',
+ array: [...sortedArray],
+ message: 'Target pattern: arr[0] < arr[1] > arr[2] < arr[3] > arr[4]... (valley-peak-valley-peak)',
+ phase: 'explanation',
+ variant: 'I',
+ comparisons,
+ swaps,
+ wiggleOperations,
+ highlightIndices: []
+ });
+
+ // Process each adjacent pair
+ for (let i = 0; i < n - 1; i++) {
+ comparisons++;
+ wiggleOperations++;
+
+ const isEvenIndex = i % 2 === 0;
+ const expectedRelation = isEvenIndex ? 'valley (<=)' : 'peak (>=)';
+ const currentValue = sortedArray[i];
+ const nextValue = sortedArray[i + 1];
+
+ // Show what we're checking
+ steps.push({
+ type: 'check-position',
+ array: [...sortedArray],
+ message: `Position ${i} should be ${expectedRelation} relative to position ${i + 1}`,
+ phase: 'checking',
+ variant: 'I',
+ currentIndex: i,
+ nextIndex: i + 1,
+ expectedPattern: isEvenIndex ? 'valley' : 'peak',
+ comparisons,
+ swaps,
+ wiggleOperations,
+ highlightIndices: [i, i + 1]
+ });
+
+ // Compare values
+ steps.push({
+ type: 'compare',
+ array: [...sortedArray],
+ message: `Comparing: ${currentValue} and ${nextValue}`,
+ phase: 'comparison',
+ variant: 'I',
+ currentIndex: i,
+ nextIndex: i + 1,
+ currentValue,
+ nextValue,
+ expectedPattern: isEvenIndex ? 'valley' : 'peak',
+ comparisons,
+ swaps,
+ wiggleOperations,
+ highlightIndices: [i, i + 1]
+ });
+
+ let needsSwap = false;
+ if (isEvenIndex) {
+ // Even index: should be valley (less than or equal to next)
+ needsSwap = currentValue > nextValue;
+ } else {
+ // Odd index: should be peak (greater than or equal to next)
+ needsSwap = currentValue < nextValue;
+ }
+
+ if (needsSwap) {
+ // Swap needed
+ [sortedArray[i], sortedArray[i + 1]] = [sortedArray[i + 1], sortedArray[i]];
+ swaps++;
+
+ steps.push({
+ type: 'swap',
+ array: [...sortedArray],
+ message: `Swapped ${currentValue} and ${nextValue} to fix ${isEvenIndex ? 'valley' : 'peak'} pattern`,
+ phase: 'swapping',
+ variant: 'I',
+ swappedIndices: [i, i + 1],
+ swappedValues: [currentValue, nextValue],
+ patternFixed: isEvenIndex ? 'valley' : 'peak',
+ comparisons,
+ swaps,
+ wiggleOperations,
+ highlightIndices: [i, i + 1]
+ });
+ } else {
+ // No swap needed
+ steps.push({
+ type: 'no-swap',
+ array: [...sortedArray],
+ message: `Pattern already correct: ${currentValue} ${isEvenIndex ? 'β€' : 'β₯'} ${nextValue}`,
+ phase: 'validation',
+ variant: 'I',
+ currentIndex: i,
+ nextIndex: i + 1,
+ patternCorrect: isEvenIndex ? 'valley' : 'peak',
+ comparisons,
+ swaps,
+ wiggleOperations,
+ highlightIndices: [i, i + 1]
+ });
+ }
+ }
+
+ // Final validation
+ steps.push({
+ type: 'validation',
+ array: [...sortedArray],
+ message: 'Validating final wiggle pattern...',
+ phase: 'validation',
+ variant: 'I',
+ comparisons,
+ swaps,
+ wiggleOperations,
+ highlightIndices: []
+ });
+
+ // Check final pattern
+ const patternAnalysis = analyzeWigglePattern(sortedArray);
+
+ steps.push({
+ type: 'complete',
+ array: [...sortedArray],
+ message: `Wiggle Sort I complete! Made ${comparisons} comparisons and ${swaps} swaps. Pattern is ${patternAnalysis.isValid ? 'valid' : 'invalid'}.`,
+ phase: 'complete',
+ variant: 'I',
+ patternAnalysis,
+ comparisons,
+ swaps,
+ wiggleOperations,
+ highlightIndices: []
+ });
+
+ return {
+ sortedArray,
+ steps,
+ metrics: { comparisons, swaps, wiggleOperations, variant: 'I' }
+ };
+}
+
+/**
+ * Wiggle Sort II with step-by-step tracking for visualization
+ * Sort-based algorithm that avoids adjacent duplicates
+ * @param {number[]} arr - Array to be sorted
+ * @returns {Object} Result with sorted array, steps, and metrics
+ */
+function wiggleSortIIWithSteps(arr) {
+ if (!arr || arr.length <= 1) {
+ return {
+ sortedArray: arr || [],
+ steps: [],
+ metrics: { comparisons: 0, swaps: 0, wiggleOperations: 0 }
+ };
+ }
+
+ const n = arr.length;
+ const steps = [];
+ let comparisons = 0;
+ let swaps = 0;
+ let wiggleOperations = 0;
+
+ // Initial state
+ steps.push({
+ type: 'start',
+ array: [...arr],
+ message: 'Starting Wiggle Sort II - sort first, then rearrange to avoid adjacent duplicates',
+ phase: 'initialization',
+ variant: 'II',
+ comparisons,
+ swaps,
+ wiggleOperations,
+ highlightIndices: []
+ });
+
+ // Step 1: Sort the array
+ const sortedArray = [...arr];
+ steps.push({
+ type: 'sort-phase',
+ array: [...sortedArray],
+ message: 'Step 1: Sorting array to prepare for rearrangement',
+ phase: 'sorting',
+ variant: 'II',
+ comparisons,
+ swaps,
+ wiggleOperations,
+ highlightIndices: []
+ });
+
+ sortedArray.sort((a, b) => {
+ comparisons++;
+ return a - b;
+ });
+
+ steps.push({
+ type: 'sort-complete',
+ array: [...sortedArray],
+ message: `Array sorted: [${sortedArray.join(', ')}]`,
+ phase: 'sorting',
+ variant: 'II',
+ comparisons,
+ swaps,
+ wiggleOperations,
+ highlightIndices: []
+ });
+
+ // Step 2: Split into two halves
+ const mid = Math.floor((n + 1) / 2);
+ const smallerHalf = sortedArray.slice(0, mid);
+ const largerHalf = sortedArray.slice(mid);
+
+ steps.push({
+ type: 'split-halves',
+ array: [...sortedArray],
+ message: `Split into smaller half [${smallerHalf.join(', ')}] and larger half [${largerHalf.join(', ')}]`,
+ phase: 'splitting',
+ variant: 'II',
+ smallerHalf: [...smallerHalf],
+ largerHalf: [...largerHalf],
+ mid,
+ comparisons,
+ swaps,
+ wiggleOperations,
+ highlightIndices: []
+ });
+
+ // Step 3: Rearrange in wiggle pattern
+ const result = new Array(n);
+ let left = mid - 1; // End of smaller half
+ let right = n - 1; // End of larger half
+
+ steps.push({
+ type: 'rearrange-start',
+ array: [...sortedArray],
+ message: 'Step 2: Rearranging - valleys (even positions) get smaller elements, peaks (odd positions) get larger elements',
+ phase: 'rearranging',
+ variant: 'II',
+ result: [...result],
+ leftPointer: left,
+ rightPointer: right,
+ comparisons,
+ swaps,
+ wiggleOperations,
+ highlightIndices: []
+ });
+
+ // Fill result array alternately
+ for (let i = 0; i < n; i++) {
+ wiggleOperations++;
+
+ if (i % 2 === 0) {
+ // Even positions get smaller elements (valleys)
+ result[i] = sortedArray[left];
+ steps.push({
+ type: 'place-valley',
+ array: [...sortedArray],
+ message: `Position ${i} (valley): placing ${sortedArray[left]} from smaller half`,
+ phase: 'rearranging',
+ variant: 'II',
+ result: [...result],
+ currentPosition: i,
+ placedValue: sortedArray[left],
+ sourceIndex: left,
+ patternType: 'valley',
+ leftPointer: left,
+ rightPointer: right,
+ comparisons,
+ swaps: swaps + 1, // Count placements as swaps
+ wiggleOperations,
+ highlightIndices: [i]
+ });
+ left--;
+ } else {
+ // Odd positions get larger elements (peaks)
+ result[i] = sortedArray[right];
+ steps.push({
+ type: 'place-peak',
+ array: [...sortedArray],
+ message: `Position ${i} (peak): placing ${sortedArray[right]} from larger half`,
+ phase: 'rearranging',
+ variant: 'II',
+ result: [...result],
+ currentPosition: i,
+ placedValue: sortedArray[right],
+ sourceIndex: right,
+ patternType: 'peak',
+ leftPointer: left,
+ rightPointer: right,
+ comparisons,
+ swaps: swaps + 1, // Count placements as swaps
+ wiggleOperations,
+ highlightIndices: [i]
+ });
+ right--;
+ }
+ swaps++; // Count placement operations
+ }
+
+ // Final validation
+ steps.push({
+ type: 'validation',
+ array: [...result],
+ message: 'Validating final wiggle pattern and checking for adjacent duplicates...',
+ phase: 'validation',
+ variant: 'II',
+ comparisons,
+ swaps,
+ wiggleOperations,
+ highlightIndices: []
+ });
+
+ const patternAnalysis = analyzeWigglePattern(result);
+ const hasAdjacentDuplicates = checkAdjacentDuplicates(result);
+ steps.push({
+ type: 'complete',
+ array: [...result],
+ message: `Wiggle Sort II complete! Made ${comparisons} comparisons and ${swaps} placements. Pattern is ${patternAnalysis.isValid ? 'valid' : 'invalid'}, adjacent duplicates: ${hasAdjacentDuplicates ? 'present' : 'avoided'}.`,
+ phase: 'complete',
+ variant: 'II',
+ patternAnalysis,
+ hasAdjacentDuplicates,
+ comparisons,
+ swaps,
+ wiggleOperations,
+ highlightIndices: []
+ });
+
return {
- sortedArray: [...arr],
- steps: [],
- metrics: { comparisons: 0, swaps: 0, wiggleOperations: 0 }
+ sortedArray: result,
+ steps,
+ metrics: { comparisons, swaps, wiggleOperations, variant: 'II' }
};
}
+/**
+ * Main wiggle sort with steps function - delegates to appropriate variant
+ * @param {number[]} arr - Array to be sorted
+ * @param {string} variant - 'I' or 'II'
+ * @returns {Object} Result with sorted array, steps, and metrics
+ */
+function wiggleSortWithSteps(arr, variant = 'I') {
+ if (variant === 'II') {
+ return wiggleSortIIWithSteps(arr);
+ } else {
+ return wiggleSortIWithSteps(arr);
+ }
+}
+
+/**
+ * Analyze wiggle pattern of an array
+ * @param {number[]} arr - Array to analyze
+ * @returns {Object} Pattern analysis
+ */
+function analyzeWigglePattern(arr) {
+ if (!arr || arr.length <= 1) {
+ return { pattern: '', isValid: true, violations: 0 };
+ }
+
+ let pattern = '';
+ let violations = 0;
+
+ for (let i = 0; i < arr.length - 1; i++) {
+ if (arr[i] < arr[i + 1]) {
+ pattern += '<';
+ // Check if this matches expected pattern
+ if (i % 2 !== 0) violations++; // Odd indices should be >=
+ } else if (arr[i] > arr[i + 1]) {
+ pattern += '>';
+ // Check if this matches expected pattern
+ if (i % 2 === 0) violations++; // Even indices should be <=
+ } else {
+ pattern += '=';
+ // Equal is okay for both patterns in wiggle sort I
+ }
+ }
+
+ return {
+ pattern,
+ isValid: violations === 0,
+ violations
+ };
+}
+
+/**
+ * Check for adjacent duplicates in array
+ * @param {number[]} arr - Array to check
+ * @returns {boolean} True if adjacent duplicates exist
+ */
+function checkAdjacentDuplicates(arr) {
+ if (!arr || arr.length <= 1) return false;
+
+ for (let i = 0; i < arr.length - 1; i++) {
+ if (arr[i] === arr[i + 1]) {
+ return true;
+ }
+ }
+ return false;
+}
+
// Export for both Node.js and browser environments
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
- wiggleSortWithSteps
+ wiggleSortWithSteps,
+ wiggleSortIWithSteps,
+ wiggleSortIIWithSteps,
+ analyzeWigglePattern,
+ checkAdjacentDuplicates
};
} else if (typeof window !== 'undefined') {
window.WiggleSortSteps = {
- wiggleSortWithSteps
+ wiggleSortWithSteps,
+ wiggleSortIWithSteps,
+ wiggleSortIIWithSteps,
+ analyzeWigglePattern,
+ checkAdjacentDuplicates
};
// Expose commonly used functions in global scope for demo configs
window.wiggleSortWithSteps = wiggleSortWithSteps;
-}
\ No newline at end of file
+ window.wiggleSortIWithSteps = wiggleSortIWithSteps;
+ window.wiggleSortIIWithSteps = wiggleSortIIWithSteps;
+}
diff --git a/algorithms-js/src/sort/wiggle-sort/wiggle-sort.config.js b/algorithms-js/src/sort/wiggle-sort/wiggle-sort.config.js
index 5205bbf4..3fd52a02 100644
--- a/algorithms-js/src/sort/wiggle-sort/wiggle-sort.config.js
+++ b/algorithms-js/src/sort/wiggle-sort/wiggle-sort.config.js
@@ -12,6 +12,7 @@ const WIGGLE_SORT_CONFIG = {
name: "Wiggle Sort",
category: "sort",
hasStepsFile: true,
+ hasVisualization: true,
problem: "Arrange array elements in a wiggling pattern where arr[0] < arr[1] > arr[2] < arr[3] > arr[4]... Elements alternate between peaks and valleys.",
// Inputs for the demo interface
@@ -36,6 +37,14 @@ const WIGGLE_SORT_CONFIG = {
}
],
+ // Multi-language source code paths
+ sourceCode: {
+ javascript: "https://github.com/sachinlala/SimplifyLearning/blob/master/algorithms-js/src/sort/wiggle-sort/wiggle-sort-core.js",
+ java: "https://github.com/sachinlala/SimplifyLearning/tree/master/algorithms-java/src/main/java/com/sl/algorithms/sort/wiggle",
+ python: "", // Coming soon
+ go: "" // Coming soon
+ },
+
explanation: {
description: 'Wiggle Sort rearranges an array so that elements alternate between being smaller and larger than their neighbors, creating a "wiggling" pattern. Wiggle Sort I achieves this in O(n) time by making local adjustments, while Wiggle Sort II handles duplicates more carefully to avoid adjacent identical elements.'
},
@@ -279,7 +288,294 @@ const WIGGLE_SORT_CONFIG = {
disadvantages: ["O(n log n) time", "Extra space needed"]
}
}
- }
+ },
+
+ customDemoFunction: `
+ function runDemo() {
+ const arrayInputStr = document.getElementById('array-input').value;
+ const variant = document.getElementById('variant').value;
+ const resultContainer = document.getElementById('result');
+ const errorContainer = document.getElementById('error-message');
+ const visualizationSection = document.getElementById('visualization-section');
+
+ // Clear previous error and result
+ errorContainer.innerHTML = '';
+ errorContainer.style.display = 'none';
+ resultContainer.innerHTML = '';
+ visualizationSection.style.display = 'none';
+
+ // Parse input array
+ let arrayInput;
+ try {
+ arrayInput = arrayInputStr.split(',').map(item => {
+ const trimmed = item.trim();
+ const asNumber = parseInt(trimmed);
+ if (isNaN(asNumber)) {
+ throw new Error('All elements must be integers');
+ }
+ return asNumber;
+ });
+ } catch (e) {
+ showError('Invalid array format. Please use comma-separated integers.');
+ return;
+ }
+
+ // Validate input
+ if (arrayInput.length === 0) {
+ showError('Array cannot be empty');
+ return;
+ }
+
+ if (arrayInput.length > 15) {
+ showError('Array size limited to 15 elements for demo purposes');
+ return;
+ }
+
+ try {
+ const startTime = performance.now();
+
+ // Execute wiggle sort using steps function for animation
+ let result;
+ if (window.WiggleSortSteps) {
+ if (variant === 'II') {
+ result = window.WiggleSortSteps.wiggleSortIIWithSteps(arrayInput);
+ } else {
+ result = window.WiggleSortSteps.wiggleSortIWithSteps(arrayInput);
+ }
+ } else if (window.wiggleSortWithSteps) {
+ result = window.wiggleSortWithSteps(arrayInput, variant);
+ } else if (window.WiggleSortCore) {
+ // Fallback to core functions without steps
+ if (variant === 'II') {
+ const coreResult = window.WiggleSortCore.wiggleSortII(arrayInput);
+ result = { ...coreResult, steps: [] };
+ } else {
+ const coreResult = window.WiggleSortCore.wiggleSortI(arrayInput);
+ result = { ...coreResult, steps: [] };
+ }
+ } else {
+ // Fallback if nothing loaded
+ result = { sortedArray: [...arrayInput].sort((a, b) => a - b), metrics: { comparisons: 0, swaps: 0 }, steps: [] };
+ }
+
+ const endTime = performance.now();
+ const executionTime = (endTime - startTime).toFixed(4);
+
+ // Analyze the pattern of the result
+ const patternAnalysis = window.WiggleSortConfigUtils ?
+ window.WiggleSortConfigUtils.analyzeWigglePattern(result.sortedArray) :
+ { pattern: 'N/A', isValid: 'Unknown' };
+
+ // Show result
+ let resultHTML =
+ 'Original Array: [' + arrayInput.join(', ') + ']
' +
+ 'Wiggle Sorted Array: [' + result.sortedArray.join(', ') + ']
' +
+ 'Variant: Wiggle Sort ' + variant + '
' +
+ 'Pattern: ' + patternAnalysis.pattern + '
' +
+ 'Valid Wiggle: ' + (patternAnalysis.isValid ? 'Yes' : 'No') + '
' +
+ 'Comparisons: ' + (result.metrics.comparisons || 0) + '
' +
+ 'Swaps: ' + (result.metrics.swaps || 0) + '
' +
+ 'Time Complexity: ' + (variant === 'II' ? 'O(n log n)' : 'O(n)') + '
' +
+ 'Space Complexity: ' + (variant === 'II' ? 'O(n)' : 'O(1)') + '
' +
+ 'Execution Time: ' + executionTime + ' ms';
+
+ resultContainer.innerHTML = resultHTML;
+
+ // Show the visualization section with wiggle sort animation
+ if (result.steps && result.steps.length > 0) {
+ showWiggleSortVisualization(arrayInput, result.steps, variant);
+ visualizationSection.style.display = 'block';
+ }
+
+ } catch (error) {
+ showError(error.message);
+ }
+ }
+
+ function showWiggleSortVisualization(originalArray, steps, variant) {
+ const arrayViz = document.getElementById('array-visualization');
+ const stepsContainer = document.getElementById('steps-container');
+
+ // Clear previous visualization
+ arrayViz.innerHTML = '';
+ stepsContainer.innerHTML = '';
+
+ // Create array visualization
+ const arrayDiv = document.createElement('div');
+ arrayDiv.className = 'array-visualization';
+ arrayDiv.id = 'wiggle-array-display';
+
+ originalArray.forEach((value, index) => {
+ const cell = document.createElement('div');
+ cell.textContent = value;
+ cell.className = 'viz-cell';
+ cell.setAttribute('data-index', index);
+ cell.setAttribute('data-value', value);
+ arrayDiv.appendChild(cell);
+ });
+
+ arrayViz.appendChild(arrayDiv);
+
+ // Add controls with legend
+ const controlsDiv = document.createElement('div');
+ controlsDiv.className = 'viz-controls';
+ controlsDiv.innerHTML =
+ 'Wiggle Sort ' + variant + ' Visualization
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
π΅ Valley (Even) | π΄ Peak (Odd) | π‘ Comparing | π’ Swapping | β
Complete' +
+ '
' +
+ '
π΅ Valley (Even)
' +
+ '
π΄ Peak (Odd)
' +
+ '
π‘ Comparing
' +
+ '
π’ Swapping
' +
+ '
β
Complete
' +
+ '
' +
+ '
';
+ arrayViz.appendChild(controlsDiv);
+
+ // Status display
+ const statusDiv = document.createElement('div');
+ statusDiv.id = 'wiggle-status';
+ statusDiv.className = 'viz-status';
+ statusDiv.textContent = 'Ready to start wiggle sort animation...';
+ arrayViz.appendChild(statusDiv);
+
+ // Animation variables
+ let currentStepIndex = 0;
+ let animationRunning = false;
+ let animationInterval;
+
+ function updateWiggleVisualization(step) {
+ const cells = arrayDiv.querySelectorAll('.viz-cell');
+ const statusDiv = document.getElementById('wiggle-status');
+
+ // Reset all cell classes
+ cells.forEach(cell => {
+ cell.className = 'viz-cell';
+ });
+
+ // Update array values
+ step.array.forEach((value, index) => {
+ if (cells[index]) {
+ cells[index].textContent = value;
+ }
+ });
+
+ // Color cells based on their position (valley or peak pattern)
+ step.array.forEach((value, index) => {
+ if (cells[index]) {
+ if (index % 2 === 0) {
+ cells[index].classList.add('valley'); // Even positions = valleys
+ } else {
+ cells[index].classList.add('peak'); // Odd positions = peaks
+ }
+ }
+ });
+
+ // Highlight current indices being processed
+ if (step.highlightIndices) {
+ step.highlightIndices.forEach(index => {
+ if (cells[index]) {
+ cells[index].classList.add('comparing');
+ }
+ });
+ }
+
+ // Highlight swapped indices
+ if (step.swappedIndices) {
+ step.swappedIndices.forEach(index => {
+ if (cells[index]) {
+ cells[index].classList.add('swapping');
+ }
+ });
+ }
+
+ // Update status
+ statusDiv.textContent = step.message;
+
+ // Show step info in container
+ const stepInfo = document.createElement('div');
+ stepInfo.className = step.type === 'complete' ? 'viz-step-info complete' : 'viz-step-info';
+
+ let stepTypeColor = '#007acc';
+ if (step.type === 'complete') stepTypeColor = '#28a745';
+ else if (step.type === 'swap') stepTypeColor = '#dc3545';
+ else if (step.type === 'compare') stepTypeColor = '#ffc107';
+
+ stepInfo.style.borderLeftColor = stepTypeColor;
+
+ stepInfo.innerHTML =
+ 'Step ' + (currentStepIndex + 1) + ': ' + step.message + '
' +
+ '' +
+ 'Phase: ' + (step.phase) + ' | ' +
+ 'Comparisons: ' + (step.comparisons || 0) + ' | ' +
+ 'Swaps: ' + (step.swaps || 0) + ' | ' +
+ 'Pattern: ' + (step.patternFixed || step.expectedPattern || 'N/A') +
+ '';
+
+ if (stepsContainer.children.length > 8) {
+ stepsContainer.removeChild(stepsContainer.firstChild);
+ }
+ stepsContainer.appendChild(stepInfo);
+ }
+
+ function startWiggleAnimation() {
+ if (animationRunning || currentStepIndex >= steps.length) return;
+
+ animationRunning = true;
+ document.getElementById('start-wiggle-animation').disabled = true;
+ document.getElementById('pause-wiggle-animation').disabled = false;
+
+ animationInterval = setInterval(() => {
+ if (currentStepIndex >= steps.length) {
+ clearInterval(animationInterval);
+ animationRunning = false;
+ document.getElementById('start-wiggle-animation').disabled = false;
+ document.getElementById('pause-wiggle-animation').disabled = true;
+ return;
+ }
+
+ updateWiggleVisualization(steps[currentStepIndex]);
+ currentStepIndex++;
+ }, 1500); // 1.5 second delay between steps
+ }
+
+ function pauseWiggleAnimation() {
+ clearInterval(animationInterval);
+ animationRunning = false;
+ document.getElementById('start-wiggle-animation').disabled = false;
+ document.getElementById('pause-wiggle-animation').disabled = true;
+ }
+
+ function resetWiggleAnimation() {
+ clearInterval(animationInterval);
+ animationRunning = false;
+ currentStepIndex = 0;
+ document.getElementById('start-wiggle-animation').disabled = false;
+ document.getElementById('pause-wiggle-animation').disabled = true;
+ stepsContainer.innerHTML = '';
+
+ // Reset visualization
+ if (steps.length > 0) {
+ updateWiggleVisualization(steps[0]);
+ }
+ document.getElementById('wiggle-status').textContent = 'Ready to start wiggle sort animation...';
+ }
+
+ // Bind control events
+ document.getElementById('start-wiggle-animation').addEventListener('click', startWiggleAnimation);
+ document.getElementById('pause-wiggle-animation').addEventListener('click', pauseWiggleAnimation);
+ document.getElementById('reset-wiggle-animation').addEventListener('click', resetWiggleAnimation);
+
+ // Show initial state
+ if (steps.length > 0) {
+ updateWiggleVisualization(steps[0]);
+ }
+ }
+ `
};
// Utility functions for config
@@ -388,77 +684,7 @@ const WiggleSortConfigUtils = {
violations,
isValid: violations === 0
};
- },
-
- customDemoFunction: `
- function runDemo() {
- const arrayInputStr = document.getElementById('array-input').value;
- const variant = document.getElementById('variant').value;
- const resultContainer = document.getElementById('result');
- const errorContainer = document.getElementById('error-message');
- const visualizationSection = document.getElementById('visualization-section');
-
- // Clear previous error and result
- errorContainer.innerHTML = '';
- errorContainer.style.display = 'none';
- resultContainer.innerHTML = '';
- visualizationSection.style.display = 'none';
-
- // Parse input array
- let arrayInput;
- try {
- arrayInput = arrayInputStr.split(',').map(item => {
- const trimmed = item.trim();
- const asNumber = parseInt(trimmed);
- if (isNaN(asNumber)) {
- throw new Error('All elements must be integers');
- }
- return asNumber;
- });
- } catch (e) {
- showError('Invalid array format. Please use comma-separated integers.');
- return;
- }
-
- // Validate input
- if (arrayInput.length === 0) {
- showError('Array cannot be empty');
- return;
- }
-
- if (arrayInput.length > 15) {
- showError('Array size limited to 15 elements for demo purposes');
- return;
- }
-
- try {
- const startTime = performance.now();
-
- // Execute wiggle sort
- const result = window.WiggleSortCore ?
- window.WiggleSortCore.wiggleSort(arrayInput, variant) :
- wiggleSort(arrayInput, variant);
-
- const endTime = performance.now();
- const executionTime = (endTime - startTime).toFixed(4);
-
- // Show result
- let resultHTML = \`
- Original Array: [\${arrayInput.join(', ')}]
- Wiggle Sorted Array: [\${result.sortedArray.join(', ')}]
- Variant: \${variant}
- Pattern: \${result.pattern || 'a0 < a1 > a2 < a3 > ...'}
- Total Swaps: \${result.metrics.swaps || 0}
- Execution Time: \${executionTime} ms
- \`;
-
- resultContainer.innerHTML = resultHTML;
-
- } catch (error) {
- showError(error.message);
- }
- }
- `
+ }
};
// Export for both Node.js and browser environments
@@ -468,11 +694,15 @@ if (typeof module !== 'undefined' && module.exports) {
WiggleSortConfigUtils
};
} else if (typeof window !== 'undefined') {
+ // Primary exports
window.WIGGLE_SORT_CONFIG = WIGGLE_SORT_CONFIG;
window.WiggleSortConfigUtils = WiggleSortConfigUtils;
- // Additional exports for universal loader compatibility
+ // Universal loader compatibility - these are the names the loader looks for
+ window.wigglesortConfig = WIGGLE_SORT_CONFIG; // algorithmName.replace(/-/g, '') + 'Config'
+ window.wigglesortconfig = WIGGLE_SORT_CONFIG; // algorithmName.replace(/-/g, '').toLowerCase() + 'Config'
+ window.wiggleSortConfig = WIGGLE_SORT_CONFIG; // toCamelCase(algorithmName) + 'Config'
+
+ // Legacy compatibility
window.WiggleSortConfig = WIGGLE_SORT_CONFIG;
- window.wigglesortConfig = WIGGLE_SORT_CONFIG;
- window.wigglesortconfig = WIGGLE_SORT_CONFIG;
}
diff --git a/algorithms-js/test-algorithm.html b/algorithms-js/test-algorithm.html
new file mode 100644
index 00000000..66663518
--- /dev/null
+++ b/algorithms-js/test-algorithm.html
@@ -0,0 +1,869 @@
+
+
+
+
+
+ Testing Lab
+
+
+
+
+
+
+
π― Algorithm Selection
+
Select an algorithm to test its implementation, step tracking, and demo functionality.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
π¦ Dependencies
+
βοΈ Configuration
+
π§ Core Functions
+
π¬ Step Tracking
+
π Demo Simulation
+
+
+
+
+
Dependency Loading Test
+
Testing if all required scripts and dependencies load correctly...
+
+
+
+
+
+
+
+
Configuration Loading Test
+
Testing if algorithm configuration loads and exports correctly...
+
+
+
+
+
+
+
+
Core Functions Test
+
Testing if algorithm core functions are available and working...
+
+
+
+
+
+
+
+
Step Tracking Test
+
Testing if step-by-step tracking works for animations...
+
+
+
+
+
+
+
+
Demo Simulation
+
Simulating the actual demo interface with full functionality...
+
+
+
+
+
+
+
π Quick Links
+
+
+
+
+
+
+
+
\ No newline at end of file