Visualizador de algoritmos de clasificación en C ++ y SDL2 Mejorado

Aug 15 2020

Publicación original: Visualizador de algoritmos de ordenación en C ++ y SDL2
Seguí los consejos que me diste y mejoré mi código. También agregué dos algoritmos de clasificación más. Principalmente busco consejos sobre legibilidad de código (construido en Windows con cmake y ninja)

Demostración


apoyos a @Zeta para la demostración

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";
    }
}

Engine.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

Respuestas

3 Edward Aug 16 2020 at 13:29

El programa definitivamente se ha mejorado con respecto a la versión anterior. ¡Buen trabajo! Aquí hay algunas cosas que pueden ayudarlo a mejorarlo aún más.

No se repita (SECO)

Hay once repeticiones del constructor, lo que me parece un poco excesivo, sobre todo porque el código es casi idéntico. Los reduciría a exactamente uno:

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

Al proporcionar un único constructor con los parámetros predeterminados, se eliminan las repeticiones y se mejora la flexibilidad. Tenga en cuenta también que la matriz de números se pasa como argumento.

Reducir el tamaño de la interfaz a solo lo necesario

Estas dos funciones son genéricas y no es necesario que sean miembros de la clase:

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

Al reducir el tamaño de la interfaz al mínimo necesario, la clase es más pequeña y más fácil de leer, comprender, probar, usar, mantener y adaptar. Tenga en cuenta también que el primer argumento toma un en std::istream&lugar de un nombre de archivo. Esto permite cosas tan útiles como cargar desde un socket o un stringstream.

Prefiero fallar temprano

Si la SDL_InitSubSystemllamada en el constructor falla, no tiene mucho sentido continuar. Por esa razón, la calculateNumbersllamada que consume más tiempo probablemente debería ser posterior a la anterior.

Solo establece el color si vas a dibujar algo

Los únicos lugares que SDL_SetRenderDrawColordeben usarse es justo antes de que se dibuje algo en la pantalla. Por esa razón, puede aparecer exactamente dos veces en este código: una en la parte superior de drawSelectiony otra en la parte superior de draw.

Mueva el trabajo fuera de los bucles donde sea práctico

En lugar de volver a calcular las cuatro partes de columncada vez a través del ciclo, es posible simplemente cambiar las que necesitan cambiar:

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;
}

Usa objetos en lugar de switches

El código contiene actualmente esto:

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

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

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

    default:
        break;
    }
}

Esto requiere la evaluación de selectedSortAlgorithmcada iteración. La mejor manera de hacer esto es crear una clase base virtual que demuestre la interfaz y luego permitir que el usuario cree una clase derivada con cualquier tipo nuevo que desee. He aquí una forma de hacerlo:

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;
    }
};

Ahora, para una máxima flexibilidad, puede pasar un objeto de este tipo Enginea su constructor:

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

Use un puntero a una función miembro

Se puede hacer algo similar para el método de dibujo si lo desea, pero es un poco más complicado ya que usa un puntero a una función miembro que tiene una sintaxis que puede ser difícil de corregir. Podríamos declarar la variable dentro de Engineesta manera:

void (Engine::*drawSelection)();

He reutilizado tu drawSelectionnombre aquí. Dentro del constructor podemos usar esto:

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

Y finalmente para llamarlo, necesitamos usar el operador de acceso de puntero a miembro. Aquí está en 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);
}

No hagas un trabajo que no sea necesario

En este momento, el bucle principal runes este:

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

¡No tiene mucho sentido para mí hacer una llamada std::is_sorteddentro de un programa que demuestra la clasificación! Me pareció que querría cerrar el programa cuando terminara la clasificación y modifiqué las rutinas de clasificación para que regresen boolcon el valor de falsesolo cuando haya terminado de ejecutarse y el vector esté ordenado. Entonces, para eso, el bucle se convierte en esto:

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

Considere agregar funciones

Sugeriría que sería bueno agregar algunas características. Esto es lo que hice:

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

Usar namespace SortVis {...}enEngine.cpp

Puede evitar repetir el espacio de nombres en cada definición de función Engine.cppal incluir todo el código en el interior namespace SortVis, tal como lo hizo en Engine.h. También es común no sangrar el código dentro de un namespace {...}bloque que cubre todo el archivo, para evitar que el código se salga de la mano derecha de la pantalla con demasiada frecuencia.

Demasiados constructores

Tienes 12 constructores diferentes, eso es demasiado. También imagina que quizás quieras agregar otro parámetro opcional en el futuro, luego duplicarás la cantidad de constructores requeridos. Esto no se puede mantener a largo plazo. Veo dos formas de reducir la cantidad de constructores necesarios:

  1. Use valores de argumento predeterminados, así:

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

    Con este enfoque, solo necesita dos constructores. Puede ser un poco más molesto de usar si solo desea especificar otro DrawMethod, pero es un precio pequeño por una capacidad de mantenimiento mucho mejor.

  2. No especifique los valores de entrada, el algoritmo y el método de dibujo en el constructor, permita que estos se establezcan mediante funciones miembro, así:

    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);
    

En general, solo haga en el constructor lo que realmente necesita hacerse en el momento de la construcción. Inicializar SDL y abrir una ventana es crucial para un motor de visualización en funcionamiento, por lo que es bueno hacerlo en el constructor.

Considere no generar / cargar números en class Engine

En lugar de tener que Enginegenerar números aleatorios o cargarlos desde un archivo, puede simplificarlo al no hacerlo en absoluto, sino simplemente permitirle usar cualquier vector que le dé. Así por ejemplo:

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

Incluso puede considerar pasarlo como no constante y run()modificar el vector de entrada dado.

Considere dividir la visualización de la clasificación

Un gran problema es que debe dividir sus algoritmos de clasificación en pasos y tener una switch()declaración step()para elegir el algoritmo correcto. También necesita enumerar los posibles algoritmos. En lugar de hacer eso, considere la posibilidad de Enginevisualizar un vector de números y, en lugar de Engineconducir los pasos del algoritmo, tenga una unidad de algoritmo Enginepara mostrar el estado del vector en cada paso. Puede hacer esto cambiando Engine::draw()para tomar una referencia a un vector de números:

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

Y un algoritmo de clasificación puede convertirse en una sola función:

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);
    }
}

Y entonces tu main()podrías lucir así:

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

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

Los beneficios de este enfoque son que separa las preocupaciones. El Engineahora solo tiene que visualizar un vector (probablemente debería cambiarse el nombre a algo así Visualizer). Puede agregar fácilmente nuevos algoritmos de clasificación sin tener que cambiar Engine.

Un problema con lo anterior es que ya no maneja eventos SDL. Puede hacer esto draw()y tener draw()un retorno que boolindique si el algoritmo debe continuar o no.

Compruebe si hay errores después de leer un archivo, no antes

En Engine::loadFile(), verifica si el archivo se abrió correctamente, pero nunca verifica si hubo un error durante la lectura. Una forma posible es:

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.");
}

Aquí usamos el hecho de que eofbitsolo se establece si llegó con éxito al final del archivo, no se establecerá si el archivo no se abrió o si ocurrió un error antes de llegar al final del archivo.