diff --git a/.idea/.idea.InternalSortMethods/.idea/deploymentTargetSelector.xml b/.idea/.idea.InternalSortMethods/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..4751e02 --- /dev/null +++ b/.idea/.idea.InternalSortMethods/.idea/deploymentTargetSelector.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.InternalSortMethods/.idea/encodings.xml b/.idea/.idea.InternalSortMethods/.idea/encodings.xml index df87cf9..21f951f 100644 --- a/.idea/.idea.InternalSortMethods/.idea/encodings.xml +++ b/.idea/.idea.InternalSortMethods/.idea/encodings.xml @@ -1,4 +1,6 @@ - + + + \ No newline at end of file diff --git a/.idea/.idea.InternalSortMethods/.idea/libraries/androidx_annotation_annotation_jvm.xml b/.idea/.idea.InternalSortMethods/.idea/libraries/androidx_annotation_annotation_jvm.xml new file mode 100644 index 0000000..d2763d2 --- /dev/null +++ b/.idea/.idea.InternalSortMethods/.idea/libraries/androidx_annotation_annotation_jvm.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.qwen/settings.json b/.qwen/settings.json new file mode 100644 index 0000000..846e455 --- /dev/null +++ b/.qwen/settings.json @@ -0,0 +1,13 @@ +{ + "permissions": { + "allow": [ + "Bash(mkdir *)", + "Bash(dotnet *)", + "Bash(powershell *)", + "Bash(del *)", + "Bash(rmdir *)", + "Bash(findstr *)" + ] + }, + "$version": 3 +} \ No newline at end of file diff --git a/.qwen/settings.json.orig b/.qwen/settings.json.orig new file mode 100644 index 0000000..9a9e4cc --- /dev/null +++ b/.qwen/settings.json.orig @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Bash(mkdir *)" + ] + } +} \ No newline at end of file diff --git a/InternalSortMethods/App.axaml b/InternalSortMethods/App.axaml index 4fa6825..8eb4e25 100644 --- a/InternalSortMethods/App.axaml +++ b/InternalSortMethods/App.axaml @@ -2,8 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:InternalSortMethods" x:Class="InternalSortMethods.App" - RequestedThemeVariant="Default"> - + RequestedThemeVariant="Dark"> @@ -11,5 +10,52 @@ + + + + + + + + + - \ No newline at end of file + diff --git a/InternalSortMethods/Converters/StateToColorConverter.cs b/InternalSortMethods/Converters/StateToColorConverter.cs new file mode 100644 index 0000000..b238d17 --- /dev/null +++ b/InternalSortMethods/Converters/StateToColorConverter.cs @@ -0,0 +1,36 @@ +using System; +using System.Globalization; +using Avalonia.Data.Converters; +using Avalonia.Media; +using InternalSortMethods.ViewModels; + +namespace InternalSortMethods.Converters; + +/// +/// Конвертирует SortItemState в соответствующий цвет столбца. +/// Используется в DataTemplate для визуализации. +/// +public class StateToColorConverter : IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is not SortItemState state) + return Colors.Transparent; + + return state switch + { + SortItemState.Default => Color.Parse("#818cf8"), // Индиго + SortItemState.Comparing => Color.Parse("#fbbf24"), // Жёлтый + SortItemState.Swapping => Color.Parse("#f87171"), // Красный + SortItemState.Writing => Color.Parse("#22d3ee"), // Голубой + SortItemState.Pivot => Color.Parse("#c084fc"), // Фиолетовый + SortItemState.Sorted => Color.Parse("#34d399"), // Зелёный + _ => Color.Parse("#818cf8") + }; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} diff --git a/InternalSortMethods/Models/BubbleSort.cs b/InternalSortMethods/Models/BubbleSort.cs new file mode 100644 index 0000000..9e256ad --- /dev/null +++ b/InternalSortMethods/Models/BubbleSort.cs @@ -0,0 +1,73 @@ +using InternalSortMethods.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace InternalSortMethods.Models; + +/// +/// Сортировка пузырьком (Bubble Sort). +/// Последовательно сравнивает соседние элементы и меняет их, если они стоят в неверном порядке. +/// Сложность: O(n²) по времени, O(1) по памяти. +/// +public sealed class BubbleSort : SortAlgorithm +{ + public override string DisplayName => "Bubble Sort"; + + public override IEnumerable GenerateSteps(int[] array, CancellationToken cancellationToken = default) + { + var arr = (int[])array.Clone(); + int n = arr.Length; + + for (int i = 0; i < n - 1; i++) + { + bool swapped = false; + + for (int j = 0; j < n - i - 1; j++) + { + cancellationToken.ThrowIfCancellationRequested(); + + // Шаг сравнения + yield return new SortStep + { + Type = SortStepType.Compare, + Indices = [j, j + 1], + ArrayState = (int[])arr.Clone() + }; + + if (arr[j] > arr[j + 1]) + { + // Перестановка + (arr[j], arr[j + 1]) = (arr[j + 1], arr[j]); + swapped = true; + + yield return new SortStep + { + Type = SortStepType.Swap, + Indices = [j, j + 1], + ArrayState = (int[])arr.Clone() + }; + } + } + + // Элемент n-i-1 встал на своё место + yield return new SortStep + { + Type = SortStepType.Sorted, + Indices = [n - i - 1], + ArrayState = (int[])arr.Clone() + }; + + if (!swapped) break; // Массив отсортирован + } + + // Помечаем все элементы как отсортированные + yield return new SortStep + { + Type = SortStepType.Sorted, + Indices = Enumerable.Range(0, n).ToArray(), + ArrayState = (int[])arr.Clone() + }; + } +} diff --git a/InternalSortMethods/Models/HeapSort.cs b/InternalSortMethods/Models/HeapSort.cs new file mode 100644 index 0000000..972ae87 --- /dev/null +++ b/InternalSortMethods/Models/HeapSort.cs @@ -0,0 +1,121 @@ +using InternalSortMethods.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace InternalSortMethods.Models; + +/// +/// Пирамидальная сортировка (Heap Sort). +/// Строит максимальную кучу, затем извлекает корень и восстанавливает кучу. +/// Сложность: O(n log n) по времени, O(1) по памяти. +/// +public sealed class HeapSort : SortAlgorithm +{ + public override string DisplayName => "Heap Sort"; + + public override IEnumerable GenerateSteps(int[] array, CancellationToken cancellationToken = default) + { + var arr = (int[])array.Clone(); + int n = arr.Length; + + // Построение кучи (heapify) + for (int i = n / 2 - 1; i >= 0; i--) + { + foreach (var step in Heapify(arr, n, i, cancellationToken)) + yield return step; + } + + // Извлечение элементов из кучи + for (int i = n - 1; i > 0; i--) + { + cancellationToken.ThrowIfCancellationRequested(); + + // Перемещаем корень (максимум) в конец + (arr[0], arr[i]) = (arr[i], arr[0]); + + yield return new SortStep + { + Type = SortStepType.Swap, + Indices = [0, i], + ArrayState = (int[])arr.Clone() + }; + + yield return new SortStep + { + Type = SortStepType.Sorted, + Indices = [i], + ArrayState = (int[])arr.Clone() + }; + + // Восстановление кучи + foreach (var step in Heapify(arr, i, 0, cancellationToken)) + yield return step; + } + + yield return new SortStep + { + Type = SortStepType.Sorted, + Indices = [0], + ArrayState = (int[])arr.Clone() + }; + } + + /// + /// Восстанавливает свойство максимальной кучи для поддерева с корнем в index. + /// + private IEnumerable Heapify(int[] arr, int n, int index, CancellationToken ct) + { + ct.ThrowIfCancellationRequested(); + + int largest = index; + int left = 2 * index + 1; + int right = 2 * index + 2; + + // Сравнение с левым потомком + if (left < n) + { + yield return new SortStep + { + Type = SortStepType.Compare, + Indices = [largest, left], + ArrayState = (int[])arr.Clone() + }; + + if (arr[left] > arr[largest]) + largest = left; + } + + // Сравнение с правым потомком + if (right < n) + { + yield return new SortStep + { + Type = SortStepType.Compare, + Indices = [largest, right], + ArrayState = (int[])arr.Clone() + }; + + if (arr[right] > arr[largest]) + largest = right; + } + + // Перестановка, если корень не максимум + if (largest != index) + { + (arr[index], arr[largest]) = (arr[largest], arr[index]); + + yield return new SortStep + { + Type = SortStepType.Swap, + Indices = [index, largest], + ArrayState = (int[])arr.Clone() + }; + + // Рекурсивный heapify для затронутого поддерева + foreach (var step in Heapify(arr, n, largest, ct)) + yield return step; + } + } +} diff --git a/InternalSortMethods/Models/InsertionSort.cs b/InternalSortMethods/Models/InsertionSort.cs new file mode 100644 index 0000000..000e2ae --- /dev/null +++ b/InternalSortMethods/Models/InsertionSort.cs @@ -0,0 +1,88 @@ +using InternalSortMethods.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace InternalSortMethods.Models; + +/// +/// Сортировка вставками (Insertion Sort). +/// Вставляет каждый элемент в уже отсортированную часть массива. +/// Сложность: O(n²) по времени, O(1) по памяти. +/// +public sealed class InsertionSort : SortAlgorithm +{ + public override string DisplayName => "Insertion Sort"; + + public override IEnumerable GenerateSteps(int[] array, CancellationToken cancellationToken = default) + { + var arr = (int[])array.Clone(); + int n = arr.Length; + + // Первый элемент считается отсортированным + yield return new SortStep + { + Type = SortStepType.Sorted, + Indices = [0], + ArrayState = (int[])arr.Clone() + }; + + for (int i = 1; i < n; i++) + { + int key = arr[i]; + int j = i - 1; + + // Подсвечиваем вставляемый элемент + yield return new SortStep + { + Type = SortStepType.Pivot, + Indices = [i], + ArrayState = (int[])arr.Clone() + }; + + while (j >= 0 && arr[j] > key) + { + cancellationToken.ThrowIfCancellationRequested(); + + // Сравнение + yield return new SortStep + { + Type = SortStepType.Compare, + Indices = [j, i], + ArrayState = (int[])arr.Clone() + }; + + // Сдвиг элемента вправо + arr[j + 1] = arr[j]; + + yield return new SortStep + { + Type = SortStepType.Write, + Indices = [j + 1], + ArrayState = (int[])arr.Clone() + }; + + j--; + } + + // Вставка ключа на правильную позицию + arr[j + 1] = key; + + yield return new SortStep + { + Type = SortStepType.Write, + Indices = [j + 1], + ArrayState = (int[])arr.Clone() + }; + + // Элементы до i включительно отсортированы + yield return new SortStep + { + Type = SortStepType.Sorted, + Indices = Enumerable.Range(0, i + 1).ToArray(), + ArrayState = (int[])arr.Clone() + }; + } + } +} diff --git a/InternalSortMethods/Models/MergeSort.cs b/InternalSortMethods/Models/MergeSort.cs new file mode 100644 index 0000000..939049d --- /dev/null +++ b/InternalSortMethods/Models/MergeSort.cs @@ -0,0 +1,140 @@ +using InternalSortMethods.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace InternalSortMethods.Models; + +/// +/// Сортировка слиянием (Merge Sort). +/// Рекурсивно делит массив пополам, сортирует части и сливает их. +/// Сложность: O(n log n) по времени, O(n) по памяти. +/// +public sealed class MergeSort : SortAlgorithm +{ + public override string DisplayName => "Merge Sort"; + + public override IEnumerable GenerateSteps(int[] array, CancellationToken cancellationToken = default) + { + var arr = (int[])array.Clone(); + var steps = MergeSortRecursive(arr, 0, arr.Length - 1, cancellationToken).ToList(); + + yield return new SortStep + { + Type = SortStepType.Sorted, + Indices = Enumerable.Range(0, arr.Length).ToArray(), + ArrayState = (int[])arr.Clone() + }; + } + + private IEnumerable MergeSortRecursive(int[] arr, int left, int right, CancellationToken ct) + { + if (left >= right) + { + if (left == right) + { + yield return new SortStep + { + Type = SortStepType.Sorted, + Indices = [left], + ArrayState = (int[])arr.Clone() + }; + } + yield break; + } + + ct.ThrowIfCancellationRequested(); + + int mid = left + (right - left) / 2; + + foreach (var step in MergeSortRecursive(arr, left, mid, ct)) + yield return step; + + foreach (var step in MergeSortRecursive(arr, mid + 1, right, ct)) + yield return step; + + foreach (var step in Merge(arr, left, mid, right, ct)) + yield return step; + } + + private IEnumerable Merge(int[] arr, int left, int mid, int right, CancellationToken ct) + { + int n1 = mid - left + 1; + int n2 = right - mid; + + var L = new int[n1]; + var R = new int[n2]; + + Array.Copy(arr, left, L, 0, n1); + Array.Copy(arr, mid + 1, R, 0, n2); + + int i = 0, j = 0, k = left; + + while (i < n1 && j < n2) + { + ct.ThrowIfCancellationRequested(); + + yield return new SortStep + { + Type = SortStepType.Compare, + Indices = [left + i, mid + 1 + j], + ArrayState = (int[])arr.Clone() + }; + + if (L[i] <= R[j]) + { + arr[k] = L[i]; + i++; + } + else + { + arr[k] = R[j]; + j++; + } + + yield return new SortStep + { + Type = SortStepType.Write, + Indices = [k], + ArrayState = (int[])arr.Clone() + }; + + k++; + } + + while (i < n1) + { + ct.ThrowIfCancellationRequested(); + + arr[k] = L[i]; + + yield return new SortStep + { + Type = SortStepType.Write, + Indices = [k], + ArrayState = (int[])arr.Clone() + }; + + i++; + k++; + } + + while (j < n2) + { + ct.ThrowIfCancellationRequested(); + + arr[k] = R[j]; + + yield return new SortStep + { + Type = SortStepType.Write, + Indices = [k], + ArrayState = (int[])arr.Clone() + }; + + j++; + k++; + } + } +} diff --git a/InternalSortMethods/Models/QuickSort.cs b/InternalSortMethods/Models/QuickSort.cs new file mode 100644 index 0000000..8bdd2cb --- /dev/null +++ b/InternalSortMethods/Models/QuickSort.cs @@ -0,0 +1,154 @@ +using InternalSortMethods.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace InternalSortMethods.Models; + +/// +/// Быстрая сортировка (Quick Sort). +/// Разделяет массив по опорному элементу (pivot) и рекурсивно сортирует части. +/// Сложность: O(n log n) в среднем, O(n²) в худшем случае. +/// +public sealed class QuickSort : SortAlgorithm +{ + public override string DisplayName => "Quick Sort"; + + public override IEnumerable GenerateSteps(int[] array, CancellationToken cancellationToken = default) + { + var arr = (int[])array.Clone(); + var steps = QuickSortRecursive(arr, 0, arr.Length - 1, cancellationToken).ToList(); + + // Помечаем все элементы как отсортированные в конце + steps.Add(new SortStep + { + Type = SortStepType.Sorted, + Indices = Enumerable.Range(0, arr.Length).ToArray(), + ArrayState = (int[])arr.Clone() + }); + + return steps; + } + + private IEnumerable QuickSortRecursive(int[] arr, int low, int high, CancellationToken ct) + { + if (low < high) + { + ct.ThrowIfCancellationRequested(); + + // Разбиение и получение опорного индекса + foreach (var step in PartitionSteps(arr, low, high, ct)) + { + yield return step; + } + + // Определяем позицию pivot после разбиения + int pivotIdx = FindPivotIndex(arr, low, high); + + // Рекурсивная сортировка левой и правой частей + foreach (var step in QuickSortRecursive(arr, low, pivotIdx - 1, ct)) + yield return step; + + foreach (var step in QuickSortRecursive(arr, pivotIdx + 1, high, ct)) + yield return step; + } + else if (low == high) + { + yield return new SortStep + { + Type = SortStepType.Sorted, + Indices = [low], + ArrayState = (int[])arr.Clone() + }; + } + } + + private int FindPivotIndex(int[] arr, int low, int high) + { + // После partition pivot стоит на правильном месте + // Ищем элемент, который уже на своём месте + for (int i = low; i <= high; i++) + { + bool isPivot = true; + for (int j = low; j < i; j++) + { + if (arr[j] > arr[i]) { isPivot = false; break; } + } + for (int j = i + 1; j <= high; j++) + { + if (arr[j] < arr[i]) { isPivot = false; break; } + } + if (isPivot) return i; + } + return low; + } + + /// + /// Генерирует шаги для операции разбиения (partition) с визуализацией. + /// + private IEnumerable PartitionSteps(int[] arr, int low, int high, CancellationToken ct) + { + var temp = (int[])arr.Clone(); + int pivot = arr[high]; + int i = low - 1; + + yield return new SortStep + { + Type = SortStepType.Pivot, + Indices = [high], + ArrayState = (int[])temp.Clone() + }; + + for (int j = low; j < high; j++) + { + ct.ThrowIfCancellationRequested(); + + yield return new SortStep + { + Type = SortStepType.Compare, + Indices = [j, high], + ArrayState = (int[])temp.Clone() + }; + + if (temp[j] <= pivot) + { + i++; + if (i != j) + { + (temp[i], temp[j]) = (temp[j], temp[i]); + + yield return new SortStep + { + Type = SortStepType.Swap, + Indices = [i, j], + ArrayState = (int[])temp.Clone() + }; + } + } + } + + if (i + 1 != high) + { + (temp[i + 1], temp[high]) = (temp[high], temp[i + 1]); + + yield return new SortStep + { + Type = SortStepType.Swap, + Indices = [i + 1, high], + ArrayState = (int[])temp.Clone() + }; + } + + yield return new SortStep + { + Type = SortStepType.Sorted, + Indices = [i + 1], + ArrayState = (int[])temp.Clone() + }; + + // Копируем результат в оригинальный массив + for (int k = low; k <= high; k++) + arr[k] = temp[k]; + } +} diff --git a/InternalSortMethods/Models/SelectionSort.cs b/InternalSortMethods/Models/SelectionSort.cs new file mode 100644 index 0000000..9e94766 --- /dev/null +++ b/InternalSortMethods/Models/SelectionSort.cs @@ -0,0 +1,83 @@ +using InternalSortMethods.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace InternalSortMethods.Models; + +/// +/// Сортировка выбором (Selection Sort). +/// Находит минимальный элемент в неотсортированной части и ставит его на позицию. +/// Сложность: O(n²) по времени, O(1) по памяти. +/// +public sealed class SelectionSort : SortAlgorithm +{ + public override string DisplayName => "Selection Sort"; + + public override IEnumerable GenerateSteps(int[] array, CancellationToken cancellationToken = default) + { + var arr = (int[])array.Clone(); + int n = arr.Length; + + for (int i = 0; i < n - 1; i++) + { + int minIdx = i; + + // Подсвечиваем текущий минимум + yield return new SortStep + { + Type = SortStepType.Pivot, + Indices = [minIdx], + ArrayState = (int[])arr.Clone() + }; + + for (int j = i + 1; j < n; j++) + { + cancellationToken.ThrowIfCancellationRequested(); + + // Сравнение с текущим минимумом + yield return new SortStep + { + Type = SortStepType.Compare, + Indices = [minIdx, j], + ArrayState = (int[])arr.Clone() + }; + + if (arr[j] < arr[minIdx]) + { + minIdx = j; + } + } + + // Перестановка минимума на позицию i + if (minIdx != i) + { + (arr[i], arr[minIdx]) = (arr[minIdx], arr[i]); + + yield return new SortStep + { + Type = SortStepType.Swap, + Indices = [i, minIdx], + ArrayState = (int[])arr.Clone() + }; + } + + // Элемент i встал на своё место + yield return new SortStep + { + Type = SortStepType.Sorted, + Indices = [i], + ArrayState = (int[])arr.Clone() + }; + } + + // Последний элемент автоматически отсортирован + yield return new SortStep + { + Type = SortStepType.Sorted, + Indices = [n - 1], + ArrayState = (int[])arr.Clone() + }; + } +} diff --git a/InternalSortMethods/Models/SortAlgorithm.cs b/InternalSortMethods/Models/SortAlgorithm.cs new file mode 100644 index 0000000..8243cd2 --- /dev/null +++ b/InternalSortMethods/Models/SortAlgorithm.cs @@ -0,0 +1,27 @@ +using InternalSortMethods.Models; +using System.Collections.Generic; +using System.Threading; + +namespace InternalSortMethods.Models; + +/// +/// Базовый класс для всех алгоритмов сортировки. +/// Генерирует последовательность шагов (SortStep), которые ViewModel выполняет +/// с задержкой для анимации. Это позволяет реализовать паузу, шаг вперёд и отмену +/// без блокировки UI-потока. +/// +public abstract class SortAlgorithm +{ + /// Отображаемое название алгоритма. + public abstract string DisplayName { get; } + + /// + /// Генерирует шаги сортировки для заданного массива. + /// Каждый шаг содержит копию состояния массива после операции, + /// что позволяет визуализировать процесс пошагово. + /// + /// Массив для сортировки. + /// Токен отмены для прерывания генерации шагов. + /// Последовательность шагов сортировки. + public abstract IEnumerable GenerateSteps(int[] array, CancellationToken cancellationToken = default); +} diff --git a/InternalSortMethods/Models/SortStep.cs b/InternalSortMethods/Models/SortStep.cs new file mode 100644 index 0000000..268959b --- /dev/null +++ b/InternalSortMethods/Models/SortStep.cs @@ -0,0 +1,43 @@ +using System; + +namespace InternalSortMethods.Models; + +/// +/// Представляет один шаг выполнения алгоритма сортировки. +/// Используется для пошаговой визуализации с анимацией. +/// +public sealed class SortStep +{ + /// Тип операции шага. + public SortStepType Type { get; init; } + + /// Индексы элементов, участвующих в операции (сравнение или перестановка). + public int[] Indices { get; init; } = []; + + /// + /// Состояние массива после выполнения данного шага. + /// Копия создаётся для того, чтобы UI мог отобразить актуальное состояние. + /// + public int[] ArrayState { get; init; } = []; +} + +/// +/// Типы операций шага сортировки для визуальной подсветки. +/// +public enum SortStepType +{ + /// Сравнение двух элементов (подсвечиваем жёлтым). + Compare, + + /// Перестановка двух элементов (подсвечиваем красным). + Swap, + + /// Запись/вставка элемента на позицию (подсвечиваем голубым). + Write, + + /// Элемент определён как опорный (pivot) — для QuickSort. + Pivot, + + /// Элемент окончательно стоит на своём месте. + Sorted +} diff --git a/InternalSortMethods/ViewModels/MainViewModel.cs b/InternalSortMethods/ViewModels/MainViewModel.cs index a93e42b..e0c4ea7 100644 --- a/InternalSortMethods/ViewModels/MainViewModel.cs +++ b/InternalSortMethods/ViewModels/MainViewModel.cs @@ -1,8 +1,383 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using System; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using InternalSortMethods.Models; namespace InternalSortMethods.ViewModels; +/// +/// ViewModel . +/// +/// MVVM: +/// - ViewModel UI- (Button, ListBox ..) +/// - (Items, IsRunning ..) INotifyPropertyChanged +/// - RelayCommand +/// - Model- +/// +/// Thread.Sleep: +/// - await Task.Delay(ms, _cancellationToken) UI- +/// - CancellationToken +/// - SemaphoreSlim / +/// public partial class MainViewModel : ViewModelBase { - [ObservableProperty] private string _greeting = "Welcome to Avalonia!"; -} \ No newline at end of file + // ===== ===== + private const int MinArraySize = 5; + private const int MaxArraySize = 100; + private const int DefaultValue = 20; + + // ===== ===== + private static readonly SortAlgorithm[] Algorithms = + [ + new BubbleSort(), + new SelectionSort(), + new InsertionSort(), + new QuickSort(), + new MergeSort(), + new HeapSort() + ]; + + // ===== ===== + private readonly SemaphoreSlim _pauseSemaphore = new(1, 1); + private CancellationTokenSource? _cancellationTokenSource; + private int[] _currentArray = []; + private int _comparisons; + private int _swaps; + + // + private System.Collections.Generic.IEnumerator? _stepEnumerator; + private System.Collections.Generic.List? _cachedSteps; + private int _currentStepIndex; + + // ===== ObservableProperty (CommunityToolkit INPC) ===== + + [ObservableProperty] private ObservableCollection _items = []; + [ObservableProperty] private int _arraySize = DefaultValue; + [ObservableProperty] private int _selectedAlgorithmIndex; + [ObservableProperty] private int _animationSpeed = 100; + [ObservableProperty] private bool _isRunning; + [ObservableProperty] private bool _isCompleted; + [ObservableProperty] private string _statusText = ""; + [ObservableProperty] private int _currentStep; + [ObservableProperty] private int _totalSteps; + [ObservableProperty] private int _comparisonsCount; + [ObservableProperty] private int _swapsCount; + [ObservableProperty] private bool _isPaused; + + // ===== ===== + + /// ComboBox. + public string[] AlgorithmNames => Algorithms.Select(a => a.DisplayName).ToArray(); + + /// . + public bool CanStart => !IsRunning && Items.Count > 0; + + /// /. + public bool CanPause => IsRunning && !IsCompleted; + + /// ( ). + public bool CanStepForward => IsRunning && IsPaused && !IsCompleted; + + /// . + public bool CanReset => IsRunning || IsCompleted || Items.Count > 0; + + // ===== ===== + + /// + /// . + /// + [RelayCommand] + private void GenerateArray() + { + if (IsRunning) return; + + ArraySize = Math.Clamp(ArraySize, MinArraySize, MaxArraySize); + ResetState(); + + var random = new Random(); + _currentArray = Enumerable.Range(0, ArraySize) + .Select(_ => random.Next(1, 100)) + .ToArray(); + + UpdateItems(_currentArray); + StatusText = $" {ArraySize} "; + } + + /// + /// . + /// + /// Thread.Sleep: + /// - Task.Delay UI- ( continuation) + /// - CancellationToken + /// + [RelayCommand] + private async Task StartSortAsync() + { + if (IsRunning || Items.Count == 0) return; + + var algorithm = Algorithms[SelectedAlgorithmIndex]; + ResetState(); + IsRunning = true; + + _cancellationTokenSource = new CancellationTokenSource(); + var token = _cancellationTokenSource.Token; + + try + { + // + _cachedSteps = algorithm.GenerateSteps(_currentArray, token).ToList(); + TotalSteps = _cachedSteps.Count; + _stepEnumerator = _cachedSteps.GetEnumerator(); + _currentStepIndex = 0; + + StatusText = $": {algorithm.DisplayName}..."; + + // + foreach (var step in _cachedSteps) + { + token.ThrowIfCancellationRequested(); + + // () + await _pauseSemaphore.WaitAsync(token); + _pauseSemaphore.Release(); + + // + if (IsPaused) + { + await WaitUntilResumed(token); + } + + token.ThrowIfCancellationRequested(); + + ApplyStep(step); + _currentStepIndex++; + CurrentStep = _currentStepIndex; + + // UI-! + // Task.Delay , Thread.Sleep + await Task.Delay(AnimationSpeed, token); + } + + IsCompleted = true; + StatusText = $" ! : {ComparisonsCount}, : {SwapsCount}"; + } + catch (OperationCanceledException) + { + StatusText = " "; + } + catch (Exception ex) + { + StatusText = $": {ex.Message}"; + } + finally + { + IsRunning = false; + } + } + + /// + /// . + /// + /// : + /// - SemaphoreSlim . + /// - permit (Wait), WaitAsync. + /// - permit (Release), . + /// - IsPaused WaitUntilResumed. + /// + [RelayCommand] + private void PauseResume() + { + if (!IsRunning || IsCompleted) return; + + IsPaused = !IsPaused; + + if (IsPaused) + { + // permit WaitAsync + _pauseSemaphore.Wait(); + StatusText = ""; + } + else + { + // permit WaitAsync + _pauseSemaphore.Release(); + StatusText = "..."; + } + } + + /// + /// . + /// . + /// + [RelayCommand] + private async Task StepForwardAsync() + { + if (!IsRunning || !IsPaused || IsCompleted) return; + + if (_currentStepIndex < TotalSteps) + { + // + IsPaused = false; + _pauseSemaphore.Release(); + + // + var step = _cachedSteps![_currentStepIndex]; + ApplyStep(step); + _currentStepIndex++; + CurrentStep = _currentStepIndex; + + await Task.Delay(50); // UI + + // + IsPaused = true; + _pauseSemaphore.Wait(); + } + } + + /// + /// . + /// + [RelayCommand] + private void Reset() + { + CancelRunningOperation(); + ResetState(); + UpdateItems(_currentArray); + StatusText = ""; + } + + // ===== ===== + + /// + /// . + /// 50, . + /// UI- await Task.Delay. + /// + private async Task WaitUntilResumed(CancellationToken token) + { + while (IsPaused) + { + token.ThrowIfCancellationRequested(); + await Task.Delay(50, token); + } + } + + /// + /// Items . + /// + private void ApplyStep(SortStep step) + { + // + foreach (var item in Items) + { + item.State = SortItemState.Default; + } + + // + foreach (var idx in step.Indices) + { + if (idx >= 0 && idx < Items.Count) + { + Items[idx].State = step.Type switch + { + SortStepType.Compare => SortItemState.Comparing, + SortStepType.Swap => SortItemState.Swapping, + SortStepType.Write => SortItemState.Writing, + SortStepType.Pivot => SortItemState.Pivot, + SortStepType.Sorted => SortItemState.Sorted, + _ => SortItemState.Default + }; + } + } + + // ( ) + for (int i = 0; i < Math.Min(step.ArrayState.Length, Items.Count); i++) + { + Items[i].Value = step.ArrayState[i]; + } + + // + if (step.Type == SortStepType.Compare) + { + ComparisonsCount++; + _comparisons++; + } + + if (step.Type == SortStepType.Swap) + { + SwapsCount++; + _swaps++; + } + + // + _currentArray = (int[])step.ArrayState.Clone(); + } + + private void UpdateItems(int[] array) + { + Items.Clear(); + + foreach (var value in array) + { + Items.Add(new SortItemViewModel(value)); + } + } + + private void ResetState() + { + IsRunning = false; + IsCompleted = false; + IsPaused = false; + CurrentStep = 0; + TotalSteps = 0; + ComparisonsCount = 0; + SwapsCount = 0; + _comparisons = 0; + _swaps = 0; + _stepEnumerator = null; + _cachedSteps = null; + _currentStepIndex = 0; + + // + foreach (var item in Items) + { + item.State = SortItemState.Default; + } + + // + if (_pauseSemaphore.CurrentCount == 0) + { + _pauseSemaphore.Release(); + } + } + + private void CancelRunningOperation() + { + _cancellationTokenSource?.Cancel(); + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = null; + + // , + if (IsPaused) + { + IsPaused = false; + if (_pauseSemaphore.CurrentCount == 0) + { + _pauseSemaphore.Release(); + } + } + } + + // ===== ObservableProperty ===== + partial void OnArraySizeChanged(int value) + { + if (!IsRunning) + { + GenerateArray(); + } + } +} diff --git a/InternalSortMethods/ViewModels/SortItemViewModel.cs b/InternalSortMethods/ViewModels/SortItemViewModel.cs new file mode 100644 index 0000000..a878b95 --- /dev/null +++ b/InternalSortMethods/ViewModels/SortItemViewModel.cs @@ -0,0 +1,62 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace InternalSortMethods.ViewModels; + +/// +/// ViewModel для одного элемента массива в визуализации. +/// Содержит значение и текущее состояние подсветки (цвет). +/// +public partial class SortItemViewModel : ObservableObject +{ + private int _value; + private SortItemState _state = SortItemState.Default; + + public SortItemViewModel(int value) + { + _value = value; + } + + /// Значение элемента (высота столбца). + public int Value + { + get => _value; + set => SetProperty(ref _value, value); + } + + /// + /// Текущее состояние элемента для визуальной подсветки. + /// Изменяется при каждом шаге сортировки. + /// + public SortItemState State + { + get => _state; + set => SetProperty(ref _state, value); + } + + /// Относительная высота для привязки к UI (0.0 – 1.0). + public double NormalizedHeight { get; set; } +} + +/// +/// Состояния элемента для цветовой подсветки. +/// +public enum SortItemState +{ + /// Обычное состояние (нейтральный цвет). + Default, + + /// Элемент сравнивается с другим (жёлтый/оранжевый). + Comparing, + + /// Элемент участвует в перестановке (красный). + Swapping, + + /// Элемент записан/вставлен (синий/голубой). + Writing, + + /// Опорный элемент — pivot для QuickSort (фиолетовый). + Pivot, + + /// Элемент окончательно отсортирован (зелёный). + Sorted +} diff --git a/InternalSortMethods/Views/MainView.axaml b/InternalSortMethods/Views/MainView.axaml index 9358c78..b548a5a 100644 --- a/InternalSortMethods/Views/MainView.axaml +++ b/InternalSortMethods/Views/MainView.axaml @@ -3,14 +3,238 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="using:InternalSortMethods.ViewModels" - mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + xmlns:converters="using:InternalSortMethods.Converters" x:Class="InternalSortMethods.Views.MainView" - x:DataType="vm:MainViewModel"> + x:DataType="vm:MainViewModel" + mc:Ignorable="d" + d:DesignWidth="800" + d:DesignHeight="700"> + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +