diff --git a/InternalSortMethods/App.axaml b/InternalSortMethods/App.axaml index 4fa6825..8ce2d48 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,34 @@ + + + + + + + + \ No newline at end of file diff --git a/InternalSortMethods/Controls/ArrayVisualizer.cs b/InternalSortMethods/Controls/ArrayVisualizer.cs new file mode 100644 index 0000000..d76d92d --- /dev/null +++ b/InternalSortMethods/Controls/ArrayVisualizer.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; + +namespace InternalSortMethods.Controls; + +public class ArrayVisualizer : Control +{ + public static readonly StyledProperty?> ItemsProperty = + AvaloniaProperty.Register?>(nameof(Items)); + + public ObservableCollection? Items + { + get => GetValue(ItemsProperty); + set => SetValue(ItemsProperty, value); + } + + static ArrayVisualizer() + { + ItemsProperty.Changed.AddClassHandler((x, e) => x.OnItemsChanged(e)); + } + + public ArrayVisualizer() + { + ClipToBounds = true; + } + + private void OnItemsChanged(AvaloniaPropertyChangedEventArgs e) + { + if (e.OldValue is ObservableCollection oldCollection) + { + oldCollection.CollectionChanged -= OnCollectionChanged; + foreach (var item in oldCollection) + item.PropertyChanged -= OnItemPropertyChanged; + } + + if (e.NewValue is ObservableCollection newCollection) + { + newCollection.CollectionChanged += OnCollectionChanged; + foreach (var item in newCollection) + item.PropertyChanged += OnItemPropertyChanged; + } + } + + private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + if (e.NewItems != null) + { + foreach (Models.ArrayItemModel item in e.NewItems) + item.PropertyChanged += OnItemPropertyChanged; + } + if (e.OldItems != null) + { + foreach (Models.ArrayItemModel item in e.OldItems) + item.PropertyChanged -= OnItemPropertyChanged; + } + InvalidateVisual(); + } + + private void OnItemPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs? e) + { + InvalidateVisual(); + } + + public override void Render(DrawingContext context) + { + if (Items == null || Items.Count == 0 || Bounds.Width <= 0 || Bounds.Height <= 0) + return; + + double width = Bounds.Width; + double height = Bounds.Height; + int count = Items.Count; + double barWidth = (width - (count - 1) * 2) / count; + double maxValue = 100.0; + + var backgroundBrush = new SolidColorBrush(Color.Parse("#1E1E2E")); + context.DrawRectangle(backgroundBrush, null, new Rect(0, 0, width, height)); + + for (int i = 0; i < count; i++) + { + var item = Items[i]; + double barHeight = (item.Value / maxValue) * height * 0.9; + double x = i * (barWidth + 2); + double y = height - barHeight; + + var brush = GetBrushForState(item.State); + context.DrawRectangle(brush, null, new Rect(x, y, barWidth, barHeight)); + } + } + + private IBrush GetBrushForState(Models.ArrayItemState state) + { + return state switch + { + Models.ArrayItemState.Comparing => new LinearGradientBrush + { + StartPoint = new RelativePoint(0, 1, RelativeUnit.Absolute), + EndPoint = new RelativePoint(0, 0, RelativeUnit.Absolute), + GradientStops = + [ + new GradientStop(Color.Parse("#FF6B6B"), 0), + new GradientStop(Color.Parse("#FF8E8E"), 1) + ] + }, + Models.ArrayItemState.Swapping => new LinearGradientBrush + { + StartPoint = new RelativePoint(0, 1, RelativeUnit.Absolute), + EndPoint = new RelativePoint(0, 0, RelativeUnit.Absolute), + GradientStops = + [ + new GradientStop(Color.Parse("#FFE66D"), 0), + new GradientStop(Color.Parse("#FFB347"), 1) + ] + }, + Models.ArrayItemState.Sorted => new LinearGradientBrush + { + StartPoint = new RelativePoint(0, 1, RelativeUnit.Absolute), + EndPoint = new RelativePoint(0, 0, RelativeUnit.Absolute), + GradientStops = + [ + new GradientStop(Color.Parse("#4ADE80"), 0), + new GradientStop(Color.Parse("#22C55E"), 1) + ] + }, + Models.ArrayItemState.Pivot => new LinearGradientBrush + { + StartPoint = new RelativePoint(0, 1, RelativeUnit.Absolute), + EndPoint = new RelativePoint(0, 0, RelativeUnit.Absolute), + GradientStops = + [ + new GradientStop(Color.Parse("#C084FC"), 0), + new GradientStop(Color.Parse("#A855F7"), 1) + ] + }, + _ => new LinearGradientBrush + { + StartPoint = new RelativePoint(0, 1, RelativeUnit.Absolute), + EndPoint = new RelativePoint(0, 0, RelativeUnit.Absolute), + GradientStops = + [ + new GradientStop(Color.Parse("#6366F1"), 0), + new GradientStop(Color.Parse("#4F46E5"), 1) + ] + } + }; + } +} \ No newline at end of file diff --git a/InternalSortMethods/Models/SortAlgorithmsProvider.cs b/InternalSortMethods/Models/SortAlgorithmsProvider.cs new file mode 100644 index 0000000..0d692f0 --- /dev/null +++ b/InternalSortMethods/Models/SortAlgorithmsProvider.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using CommunityToolkit.Mvvm.ComponentModel; + +namespace InternalSortMethods.Models; + +public static class SortAlgorithmsProvider +{ + public static IReadOnlyList Algorithms { get; } = + [ + new BubbleSort(), + new SelectionSort(), + new InsertionSort(), + new QuickSort(), + new MergeSort(), + new HeapSort() + ]; +} + +public partial class ArrayItemModel : ObservableObject +{ + [ObservableProperty] + private int _value; + + [ObservableProperty] + private int _index; + + [ObservableProperty] + private ArrayItemState _state = ArrayItemState.Normal; +} + +public enum ArrayItemState +{ + Normal, + Comparing, + Swapping, + Sorted, + Pivot +} \ No newline at end of file diff --git a/InternalSortMethods/Models/SortStep.cs b/InternalSortMethods/Models/SortStep.cs new file mode 100644 index 0000000..f89bae4 --- /dev/null +++ b/InternalSortMethods/Models/SortStep.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace InternalSortMethods.Models; + +public enum SortActionType +{ + Compare, + Swap, + Set, + MarkSorted, + MarkPivot +} + +public record SortStep(int Index1, int Index2, SortActionType Action, int[]? ArraySnapshot = null); + +public record SortState(int[] Array, List Steps, int CurrentStep, bool IsComplete); + +public class SortingResult +{ + public required int[] SortedArray { get; init; } + public required List Steps { get; init; } = []; +} \ No newline at end of file diff --git a/InternalSortMethods/Models/SortingAlgorithms.cs b/InternalSortMethods/Models/SortingAlgorithms.cs new file mode 100644 index 0000000..df8b438 --- /dev/null +++ b/InternalSortMethods/Models/SortingAlgorithms.cs @@ -0,0 +1,277 @@ +using System.Collections.Generic; + +namespace InternalSortMethods.Models; + +public abstract class SortingAlgorithm +{ + public abstract string Name { get; } + public abstract string NameRu { get; } + + protected List Steps { get; } = []; + protected int[] _array = []; + + public SortingResult Sort(int[] array) + { + _array = (int[])array.Clone(); + Steps.Clear(); + OnSort(); + return new SortingResult + { + SortedArray = _array, + Steps = [.. Steps] + }; + } + + protected void Compare(int i, int j) + { + Steps.Add(new SortStep(i, j, SortActionType.Compare)); + } + + protected void Swap(int i, int j) + { + (_array[i], _array[j]) = (_array[j], _array[i]); + Steps.Add(new SortStep(i, j, SortActionType.Swap, (int[])_array.Clone())); + } + + protected void SetValue(int index, int value) + { + _array[index] = value; + Steps.Add(new SortStep(index, -1, SortActionType.Set, (int[])_array.Clone())); + } + + protected void MarkSorted(int index) + { + Steps.Add(new SortStep(index, -1, SortActionType.MarkSorted)); + } + + protected void MarkPivot(int index) + { + Steps.Add(new SortStep(index, -1, SortActionType.MarkPivot)); + } + + protected abstract void OnSort(); +} + +public class BubbleSort : SortingAlgorithm +{ + public override string Name => "Bubble Sort"; + public override string NameRu => "Пузырьковая"; + + protected override void OnSort() + { + int n = _array.Length; + for (int i = 0; i < n - 1; i++) + { + bool swapped = false; + for (int j = 0; j < n - i - 1; j++) + { + Compare(j, j + 1); + if (_array[j] > _array[j + 1]) + { + Swap(j, j + 1); + swapped = true; + } + } + MarkSorted(n - i - 1); + if (!swapped) break; + } + MarkSorted(0); + } +} + +public class SelectionSort : SortingAlgorithm +{ + public override string Name => "Selection Sort"; + public override string NameRu => "Выбором"; + + protected override void OnSort() + { + int n = _array.Length; + for (int i = 0; i < n - 1; i++) + { + int minIdx = i; + for (int j = i + 1; j < n; j++) + { + Compare(j, minIdx); + if (_array[j] < _array[minIdx]) + minIdx = j; + } + if (minIdx != i) + Swap(i, minIdx); + MarkSorted(i); + } + MarkSorted(n - 1); + } +} + +public class InsertionSort : SortingAlgorithm +{ + public override string Name => "Insertion Sort"; + public override string NameRu => "Вставками"; + + protected override void OnSort() + { + int n = _array.Length; + for (int i = 1; i < n; i++) + { + int key = _array[i]; + int j = i - 1; + Compare(j, i); + while (j >= 0 && _array[j] > key) + { + SetValue(j + 1, _array[j]); + j--; + if (j >= 0) Compare(j, i); + } + SetValue(j + 1, key); + } + MarkSorted(0); + } +} + +public class QuickSort : SortingAlgorithm +{ + public override string Name => "Quick Sort"; + public override string NameRu => "Быстрая"; + + protected override void OnSort() + { + QuickSortRecursive(0, _array.Length - 1); + MarkSorted(0); + } + + private void QuickSortRecursive(int low, int high) + { + if (low < high) + { + int pi = Partition(low, high); + QuickSortRecursive(low, pi - 1); + QuickSortRecursive(pi + 1, high); + } + } + + private int Partition(int low, int high) + { + int pivot = _array[high]; + MarkPivot(high); + int i = low - 1; + for (int j = low; j < high; j++) + { + Compare(j, high); + if (_array[j] < pivot) + { + i++; + if (i != j) Swap(i, j); + } + } + if (i + 1 != high) Swap(i + 1, high); + MarkSorted(i + 1); + return i + 1; + } +} + +public class MergeSort : SortingAlgorithm +{ + public override string Name => "Merge Sort"; + public override string NameRu => "Слиянием"; + + protected override void OnSort() + { + MergeSortRecursive(0, _array.Length - 1); + MarkSorted(0); + } + + private void MergeSortRecursive(int left, int right) + { + if (left < right) + { + int mid = left + (right - left) / 2; + MergeSortRecursive(left, mid); + MergeSortRecursive(mid + 1, right); + Merge(left, mid, right); + } + } + + private void Merge(int left, int mid, int right) + { + int n1 = mid - left + 1; + int n2 = right - mid; + int[] leftArr = new int[n1]; + int[] rightArr = new int[n2]; + + for (int x = 0; x < n1; x++) leftArr[x] = _array[left + x]; + for (int x = 0; x < n2; x++) rightArr[x] = _array[mid + 1 + x]; + + int i = 0, j = 0, k = left; + while (i < n1 && j < n2) + { + Compare(left + i, mid + 1 + j); + if (leftArr[i] <= rightArr[j]) + { + SetValue(k, leftArr[i]); + i++; + } + else + { + SetValue(k, rightArr[j]); + j++; + } + k++; + } + while (i < n1) + { + SetValue(k, leftArr[i]); + i++; k++; + } + while (j < n2) + { + SetValue(k, rightArr[j]); + j++; k++; + } + } +} + +public class HeapSort : SortingAlgorithm +{ + public override string Name => "Heap Sort"; + public override string NameRu => "Кучей"; + + protected override void OnSort() + { + int n = _array.Length; + for (int i = n / 2 - 1; i >= 0; i--) + Heapify(n, i); + for (int i = n - 1; i > 0; i--) + { + Swap(0, i); + MarkSorted(i); + Heapify(i, 0); + } + MarkSorted(0); + } + + private void Heapify(int heapSize, int root) + { + int largest = root; + int left = 2 * root + 1; + int right = 2 * root + 2; + + if (left < heapSize) + { + Compare(left, largest); + if (_array[left] > _array[largest]) + largest = left; + } + if (right < heapSize) + { + Compare(right, largest); + if (_array[right] > _array[largest]) + largest = right; + } + if (largest != root) + { + Swap(root, largest); + Heapify(heapSize, largest); + } + } +} \ No newline at end of file diff --git a/InternalSortMethods/ViewModels/MainViewModel.cs b/InternalSortMethods/ViewModels/MainViewModel.cs index a93e42b..6acb245 100644 --- a/InternalSortMethods/ViewModels/MainViewModel.cs +++ b/InternalSortMethods/ViewModels/MainViewModel.cs @@ -1,8 +1,262 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using System; +using System.Collections.ObjectModel; +using System.Threading; +using System.Threading.Tasks; +using Avalonia.Threading; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using InternalSortMethods.Models; namespace InternalSortMethods.ViewModels; public partial class MainViewModel : ViewModelBase { - [ObservableProperty] private string _greeting = "Welcome to Avalonia!"; + private CancellationTokenSource? _cancellationTokenSource; + private SortingResult? _currentResult; + private int _currentStepIndex; + private readonly ManualResetEventSlim _pauseEvent = new(true); + + [ObservableProperty] private ObservableCollection _arrayItems = []; + [ObservableProperty] private ObservableCollection _algorithms = []; + [ObservableProperty] private SortingAlgorithm? _selectedAlgorithm; + [ObservableProperty] private int _arraySize = 20; + [ObservableProperty] private double _animationSpeed = 500; + [ObservableProperty] private bool _isRunning; + [ObservableProperty] private bool _isPaused; + [ObservableProperty] private bool _isComplete; + [ObservableProperty] private string _statusText = "Готов"; + + public MainViewModel() + { + foreach (var algo in SortAlgorithmsProvider.Algorithms) + Algorithms.Add(algo); + SelectedAlgorithm = Algorithms[0]; + GenerateArray(); + } + + partial void OnSelectedAlgorithmChanged(SortingAlgorithm? value) + { + if (value != null && !IsRunning) + ResetCommand.Execute(null); + } + + partial void OnArraySizeChanged(int value) + { + if (!IsRunning) + GenerateArray(); + } + + [RelayCommand] + private void GenerateArray() + { + ArrayItems.Clear(); + var random = new Random(); + int maxVal = 100; + for (int i = 0; i < ArraySize; i++) + { + ArrayItems.Add(new ArrayItemModel + { + Value = random.Next(10, maxVal), + Index = i, + State = ArrayItemState.Normal + }); + } + ResetStates(); + StatusText = "Массив сгенерирован"; + } + + [RelayCommand(CanExecute = nameof(CanStart))] + private async Task StartAsync() + { + if (IsComplete || _currentResult == null) + { + _cancellationTokenSource?.Cancel(); + _cancellationTokenSource = new CancellationTokenSource(); + var token = _cancellationTokenSource.Token; + + IsRunning = true; + IsComplete = false; + StatusText = $"Сортировка: {SelectedAlgorithm?.NameRu}"; + + var arr = GetCurrentArray(); + _currentResult = SelectedAlgorithm!.Sort(arr); + _currentStepIndex = 0; + + _ = Task.Run(() => ProcessStepsAsync(token), token); + } + else + { + _pauseEvent.Set(); + IsPaused = false; + StatusText = $"Продолжено"; + } + + StartCommand.NotifyCanExecuteChanged(); + PauseCommand.NotifyCanExecuteChanged(); + StepForwardCommand.NotifyCanExecuteChanged(); + ResetCommand.NotifyCanExecuteChanged(); + } + + private bool CanStart() => !IsRunning || IsPaused || IsComplete; + + [RelayCommand(CanExecute = nameof(CanPause))] + private void Pause() + { + _pauseEvent.Reset(); + IsPaused = true; + StatusText = "Пауза"; + + StartCommand.NotifyCanExecuteChanged(); + PauseCommand.NotifyCanExecuteChanged(); + StepForwardCommand.NotifyCanExecuteChanged(); + } + + private bool CanPause() => IsRunning && !IsPaused; + + [RelayCommand(CanExecute = nameof(CanStep))] + private async Task StepForward() + { + if (_currentResult == null || _currentStepIndex >= _currentResult.Steps.Count) + return; + + _pauseEvent.Reset(); + IsPaused = true; + + await ApplyStepAsync(_currentResult.Steps[_currentStepIndex]); + _currentStepIndex++; + + if (_currentStepIndex >= _currentResult.Steps.Count) + Complete(); + + StartCommand.NotifyCanExecuteChanged(); + PauseCommand.NotifyCanExecuteChanged(); + StepForwardCommand.NotifyCanExecuteChanged(); + } + + private bool CanStep() => IsRunning && _currentResult != null && _currentStepIndex < _currentResult.Steps.Count; + + [RelayCommand] + private void Reset() + { + _cancellationTokenSource?.Cancel(); + _cancellationTokenSource = null; + _currentResult = null; + _currentStepIndex = 0; + IsRunning = false; + IsPaused = false; + IsComplete = false; + _pauseEvent.Set(); + + ResetStates(); + StatusText = "Сброшено"; + + StartCommand.NotifyCanExecuteChanged(); + PauseCommand.NotifyCanExecuteChanged(); + StepForwardCommand.NotifyCanExecuteChanged(); + ResetCommand.NotifyCanExecuteChanged(); + } + + private async Task ProcessStepsAsync(CancellationToken token) + { + try + { + while (_currentStepIndex < _currentResult!.Steps.Count && !token.IsCancellationRequested) + { + _pauseEvent.Wait(token); + + if (token.IsCancellationRequested) break; + + await ApplyStepAsync(_currentResult.Steps[_currentStepIndex]); + _currentStepIndex++; + + await Task.Delay((int)AnimationSpeed, token); + } + + if (!token.IsCancellationRequested) + Complete(); + } + catch (OperationCanceledException) { } + } + + private async Task ApplyStepAsync(SortStep step) + { + await Dispatcher.UIThread.InvokeAsync(() => + { + for (int i = 0; i < ArrayItems.Count; i++) + { + if (ArrayItems[i].State != ArrayItemState.Sorted) + ArrayItems[i].State = ArrayItemState.Normal; + } + + int i1 = step.Index1; + int i2 = step.Index2; + + switch (step.Action) + { + case SortActionType.Compare: + if (i1 >= 0 && i1 < ArrayItems.Count) + ArrayItems[i1].State = ArrayItemState.Comparing; + if (i2 >= 0 && i2 < ArrayItems.Count) + ArrayItems[i2].State = ArrayItemState.Comparing; + break; + + case SortActionType.Swap: + if (i1 >= 0 && i1 < ArrayItems.Count && i2 >= 0 && i2 < ArrayItems.Count) + { + (ArrayItems[i1].Value, ArrayItems[i2].Value) = (ArrayItems[i2].Value, ArrayItems[i1].Value); + ArrayItems[i1].State = ArrayItemState.Swapping; + ArrayItems[i2].State = ArrayItemState.Swapping; + } + break; + + case SortActionType.Set: + if (i1 >= 0 && i1 < ArrayItems.Count && step.ArraySnapshot != null && i1 < step.ArraySnapshot.Length) + ArrayItems[i1].Value = step.ArraySnapshot[i1]; + break; + + case SortActionType.MarkSorted: + if (i1 >= 0 && i1 < ArrayItems.Count) + ArrayItems[i1].State = ArrayItemState.Sorted; + break; + + case SortActionType.MarkPivot: + if (i1 >= 0 && i1 < ArrayItems.Count) + ArrayItems[i1].State = ArrayItemState.Pivot; + break; + } + }); + } + + private void Complete() + { + IsRunning = false; + IsComplete = true; + + Dispatcher.UIThread.Post(() => + { + foreach (var item in ArrayItems) + item.State = ArrayItemState.Sorted; + + StatusText = "Сортировка завершена!"; + + StartCommand.NotifyCanExecuteChanged(); + PauseCommand.NotifyCanExecuteChanged(); + StepForwardCommand.NotifyCanExecuteChanged(); + ResetCommand.NotifyCanExecuteChanged(); + }); + } + + private int[] GetCurrentArray() + { + int[] arr = new int[ArrayItems.Count]; + for (int i = 0; i < ArrayItems.Count; i++) + arr[i] = ArrayItems[i].Value; + return arr; + } + + private void ResetStates() + { + foreach (var item in ArrayItems) + item.State = ArrayItemState.Normal; + } } \ No newline at end of file diff --git a/InternalSortMethods/Views/MainView.axaml b/InternalSortMethods/Views/MainView.axaml index 9358c78..fdcc693 100644 --- a/InternalSortMethods/Views/MainView.axaml +++ b/InternalSortMethods/Views/MainView.axaml @@ -3,14 +3,110 @@ 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:controls="using:InternalSortMethods.Controls" + mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600" x:Class="InternalSortMethods.Views.MainView" x:DataType="vm:MainViewModel"> - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +