Visualizador de algoritmo de classificação em C ++ e SDL2 aprimorado

Aug 15 2020

Post original: Visualizador de algoritmos de classificação em C ++ e SDL2
Segui o conselho que você me deu e melhorei meu código. Também adicionei mais dois algoritmos de classificação. Estou procurando principalmente conselhos de legibilidade de código (integrado ao Windows com cmake e ninja)

Demonstração


adereços para @Zeta para a demonstração

main.cpp

#include "Engine.h"
#undef main

int main()
{
    try
    {
        // If the max number is higher than the window width it draws nothing other than a black screen :^)
        // Default draw method is line
        // Default sorting algorithm is bubble sort
        SortVis::Engine visualization(
            { 1024, 768 },
            1024,
            SortVis::Engine::SortAlgorithm::insertionSort,
            SortVis::Engine::DrawMethod::point
        );
        visualization.run();
    }
    catch (std::runtime_error& error)
    {
        std::cerr << error.what() << "\n";
    }
}

Motor.h

#ifndef ENGINE_H
#define ENGINE_H

#include "Coord.h"
#include <SDL.h>
#include <vector>
#include <iostream>

namespace SortVis
{
    class Engine
    {
    public:

        enum class DrawMethod
        {
            line,
            point
        };

        enum class SortAlgorithm
        {
            selectionSort,
            insertionSort,
            bubbleSort
        };

        // Random number generation
        Engine() = delete;
        Engine(Coord windowSize, int maxNumber);
        Engine(Coord windowSize, int maxNumber, SortAlgorithm algorithm);
        Engine(Coord windowSize, int maxNumber, SortAlgorithm algorithm, DrawMethod method);
        Engine(Coord windowSize, int maxNumber, const char* windowTitle);
        Engine(Coord windowSize, int maxNumber, const char* windowTitle, SortAlgorithm algorithm);
        Engine(Coord windowSize, int maxNumber, const char* windowTitle, SortAlgorithm algorithm, DrawMethod method);

        // Load from file
        Engine(Coord windowSize, const char* pathToNumbersFile);
        Engine(Coord windowSize, const char* pathToNumbersFile, SortAlgorithm algorithm);
        Engine(Coord windowSize, const char* pathToNumbersFile, SortAlgorithm algorithm, DrawMethod method);
        Engine(Coord windowSize, const char* pathToNumbersFile, const char* windowTitle);
        Engine(Coord windowSize, const char* pathToNumbersFile, const char* windowTitle, SortAlgorithm algorithm);
        Engine(Coord windowSize, const char* pathToNumbersFile, const char* windowTitle, SortAlgorithm algorithm, DrawMethod method);

        ~Engine();

        void run();     

    private:

        const Coord windowSize;
        const SortAlgorithm selectedSortAlgorithm = SortAlgorithm::bubbleSort;
        const DrawMethod selectedDrawMethod = DrawMethod::line;

        SDL_Window* window = nullptr;
        SDL_Renderer* renderer = nullptr;
        
        std::vector<int> numbers = { };
        int columnWidth = 0;
        int maxValue = 0;
        bool running = true;

        void initWindow(Coord windowSize, const char* windowTitle);
        void initRenderer();
        void calculateNumbers();        
        void loadFile(const char* pathToNumbersFile);
        
        void handleEvents();

        void draw();
        void drawSelection();

        void drawColumns();
        void drawPoints();

        void step();
        void stepBubbleSort();
        void stepInsertionSort();
        void stepSelectionSort();

        std::vector<int> generateRandom(int maxNumber);     
    };
}

#endif // ENGINE_H

Engine.cpp

#include "Engine.h"

#include <filesystem>
#include <fstream>
#include <random>
#include <utility>
#include <algorithm>
#include <numeric>
#include <string>

// --- CONSTRUCTORS --- (fml)

SortVis::Engine::Engine(Coord windowSize, int maxNumber)
    : windowSize(windowSize), numbers(generateRandom(maxNumber))
{
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, "Sort visualizer");
    initRenderer(); 
}

SortVis::Engine::Engine(Coord windowSize, int maxNumber, SortAlgorithm algorithm)
    : windowSize(windowSize), numbers(generateRandom(maxNumber)), selectedSortAlgorithm(algorithm)
{
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, "Sort visualizer");
    initRenderer();
}

SortVis::Engine::Engine(Coord windowSize, int maxNumber, SortAlgorithm algorithm, DrawMethod method)
    : windowSize(windowSize), numbers(generateRandom(maxNumber)),
    selectedSortAlgorithm(algorithm), selectedDrawMethod(method)
{
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, "Sort visualizer");
    initRenderer();
}

SortVis::Engine::Engine(Coord windowSize, int maxNumber, const char* windowTitle)
    : windowSize(windowSize), numbers(generateRandom(maxNumber))
{
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, windowTitle);
    initRenderer();
}

SortVis::Engine::Engine(Coord windowSize, int maxNumber, const char* windowTitle, SortAlgorithm algorithm)
    : windowSize(windowSize), numbers(generateRandom(maxNumber)), selectedSortAlgorithm(algorithm)
{
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, windowTitle);
    initRenderer();
}

SortVis::Engine::Engine(Coord windowSize, int maxNumber, const char* windowTitle, SortAlgorithm algorithm, DrawMethod method)
    : windowSize(windowSize), numbers(generateRandom(maxNumber)),
    selectedSortAlgorithm(algorithm), selectedDrawMethod(method)
{
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, windowTitle);
    initRenderer();
}

SortVis::Engine::Engine(Coord windowSize, const char* pathToNumbersFile)
    : windowSize(windowSize)
{
    if (!std::filesystem::exists(pathToNumbersFile))
    {
        throw std::runtime_error("That file does not exist. Make sure the path is correct.");
    }
    else
    {
        loadFile(pathToNumbersFile);
    }
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, "Sort visualizer");
    initRenderer();
}

SortVis::Engine::Engine(Coord windowSize, const char* pathToNumbersFile, SortAlgorithm algorithm)
    : windowSize(windowSize), selectedSortAlgorithm(algorithm)
{
    if (!std::filesystem::exists(pathToNumbersFile))
    {
        throw std::runtime_error("That file does not exist. Make sure the path is correct.");
    }
    else
    {
        loadFile(pathToNumbersFile);
    }
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, "Sort visualizer");
    initRenderer();
}

SortVis::Engine::Engine(Coord windowSize, const char* pathToNumbersFile, SortAlgorithm algorithm, DrawMethod method)
    : windowSize(windowSize), selectedSortAlgorithm(algorithm), selectedDrawMethod(method)
{
    if (!std::filesystem::exists(pathToNumbersFile))
    {
        throw std::runtime_error("That file does not exist. Make sure the path is correct.");
    }
    else
    {
        loadFile(pathToNumbersFile);
    }
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, "Sort visualizer");
    initRenderer();
}

SortVis::Engine::Engine(Coord windowSize, const char* pathToNumbersFile, const char* windowTitle)
    : windowSize(windowSize)
{
    if (!std::filesystem::exists(pathToNumbersFile))
    {
        throw std::runtime_error("That file does not exist. Make sure the path is correct.");
    }
    else
    {
        loadFile(pathToNumbersFile);
    }
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, windowTitle);
    initRenderer();
}

SortVis::Engine::Engine(Coord windowSize, const char* pathToNumbersFile, const char* windowTitle, SortAlgorithm algorithm)
    : windowSize(windowSize), selectedSortAlgorithm(algorithm)
{
    if (!std::filesystem::exists(pathToNumbersFile))
    {
        throw std::runtime_error("That file does not exist. Make sure the path is correct.");
    }
    else
    {
        loadFile(pathToNumbersFile);
    }
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, windowTitle);
    initRenderer();
}

SortVis::Engine::Engine(Coord windowSize, const char* pathToNumbersFile, const char* windowTitle, SortAlgorithm algorithm, DrawMethod method)
    : windowSize(windowSize), selectedSortAlgorithm(algorithm), selectedDrawMethod(method)
{
    if (!std::filesystem::exists(pathToNumbersFile))
    {
        throw std::runtime_error("That file does not exist. Make sure the path is correct.");
    }
    else
    {
        loadFile(pathToNumbersFile);
    }
    calculateNumbers();

    if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
    {
        throw std::runtime_error("Could not initialize SDL");
    }

    initWindow(windowSize, windowTitle);
    initRenderer();
}

// --- END OF CONSTRUCTORS ---

SortVis::Engine::~Engine()
{
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);

    SDL_Quit();
}

void SortVis::Engine::run()
{
    // Sets render draw color to black
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);

    while (running)
    {
        handleEvents();
        if (!std::is_sorted(numbers.begin(), numbers.end()))
        {
            step();
        }
        draw();
    }
}

void SortVis::Engine::step()
{
    switch (selectedSortAlgorithm)
    {
    case SortAlgorithm::bubbleSort:
        stepBubbleSort();
        break;

    case SortAlgorithm::insertionSort:
        stepInsertionSort();
        break;

    case SortAlgorithm::selectionSort:
        stepSelectionSort();
        break;

    default:
        break;
    }
}

void SortVis::Engine::stepBubbleSort()
{   
    static int i = 0;
    static int size = numbers.size();
    for (int j = 0; j < size - i - 1; ++j)
    {
        if (numbers[j] > numbers[j + 1])
        {
            std::swap(numbers[j], numbers[j + 1]);
        }           
    }
    ++i;
}

void SortVis::Engine::stepInsertionSort()
{
    static int i = 1;   
    for (int j = i; j > 0 && numbers[j - 1] > numbers[j]; --j)
    {
        std::swap(numbers[j - 1], numbers[j]);
    }
    ++i;
}

void SortVis::Engine::stepSelectionSort()
{
    static int i = 0;
    std::swap(numbers[i], numbers[std::min_element(numbers.begin() + i, numbers.end()) - numbers.begin()]);
    ++i;
}

void SortVis::Engine::draw()
{
    SDL_RenderClear(renderer);

    drawSelection();

    SDL_RenderPresent(renderer);
}

void SortVis::Engine::drawSelection()
{
    switch (selectedDrawMethod)
    {
    case DrawMethod::line:
        drawColumns();
        break;

    case DrawMethod::point:
        drawPoints();
        break;

    default:
        break;
    }
}

void SortVis::Engine::drawColumns()
{
    SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);

    SDL_Rect column;
    for (int i = numbers.size(); i > 0; --i)
    {
        column.x = (i-1) * columnWidth;
        column.w = columnWidth;
        column.h = (numbers[i - 1] * windowSize.Y) / maxValue;
        column.y = windowSize.Y - column.h;
        SDL_RenderFillRect(renderer, &column);
    }

    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
}

void SortVis::Engine::drawPoints()
{
    SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);

    // SDL_Point point;
    for (int i = numbers.size(); i > 0; --i)
    {
        // point.x = i - 1;
        // point.y = windowSize.Y - ((numbers[i - 1] * windowSize.Y) / maxValue);
        SDL_RenderDrawPoint(renderer, i - 1, windowSize.Y - ((numbers[i - 1] * windowSize.Y) / maxValue));
    }
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
}

void SortVis::Engine::handleEvents()
{
    SDL_Event Event;

    while (SDL_PollEvent(&Event))
    {
        switch (Event.type)
        {
        case SDL_QUIT:
            running = false;
            break;

        default:
            break;
        }
    }
}

std::vector<int> SortVis::Engine::generateRandom(int maxNumber)
{
    std::mt19937 seed(std::random_device{}());
    std::vector<int> num(maxNumber);
    std::iota(num.begin(), num.end(), 0);
    std::shuffle(num.begin(), num.end(), seed);

    std::cout << "Generated random number sequence.\n";

    return num;
}

void SortVis::Engine::calculateNumbers()
{
    columnWidth = windowSize.X / numbers.size();
    maxValue = *std::max_element(numbers.begin(), numbers.end());
}

void SortVis::Engine::loadFile(const char* pathToNumbersFile)
{
    std::ifstream NumbersFile(pathToNumbersFile);
    if (NumbersFile.is_open())
    {
        std::string Number;
        while (std::getline(NumbersFile, Number))
        {
            numbers.push_back(std::stoi(Number));
        }
    }
    else
    {
        throw std::runtime_error("Couldn't open numbers file.");
    }

    if (numbers.empty())
    {
        throw std::runtime_error("Numbers file is empty.");
    }

    std::cout << "Loaded numbers file.\n";
}

void SortVis::Engine::initWindow(Coord windowSize, const char* windowTitle)
{
    window = SDL_CreateWindow(
        windowTitle,
        SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED,
        windowSize.X,
        windowSize.Y,
        SDL_WINDOW_SHOWN
    );

    if (window == nullptr)
    {
        throw std::runtime_error("Could not initialize SDL window");
    }
}

void SortVis::Engine::initRenderer()
{
    renderer = SDL_CreateRenderer(
        window,
        -1,
        SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED
    );

    if (renderer == nullptr)
    {
        throw std::runtime_error("Could not initialize SDL renderer");
    }
}

Coord.h

#ifndef COORD_H
#define COORD_H

namespace SortVis
{
    struct Coord
    {
        int X;
        int Y;
    };
}

#endif // COORD_H

Respostas

3 Edward Aug 16 2020 at 13:29

O programa foi definitivamente melhorado em relação à versão anterior. Bom trabalho! Aqui estão algumas coisas que podem ajudá-lo a melhorá-lo ainda mais.

Não se repita (DRY)

Existem onze repetições do construtor, o que me parece um pouco excessivo, principalmente porque o código é quase idêntico. Eu os reduziria a exatamente um:

Engine(
    Coord windowSize, 
    std::vector<int>&& numbers, 
    SortAlgorithm algorithm = SortAlgorithm::bubbleSort, 
    DrawMethod method = DrawMethod::point,
    const char* windowTitle = "Sort visualizer"
); 

Ao fornecer um único construtor com parâmetros padrão, as repetições são eliminadas e a flexibilidade é aprimorada. Observe também que a matriz de números é passada como um argumento.

Reduza o tamanho da interface para apenas o necessário

Essas duas funções são genéricas e não precisam ser membros da classe:

std::vector<int> SortVis::loadFile(std::istream& numberFile);
std::vector<int> SortVis::generateRandom(int maxNumber);

Ao reduzir o tamanho da interface ao mínimo necessário, a classe fica menor e mais fácil de ler, entender, testar, usar, manter e adaptar. Observe também que o primeiro argumento recebe um em std::istream&vez de um nome de arquivo. Isso permite coisas úteis como carregar de um socket ou stringstream.

Prefiro falhar cedo

Se a SDL_InitSubSystemchamada no construtor falhar, não há muito sentido em continuar. Por esse motivo, a calculateNumberschamada mais demorada provavelmente deve vir depois, e não antes.

Só defina a cor se for desenhar algo

Os únicos lugares que SDL_SetRenderDrawColordevem ser usados ​​são imediatamente antes de algo ser realmente desenhado na tela. Por esse motivo, ele pode aparecer exatamente duas vezes neste código: uma vez no topo drawSelectione uma vez no topo draw.

Mova o trabalho para fora dos loops onde for prático

Em vez de recalcular todas as quatro partes de columncada vez por meio do loop, é possível simplesmente alterar aquelas que precisam ser alteradas:

SDL_Rect column{ 0, 0, columnWidth, 0 };
for (const auto n : numbers)
{
    column.h = n * windowSize.Y / maxValue;
    column.y = windowSize.Y - column.h;
    SDL_RenderFillRect(renderer, &column);
    column.x += columnWidth;
}

Use objetos em vez de switches

O código atualmente contém isto:

void SortVis::Engine::step()
{
    switch (selectedSortAlgorithm)
    {
    case SortAlgorithm::bubbleSort:
        stepBubbleSort();
        break;

    case SortAlgorithm::insertionSort:
        stepInsertionSort();
        break;

    case SortAlgorithm::selectionSort:
        stepSelectionSort();
        break;

    default:
        break;
    }
}

Isso requer a avaliação de selectedSortAlgorithmcada iteração. A melhor maneira de fazer isso é criar uma classe base virtual que demonstra a interface e, em seguida, permitir que o usuário crie uma classe derivada com qualquer novo tipo de tipo que desejar. Esta é uma maneira de fazer isso:

using Collection = std::vector<int>;
struct Sorter {
    std::string_view name;
    virtual bool step(Collection &) = 0;
    Sorter(Sorter&) = delete;
    Sorter(std::string_view name) 
        : name{name}
    {
    }
    virtual void reset(Collection& numbers) {
        a = original_a = 0;
        original_b = numbers.size();
    }
    virtual ~Sorter() = default;
    std::size_t a;
    std::size_t original_a; 
    std::size_t original_b;
};

struct BubbleSorter : public Sorter {
    BubbleSorter() : Sorter{"Bubble Sort"} { }
    bool step(Collection& numbers) {
        auto lag{original_a};
        for (auto it{lag + 1}; it < original_b; ++it, ++lag) {
            if (numbers[lag] > numbers[it]) {
                std::swap(numbers[lag], numbers[it]);
            }           
        }
        return ++a != original_b;
    }
};

Agora, para obter o máximo de flexibilidade, você pode passar esse objeto para o Engineem seu construtor:

Engine visualization{
    { 1024, 768 },
    generateRandom(1024),
    std::make_unique<BubbleSorter>()
};

Use um ponteiro para uma função-membro

Uma coisa semelhante pode ser feita para o método de desenho, se quiser, mas é um pouco mais complicado, pois usa um ponteiro para uma função de membro que tem uma sintaxe que pode ser difícil de acertar. Podemos declarar a variável dentro Enginedesta forma:

void (Engine::*drawSelection)();

Eu reaprovei seu drawSelectionnome aqui. Dentro do construtor, podemos usar isto:

drawSelection{method == DrawMethod::point ? &Engine::drawPoints : &Engine::drawColumns}

E, finalmente, para chamá-lo, precisamos usar o operador de acesso de ponteiro para membro. Aqui está no contexto:

void SortVis::Engine::draw() {
    // Sets render draw color to black
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
    SDL_RenderClear(renderer);
    SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
    (this->*(drawSelection))();
    SDL_RenderPresent(renderer);
}

Não faça trabalho que não seja necessário

No momento, o loop principal runé este:

while (running)
{
    handleEvents();
    if (!std::is_sorted(numbers.begin(), numbers.end()))
    {
        step();
    }
    draw();
}

Não faz muito sentido para mim fazer uma chamada para std::is_sorteddentro de um programa que demonstra classificação! Pareceu-me que gostaria de fechar o programa quando ele terminasse a classificação e modifiquei as rotinas de classificação para retornar boolcom o valor falseapenas quando a execução for concluída e o vetor for classificado. Então, para isso, o loop se transforma em:

while (running) {
    handleEvents();
    running &= sorter->step(numbers);
    draw();
}

Considere adicionar recursos

Eu sugeriria que seria bom adicionar alguns recursos. Aqui está o que eu fiz:

void SortVis::Engine::handleEvents() {
    SDL_Event Event;
    while (SDL_PollEvent(&Event)) {
        switch (Event.type) {
        case SDL_QUIT:
            running = false;
            break;
        case SDL_KEYDOWN:
            switch (Event.key.keysym.sym) {
                case SDLK_r:
                    numbers = generateRandom(maxValue);
                    sorter->reset(numbers);
                    break;
                case SDLK_b:
                    numbers = generateRandom(maxValue);
                    sorter = std::move(std::make_unique<BubbleSorter>());
                    sorter->reset(numbers);
                    break;
                case SDLK_i:
                    numbers = generateRandom(maxValue);
                    sorter = std::move(std::make_unique<InsertionSorter>());
                    sorter->reset(numbers);
                    break;
                case SDLK_s:
                    numbers = generateRandom(maxValue);
                    sorter = std::move(std::make_unique<SelectionSorter>());
                    sorter->reset(numbers);
                    break;
                case SDLK_l:
                    drawSelection = &Engine::drawColumns;
                    break;
                case SDLK_p:
                    drawSelection = &Engine::drawPoints;
                    break;
                case SDLK_q:
                    running = false;
                    break;
            }

        default:
            break;
        }
    }
}
2 G.Sliepen Aug 16 2020 at 10:34

Use namespace SortVis {...}emEngine.cpp

Você pode evitar a repetição do namespace em cada definição de função Engine.cppenvolvendo todo o código dentro dele namespace SortVis, assim como fez em Engine.h. Também é comum não recuar o código dentro de um namespace {...}bloco que cobre todo o arquivo, para evitar que o código saia do lado direito da tela com muita frequência.

Muitos construtores

Você tem 12 construtores diferentes, isso é um pouco demais. Imagine também que você possa querer adicionar outro parâmetro opcional no futuro e, em seguida, dobrar o número de construtores necessários. Isso não é sustentável a longo prazo. Vejo duas maneiras de reduzir o número de construtores necessários:

  1. Use valores de argumento padrão, como:

    Engine(Coord windowSize, int maxNumber,
           const char *windowTitle = "Sort visualizer",
           SortAlgorithm algorithm = SortAlgorithm::bubbleSort,
           DrawMethod method = DrawMethod::line);
    

    Com essa abordagem, você só precisa de dois construtores. Pode ser um pouco mais incômodo de usar se você quiser apenas especificar outro DrawMethod, mas é um preço pequeno para uma capacidade de manutenção muito melhor.

  2. Não especifique os valores de entrada, algoritmo e método de desenho no construtor, permita que eles sejam definidos por funções-membro, como:

    Engine(Coord windowSize, const char *windowTitle = "Sort visualizer");
    void generateNumbers(int maxNumber);
    void loadNumbers(const char *pathToNumbersFile);
    void setAlgorithm(SortAlgorithm algorithm);
    void setDrawMethod(DrawMethod method);
    

Em geral, faça no construtor apenas o que realmente precisa ser feito na hora da construção. Inicializar o SDL e abrir uma janela é crucial para um mecanismo de visualização em funcionamento, portanto, é bom fazer isso no construtor.

Considere não gerar / carregar números em class Engine

Em vez de ter de Enginegerar números aleatórios ou carregá-los de um arquivo, você pode simplificar não fazendo nada, mas apenas permitindo que ele use qualquer vetor que você fornecer. Então, por exemplo:

void run(const std::vector<int> &input) {
    numbers = input;
    ...
}

Você pode até considerar transmiti-lo como não constante e run()modificar o vetor de entrada fornecido.

Considere dividir a visualização da classificação

Um grande problema é que você precisa dividir seus algoritmos de classificação em etapas e ter uma switch()instrução step()para escolher o algoritmo certo. Você também precisa enumerar os algoritmos possíveis. Em vez de fazer isso, considere fazer Engineapenas visualizar um vetor de números e, em vez de Engineconduzir as etapas do algoritmo, tenha uma unidade de algoritmo Enginepara mostrar o estado do vetor em cada etapa. Você pode fazer isso alterando Engine::draw()para tomar uma referência a um vetor de números:

void Engine::draw(const std::vector<int> &numbers) {
     ...
}

E um algoritmo de classificação pode se tornar uma única função:

void bubbleSort(std::vector<int> &numbers, Engine &visualization) {
    for (size_t i = 0; i < numbers.size() - 1; ++i) {
        for (size_t j = 0; j < numbers.size() - 1; ++j) {
            if (numbers[j] > numbers[j + 1])) {
                std::swap(numbers[j], numbers[j + 1]);
            }
        }

        visualization.draw(numbers);
    }
}

E então você main()pode ficar assim:

int main() {
    std::vector<int> numbers = {...}; // generate some vector here

    SortVis::Engine visualization({1024, 768});
    SortVis::bubbleSort(numbers, visualization);
}

Os benefícios dessa abordagem são que você separa as preocupações. O Engineagora só precisa visualizar um vetor (provavelmente deve ser renomeado para algo parecido Visualizer). Você pode adicionar facilmente novos algoritmos de classificação sem ter que mudar Engine.

Um problema com o acima é que ele não lida mais com eventos SDL. Você poderia fazer isso em draw()e draw()retornar um boolindicando se o algoritmo deve continuar ou não.

Verifique se há erros depois de ler um arquivo, não antes

Em Engine::loadFile(), você verifica se o arquivo está aberto corretamente, mas nunca verifica se ocorreu um erro durante a leitura. Uma maneira possível é:

std::ifstream NumbersFile(pathToNumbersFile);

std::string Number;
while (std::getline(NumbersFile, Number) {
    numbers.push_back(std::stoi(Number));
}

if (!NumbersFile.eof()) {
    throw std::runtime_error("Error while reading numbers file.");
}

Aqui usamos o fato de que o eofbitsó é definido se atingir o final do arquivo com sucesso, não será definido se o arquivo falhar ao abrir ou se ocorrer um erro antes de chegar ao final do arquivo.