Visualizzatore algoritmo di ordinamento in C ++ e SDL2 Migliorato

Aug 15 2020

Post originale: Visualizzatore algoritmo di ordinamento in C ++ e SDL2
Ho seguito i consigli che mi hai dato e ho migliorato il mio codice. Ho anche aggiunto altri due algoritmi di ordinamento. Sto principalmente cercando consigli sulla leggibilità del codice (costruito su Windows con cmake e ninja)

Dimostrazione


puntelli a @Zeta per la dimostrazione

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

Risposte

3 Edward Aug 16 2020 at 13:29

Il programma è decisamente migliorato rispetto alla versione precedente. Bel lavoro! Ecco alcune cose che potrebbero aiutarti a migliorarlo ulteriormente.

Non ripetere te stesso (DRY)

Ci sono undici ripetizioni del costruttore, il che mi sembra un po 'eccessivo, soprattutto perché il codice è quasi identico. Li ridurrei esattamente a uno:

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

Fornendo un unico costruttore con parametri predefiniti, le ripetizioni vengono eliminate e la flessibilità è migliorata. Nota anche che l'array di numeri viene passato come argomento.

Riduci le dimensioni dell'interfaccia solo a ciò che è necessario

Queste due funzioni sono generiche e non devono essere membri della classe:

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

Riducendo le dimensioni dell'interfaccia al minimo necessario, la classe è più piccola e più facile da leggere, comprendere, testare, utilizzare, mantenere e adattare. Nota anche che il primo argomento accetta a std::istream&invece di un nome di file. Ciò consente cose utili come il caricamento da un socket o da uno stringstream.

Preferisco fallire presto

Se la SDL_InitSubSystemchiamata nel costruttore fallisce, non ha molto senso continuare. Per questo motivo, la calculateNumberschiamata che richiede più tempo dovrebbe probabilmente arrivare dopo piuttosto che prima.

Imposta il colore solo se vuoi disegnare qualcosa

Gli unici posti che SDL_SetRenderDrawColordovrebbero essere usati sono appena prima che qualcosa venga effettivamente disegnato sullo schermo. Per questo motivo, può apparire esattamente due volte in questo codice: una volta all'inizio drawSelectione una volta all'inizio di draw.

Spostare il lavoro al di fuori degli anelli, se possibile

Invece di ricalcolare tutte e quattro le porzioni di columnogni volta attraverso il ciclo, è possibile cambiare semplicemente quelle che devono cambiare:

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 oggetti invece di switches

Il codice attualmente contiene questo:

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

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

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

    default:
        break;
    }
}

Ciò richiede la valutazione di selectedSortAlgorithmogni iterazione. Il modo migliore per farlo è creare una classe base virtuale che dimostri l'interfaccia e quindi consentire all'utente di creare una classe derivata con qualsiasi nuovo tipo di ordinamento che preferisce. Ecco un modo per farlo:

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

Ora per la massima flessibilità, puoi passare un tale oggetto al Enginenel suo costruttore:

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

Usa un puntatore a una funzione membro

Una cosa simile può essere fatta per il metodo di disegno, se lo desideri, ma è un po 'più complicato poiché utilizza un puntatore a una funzione membro che ha una sintassi che può essere difficile da ottenere correttamente. Potremmo dichiarare la variabile all'interno in Enginequesto modo:

void (Engine::*drawSelection)();

Ho riproposto il tuo drawSelectionnome qui. All'interno del costruttore possiamo usare questo:

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

Infine, per chiamarlo, dobbiamo usare l'operatore di accesso pointer-to-member. Eccolo nel contesto:

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

Non fare lavori non necessari

In questo momento, il ciclo principale runè questo:

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

Non ha molto senso per me fare una chiamata std::is_sortedall'interno di un programma che dimostra l'ordinamento! Mi è sembrato di voler chiudere il programma una volta terminato l'ordinamento e ho modificato le routine di ordinamento per tornare boolcon il valore di falsesolo quando ha finito di funzionare e il vettore è ordinato. Quindi, per questo, il ciclo si trasforma in questo:

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

Considera l'aggiunta di funzionalità

Suggerirei che potrebbe essere bello aggiungere alcune funzionalità. Ecco cosa ho fatto:

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

Usa namespace SortVis {...}inEngine.cpp

Puoi evitare di ripetere lo spazio dei nomi in ciascuna definizione di funzione Engine.cppavvolgendo tutto il codice all'interno namespace SortVis, proprio come hai fatto in Engine.h. È anche comune non indentare il codice all'interno di un namespace {...}blocco che copre l'intero file, per evitare che il codice scorra troppo spesso dalla mano destra dello schermo.

Troppi costruttori

Hai 12 costruttori diversi, questo è un po 'troppo. Immagina anche di voler aggiungere un altro parametro facoltativo in futuro, quindi raddoppi il numero di costruttori richiesti. Questo non è gestibile a lungo termine. Vedo due modi per ridurre il numero di costruttori richiesti:

  1. Usa i valori degli argomenti predefiniti, in questo modo:

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

    Con questo approccio, sono necessari solo due costruttori. Potrebbe essere leggermente più fastidioso da usare se vuoi solo specificarne un altro DrawMethod, ma è un piccolo prezzo per una manutenibilità molto migliorata.

  2. Non specificare i valori di input, l'algoritmo e il metodo di disegno nel costruttore, consenti a questi di essere impostati dalle funzioni membro, in questo modo:

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

In generale, fai solo nel costruttore ciò che deve essere fatto veramente al momento della costruzione. L'inizializzazione di SDL e l'apertura di una finestra sono fondamentali per un motore di visualizzazione funzionante, quindi è bene farlo nel costruttore.

Considera l'idea di non generare / caricare numeri in formato class Engine

Invece di Enginegenerare numeri casuali o caricarli da un file, puoi semplificarlo non farlo affatto, ma piuttosto consentirgli di utilizzare qualsiasi vettore che gli dai. Quindi per esempio:

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

Puoi anche considerare di passarlo come non-const e run()modificare il vettore di input specificato.

Considera l'idea di dividere la visualizzazione dall'ordinamento

Un grosso problema è che devi dividere i tuoi algoritmi di ordinamento in passaggi e avere una switch()dichiarazione step()per scegliere l'algoritmo giusto. È inoltre necessario enumerare i possibili algoritmi. Invece di farlo, considera la possibilità di Enginevisualizzare solo un vettore di numeri e invece di Engineguidare i passaggi dell'algoritmo, fai in modo che un algoritmo Enginemostri lo stato del vettore ad ogni passaggio. Puoi farlo cambiando Engine::draw()per prendere un riferimento a un vettore di numeri:

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

E un algoritmo di ordinamento può diventare una singola funzione:

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 poi main()potresti sembrare così:

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

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

I vantaggi di questo approccio sono che separa le preoccupazioni. L' Engineora deve solo visualizzare un vettore (probabilmente dovrebbe essere rinominato in qualcosa di simile Visualizer). Puoi aggiungere facilmente nuovi algoritmi di ordinamento senza doverli modificare Engine.

Un problema con quanto sopra è che non gestisce più gli eventi SDL. Puoi farlo in draw()e draw()restituire un che boolindica se l'algoritmo deve continuare o meno.

Verificare la presenza di errori dopo aver letto un file, non prima

In Engine::loadFile(), controlli se il file è aperto correttamente, ma non controlli mai se si è verificato un errore durante la lettura. Un modo possibile è:

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

Qui usiamo il fatto che eofbitviene impostato solo se ha raggiunto con successo la fine del file, non verrà impostato se il file non si è aperto o se si è verificato un errore prima di raggiungere la fine del file.