Compare commits

...

No commits in common. "64b8319019ec9c4edddf8ef343c431320b9952b5" and "73504b1bce428de4cf65927c056525a5f40a219f" have entirely different histories.

6 changed files with 3882 additions and 11 deletions

35
.gitignore vendored Normal file
View file

@ -0,0 +1,35 @@
# Compiled C executables
*.o
*.out
*.exe
quicksort_test
quicksort_mit_zeigern_test
quicksort_*
# Rust
Rust_Version/target/
**/*.rs.bk
Cargo.lock
# OS files
.DS_Store
Thumbs.db
# Editor
.vscode/
.idea/
*.swp
*.swo
*~
# Build artifacts
*.map
*.min.js
dist/
build/
# Node modules (if ever added)
node_modules/
# Logs
*.log

325
BEDIENUNGSANLEITUNG.md Normal file
View file

@ -0,0 +1,325 @@
# Bedienungsanleitung — Sortier-Algorithmen Visualisierung
## Inhalt
1. [Was ist das?](#1-was-ist-das)
2. [Oberfläche](#2-oberfläche)
3. [Schnellstart](#3-schnellstart)
4. [Auswahlmöglichkeiten](#4-auswahlmöglichkeiten)
5. [Steuerung](#5-steuerung)
6. [Tastaturkürzel](#6-tastaturkürzel)
7. [Farbcode](#7-farbcode)
8. [Statistik-Karten](#8-statistik-karten)
9. [Speicher-Ansicht](#9-speicher-ansicht)
10. [URL-Parameter](#10-url-parameter)
11. [Warum sind Sortieralgorithmen wichtig?](#11-warum-sind-sortieralgorithmen-wichtig)
12. [Algorithmus-Übersicht](#12-algorithmus-übersicht)
---
## 1. Was ist das?
Dieses Programm visualisiert die Funktionsweise von **20 Sortieralgorithmen** — von Bubble Sort bis Fisher-Yates Shuffle. Es zeigt Schritt für Schritt, wie ein Algorithmus ein Array sortiert, macht die Datenstrukturen und Speichernutzung sichtbar und vermittelt ein intuitives Verständnis dafür, warum der eine Algorithmus schneller ist als der andere.
**Einschränkung:** Bogo Sort ist auf 8 Elemente begrenzt, da seine O(n·n!)-Komplexität bei größeren Arrays den Browser einfrieren lässt.
---
## 2. Oberfläche
```
┌──────────────────────────────────────────────────────────┐
│ Sortier-Algorithmen [Bedienung] │
├──────────────┬─────────────────────────────────────────────┤
│ │ ┌─ Phase-Label ──────────────────────┐ │
│ Algorithmus │ │ Vergleich: arr[3]=42 mit arr[4]=17 │ │
│ [Dropdown] │ └────────────────────────────────────┘ │
│ │ │
│ Daten │ ┌─────────── Canvas ─────────────────┐ │
│ [Vorlage] │ │ │ │
│ │ │ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ │ │
│ Eigene │ │ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ │ │
│ Werte │ │ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ │ │
│ │ │ │ │
│ Array-Größe │ └────────────────────────────────────┘ │
│ ─────────── │ │
│ Geschw. │ [▶][⏸][⏭][🔀][↺][🌙] │
│ ─────────── │ │
│ Legend │ Vergleiche: 128 Tausche: 64 Zeit: 1.2s │
│ │ │
│ [▶ Play] │ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ │
└──────────────┴─────────────────────────────────────────────┘
```
- **Links (Sidebar):** Algorithmus wählen, Datensatz, eigene Werte, Geschwindigkeit, Legende
- **Rechts (Hauptbereich):** Canvas mit Animation, Steuerung, Statistik-Karten
- **Unten:** Einklappbare Abschnitte „Über die Algorithmen" und „Bedienung"
---
## 3. Schnellstart
1. Öffne `sorting_visualization.html` im Browser
2. Wähle einen Algorithmus aus dem Dropdown (z.B. „Quick Sort")
3. Stelle Array-Größe und Geschwindigkeit ein
4. Drücke `Space` zum Starten — oder den Play-Button ▶
---
## 4. Auswahlmöglichkeiten
### Algorithmus
20 Algorithmen in 4 Kategorien:
| Kategorie | Algorithmen |
|---|---|
| O(n²) — Einfache Verfahren | Bubble Sort, Selection Sort, Insertion Sort, Cocktail Shaker Sort |
| O(n log n) — Effiziente Verfahren | Merge Sort, Heap Sort, Quick Sort, 3-Way Quick Sort, Dual-Pivot Quick Sort, Introsort, Shell Sort, Tree Sort, Timsort |
| O(n·k) — Linear / Nicht-vergleichsbasiert | Counting Sort, Radix Sort, Bucket Sort |
| Spezial | Pancake Sort, Cycle Sort, Bogo Sort, Fisher-Yates Shuffle |
### Datensatz-Vorlagen (Presets)
| Vorlage | Beschreibung |
|---|---|
| **Zufall** | Zufällig verteilte Werte — der Standard-Testfall |
| **Sortiert** | Bereits aufsteigend sortiert — manche Algorithmen (z.B. Quick Sort mit erstem Element als Pivot) werden dadurch extrem langsam |
| **Umgekehrt** | Absteigend sortiert — das Gegenstück und ein fairer Test für die meisten Algorithmen |
| **Fast sortiert** | Nur wenige Elemente an der falschen Stelle — adaptiven Algorithmen (Insertion Sort, Timsort) spielen hier ihre Stärke aus |
| **Duplikate** | Viele gleiche Werte — der Härtetest: klassischer Quick Sort degeneriert zu O(n²), 3-Way Quick Sort brilliert hier |
### Eigene Werte
Du kannst eigene Zahlen eingeben, getrennt durch Kommas, Leerzeichen oder Zeilenumbrüche. Beispiele:
```
5, 3, 8, 1, 9, 2
```
Über den Clipboard-Button kannst du auch direkt aus der Zwischenablage einfügen (Mehrfachpaste wird unterstützt). Die Anzahl der eingegebenen Werte wird oben rechts im Feld angezeigt.
**Wichtig:** Eigene Werte überschreiben das Preset. Sobald du eigene Werte eingibst, verwendet das Programm diese statt der Vorlage.
### Array-Größe
Schieberegler von 5 bis 200 Elemente. Wird zurückgesetzt, wenn du eigene Werte eingibst.
- **Klein (520):** Gut zum Verstehen der Abläufe
- **Mittel (3080):** Guter Kompromiss aus Übersicht und Realismus
- **Groß (100200):** Zeigt das Skalierungsverhalten
### Geschwindigkeit
Schieberegler von 1 bis 100 — er represents die Verzögerung zwischen zwei Schritten in Millisekunden:
- **1 (links):** ~1000 ms/Schritt — sehr langsam, zum Beobachten
- **50 (Mitte):** ~500 ms/Schritt — ausgewogen
- **100 (rechts):** ~10 ms/Schritt — sehr schnell
Die tatsächliche Geschwindigkeit hängt auch von der Browser-Leistung und der Array-Größe ab.
---
## 5. Steuerung
| Button | Funktion |
|---|---|
| **⏮** | Einen Schritt zurück — zeigt die vorherige Operation |
| **▶** | Animation starten — läuft bis zum Ende oder bis Pause |
| **⏸** | Animation anhalten — kann mit ▶ fortgesetzt werden |
| **⏭** | Einen Schritt vor — führt genau eine Operation aus |
| **🔀** | Mischen — Fisher-Yates Shuffle auf das aktuelle Array |
| **↺** | Reset — setzt das Array in den Ausgangszustand zurück (vor dem Start der Animation) |
| **🌙/☀** | Theme wechseln — zwischen hellem und dunklem Modus |
### Reset vs. Mischen
- **Reset** stellt das Array so her, wie es beim letzten Start der Animation war. Das kann ein frisches Zufalls-Array sein oder deine eigenen Werte.
- **Mischen** führt einen Fisher-Yates-Shuffle auf das aktuelle Array aus — nützlich, um ein sortiertes Array wieder zu mischen und einen anderen Algorithmus zu testen.
---
## 6. Tastaturkürzel
Alle Funktionen sind auch ohne Maus bedienbar:
| Taste | Funktion |
|---|---|
| `Space` / `K` | Play / Pause — Animation starten oder anhalten |
| `→` / `L` | Ein Schritt vor — nächste Operation |
| `←` / `J` | Ein Schritt zurück — vorherige Operation |
| `R` | Reset — Array zurücksetzen |
| `T` | Theme — zwischen hell und dunkel wechseln |
| `S` | Shuffle — Array mischen (Fisher-Yates) |
**Hinweis:** Tastaturkürzel funktionieren nicht, wenn ein Eingabefeld oder Dropdown den Fokus hat.
---
## 7. Farbcode
Jede Farbe hat eine feste Bedeutung:
| Farbe | Bedeutung |
|---|---|
| **Blau (Unsortiert)** | Normaler Zustand — Elemente die noch nicht sortiert sind |
| **Orange (Vergleich)** | Zwei Elemente werden gerade verglichen — ein Bogen verbindet sie |
| **Rot (Tausch)** | Zwei Elemente werden getauscht — ein roter Bogen zeigt die Tauschpartner |
| **Lila (Verschiebung)** | Ein Element wird verschoben (nicht getauscht) — z.B. bei Insertion Sort oder Merge Sort |
| **Grün (Sortiert/Fertig)** | Elemente an ihrer endgültigen Position — der Bereich wächst mit fortschreitender Sortierung |
Ein **Bogen** über zwei Balken zeigt immer eine Beziehung zwischen den beiden Elementen:
- Oranger Bogen = Vergleich
- Roter Bogen = Tausch
---
## 8. Statistik-Karten
Unter dem Canvas zeigen vier Karten Echtzeit-Statistiken:
| Karte | Beschreibung |
|---|---|
| **Zeit** | Vergangene Echtzeit seit Start der Animation in Millisekunden |
| **Schritte** | Anzahl der ausgeführten Schritte / Operationen |
| **Vergleiche** | Anzahl der Vergleichsoperationen (nur bei vergleichsbasierten Algorithmen) |
| **Tausche** | Anzahl der Tausch- oder Verschiebungsoperationen |
**Hinweis:** Nicht-vergleichsbasierte Algorithmen (Counting, Radix, Bucket) zeigen bei „Vergleiche" keine sinnvollen Werte, da sie nicht Element-für-Element vergleichen.
---
## 9. Speicher-Ansicht
Bei einigen Algorithmen wird unterhalb des Haupt-Arrays ein zusätzlicher Speicherbereich visualisiert. Das hilft zu verstehen, wie Algorithmen temporären Platz benötigen:
| Algorithmus | Speicher-Ansicht |
|---|---|
| **Merge Sort** | Zwei Hilfs-Arrays (blau und lila) — zeigt das Zusammenführen zweier sortierter Hälften |
| **Heap Sort** | Max-Heap als Baumdiagramm — zeigt die Hierarchie der Eltern-Kind-Beziehungen |
| **Tree Sort** | Binary Search Tree — zeigt die Baumstruktur die beim Sortieren aufgebaut wird |
| **Timsort** | Hilfs-Arrays — zeigt die verwalteten Runs und Merges |
| **Counting Sort** | Häufigkeitstabelle — zählt wie oft jeder Wert vorkommt |
| **Radix Sort** | Bucket-Verteilung — zeigt wie Ziffern in Buckets einsortiert werden |
| **Bucket Sort** | Bucket-Arrays — zeigt die Verteilung und Sortierung in den Buckets |
---
## 10. URL-Parameter
Der aktuelle Zustand (Algorithmus, Vorlage, Größe, Geschwindigkeit) wird automatisch in die URL geschrieben. Das bedeutet:
- Du kannst die URL kopieren und an andere weitergeben — sie sehen dieselbe Konfiguration
- Beim Neuladen der Seite bleibt die Auswahl erhalten
- Du kannst Parameter auch manuell setzen
**URL-Parameter:**
```
sorting_visualization.html?algo=quick&preset=random&size=50&speed=50
```
| Parameter | Werte | Beschreibung |
|---|---|---|
| `algo` | `bubble`, `selection`, `insertion`, `cocktail`, `merge`, `heap`, `quick`, `quick3way`, `dualpivot`, `introsort`, `shell`, `tree`, `timsort`, `counting`, `radix`, `bucket`, `pancake`, `cycle`, `bogo`, `shuffle` | Ausgewählter Algorithmus |
| `preset` | `random`, `sorted`, `reversed`, `nearly`, `duplicates` | Datensatz-Vorlage |
| `size` | 5200 | Array-Größe |
| `speed` | 1100 | Geschwindigkeit |
---
## 11. Warum sind Sortieralgorithmen wichtig?
Sortieren ist eines der am intensivsten erforschten Probleme der Informatik — nicht aus akademischer Überheblichkeit, sondern weil es in der Praxis überall vorkommt:
- **Datenbanken** müssen Abfrageergebnisse sortieren — bei Millionen von Datensätzen entscheidet der Algorithmus über Antwortzeiten
- **Betriebssysteme** verwalten Prioritätswarteschlangen und müssen Prozesse nach Dringlichkeit ordnen
- **Compiler** nutzen Sortieralgorithmen für die Optimierung (z.B. Register-Allokation)
- **Internetsuchen** beginnen oft mit sortierten Indices (z.B. B-Bäume in Datenbanken)
- **Empfehlungssysteme** sortieren Ergebnisse nach Relevanz
### Der fundamentale Kompromiss
Jeder Algorithmus repräsentiert einen anderen Kompromiss zwischen:
- **Geschwindigkeit** — Wie viele Operationen braucht der Algorithmus?
- **Speicher** — Wie viel zusätzlichen Platz benötigt er?
- **Stabilität** — Behalten gleiche Werte ihre Reihenfolge?
- **Adaptivität** — Nutzt er vorhandene Ordnung aus?
- **Einfachheit** — Wie verständlich ist der Code?
Wer diese Kompromisse versteht, kann für jede Situation die richtige Wahl treffen — sei es beim Sortieren von 100 Datensätzen auf einem Microcontroller oder 100 Millionen in einer Datenbank.
### Was diese Visualisierung lehrt
- **Bubble Sort** bei sortierten Daten: O(n) statt O(n²) — plötzliche Effizienz durch die „swapped"-Optimierung
- **Quick Sort** bei sortierten Daten mit erstem Pivot: O(n²) — warum naive Pivot-Wahl katastrophal sein kann
- **Timsort** bei fast-sortierten Daten: adaptiv und schnell — warum er in Python und Java Standard ist
- **3-Way Quick Sort** bei Duplikaten: O(n) statt O(n²) — warum Duplikate-Handling entscheidend ist
- **Fisher-Yates Shuffle**: Mischen ist O(n), Sortieren ist O(n log n) — ein Shuffle ist immer schneller als jede Sortierung
---
## 12. Algorithmus-Übersicht
### Eigenschaften
| Symbol | Bedeutung |
|---|---|
| **Stabil** | Gleiche Werte behalten ihre ursprüngliche Reihenfolge bei |
| **Nicht stabil** | Die Reihenfolge gleicher Werte kann sich ändern |
| **In-place** | Sortiert im vorhandenen Array, braucht nur O(1) zusätzlichen Speicher |
| **O(n) Speicher** | Benötigt ein zusätzliches Hilfsarray proportional zur Eingabegröße |
| **Adaptiv** | Nutzt vorhandene Ordnung aus — bei fast sortierten Daten schneller |
| **Hybrid** | Kombiniert mehrere Strategien und wechselt je nach Situation |
### Komplexitäts-Kategorien
**O(n²) — Quadratisch** (einfache Verfahren)
Diese Algorithmen sind leicht zu verstehen, aber skalieren schlecht. Geeignet für kleine Datenmengen oder Lehrzwecke.
- Bubble Sort — Stabil, In-place, adaptiv bei sortierten Daten
- Selection Sort — Nicht stabil, In-place, min. Schreiboperationen
- Insertion Sort — Stabil, In-place, adaptiv, der beste O(n²)
- Cocktail Shaker Sort — Stabil, In-place, löst das Schildkröten-Problem
**O(n log n) — Quasi-linear** (effiziente Verfahren)
Die Standardwahl für allgemeine Sortierprobleme. Garantieren gute Leistung auch im schlimmsten Fall.
- Merge Sort — Stabil, O(n) Speicher, garantiert O(n log n)
- Heap Sort — Nicht stabil, In-place, garantiert O(n log n)
- Quick Sort — Nicht stabil, In-place, O(n log n) im Durchschnitt
- 3-Way Quick Sort — Nicht stabil, In-place, O(n) bei vielen Duplikaten
- Dual-Pivot Quick Sort — Nicht stabil, In-place, Java Standard (JDK)
- Introsort — Nicht stabil, In-place, wechselt bei Rekursionstiefe zu Heap Sort
- Shell Sort — Nicht stabil, In-place, schneller als O(n²) durch Gap-Sequenz
- Tree Sort — Stabil (bei BST), O(n) Speicher, abhängig von Baumhöhe
- Timsort — Stabil, adaptiv, Hybrid aus Merge Sort und Insertion Sort, Python/Java Standard
**O(n·k) — Linear** (nicht-vergleichsbasiert)
Diese Algorithmen nutzen spezielle Eigenschaften der Daten (z.B. Ganzzahl-Bereich) und können schneller sein als O(n log n). Funktionieren nicht mit beliebigen Vergleichbaren Werten.
- Counting Sort — Stabil, O(n+k) mit k = Wertbereich, nur für Ganzzahlen
- Radix Sort — Stabil, O(n·k) mit k = Anzahl Ziffern, nur für Ganzzahlen
- Bucket Sort — Stabil, O(n+k) im Durchschnitt, adaptiv je nach Verteilung
**Spezial**
Algorithmen mit besonderen Eigenschaften oder Anwendungsfällen.
- Pancake Sort — Nicht stabil, In-place, nur Präfix-Flip-Operationen erlaubt
- Cycle Sort — Nicht stabil, In-place, min. Schreiboperationen (für Flash-Speicher)
- Bogo Sort — Nicht stabil, In-place, O(n·n!) erwartet, **auf 8 Elemente begrenzt**
- Fisher-Yates Shuffle — Stabil, In-place, O(n), uniform zufällig
---
## Lizenz
Dieses Projekt ist eine Open-Source-Lehr- und Lernressource.
---
## Autor
© 2026 Dieter Schlüter <dieter(dot)schlueter(at)linix(dot)de>

View file

@ -1,9 +0,0 @@
MIT License
Copyright (c) 2026 dschlueter
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,3 +1,89 @@
# sorting # Sortier-Algorithmen Visualisierung
Visualisiert Sortier-Algorithmen in HTML mit Javascript Interaktive Visualisierung von 20 Sortieralgorithmen mit Schritt-für-Schritt-Darstellung, Speicher-Ansicht und Keyboard-Steuerung.
**Datei:** `sorting_visualization.html` — Single-File, keine Abhängigkeiten, läuft direkt im Browser.
[Online ansehen](sorting_visualization.html) · [Tests bestehen: 198/198](test_algorithms.js)
---
## Algorithmus-Übersicht
| Kategorie | Algorithmen |
|---|---|
| O(n²) | Bubble Sort, Selection Sort, Insertion Sort, Cocktail Shaker Sort |
| O(n log n) | Merge Sort, Heap Sort, Quick Sort, 3-Way Quick Sort, Dual-Pivot Quick Sort, Introsort, Shell Sort, Tree Sort, Timsort |
| Linear | Counting Sort, Radix Sort, Bucket Sort |
| Spezial | Pancake Sort, Cycle Sort, Bogo Sort, Fisher-Yates Shuffle |
---
## Features
- **20 Algorithmen** — vom klassischen Bubble Sort bis zum Fisher-Yates Shuffle
- **Speicher-Ansicht** — zeigt Hilfsdatenstrukturen bei Merge Sort, Heap Sort, Timsort, Counting/Radix/Bucket Sort
- **5 Datensatz-Presets** — Zufällig, Sortiert, Umgekehrt, Fast sortiert, Duplikate
- **Schritt-Navigation** — vor, zurück, Play/Pause
- **Keyboard-Steuerung** — alle Funktionen auch ohne Maus bedienbar
- **URL-State** — Auswahl bleibt beim Neuladen erhalten
- **Mobile optimiert** — Touch-Support, zoom-resistent
- **Hell/Dunkel-Modus** — per Button oder `T`
- **Barrierefreiheit** — ARIA-Labels, Screen-Reader-Ankündigungen
---
## Schnellstart
1. `sorting_visualization.html` im Browser öffnen
2. Algorithmus wählen → Start mit `Space`
3. Oder: Play-Button drücken
---
## Tastaturkürzel
| Taste | Funktion |
|---|---|
| `Space` / `K` | Play / Pause |
| `→` / `L` | Ein Schritt vor |
| `←` / `J` | Ein Schritt zurück |
| `R` | Reset |
| `T` | Theme wechseln |
| `S` | Mischen (Fisher-Yates) |
---
## Tests
```bash
node test_algorithms.js
```
Alle 198 Tests müssen bestehen.
---
## Dateien
```
sorting_visualization.html # Hauptprogramm (Single-File)
test_algorithms.js # Node.js Test-Suite
.gitignore # Git-Konfiguration
README.md # Dieses Dokument
```
---
## Entwicklung
- **Kein Build-Schritt** — pure HTML/JS/CSS
- **Keine externen Abhängigkeiten** — läuft offline
- **Externe CDN** (optional, für Icons): Lucide Icons, Tailwind CSS
- **Version** — wird automatisch via `pre-commit`-Hook erhöht
---
## Autor
© 2026 Dieter Schlüter <dieter(dot)schlueter(at)linix(dot)de>

2986
sorting_visualization.html Normal file

File diff suppressed because it is too large Load diff

448
test_algorithms.js Normal file
View file

@ -0,0 +1,448 @@
/**
* Test-Skript für Sortieralgorithmen
* Extrahiert buildSteps aus sorting_visualization.html und testet alle Algorithmen
*
* Usage:
* node test_algorithms.js # Kurzbericht
* node test_algorithms.js --verbose # Detaillierte Ausgabe
*/
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import vm from 'node:vm';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const verbose = process.argv.includes('--verbose');
// ── HTML parsen und JS extrahieren ──
const html = fs.readFileSync(path.join(__dirname, 'sorting_visualization.html'), 'utf-8');
// Finde den letzten <script>-Block (der unseren Code enthält), ignoriere CDN-Scripts
const scriptBlocks = [];
let idx = 0;
while (true) {
const openIdx = html.indexOf('<script', idx);
if (openIdx === -1) break;
const closeIdx = html.indexOf('>', openIdx);
if (closeIdx === -1) break;
const tag = html.substring(openIdx, closeIdx + 1);
const endIdx = html.indexOf('</script>', closeIdx);
if (endIdx === -1) break;
const content = html.substring(closeIdx + 1, endIdx);
scriptBlocks.push({ tag, content, start: closeIdx + 1 });
idx = endIdx + 9;
}
// Nimm den Block der 'function buildSteps' enthält
const codeBlock = scriptBlocks.find(b => b.content.includes('function buildSteps'));
if (!codeBlock) {
console.error('Fehler: Kein <script>-Block mit buildSteps gefunden');
process.exit(1);
}
const rawJs = codeBlock.content;
// ── Mocks ──
const mockEl = (overrides = {}) => ({
classList: { add: () => {}, remove: () => {}, contains: () => false },
style: {},
textContent: '',
innerHTML: '',
value: '50',
min: '5',
max: '200',
clientWidth: 800,
getBoundingClientRect: () => ({ left: 0, top: 0, width: 800, height: 400 }),
addEventListener: () => {},
setPointerCapture: () => {},
dispatchEvent: () => {},
...overrides,
});
const mockCtx = {
clearRect: () => {}, fillRect: () => {}, strokeRect: () => {},
beginPath: () => {}, closePath: () => {}, moveTo: () => {},
lineTo: () => {}, quadraticCurveTo: () => {}, arc: () => {},
fill: () => {}, stroke: () => {}, save: () => {}, restore: () => {},
setTransform: () => {},
};
const mockCanvasEl = {
width: 800,
height: 400,
style: {},
parentElement: { clientWidth: 800 },
getContext: () => mockCtx,
};
// ── VM-Kontext erstellen ──
const context = vm.createContext({
console, Math, Array, Object, String, Number,
parseInt, parseFloat, Date, Set, Map, JSON,
Infinity, NaN, undefined,
navigator: { hardwareConcurrency: 4 },
window: {
devicePixelRatio: 1,
setInterval: () => 0,
clearInterval: () => {},
setTimeout: () => 0,
clearTimeout: () => {},
matchMedia: () => ({ matches: false }),
localStorage: { getItem: () => null, setItem: () => {} },
history: { replaceState: () => {} },
location: { pathname: '/', search: '' },
innerWidth: 800,
addEventListener: () => {},
},
document: {
getElementById: (id) => {
if (id === 'sortCanvas' || id === 'memCanvas') return mockCanvasEl;
return mockEl();
},
querySelector: () => mockEl(),
querySelectorAll: () => [],
createElement: () => mockEl(),
documentElement: { classList: { contains: () => true } },
},
canvas: mockCanvasEl,
ctx: mockCtx,
memCanvas: mockCanvasEl,
memCtx: mockCtx,
lucide: { createIcons: () => {} },
$algoSelect: mockEl({ value: 'bubble' }),
$sizeSlider: mockEl(),
$speedSlider: mockEl(),
$threadSlider: mockEl(),
$presetSelect: mockEl({ value: 'random' }),
$customArray: mockEl({ value: '' }),
$btnPlayPause: mockEl(),
$btnStep: mockEl(),
$btnStepBack: mockEl(),
$btnReset: mockEl(),
$btnTheme: mockEl(),
$btnClearCustom: mockEl(),
$statComp: mockEl(),
$statSwap: mockEl(),
$statMove: mockEl(),
$statStep: mockEl(),
$statTime: mockEl(),
$speedVal: mockEl(),
$sizeVal: mockEl(),
$threadVal: mockEl(),
$memContainer: mockEl(),
$memLabel: mockEl(),
$progressFill: mockEl(),
$stepCounter: mockEl(),
$phaseLabel: mockEl(),
$stepExplanation: mockEl(),
// State-Variablen die von 'let' zu 'var'-artigen werden
array: [],
steps: [],
stepIndex: 0,
animTimer: null,
isRunning: false,
isPaused: false,
arrayFresh: false,
startTime: 0,
elapsedMs: 0,
});
// ── JS vorbereiten und ausführen ──
// Finde die buildSteps-Funktion
const buildStepsStart = rawJs.indexOf('function buildSteps(algoName)');
if (buildStepsStart === -1) {
console.error('Fehler: buildSteps-Funktion nicht gefunden');
process.exit(1);
}
// Finde das Ende von buildSteps durch Brace-Counting
let braceCount = 0;
let funcEnd = rawJs.length;
for (let i = buildStepsStart; i < rawJs.length; i++) {
if (rawJs[i] === '{') braceCount++;
if (rawJs[i] === '}') {
braceCount--;
if (braceCount === 0) {
funcEnd = i + 1;
break;
}
}
}
// Nimm alles vom Anfang bis zum Ende von buildSteps
// Finde den Anfang: const COLORS
const colorsStart = rawJs.indexOf('const COLORS');
if (colorsStart === -1) {
console.error('Fehler: COLORS-Konstanten nicht gefunden');
process.exit(1);
}
const codeToRun = rawJs.substring(colorsStart, funcEnd);
// Bereinige: Entferne Code der nicht benötigt wird oder fehlschlägt
const cleanedCode = codeToRun
.replace(/makeTouchSlider\(\$.*?\);/g, '')
.replace(/resizeCanvas\(\);/g, '')
.replace(/const prefersReducedMotion[\s\S]*?el\.classList\.add\('anim-stat'\);\s*\}\s*\)\s*;?/g, '')
.replace(/lucide\.createIcons\(\);/g, '')
.replace(/\(function initTheme\(\)[\s\S]*?\}\)\(\);/g, '')
.replace(/refreshColors\(\);/g, '')
.replace(/updateSpeedLabel\(\);/g, '')
.replace(/updateSizeLabel\(\);/g, '')
.replace(/updateThreadSlider\(\);/g, '')
.replace(/updateButtonStates\(\);/g, '')
.replace(/updateProgress\(0, 0\);/g, '')
.replace(/renderCurrent\(\);/g, '')
.replace(/generateArray\(\);/g, '')
.replace(/arrayFresh = true;/g, '')
// Ersetze 'let array = []' durch Zuweisung an context.array
.replace(/let array\s*=\s*\[\];/, 'array = [];')
// Entferne andere let-Deklarationen die schon im Kontext existieren
.replace(/let steps\s*=\s*\[\];/, 'steps = [];')
.replace(/let stepIndex\s*=\s*0;/, 'stepIndex = 0;')
.replace(/let animTimer\s*=\s*null;/, 'animTimer = null;')
.replace(/let isRunning\s*=\s*false;/, 'isRunning = false;')
.replace(/let isPaused\s*=\s*false;/, 'isPaused = false;')
.replace(/let arrayFresh\s*=\s*false;[\s\S]*?let startTime\s*=\s*0;[\s\S]*?let elapsedMs\s*=\s*0;/,
'arrayFresh = false;\nlet startTime = 0;\nlet elapsedMs = 0;');
try {
vm.runInContext(cleanedCode, context, { filename: 'sorting_visualization.html' });
} catch (e) {
console.error('Fehler beim Ausführen des JavaScript-Codes:', e.message);
console.error(e.stack.split('\n').slice(0, 5).join('\n'));
process.exit(1);
}
const buildSteps = context.buildSteps;
if (!buildSteps) {
console.error('Fehler: buildSteps wurde nicht exportiert');
process.exit(1);
}
// ── Test-Framework ──
const PRESETS = ['random', 'sorted', 'reversed', 'nearly', 'duplicates'];
const ALGORITHMS = [
'bubble', 'selection', 'insertion', 'cocktail',
'merge', 'heap', 'quick', 'quick3way', 'dualpivot', 'introsort',
'shell', 'tree', 'timsort',
'counting', 'radix', 'bucket',
'pancake', 'cycle', 'bogo', 'shuffle',
];
const Bogo_MAX_SIZE = 6;
let totalTests = 0;
let passed = 0;
let failed = 0;
const failures = [];
function isSorted(arr) {
for (let i = 0; i < arr.length - 1; i++) {
if (arr[i] > arr[i + 1]) return false;
}
return true;
}
function arraysEqual(a, b) {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false;
}
return true;
}
function generatePresetArray(preset, size) {
let arr = [];
switch (preset) {
case 'random':
for (let i = 0; i < size; i++) arr.push(Math.floor(Math.random() * 100) + 1);
break;
case 'sorted':
for (let i = 1; i <= size; i++) arr.push(i);
break;
case 'reversed':
for (let i = size; i >= 1; i--) arr.push(i);
break;
case 'nearly': {
for (let i = 1; i <= size; i++) arr.push(i);
const swaps = Math.max(1, Math.floor(size * 0.05));
for (let s = 0; s < swaps; s++) {
const i = Math.floor(Math.random() * size);
const j = Math.floor(Math.random() * size);
[arr[i], arr[j]] = [arr[j], arr[i]];
}
break;
}
case 'duplicates': {
const values = [5, 15, 25, 35, 45];
for (let i = 0; i < size; i++) arr.push(values[Math.floor(Math.random() * values.length)]);
break;
}
}
return arr;
}
function testAlgorithm(algo, preset, size) {
totalTests++;
const arr = generatePresetArray(preset, size);
context.array = arr.slice();
try {
const steps = buildSteps(algo);
const lastStep = steps[steps.length - 1];
if (lastStep.type !== 'done') {
throw new Error(`Letzter Step type '${lastStep.type}', erwartet 'done'`);
}
if (!isSorted(lastStep.array)) {
throw new Error(`Array ist nicht sortiert`);
}
// Prüfe Array-Länge (einige Algos wie Counting/Radix runden Werte)
if (lastStep.array.length !== arr.length) {
throw new Error(`Array-Länge geändert: ${lastStep.array.length} statt ${arr.length}`);
}
passed++;
if (verbose) {
console.log(`${algo} + ${preset} (${size} Elemente, ${steps.length} Steps)`);
}
} catch (e) {
failed++;
failures.push({ algo, preset, size, error: e.message });
console.log(`${algo} + ${preset} (${size} Elemente): ${e.message}`);
}
}
// ── Tests ausführen ──
console.log('\n🧪 Sortieralgorithmen-Tests\n');
console.log('═'.repeat(50));
const NORMAL_SIZES = [10, 50];
for (const algo of ALGORITHMS) {
if (algo === 'shuffle') continue;
if (verbose) console.log(`\n📦 ${algo}:`);
for (const preset of PRESETS) {
for (const size of NORMAL_SIZES) {
if (algo === 'bogo' && size > Bogo_MAX_SIZE) continue;
testAlgorithm(algo, preset, size);
}
}
}
// ── Shuffle-Tests (speziell: nicht sortiert, nur permutiert) ──
if (verbose) console.log('\n📦 shuffle:');
for (const preset of PRESETS) {
for (const size of NORMAL_SIZES) {
totalTests++;
const arr = generatePresetArray(preset, size);
const sortedArr = arr.slice().sort((a, b) => a - b);
context.array = arr.slice();
try {
const steps = buildSteps('shuffle');
const lastStep = steps[steps.length - 1];
if (lastStep.type !== 'done') {
throw new Error(`Letzter Step type '${lastStep.type}', erwartet 'done'`);
}
if (lastStep.array.length !== arr.length) {
throw new Error(`Array-Länge geändert: ${lastStep.array.length} statt ${arr.length}`);
}
const sortedResult = lastStep.array.slice().sort((a, b) => a - b);
if (!arraysEqual(sortedResult, sortedArr)) {
throw new Error(`Werte stimmen nicht überein nach Shuffle`);
}
passed++;
if (verbose) {
console.log(` ✅ shuffle + ${preset} (${size} Elemente, ${steps.length} Steps)`);
}
} catch (e) {
failed++;
failures.push({ algo: 'shuffle', preset, size, error: e.message });
console.log(` ❌ shuffle + ${preset} (${size} Elemente): ${e.message}`);
}
}
}
// Edge Cases
if (verbose) console.log('\n📦 Edge Cases:');
context.array = [];
try {
const steps = buildSteps('bubble');
if (steps.length > 0 && steps[steps.length - 1].type === 'done') {
passed++; totalTests++;
if (verbose) console.log(' ✅ bubble + empty array');
} else {
throw new Error('Kein done-Step');
}
} catch (e) {
failed++; totalTests++;
console.log(` ❌ bubble + empty array: ${e.message}`);
}
context.array = [42];
try {
const steps = buildSteps('bubble');
const last = steps[steps.length - 1];
if (last.type === 'done' && isSorted(last.array)) {
passed++; totalTests++;
if (verbose) console.log(' ✅ bubble + single element');
} else {
throw new Error('Nicht sortiert');
}
} catch (e) {
failed++; totalTests++;
console.log(` ❌ bubble + single element: ${e.message}`);
}
// Performance-Test (100 Elemente)
if (verbose) console.log('\n📦 Performance-Test (100 Elemente):');
for (const algo of ['bubble', 'merge', 'quick', 'heap', 'shell', 'timsort']) {
const arr = [];
for (let i = 0; i < 100; i++) arr.push(Math.floor(Math.random() * 1000) + 1);
context.array = arr.slice();
const start = Date.now();
const steps = buildSteps(algo);
const elapsed = Date.now() - start;
totalTests++;
if (isSorted(steps[steps.length - 1].array)) {
passed++;
if (verbose) console.log(`${algo}: ${steps.length} Steps in ${elapsed}ms`);
} else {
failed++;
console.log(`${algo}: nicht sortiert (${elapsed}ms)`);
}
}
// ── Ergebnis ──
console.log('\n' + '═'.repeat(50));
console.log(`\nErgebnis: ${passed}/${totalTests} bestanden${failed > 0 ? `, ${failed} fehlgeschlagen` : ''}`);
if (failures.length > 0) {
console.log('\nFehler:');
for (const f of failures) {
console.log(` - ${f.algo} + ${f.preset} (${f.size}): ${f.error}`);
}
process.exit(1);
} else {
console.log('\n✅ Alle Tests bestanden!');
process.exit(0);
}