Compare commits

1 Commits
master ... qwen

Author SHA1 Message Date
912daa6069 - add: sort think 2026-04-12 19:15:09 +03:00
18 changed files with 1525 additions and 12 deletions

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="InternalSortMethods.Android">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>

View File

@@ -1,4 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise">
<file url="file://$PROJECT_DIR$/InternalSortMethods/ViewModels/MainViewModel.cs" charset="UTF-8" />
</component>
</project>

View File

@@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="androidx.annotation.annotation-jvm">
<CLASSES>
<root url="jar://$USER_HOME$/.nuget/packages/xamarin.androidx.annotation.jvm/1.9.1.7/jar/androidx.annotation.annotation-jvm.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

13
.qwen/settings.json Normal file
View File

@@ -0,0 +1,13 @@
{
"permissions": {
"allow": [
"Bash(mkdir *)",
"Bash(dotnet *)",
"Bash(powershell *)",
"Bash(del *)",
"Bash(rmdir *)",
"Bash(findstr *)"
]
},
"$version": 3
}

7
.qwen/settings.json.orig Normal file
View File

@@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"Bash(mkdir *)"
]
}
}

View File

@@ -2,8 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:InternalSortMethods"
x:Class="InternalSortMethods.App"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
RequestedThemeVariant="Dark">
<Application.DataTemplates>
<local:ViewLocator/>
@@ -11,5 +10,52 @@
<Application.Styles>
<FluentTheme />
<!-- Liquid Glass стили встроены напрямую -->
<Style Selector="Button.glass-btn">
<Setter Property="Background">
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Color="#ffffff15" Offset="0.0"/>
<GradientStop Color="#ffffff0a" Offset="1.0"/>
</LinearGradientBrush>
</Setter>
<Setter Property="Foreground" Value="#f0f0f5"/>
<Setter Property="BorderBrush">
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Color="#ffffff25" Offset="0.0"/>
<GradientStop Color="#ffffff0d" Offset="1.0"/>
</LinearGradientBrush>
</Setter>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Padding" Value="16,10"/>
<Style Selector="^:disabled">
<Setter Property="Opacity" Value="0.4"/>
</Style>
</Style>
<Style Selector="Button.accent-btn">
<Setter Property="Background">
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Color="#7c5cfc" Offset="0.0"/>
<GradientStop Color="#6d28d9" Offset="1.0"/>
</LinearGradientBrush>
</Setter>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="FontWeight" Value="SemiBold"/>
<Setter Property="Padding" Value="16,10"/>
<Style Selector="^:pointerover">
<Setter Property="Opacity" Value="0.9"/>
</Style>
<Style Selector="^:pressed">
<Setter Property="Opacity" Value="0.8"/>
</Style>
<Style Selector="^:disabled">
<Setter Property="Opacity" Value="0.4"/>
</Style>
</Style>
</Application.Styles>
</Application>
</Application>

View File

@@ -0,0 +1,36 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
using Avalonia.Media;
using InternalSortMethods.ViewModels;
namespace InternalSortMethods.Converters;
/// <summary>
/// Конвертирует SortItemState в соответствующий цвет столбца.
/// Используется в DataTemplate для визуализации.
/// </summary>
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();
}
}

View File

@@ -0,0 +1,73 @@
using InternalSortMethods.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace InternalSortMethods.Models;
/// <summary>
/// Сортировка пузырьком (Bubble Sort).
/// Последовательно сравнивает соседние элементы и меняет их, если они стоят в неверном порядке.
/// Сложность: O(n²) по времени, O(1) по памяти.
/// </summary>
public sealed class BubbleSort : SortAlgorithm
{
public override string DisplayName => "Bubble Sort";
public override IEnumerable<SortStep> 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()
};
}
}

View File

@@ -0,0 +1,121 @@
using InternalSortMethods.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace InternalSortMethods.Models;
/// <summary>
/// Пирамидальная сортировка (Heap Sort).
/// Строит максимальную кучу, затем извлекает корень и восстанавливает кучу.
/// Сложность: O(n log n) по времени, O(1) по памяти.
/// </summary>
public sealed class HeapSort : SortAlgorithm
{
public override string DisplayName => "Heap Sort";
public override IEnumerable<SortStep> 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()
};
}
/// <summary>
/// Восстанавливает свойство максимальной кучи для поддерева с корнем в index.
/// </summary>
private IEnumerable<SortStep> 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;
}
}
}

View File

@@ -0,0 +1,88 @@
using InternalSortMethods.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace InternalSortMethods.Models;
/// <summary>
/// Сортировка вставками (Insertion Sort).
/// Вставляет каждый элемент в уже отсортированную часть массива.
/// Сложность: O(n²) по времени, O(1) по памяти.
/// </summary>
public sealed class InsertionSort : SortAlgorithm
{
public override string DisplayName => "Insertion Sort";
public override IEnumerable<SortStep> 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()
};
}
}
}

View File

@@ -0,0 +1,140 @@
using InternalSortMethods.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace InternalSortMethods.Models;
/// <summary>
/// Сортировка слиянием (Merge Sort).
/// Рекурсивно делит массив пополам, сортирует части и сливает их.
/// Сложность: O(n log n) по времени, O(n) по памяти.
/// </summary>
public sealed class MergeSort : SortAlgorithm
{
public override string DisplayName => "Merge Sort";
public override IEnumerable<SortStep> 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<SortStep> 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<SortStep> 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++;
}
}
}

View File

@@ -0,0 +1,154 @@
using InternalSortMethods.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace InternalSortMethods.Models;
/// <summary>
/// Быстрая сортировка (Quick Sort).
/// Разделяет массив по опорному элементу (pivot) и рекурсивно сортирует части.
/// Сложность: O(n log n) в среднем, O(n²) в худшем случае.
/// </summary>
public sealed class QuickSort : SortAlgorithm
{
public override string DisplayName => "Quick Sort";
public override IEnumerable<SortStep> 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<SortStep> 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;
}
/// <summary>
/// Генерирует шаги для операции разбиения (partition) с визуализацией.
/// </summary>
private IEnumerable<SortStep> 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];
}
}

View File

@@ -0,0 +1,83 @@
using InternalSortMethods.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace InternalSortMethods.Models;
/// <summary>
/// Сортировка выбором (Selection Sort).
/// Находит минимальный элемент в неотсортированной части и ставит его на позицию.
/// Сложность: O(n²) по времени, O(1) по памяти.
/// </summary>
public sealed class SelectionSort : SortAlgorithm
{
public override string DisplayName => "Selection Sort";
public override IEnumerable<SortStep> 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()
};
}
}

View File

@@ -0,0 +1,27 @@
using InternalSortMethods.Models;
using System.Collections.Generic;
using System.Threading;
namespace InternalSortMethods.Models;
/// <summary>
/// Базовый класс для всех алгоритмов сортировки.
/// Генерирует последовательность шагов (SortStep), которые ViewModel выполняет
/// с задержкой для анимации. Это позволяет реализовать паузу, шаг вперёд и отмену
/// без блокировки UI-потока.
/// </summary>
public abstract class SortAlgorithm
{
/// <summary>Отображаемое название алгоритма.</summary>
public abstract string DisplayName { get; }
/// <summary>
/// Генерирует шаги сортировки для заданного массива.
/// Каждый шаг содержит копию состояния массива после операции,
/// что позволяет визуализировать процесс пошагово.
/// </summary>
/// <param name="array">Массив для сортировки.</param>
/// <param name="cancellationToken">Токен отмены для прерывания генерации шагов.</param>
/// <returns>Последовательность шагов сортировки.</returns>
public abstract IEnumerable<SortStep> GenerateSteps(int[] array, CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,43 @@
using System;
namespace InternalSortMethods.Models;
/// <summary>
/// Представляет один шаг выполнения алгоритма сортировки.
/// Используется для пошаговой визуализации с анимацией.
/// </summary>
public sealed class SortStep
{
/// <summary>Тип операции шага.</summary>
public SortStepType Type { get; init; }
/// <summary>Индексы элементов, участвующих в операции (сравнение или перестановка).</summary>
public int[] Indices { get; init; } = [];
/// <summary>
/// Состояние массива после выполнения данного шага.
/// Копия создаётся для того, чтобы UI мог отобразить актуальное состояние.
/// </summary>
public int[] ArrayState { get; init; } = [];
}
/// <summary>
/// Типы операций шага сортировки для визуальной подсветки.
/// </summary>
public enum SortStepType
{
/// <summary>Сравнение двух элементов (подсвечиваем жёлтым).</summary>
Compare,
/// <summary>Перестановка двух элементов (подсвечиваем красным).</summary>
Swap,
/// <summary>Запись/вставка элемента на позицию (подсвечиваем голубым).</summary>
Write,
/// <summary>Элемент определён как опорный (pivot) — для QuickSort.</summary>
Pivot,
/// <summary>Элемент окончательно стоит на своём месте.</summary>
Sorted
}

View File

@@ -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;
/// <summary>
/// Ãëàâíàÿ ViewModel ïðèëîæåíèÿ.
///
/// Ðàçäåëåíèå îòâåòñòâåííîñòè ïî MVVM:
/// - ViewModel ÍÅ ñîäåðæèò ññûëîê íà UI-ýëåìåíòû (Button, ListBox è ò.ä.)
/// - Âñå äàííûå äëÿ îòîáðàæåíèÿ (Items, IsRunning è ò.ä.) — ýòî ñâîéñòâà ñ INotifyPropertyChanged
/// - Âñå äåéñòâèÿ ïîëüçîâàòåëÿ îáðàáàòûâàþòñÿ ÷åðåç RelayCommand
/// - Àëãîðèòìû ñîðòèðîâêè íàõîäÿòñÿ â îòäåëüíûõ Model-êëàññàõ
///
/// Àñèíõðîííîñòü áåç Thread.Sleep:
/// - Èñïîëüçóåòñÿ await Task.Delay(ms, _cancellationToken) — ýòî ÍÅ áëîêèðóåò UI-ïîòîê
/// - CancellationToken ïîçâîëÿåò ìãíîâåííî îòìåíèòü âûïîëíåíèå
/// - SemaphoreSlim èñïîëüçóåòñÿ äëÿ ïàóçû/ïðîäîëæåíèÿ
/// </summary>
public partial class MainViewModel : ViewModelBase
{
[ObservableProperty] private string _greeting = "Welcome to Avalonia!";
}
// ===== Êîíñòàíòû =====
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<SortStep>? _stepEnumerator;
private System.Collections.Generic.List<SortStep>? _cachedSteps;
private int _currentStepIndex;
// ===== ObservableProperty (CommunityToolkit ãåíåðèðóåò INPC) =====
[ObservableProperty] private ObservableCollection<SortItemViewModel> _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;
// ===== Âû÷èñëÿåìûå ñâîéñòâà =====
/// <summary>Ñïèñîê íàçâàíèé àëãîðèòìîâ äëÿ ComboBox.</summary>
public string[] AlgorithmNames => Algorithms.Select(a => a.DisplayName).ToArray();
/// <summary>Ìîæíî ëè çàïóñòèòü ñîðòèðîâêó.</summary>
public bool CanStart => !IsRunning && Items.Count > 0;
/// <summary>Ìîæíî ëè ïîñòàâèòü íà ïàóçó/ïðîäîëæèòü.</summary>
public bool CanPause => IsRunning && !IsCompleted;
/// <summary>Ìîæíî ëè ñäåëàòü øàã âïåð¸ä (ðàáîòàåò òîëüêî íà ïàóçå).</summary>
public bool CanStepForward => IsRunning && IsPaused && !IsCompleted;
/// <summary>Ìîæíî ëè ñáðîñèòü.</summary>
public bool CanReset => IsRunning || IsCompleted || Items.Count > 0;
// ===== Êîìàíäû =====
/// <summary>
/// Ãåíåðàöèÿ íîâîãî ñëó÷àéíîãî ìàññèâà.
/// </summary>
[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} ýëåìåíòîâ";
}
/// <summary>
/// Çàïóñê ñîðòèðîâêè.
///
/// Àñèíõðîííîñòü áåç Thread.Sleep:
/// - Task.Delay ÍÅ áëîêèðóåò UI-ïîòîê (èñïîëüçóåòñÿ continuation)
/// - CancellationToken ïîçâîëÿåò îòìåíèòü çàäåðæêó
/// </summary>
[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;
}
}
/// <summary>
/// Ïàóçà èëè ïðîäîëæåíèå âûïîëíåíèÿ.
///
/// Êàê ðàáîòàåò ïàóçà:
/// - SemaphoreSlim âûñòóïàåò êàê «âîðîòà».
/// - Ïðè ïàóçå ìû «çàáèðàåì» permit (Wait), è öèêë ñîðòèðîâêè áëîêèðóåòñÿ íà WaitAsync.
/// - Ïðè ïðîäîëæåíèè ìû «âîçâðàùàåì» permit (Release), è öèêë ïðîäîëæàåò ðàáîòó.
/// - Äîïîëíèòåëüíî ôëàã IsPaused àêòèâèðóåò îæèäàíèå ÷åðåç WaitUntilResumed.
/// </summary>
[RelayCommand]
private void PauseResume()
{
if (!IsRunning || IsCompleted) return;
IsPaused = !IsPaused;
if (IsPaused)
{
// Çàáèðàåì permit — ñëåäóþùèé WaitAsync çàáëîêèðóåòñÿ
_pauseSemaphore.Wait();
StatusText = "Ïàóçà";
}
else
{
// Âîçâðàùàåì permit — ðàçáëîêèðóåì WaitAsync
_pauseSemaphore.Release();
StatusText = "Ïðîäîëæåíèå...";
}
}
/// <summary>
/// Øàã âïåð¸ä — âûïîëíÿåò îäèí øàã ñîðòèðîâêè.
/// Ðàáîòàåò òîëüêî êîãäà ñîðòèðîâêà íà ïàóçå.
/// </summary>
[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();
}
}
/// <summary>
/// Ñáðîñ — îòìåíÿåò âûïîëíåíèå è âîçâðàùàåò èñõîäíîå ñîñòîÿíèå.
/// </summary>
[RelayCommand]
private void Reset()
{
CancelRunningOperation();
ResetState();
UpdateItems(_currentArray);
StatusText = "Ãîòîâî";
}
// ===== Âñïîìîãàòåëüíûå ìåòîäû =====
/// <summary>
/// Àñèíõðîííîå îæèäàíèå ñíÿòèÿ ïàóçû.
/// Ïðîâåðÿåì êàæäûå 50ìñ, íå áûëà ëè îòìåíåíà îïåðàöèÿ.
/// Ýòî ÍÅ áëîêèðóåò UI-ïîòîê áëàãîäàðÿ await Task.Delay.
/// </summary>
private async Task WaitUntilResumed(CancellationToken token)
{
while (IsPaused)
{
token.ThrowIfCancellationRequested();
await Task.Delay(50, token);
}
}
/// <summary>
/// Ïðèìåíÿåò îäèí øàã ñîðòèðîâêè ê Items è îáíîâëÿåò ñòàòèñòèêó.
/// </summary>
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();
}
}
}

View File

@@ -0,0 +1,62 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace InternalSortMethods.ViewModels;
/// <summary>
/// ViewModel для одного элемента массива в визуализации.
/// Содержит значение и текущее состояние подсветки (цвет).
/// </summary>
public partial class SortItemViewModel : ObservableObject
{
private int _value;
private SortItemState _state = SortItemState.Default;
public SortItemViewModel(int value)
{
_value = value;
}
/// <summary>Значение элемента (высота столбца).</summary>
public int Value
{
get => _value;
set => SetProperty(ref _value, value);
}
/// <summary>
/// Текущее состояние элемента для визуальной подсветки.
/// Изменяется при каждом шаге сортировки.
/// </summary>
public SortItemState State
{
get => _state;
set => SetProperty(ref _state, value);
}
/// <summary>Относительная высота для привязки к UI (0.0 1.0).</summary>
public double NormalizedHeight { get; set; }
}
/// <summary>
/// Состояния элемента для цветовой подсветки.
/// </summary>
public enum SortItemState
{
/// <summary>Обычное состояние (нейтральный цвет).</summary>
Default,
/// <summary>Элемент сравнивается с другим (жёлтый/оранжевый).</summary>
Comparing,
/// <summary>Элемент участвует в перестановке (красный).</summary>
Swapping,
/// <summary>Элемент записан/вставлен (синий/голубой).</summary>
Writing,
/// <summary>Опорный элемент — pivot для QuickSort (фиолетовый).</summary>
Pivot,
/// <summary>Элемент окончательно отсортирован (зелёный).</summary>
Sorted
}

View File

@@ -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">
<Design.DataContext>
<!-- This only sets the DataContext for the previewer in an IDE,
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
<vm:MainViewModel />
</Design.DataContext>
<TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<!-- ===== Конвертеры ===== -->
<UserControl.Resources>
<converters:StateToColorConverter x:Key="StateToColor"/>
</UserControl.Resources>
<!-- Основной фон (градиент) -->
<Panel>
<Panel.Background>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Color="#0f0c29" Offset="0.0"/>
<GradientStop Color="#302b63" Offset="0.5"/>
<GradientStop Color="#24243e" Offset="1.0"/>
</LinearGradientBrush>
</Panel.Background>
<ScrollViewer VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<Grid RowDefinitions="Auto,*,Auto" Margin="16">
<!-- ===== Заголовок ===== -->
<StackPanel Grid.Row="0" Spacing="4" Margin="0,0,0,16">
<TextBlock Text="Методы внутренней сортировки"
FontSize="24" FontWeight="Bold"
Foreground="#f0f0f5"
HorizontalAlignment="Center"/>
<TextBlock Text="Визуализация алгоритмов сортировки"
FontSize="13"
Foreground="#a0a0b0"
HorizontalAlignment="Center"/>
</StackPanel>
<!-- ===== Область визуализации ===== -->
<Border Grid.Row="1" Margin="0,0,0,16" MinHeight="250"
CornerRadius="16" Padding="16"
BoxShadow="0 8 32 0 #00000050">
<Border.Background>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Color="#121212" Offset="0.0"/>
<GradientStop Color="#131313" Offset="1.0"/>
</LinearGradientBrush>
</Border.Background>
<DockPanel>
<!-- Статус и прогресс -->
<StackPanel DockPanel.Dock="Top" Spacing="8" Margin="0,0,0,12">
<ProgressBar Value="{Binding CurrentStep}"
Maximum="{Binding TotalSteps}"
Height="8" CornerRadius="4">
<ProgressBar.Background>
<SolidColorBrush Color="#ffffff15"/>
</ProgressBar.Background>
<ProgressBar.Foreground>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,0%">
<GradientStop Color="#7c5cfc" Offset="0.0"/>
<GradientStop Color="#6d28d9" Offset="1.0"/>
</LinearGradientBrush>
</ProgressBar.Foreground>
</ProgressBar>
<Grid ColumnDefinitions="*,Auto,Auto">
<TextBlock Grid.Column="0"
Text="{Binding StatusText}"
FontSize="13" Foreground="#a0a0b0"
VerticalAlignment="Center"/>
<StackPanel Grid.Column="1" Orientation="Horizontal" Spacing="12"
VerticalAlignment="Center">
<TextBlock FontSize="12" Foreground="#fbbf24"
Text="{Binding ComparisonsCount, StringFormat='⚡ {0}'}"/>
<TextBlock FontSize="12" Foreground="#f87171"
Text="{Binding SwapsCount, StringFormat='⇄ {0}'}"/>
</StackPanel>
<TextBlock Grid.Column="2" FontSize="12" Foreground="#818cf8"
VerticalAlignment="Center" Margin="12,0,0,0">
<TextBlock.Text>
<MultiBinding StringFormat="Шаг {0}/{1}">
<Binding Path="CurrentStep"/>
<Binding Path="TotalSteps"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</Grid>
</StackPanel>
<!-- Столбчатая диаграмма -->
<Viewbox Stretch="Uniform" StretchDirection="Both"
VerticalAlignment="Bottom">
<ItemsControl ItemsSource="{Binding Items}"
MinHeight="200" MaxHeight="400">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Spacing="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:SortItemViewModel">
<!-- Столбец: высота = Value, цвет = по State -->
<Border Width="14"
Height="{Binding Value}"
VerticalAlignment="Bottom"
CornerRadius="3,3,0,0"
BoxShadow="0 2 4 0 #00000040">
<Border.Background>
<SolidColorBrush Color="{Binding State, Converter={StaticResource StateToColor}}"/>
</Border.Background>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Viewbox>
</DockPanel>
</Border>
<!-- ===== Панель управления ===== -->
<Border Grid.Row="2" CornerRadius="16" Padding="16"
BoxShadow="0 8 32 0 #00000050">
<Border.Background>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Color="#121212" Offset="0.0"/>
<GradientStop Color="#131313" Offset="1.0"/>
</LinearGradientBrush>
</Border.Background>
<StackPanel Spacing="16">
<!-- Алгоритм + Размер массива -->
<Grid ColumnDefinitions="*,*" ColumnSpacing="16">
<StackPanel Grid.Column="0" Spacing="6">
<TextBlock Text="Алгоритм" FontSize="11" Foreground="#a0a0b0"
FontWeight="SemiBold"/>
<ComboBox ItemsSource="{Binding AlgorithmNames}"
SelectedIndex="{Binding SelectedAlgorithmIndex}"
CornerRadius="12" MinHeight="48" FontSize="14">
<ComboBox.Background>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Color="#121212" Offset="0.0"/>
<GradientStop Color="#131313" Offset="1.0"/>
</LinearGradientBrush>
</ComboBox.Background>
<ComboBox.Foreground>
<SolidColorBrush Color="#f0f0f5"/>
</ComboBox.Foreground>
</ComboBox>
</StackPanel>
<StackPanel Grid.Column="1" Spacing="6">
<TextBlock Text="Размер массива" FontSize="11" Foreground="#a0a0b0"
FontWeight="SemiBold"/>
<Grid ColumnDefinitions="*,Auto">
<Slider Grid.Column="0"
Value="{Binding ArraySize}"
Minimum="5" Maximum="80"
TickFrequency="1" IsSnapToTickEnabled="True"
MinHeight="48"/>
<TextBlock Grid.Column="1"
Text="{Binding ArraySize}"
FontSize="16" FontWeight="Bold"
Foreground="#f0f0f5"
VerticalAlignment="Center"
Margin="12,0,0,0" MinWidth="32"
TextAlignment="Center"/>
</Grid>
</StackPanel>
</Grid>
<!-- Скорость -->
<StackPanel Spacing="6">
<Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0" Text="Скорость"
FontSize="11" Foreground="#a0a0b0"
FontWeight="SemiBold" VerticalAlignment="Center"/>
<Slider Grid.Column="1"
Value="{Binding AnimationSpeed}"
Minimum="1" Maximum="500"
TickFrequency="10" IsSnapToTickEnabled="True"
MinHeight="48"/>
<TextBlock Grid.Column="2"
Text="{Binding AnimationSpeed, StringFormat={}{0} мс}"
FontSize="13" Foreground="#a0a0b0"
VerticalAlignment="Center" MinWidth="60"
TextAlignment="Right" Margin="12,0,0,0"/>
</Grid>
</StackPanel>
<!-- Кнопки -->
<Grid ColumnDefinitions="*,*,Auto,Auto,Auto" ColumnSpacing="8">
<Button Grid.Column="0" Content="Генерация"
Classes="glass-btn"
Command="{Binding GenerateArrayCommand}"
CornerRadius="12" MinHeight="48"
HorizontalAlignment="Stretch"/>
<Button Grid.Column="1" Content="▶ Старт"
Classes="accent-btn"
Command="{Binding StartSortCommand}"
IsEnabled="{Binding CanStart}"
CornerRadius="12" MinHeight="48"
HorizontalAlignment="Stretch"/>
<Button Grid.Column="2" Content="⏸"
Classes="glass-btn"
Command="{Binding PauseResumeCommand}"
IsEnabled="{Binding CanPause}"
CornerRadius="12" MinHeight="48" MinWidth="52"
Padding="12,10" FontSize="18"/>
<Button Grid.Column="3" Content="⏭"
Classes="glass-btn"
Command="{Binding StepForwardCommand}"
IsEnabled="{Binding CanStepForward}"
CornerRadius="12" MinHeight="48" MinWidth="52"
Padding="12,10" FontSize="18"/>
<Button Grid.Column="4" Content="↺"
Classes="glass-btn"
Command="{Binding ResetCommand}"
IsEnabled="{Binding CanReset}"
CornerRadius="12" MinHeight="48" MinWidth="52"
Padding="12,10" FontSize="18"/>
</Grid>
</StackPanel>
</Border>
</Grid>
</ScrollViewer>
</Panel>
</UserControl>