diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..5ef08da --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,79 @@ +# Gemini CLI Context for playground-frontend + +## Project Overview + +This is the frontend of the Playground project, built with Angular 21 and Angular Material. It includes features like a light/dark theme toggle and multi-language support via ngx-translate. The application is a static Single Page Application (SPA) served by NGINX. + +**Key Technologies:** +* **Frontend Framework:** Angular 21 +* **UI Components & Theming:** Angular Material +* **Internationalization:** ngx-translate +* **Server:** NGINX (for serving the SPA) +* **Containerization:** Docker +* **CI/CD:** GitHub Actions + +## Building and Running + +### Local Development + +1. **Install dependencies:** + ```bash + npm install + ``` +2. **Start development server:** + ```bash + ng serve --open + ``` + The app will run at `http://localhost:4200`. + +### Building for Production + +To build the project for production, which creates the optimized static files: +```bash +ng build +``` + +### Running Tests + +To run the unit tests: +```bash +ng test +``` + +### Linting + +To lint the codebase: +```bash +ng lint +``` + +### Docker + +To build and run the application using Docker locally: + +1. **Build Docker image:** + ```bash + docker build -t playground-frontend:local . + ``` +2. **Run Docker container:** + ```bash + docker run -p 8080:80 playground-frontend:local + ``` + Then open `http://localhost:8080` in your browser. + +## Development Conventions + +* **Language:** TypeScript +* **Framework:** Angular +* **Styling:** SCSS (based on `styles.scss` and component-specific `.scss` files). +* **Linting:** ESLint is configured (see `eslint.config.js` and `package.json` scripts). +* **Internationalization:** Uses `ngx-translate` with `en.json` and `de.json` asset files. + +## Project Structure (Key Areas) + +* `src/app/`: Contains the main application logic, components, services, and routing. +* `src/app/pages/`: Specific pages of the application (e.g., about, algorithms, imprint, projects). +* `src/assets/`: Static assets including images, internationalization files (`i18n`), and logos. +* `Dockerfile`: Defines the Docker image for the application. +* `nginx.conf`: NGINX configuration for serving the SPA. +* `.gitea/workflows/`: Contains CI/CD workflows (e.g., `build-Frontend-a.yml`). diff --git a/package-lock.json b/package-lock.json index cbbb234..c110d85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2187,9 +2187,9 @@ } }, "node_modules/@isaacs/brace-expansion": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz", - "integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz", + "integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index c416983..55aa00e 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -8,6 +8,7 @@ export const routes: Routes = [ { path: RouterConstants.PROJECTS.PATH, component: RouterConstants.PROJECTS.COMPONENT}, { path: RouterConstants.ALGORITHMS.PATH, component: RouterConstants.ALGORITHMS.COMPONENT}, { path: RouterConstants.PATHFINDING.PATH, component: RouterConstants.PATHFINDING.COMPONENT}, + { path: RouterConstants.SORTING.PATH, component: RouterConstants.SORTING.COMPONENT}, { path: RouterConstants.IMPRINT.PATH, component: RouterConstants.IMPRINT.COMPONENT}, ]; diff --git a/src/app/constants/RouterConstants.ts b/src/app/constants/RouterConstants.ts index 50e2d6d..ce20694 100644 --- a/src/app/constants/RouterConstants.ts +++ b/src/app/constants/RouterConstants.ts @@ -3,6 +3,7 @@ import {ProjectsComponent} from '../pages/projects/projects.component'; import {ImprintComponent} from '../pages/imprint/imprint.component'; import {AlgorithmsComponent} from '../pages/algorithms/algorithms.component'; import {PathfindingComponent} from '../pages/algorithms/pathfinding/pathfinding.component'; +import {SortingComponent} from '../pages/algorithms/sorting/sorting.component'; export class RouterConstants { @@ -30,6 +31,12 @@ export class RouterConstants { COMPONENT: PathfindingComponent }; + static readonly SORTING = { + PATH: 'algorithms/sorting', + LINK: '/algorithms/sorting', + COMPONENT: SortingComponent + }; + static readonly IMPRINT = { PATH: 'imprint', LINK: '/imprint', diff --git a/src/app/constants/UrlConstants.ts b/src/app/constants/UrlConstants.ts index a730a49..719b7f1 100644 --- a/src/app/constants/UrlConstants.ts +++ b/src/app/constants/UrlConstants.ts @@ -3,4 +3,7 @@ static readonly GIT_HUB = 'https://github.com/LoboTheDark'; static readonly DIJKSTRA_WIKI = 'https://de.wikipedia.org/wiki/Dijkstra-Algorithmus' static readonly ASTAR_WIKI = 'https://de.wikipedia.org/wiki/A*-Algorithmus' + static readonly BUBBLE_SORT_WIKI = 'https://de.wikipedia.org/wiki/Bubblesort' + static readonly QUICK_SORT_WIKI = 'https://de.wikipedia.org/wiki/Quicksort' + static readonly HEAP_SORT_WIKI = 'https://de.wikipedia.org/wiki/Heapsort' } diff --git a/src/app/pages/algorithms/pathfinding/pathfinding.component.html b/src/app/pages/algorithms/pathfinding/pathfinding.component.html index d457fc3..931dcf3 100644 --- a/src/app/pages/algorithms/pathfinding/pathfinding.component.html +++ b/src/app/pages/algorithms/pathfinding/pathfinding.component.html @@ -1,87 +1,89 @@ -
-

{{ 'PATHFINDING.TITLE' | translate }}

+ + + {{ 'PATHFINDING.TITLE' | translate }} + + +
+

{{ 'PATHFINDING.EXPLANATION.TITLE' | translate }}

-
-

{{ 'PATHFINDING.EXPLANATION.TITLE' | translate }}

+

+ Dijkstra {{ 'PATHFINDING.EXPLANATION.DIJKSTRA_EXPLANATION' | translate }} + Wikipedia +

-

- Dijkstra {{ 'PATHFINDING.EXPLANATION.DIJKSTRA_EXPLANATION' | translate }} - Wikipedia -

+

+ A* {{ 'PATHFINDING.EXPLANATION.ASTAR_EXPLANATION' | translate}} + Wikipedia +

-

- A* {{ 'PATHFINDING.EXPLANATION.ASTAR_EXPLANATION' | translate}} - Wikipedia -

- -

- {{ 'PATHFINDING.EXPLANATION.NOTE' | translate}} {{ 'PATHFINDING.EXPLANATION.DISCLAIMER' | translate}} -

-
- -
-
- - -
-
- - - +

+ {{ 'PATHFINDING.EXPLANATION.NOTE' | translate}} {{ 'PATHFINDING.EXPLANATION.DISCLAIMER' | translate}} +

-
- - {{ 'PATHFINDING.START_NODE' | translate }} - {{ 'PATHFINDING.END_NODE' | translate }} - {{ 'PATHFINDING.WALL' | translate }} - {{ 'PATHFINDING.CLEAR_NODE' | translate }} - -
+
+
+ + +
+
+ + + +
-
-
- - {{ 'ALGORITHM.PATHFINDING.GRID_HEIGHT' | translate }} - - +
+ + {{ 'PATHFINDING.START_NODE' | translate }} + {{ 'PATHFINDING.END_NODE' | translate }} + {{ 'PATHFINDING.WALL' | translate }} + {{ 'PATHFINDING.CLEAR_NODE' | translate }} + +
- - {{ 'ALGORITHM.PATHFINDING.GRID_WIDTH' | translate }} - - +
+
+ + {{ 'PATHFINDING.GRID_HEIGHT' | translate }} + + + + + {{ 'PATHFINDING.GRID_WIDTH' | translate }} + + +
+
+ +
+ {{ 'PATHFINDING.START_NODE' | translate }} + {{ 'PATHFINDING.END_NODE' | translate }} + {{ 'PATHFINDING.WALL' | translate }} + {{ 'PATHFINDING.VISITED' | translate }} + {{ 'PATHFINDING.PATH' | translate }}
-
- {{ 'PATHFINDING.START_NODE' | translate }} - {{ 'PATHFINDING.END_NODE' | translate }} - {{ 'PATHFINDING.WALL' | translate }} - {{ 'PATHFINDING.VISITED' | translate }} - {{ 'PATHFINDING.PATH' | translate }} +
+

{{ 'PATHFINDING.PATH_LENGTH' | translate }}: {{ pathLength }}

+

{{ 'PATHFINDING.EXECUTION_TIME' | translate }}: {{ executionTime | number:'1.2-2' }} ms

-
-
-

{{ 'PATHFINDING.PATH_LENGTH' | translate }}: {{ pathLength }}

-

{{ 'PATHFINDING.EXECUTION_TIME' | translate }}: {{ executionTime | number:'1.2-2' }} ms

-
- - -
+ + diff --git a/src/app/pages/algorithms/pathfinding/pathfinding.component.scss b/src/app/pages/algorithms/pathfinding/pathfinding.component.scss index 070efd4..3e30d18 100644 --- a/src/app/pages/algorithms/pathfinding/pathfinding.component.scss +++ b/src/app/pages/algorithms/pathfinding/pathfinding.component.scss @@ -2,25 +2,6 @@ padding: 2rem; } -.algo-info { - margin: 0 0 1rem 0; - padding: 0.75rem 1rem; - border: 1px solid #ddd; - border-radius: 8px; - - h3 { - margin: 0 0 0.5rem 0; - } - - p { - margin: 0.5rem 0; - } - - a { - margin-left: 0.25rem; - } -} - .controls-container { display: flex; flex-direction: column; diff --git a/src/app/pages/algorithms/pathfinding/pathfinding.component.ts b/src/app/pages/algorithms/pathfinding/pathfinding.component.ts index a50c270..c6ed385 100644 --- a/src/app/pages/algorithms/pathfinding/pathfinding.component.ts +++ b/src/app/pages/algorithms/pathfinding/pathfinding.component.ts @@ -12,6 +12,7 @@ import {TranslateModule, TranslateService} from '@ngx-translate/core'; import {DEFAULT_GRID_COLS, DEFAULT_GRID_ROWS, MAX_GRID_PX, MAX_GRID_SIZE, MIN_GRID_SIZE, Node} from './pathfinding.models'; import {PathfindingService} from './service/pathfinding.service'; import {UrlConstants} from '../../../constants/UrlConstants'; +import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card'; enum NodeType { Start = 'start', @@ -32,7 +33,11 @@ interface GridPos { row: number; col: number } MatButtonToggleModule, MatFormFieldModule, MatInputModule, - TranslateModule + TranslateModule, + MatCard, + MatCardHeader, + MatCardTitle, + MatCardContent ], templateUrl: './pathfinding.component.html', styleUrls: ['./pathfinding.component.scss'] diff --git a/src/app/pages/algorithms/service/algorithms.service.ts b/src/app/pages/algorithms/service/algorithms.service.ts index a317759..6fd5b0c 100644 --- a/src/app/pages/algorithms/service/algorithms.service.ts +++ b/src/app/pages/algorithms/service/algorithms.service.ts @@ -14,6 +14,12 @@ export class AlgorithmsService { title: 'ALGORITHM.PATHFINDING.TITLE', description: 'ALGORITHM.PATHFINDING.DESCRIPTION', routerLink: RouterConstants.PATHFINDING.LINK + }, + { + id: 'sorting', + title: 'ALGORITHM.SORTING.TITLE', + description: 'ALGORITHM.SORTING.DESCRIPTION', + routerLink: RouterConstants.SORTING.LINK } ]; diff --git a/src/app/pages/algorithms/sorting/service/sorting.service.ts b/src/app/pages/algorithms/sorting/service/sorting.service.ts new file mode 100644 index 0000000..d0972d2 --- /dev/null +++ b/src/app/pages/algorithms/sorting/service/sorting.service.ts @@ -0,0 +1,178 @@ +import { Injectable } from '@angular/core'; +import {SortData, SortSnapshot} from '../sorting.models'; + +@Injectable({ + providedIn: 'root' +}) +export class SortingService { + + constructor() { } + + private createSnapshot(array: SortData[]): SortSnapshot { + return { + array: array.map(item => ({ ...item })) + }; + } + + bubbleSort(array: SortData[]): SortSnapshot[] { + const snapshots: SortSnapshot[] = []; + const arr = array.map(item => ({ ...item })); + const n = arr.length; + + snapshots.push(this.createSnapshot(arr)); + + for (let i = 0; i < n - 1; i++) { + for (let j = 0; j < n - i - 1; j++) { + arr[j].state = 'comparing'; + arr[j + 1].state = 'comparing'; + snapshots.push(this.createSnapshot(arr)); // Snapshot Vergleich + + if (arr[j].value > arr[j + 1].value) { + const temp = arr[j].value; + arr[j].value = arr[j + 1].value; + arr[j + 1].value = temp; + + snapshots.push(this.createSnapshot(arr)); + } + + arr[j].state = 'unsorted'; + arr[j + 1].state = 'unsorted'; + } + arr[n - 1 - i].state = 'sorted'; + snapshots.push(this.createSnapshot(arr)); + } + + arr[0].state = 'sorted'; + snapshots.push(this.createSnapshot(arr)); + + return snapshots; + } + + // --- QUICK SORT --- + quickSort(array: SortData[]): SortSnapshot[] { + const snapshots: SortSnapshot[] = []; + const arr = array.map(item => ({ ...item })); + snapshots.push(this.createSnapshot(arr)); + + this.quickSortHelper(arr, 0, arr.length - 1, snapshots); + + arr.forEach(i => i.state = 'sorted'); + snapshots.push(this.createSnapshot(arr)); + + return snapshots; + } + + private quickSortHelper(arr: SortData[], low: number, high: number, snapshots: SortSnapshot[]) { + if (low < high) { + const pi = this.partition(arr, low, high, snapshots); + + this.quickSortHelper(arr, low, pi - 1, snapshots); + this.quickSortHelper(arr, pi + 1, high, snapshots); + } else if (low >= 0 && high >= 0 && low === high) { + arr[low].state = 'sorted'; + snapshots.push(this.createSnapshot(arr)); + } + } + + private partition(arr: SortData[], low: number, high: number, snapshots: SortSnapshot[]): number { + const pivot = arr[high]; + arr[high].state = 'comparing'; // Pivot visualisieren + snapshots.push(this.createSnapshot(arr)); + + let i = (low - 1); + + for (let j = low; j <= high - 1; j++) { + arr[j].state = 'comparing'; + snapshots.push(this.createSnapshot(arr)); + + if (arr[j].value < pivot.value) { + i++; + this.swap(arr, i, j); + snapshots.push(this.createSnapshot(arr)); + } + arr[j].state = 'unsorted'; + } + this.swap(arr, i + 1, high); + + arr[high].state = 'unsorted'; + arr[i + 1].state = 'sorted'; + snapshots.push(this.createSnapshot(arr)); + + return i + 1; + } + + // --- HEAP SORT --- + heapSort(array: SortData[]): SortSnapshot[] { + const snapshots: SortSnapshot[] = []; + const arr = array.map(item => ({ ...item })); + const n = arr.length; + + snapshots.push(this.createSnapshot(arr)); + + for (let i = Math.floor(n / 2) - 1; i >= 0; i--) { + this.heapify(arr, n, i, snapshots); + } + + for (let i = n - 1; i > 0; i--) { + arr[0].state = 'comparing'; + arr[i].state = 'comparing'; + snapshots.push(this.createSnapshot(arr)); + + this.swap(arr, 0, i); + + arr[0].state = 'unsorted'; + arr[i].state = 'sorted'; + snapshots.push(this.createSnapshot(arr)); + + this.heapify(arr, i, 0, snapshots); + } + arr[0].state = 'sorted'; + snapshots.push(this.createSnapshot(arr)); + + return snapshots; + } + + private heapify(arr: SortData[], n: number, i: number, snapshots: SortSnapshot[]) { + let largest = i; + const l = 2 * i + 1; + const r = 2 * i + 2; + + if (l < n) { + arr[l].state = 'comparing'; + arr[largest].state = 'comparing'; + snapshots.push(this.createSnapshot(arr)); + + if (arr[l].value > arr[largest].value) { + largest = l; + } + arr[l].state = 'unsorted'; + arr[largest].state = 'unsorted'; + } + + // Vergleich Rechts + if (r < n) { + arr[r].state = 'comparing'; + arr[largest].state = 'comparing'; + snapshots.push(this.createSnapshot(arr)); + + if (arr[r].value > arr[largest].value) { + largest = r; + } + arr[r].state = 'unsorted'; + arr[largest].state = 'unsorted'; + } + + if (largest !== i) { + this.swap(arr, i, largest); + snapshots.push(this.createSnapshot(arr)); + + this.heapify(arr, n, largest, snapshots); + } + } + + private swap(arr: SortData[], i: number, j: number) { + const temp = arr[i].value; + arr[i].value = arr[j].value; + arr[j].value = temp; + } +} diff --git a/src/app/pages/algorithms/sorting/sorting.component.html b/src/app/pages/algorithms/sorting/sorting.component.html new file mode 100644 index 0000000..7880ace --- /dev/null +++ b/src/app/pages/algorithms/sorting/sorting.component.html @@ -0,0 +1,88 @@ +
+ + + {{ 'SORTING.TITLE' | translate }} + + +
+

{{ 'SORTING.EXPLANATION.TITLE' | translate }}

+ +

+ Bubble Sort {{ 'SORTING.EXPLANATION.BUBBLE_SORT_EXPLANATION' | translate }} + Wikipedia +

+ +

+ Quick Sort {{ 'SORTING.EXPLANATION.QUICK_SORT_EXPLANATION' | translate}} + Wikipedia +

+ +

+ Heap Sort {{ 'SORTING.EXPLANATION.HEAP_SORT_EXPLANATION' | translate}} + Wikipedia +

+ +

+ {{ 'SORTING.EXPLANATION.NOTE' | translate}} {{ 'SORTING.EXPLANATION.DISCLAIMER' | translate}} +

+
    +
  • {{ 'SORTING.EXPLANATION.DISCLAIMER_1' | translate}}
  • +
  • {{ 'SORTING.EXPLANATION.DISCLAIMER_2' | translate}}
  • +
  • {{ 'SORTING.EXPLANATION.DISCLAIMER_3' | translate}}
  • +
+

+ {{ 'SORTING.EXPLANATION.DISCLAIMER_4' | translate}} +

+
+
+ + {{ 'SORTING.ALGORITHM' | translate }} + + @for (algo of availableAlgorithms; track algo.value) { + {{ algo.name }} + } + + + + + {{ 'SORTING.ARRAY_SIZE' | translate }} + + +
+
+ + + +
+ +
+

{{ 'SORTING.EXECUTION_TIME' | translate }}: {{ executionTime }} ms

+
+
+ @for (item of sortArray; track $index) { +
+ } +
+
+
+
diff --git a/src/app/pages/algorithms/sorting/sorting.component.scss b/src/app/pages/algorithms/sorting/sorting.component.scss new file mode 100644 index 0000000..b911eac --- /dev/null +++ b/src/app/pages/algorithms/sorting/sorting.component.scss @@ -0,0 +1,62 @@ +.sorting-container { + display: flex; + justify-content: center; + align-items: flex-start; + padding: 20px; + height: 100%; + box-sizing: border-box; + + .sorting-card { + width: 100%; + max-width: 1200px; + padding: 20px; + + .controls-panel { + display: flex; + gap: 10px; + margin-bottom: 20px; + align-items: center; + flex-wrap: wrap; + + mat-form-field { + width: 200px; + } + } + + .visualization-area { + display: flex; + align-items: flex-end; + height: 300px; /* Max height for bars */ + border-bottom: 1px solid #ccc; + margin-bottom: 20px; + gap: 1px; + background-color: #f0f0f0; + + .bar { + flex-grow: 1; + background-color: #424242; /* Default unsorted color */ + transition: height 0.05s ease-in-out, background-color 0.05s ease-in-out; + width: 10px; /* Default width, flex-grow will adjust */ + min-width: 1px; /* Ensure bars are always visible */ + + &.unsorted { + background-color: #424242; + } + + &.comparing { + background-color: #ffeb3b; /* Yellow for comparing */ + } + + &.sorted { + background-color: #4caf50; /* Green for sorted */ + } + } + } + + .info-panel { + margin-top: 10px; + font-size: 0.9em; + color: #FFFFFF; + } + } +} diff --git a/src/app/pages/algorithms/sorting/sorting.component.ts b/src/app/pages/algorithms/sorting/sorting.component.ts new file mode 100644 index 0000000..8cbca5d --- /dev/null +++ b/src/app/pages/algorithms/sorting/sorting.component.ts @@ -0,0 +1,143 @@ +import {ChangeDetectorRef, Component, OnInit} from '@angular/core'; +import { CommonModule } from '@angular/common'; +import {MatCardModule} from "@angular/material/card"; +import {MatFormFieldModule} from "@angular/material/form-field"; +import {MatSelectModule} from "@angular/material/select"; +import {MatButtonModule} from "@angular/material/button"; +import {MatIconModule} from "@angular/material/icon"; +import {TranslateModule} from "@ngx-translate/core"; +import { SortingService } from './service/sorting.service'; +import {SortData, SortSnapshot} from './sorting.models'; +import { FormsModule } from '@angular/forms'; +import {MatInput} from '@angular/material/input'; +import {UrlConstants} from '../../../constants/UrlConstants'; +@Component({ + selector: 'app-sorting', + standalone: true, + imports: [CommonModule, MatCardModule, MatFormFieldModule, MatSelectModule, MatButtonModule, MatIconModule, TranslateModule, FormsModule, MatInput], + templateUrl: './sorting.component.html', + styleUrls: ['./sorting.component.scss'] +}) +export class SortingComponent implements OnInit { + + readonly MAX_ARRAY_SIZE: number = 200; + readonly MIN_ARRAY_SIZE: number = 20; + + private timeoutIds: any[] = []; + sortArray: SortData[] = []; + unsortedArrayCopy: SortData[] = []; + arraySize: number = 100; + maxArrayValue: number = 100; + animationSpeed: number = 50; // Milliseconds per step + + // Placeholder for available sorting algorithms + availableAlgorithms: { name: string; value: string }[] = [ + { name: 'Bubble Sort', value: 'bubbleSort' }, + { name: 'Quick Sort', value: 'quickSort' }, + { name: 'Heap Sort', value: 'heapSort' }, + ]; + selectedAlgorithm: string = this.availableAlgorithms[0].value; + executionTime: number = 0; + + constructor(private readonly sortingService: SortingService, private readonly cdr: ChangeDetectorRef) { } + + ngOnInit(): void { + this.generateNewArray(); + } + + newArraySizeSet() + { + if (this.arraySize == this.sortArray.length) + { + return; + } + this.generateNewArray(); + } + + generateNewArray(): void { + this.resetSorting(); + this.executionTime = 0; + this.unsortedArrayCopy = []; + this.sortArray = []; + + for (let i = 0; i < this.arraySize; i++) { + const randomValue = Math.floor(Math.random() * this.maxArrayValue) + 1; + this.sortArray.push({ + value: randomValue, + state: 'unsorted' + }); + + this.unsortedArrayCopy.push({ + value: randomValue, + state: 'unsorted' + }); + } + } + + private resetSortState() { + for (let i = 0; i < this.sortArray.length; i++) { + let element = this.sortArray[i]; + let unsortedElement = this.unsortedArrayCopy[i]; + element.value = unsortedElement.value; + element.state = 'unsorted'; + } + } + + async startSorting(): Promise { + this.resetSorting(); + const startTime = performance.now(); + let snapshots: SortSnapshot[] = []; + + switch (this.selectedAlgorithm) { + case 'bubbleSort': + snapshots = this.sortingService.bubbleSort(this.sortArray); + break; + case 'quickSort': + snapshots = this.sortingService.quickSort(this.sortArray); + break; + case 'heapSort': + snapshots = this.sortingService.heapSort(this.sortArray); + break; + } + + const endTime = performance.now(); + this.executionTime = parseFloat((endTime - startTime).toFixed(4)); + + console.log(snapshots.length); + this.animateSorting(snapshots); + } + + private animateSorting(snapshots: SortSnapshot[]): void { + snapshots.forEach((snapshot, index) => { + const id = setTimeout(() => { + for (let i = 0; i < this.sortArray.length; i++) { + if (snapshot.array[i]) { + this.sortArray[i].value = snapshot.array[i].value; + this.sortArray[i].state = snapshot.array[i].state; + } + } + + this.cdr.detectChanges(); + + if (index === snapshots.length - 1) { + this.sortArray.forEach(item => item.state = 'sorted'); + this.cdr.detectChanges(); + } + }, index * this.animationSpeed); + + this.timeoutIds.push(id); + }); + } + + private stopAnimations(): void { + this.timeoutIds.forEach(id => clearTimeout(id)); + this.timeoutIds = []; + } + + resetSorting(): void { + this.stopAnimations(); + this.resetSortState(); + } + + protected readonly UrlConstants = UrlConstants; +} diff --git a/src/app/pages/algorithms/sorting/sorting.models.ts b/src/app/pages/algorithms/sorting/sorting.models.ts new file mode 100644 index 0000000..e4d8bce --- /dev/null +++ b/src/app/pages/algorithms/sorting/sorting.models.ts @@ -0,0 +1,8 @@ +export interface SortData { + value: number; + state: 'sorted' | 'comparing' | 'unsorted'; +} + +export interface SortSnapshot { + array: SortData[]; +} diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index d30988a..a50598d 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -317,15 +317,41 @@ }, "ALERT": { "START_END_NODES": "Bitte wählen Sie einen Start- und Endknoten aus, bevor Sie den Algorithmus starten." + }, + "GRID_HEIGHT": "Höhe", + "GRID_WIDTH": "Beite" + }, + "SORTING": { + "TITLE": "Sortieralgorithmen", + "ALGORITHM": "Algorithmen", + "START": "Sortierung starten", + "RESET": "Zurücksetzen", + "GENERATE_NEW_ARRAY": "Neues Array generieren", + "EXECUTION_TIME": "Ausführungszeit", + "ARRAY_SIZE": "Anzahl der Balken", + "EXPLANATION": { + "TITLE": "Algorithmen", + "BUBBLE_SORT_EXPLANATION":"vergleicht wiederholt benachbarte Elemente und tauscht sie, wenn sie in der falschen Reihenfolge stehen. Das größte Element \"blubbert\" dabei wie eine Luftblase ans Ende der Liste. Vorteil: Extrem einfach zu verstehen und zu implementieren; erkennt bereits sortierte Listen sehr schnell. Nachteil: Sehr ineffizient bei großen Listen (Laufzeit O(n²)). In der Praxis kaum genutzt.", + "QUICK_SORT_EXPLANATION": "folgt dem \"Teile und Herrsche\"-Prinzip. Ein \"Pivot\"-Element wird gewählt, und das Array wird in zwei Hälften geteilt: Elemente kleiner als das Pivot und Elemente größer als das Pivot. Vorteil: Im Durchschnitt einer der schnellsten Sortieralgorithmen (O(n log n)); benötigt keinen zusätzlichen Speicher (In-Place). Nachteil: Im schlechtesten Fall (Worst Case) langsam (O(n²)), wenn das Pivot ungünstig gewählt wird. Ist nicht stabil (ändert Reihenfolge gleicher Elemente).", + "HEAP_SORT_EXPLANATION": "organisiert die Daten zunächst in einer speziellen Baumstruktur (Binary Heap). Das größte Element (die Wurzel) wird entnommen und ans Ende sortiert, dann wird der Baum repariert. Vorteil: Garantiert eine schnelle Laufzeit von O(n log n), selbst im schlechtesten Fall. Benötigt fast keinen zusätzlichen Speicher. Nachteil: In der Praxis oft etwas langsamer als Quick Sort, da die Sprünge im Speicher (Heap-Struktur) den CPU-Cache schlechter nutzen.", + "NOTE": "HINWEIS", + "DISCLAIMER": "Die Wahl des \"besten\" Sortieralgorithmus hängt stark von den Daten und den Rahmenbedingungen ab. In der Informatik betrachtet man oft drei Szenarien:", + "DISCLAIMER_1": "Best Case: Die Daten sind schon fast sortiert (hier glänzt z.B. Bubble Sort).", + "DISCLAIMER_2": "Average Case: Der statistische Normalfall.", + "DISCLAIMER_3": "Worst Case: Die Daten sind maximal ungünstig angeordnet (hier bricht Quick Sort ohne Optimierung ein, während Heap Sort stabil bleibt).", + "DISCLAIMER_4": "Zusätzlich gibt es fast immer einen Time-Space Trade-off (Zeit-Speicher-Kompromiss): Algorithmen, die extrem schnell sind (wie Merge Sort), benötigen oft viel zusätzlichen Arbeitsspeicher. Algorithmen, die direkt im vorhandenen Speicher arbeiten (wie Heap Sort), sparen Platz, sind aber manchmal komplexer oder minimal langsamer. Es gibt also keine \"One-Size-Fits-All\"-Lösung." } }, "ALGORITHM": { "TITLE": "Algorithmen", "PATHFINDING": { "TITLE": "Wegfindung", - "DESCRIPTION": "Vergleich von Dijkstra vs. A*.", - "GRID_HEIGHT": "Höhe", - "GRID_WIDTH": "Beite" + "DESCRIPTION": "Vergleich von Dijkstra vs. A*." + }, + "SORTING": { + "TITLE": "Sortierung", + "DESCRIPTION": "Visualisierung verschiedener Sortieralgorithmen." + } } } diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 71e8757..7e754c2 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -317,15 +317,40 @@ }, "ALERT": { "START_END_NODES": "Please select a start and end node before running the algorithm." + }, + "GRID_HEIGHT": "Height", + "GRID_WIDTH": "Width" + }, + "SORTING": { + "TITLE": "Sorting Algorithms", + "ALGORITHM": "Algorithm", + "START": "Start Sorting", + "RESET": "Reset", + "GENERATE_NEW_ARRAY": "Generate New Array", + "EXECUTION_TIME": "Execution Time", + "ARRAY_SIZE": "Number of Bars", + "EXPLANATION": { + "TITLE": "Algorithms", + "BUBBLE_SORT_EXPLANATION": "repeatedly compares adjacent elements and swaps them if they are in the wrong order. The largest element \"bubbles\" to the end of the list like an air bubble. Advantage: Extremely simple to understand and implement; detects already sorted lists very quickly. Disadvantage: Very inefficient for large lists (runtime O(n²)). Rarely used in practice.", + "QUICK_SORT_EXPLANATION": "follows the \"divide and conquer\" principle. A \"pivot\" element is selected, and the array is divided into two halves: elements smaller than the pivot and elements larger than the pivot. Advantage: On average one of the fastest sorting algorithms (O(n log n)); requires no additional memory (in-place). Disadvantage: Slow in the worst case (O(n²)) if the pivot is chosen poorly. Is not stable (changes order of equal elements).", + "HEAP_SORT_EXPLANATION": "organizes the data initially into a special tree structure (Binary Heap). The largest element (the root) is extracted and sorted to the end, then the tree is repaired. Advantage: Guarantees a fast runtime of O(n log n), even in the worst case. Requires almost no additional memory. Disadvantage: Often slightly slower than Quick Sort in practice because the jumps in memory (heap structure) utilize the CPU cache less effectively.", + "NOTE": "NOTE", + "DISCLAIMER": "The choice of the \"best\" sorting algorithm depends heavily on the data and the constraints. In computer science, three scenarios are often considered:", + "DISCLAIMER_1": "Best Case: The data is already nearly sorted (Bubble Sort shines here, for example).", + "DISCLAIMER_2": "Average Case: The statistical norm.", + "DISCLAIMER_3": "Worst Case: The data is arranged in the most unfavorable way possible (Quick Sort performs poorly here without optimization, while Heap Sort remains stable).", + "DISCLAIMER_4": "Additionally, there is almost always a Time-Space Trade-off: Algorithms that are extremely fast (like Merge Sort) often require a lot of additional working memory. Algorithms that work directly in existing memory (like Heap Sort) save space but are sometimes more complex or slightly slower. Thus, there is no \"one-size-fits-all\" solution." } }, "ALGORITHM": { "TITLE": "Algorithms", "PATHFINDING": { "TITLE": "Pathfinding", - "DESCRIPTION": "Comparing of Dijkstra vs. A*.", - "GRID_HEIGHT": "Height", - "GRID_WIDTH": "Width" + "DESCRIPTION": "Comparing of Dijkstra vs. A*." + }, + "SORTING": { + "TITLE": "Sorting", + "DESCRIPTION": "Visualizing various sorting algorithms." } } } diff --git a/src/styles.scss b/src/styles.scss index dc63ed5..3a12a96 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -211,3 +211,23 @@ a { height: 18px; width: 18px; } + +// algos +.algo-info { + margin: 0 0 1rem 0; + padding: 0.75rem 1rem; + border: 1px solid #ddd; + border-radius: 8px; + + h3 { + margin: 0 0 0.5rem 0; + } + + p { + margin: 0.5rem 0; + } + + a { + margin-left: 0.25rem; + } +}