Visualisierer für Sortieralgorithmen in C ++ und SDL2 verbessert

Aug 15 2020

Ursprünglicher Beitrag: Sortieralgorithmus Visualizer in C ++ und SDL2
Ich habe den Rat befolgt, den Sie mir gegeben haben, und meinen Code verbessert. Ich habe auch zwei weitere Sortieralgorithmen hinzugefügt. Ich suche hauptsächlich nach Ratschlägen zur Lesbarkeit von Code (Gebaut unter Windows mit cmake und ninja)

Demonstration


Requisiten an @Zeta für die Demonstration

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

Antworten

3 Edward Aug 16 2020 at 13:29

Das Programm ist definitiv gegenüber der früheren Version verbessert. Gute Arbeit! Hier sind einige Dinge, die Ihnen helfen können, es weiter zu verbessern.

Wiederholen Sie sich nicht (DRY)

Es gibt elf Wiederholungen des Konstruktors, was mir etwas übertrieben erscheint, insbesondere weil der Code nahezu identisch ist. Ich würde diese auf genau eins reduzieren:

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

Durch die Bereitstellung eines Standardkonstruktors mit Standardparametern werden die Wiederholungen eliminiert und die Flexibilität erhöht. Beachten Sie auch, dass das Zahlenarray als Argument übergeben wird.

Reduzieren Sie die Größe der Schnittstelle auf das, was benötigt wird

Diese beiden Funktionen sind generisch und müssen keine Klassenmitglieder sein:

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

Durch die Reduzierung der Größe der Benutzeroberfläche auf das erforderliche Minimum wird die Klasse kleiner und ist leichter zu lesen, zu verstehen, zu testen, zu verwenden, zu warten und anzupassen. Beachten Sie auch, dass das erste Argument einen std::istream&anstelle eines Dateinamens verwendet. Dies ermöglicht so praktische Dinge wie das Laden von einem Socket oder einem Stringstream.

Lieber früh scheitern

Wenn der SDL_InitSubSystemAufruf im Konstruktor fehlschlägt, macht es nicht viel Sinn, fortzufahren. Aus diesem Grund sollte der zeitaufwändigere calculateNumbersAnruf wahrscheinlich eher nach als nach kommen.

Stellen Sie die Farbe nur ein, wenn Sie etwas zeichnen möchten

Die einzigen Stellen, SDL_SetRenderDrawColordie verwendet werden sollten, sind kurz bevor etwas tatsächlich auf dem Bildschirm gezeichnet wird. Aus diesem Grund kann es in diesem Code genau zweimal vorkommen: einmal oben drawSelectionund einmal oben draw.

Bewegen Sie die Arbeit außerhalb der Schleifen, wo dies sinnvoll ist

Anstatt alle vier Teile columnjedes Mal durch die Schleife neu zu berechnen , können Sie einfach diejenigen ändern, die geändert werden müssen:

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

Verwenden Sie Objekte anstelle von switches

Der Code enthält derzeit Folgendes:

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

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

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

    default:
        break;
    }
}

Dies erfordert die Auswertung selectedSortAlgorithmjeder Iteration. Der bessere Weg, dies zu tun, besteht darin, eine virtuelle Basisklasse zu erstellen, die die Schnittstelle demonstriert, und dem Benutzer dann zu ermöglichen, eine abgeleitete Klasse mit einer beliebigen neuen Art von Sortierung zu erstellen. Hier ist eine Möglichkeit, dies zu tun:

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

Für maximale Flexibilität können Sie jetzt ein solches Objekt an den EngineKonstruktor übergeben:

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

Verwenden Sie einen Zeiger auf eine Elementfunktion

Ähnliches kann für die Zeichenmethode getan werden, wenn Sie möchten, aber es ist etwas schwieriger, da ein Zeiger auf eine Elementfunktion verwendet wird, deren Syntax schwierig zu korrigieren sein kann. Wir könnten die Variable darin Enginewie folgt deklarieren :

void (Engine::*drawSelection)();

Ich habe Ihren drawSelectionNamen hier neu verwendet. Innerhalb des Konstruktors können wir Folgendes verwenden:

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

Und schließlich müssen wir den Zeiger-zu-Mitglied-Zugriffsoperator verwenden, um es aufzurufen. Hier ist es im Kontext:

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

Arbeiten Sie nicht, was nicht benötigt wird

Im Moment ist die Hauptschleife runfolgende:

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

Es macht für mich nicht viel Sinn, std::is_sortedinnerhalb eines Programms aufzurufen, das das Sortieren demonstriert! Es schien mir, dass ich das Programm schließen möchte, wenn die Sortierung abgeschlossen ist, und ich habe die Sortierroutinen so geändert, dass sie erst dann boolmit dem Wert zurückgegeben werden, falsewenn sie ausgeführt werden und der Vektor sortiert ist. Dafür verwandelt sich die Schleife in Folgendes:

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

Erwägen Sie das Hinzufügen von Funktionen

Ich würde vorschlagen, dass es schön sein könnte, einige Funktionen hinzuzufügen. Folgendes habe ich getan:

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

Verwenden Sie namespace SortVis {...}inEngine.cpp

Sie können vermeiden, dass der Namespace in jeder Funktionsdefinition wiederholt Engine.cppwird, indem Sie den gesamten Code namespace SortViswie in umschließen Engine.h. Es ist auch üblich, den Code nicht in einen namespace {...}Block einzurücken, der die gesamte Datei abdeckt, um zu vermeiden, dass Code zu oft von der rechten Seite des Bildschirms abläuft.

Zu viele Konstruktoren

Sie haben 12 verschiedene Konstruktoren, das ist ein bisschen viel. Stellen Sie sich auch vor, Sie möchten möglicherweise in Zukunft einen weiteren optionalen Parameter hinzufügen und dann die Anzahl der erforderlichen Konstruktoren verdoppeln. Dies ist auf lange Sicht nicht wartbar. Ich sehe zwei Möglichkeiten, um die Anzahl der erforderlichen Konstruktoren zu verringern:

  1. Verwenden Sie folgende Standardargumentwerte:

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

    Bei diesem Ansatz benötigen Sie nur zwei Konstruktoren. Die Verwendung ist möglicherweise etwas ärgerlicher, wenn Sie nur eine andere angeben möchten DrawMethod, aber es ist ein geringer Preis für eine deutlich verbesserte Wartbarkeit.

  2. Geben Sie die Eingabewerte, den Algorithmus und die Zeichenmethode nicht im Konstruktor an, sondern lassen Sie diese von Elementfunktionen wie folgt festlegen:

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

Im Allgemeinen tun Sie im Konstruktor nur das, was zur Bauzeit wirklich getan werden muss. Das Initialisieren von SDL und das Öffnen eines Fensters ist für eine funktionierende Visualisierungs-Engine von entscheidender Bedeutung. Dies ist im Konstruktor sinnvoll.

Erwägen Sie, keine Nummern in zu generieren / zu laden class Engine

Anstatt EngineZufallszahlen zu generieren oder aus einer Datei zu laden, können Sie dies vereinfachen, indem Sie dies überhaupt nicht tun, sondern nur zulassen, dass ein beliebiger Vektor verwendet wird, den Sie ihm geben. Also zum Beispiel:

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

Sie können sogar in Betracht ziehen, es als nicht konstant zu übergeben und run()den angegebenen Eingabevektor zu ändern.

Ziehen Sie in Betracht, die Visualisierung von der Sortierung zu trennen

Ein großes Problem ist, dass Sie Ihre Sortieralgorithmen in Schritte aufteilen und eine switch()Anweisung step()eingeben müssen, um den richtigen Algorithmus auszuwählen. Sie müssen auch die möglichen Algorithmen auflisten. Anstatt dies zu tun, sollten Sie Enginenur einen Vektor von Zahlen visualisieren und statt Enginedie Schritte des Algorithmus zu steuern, einen Algorithmus ansteuern Engine, um den Zustand des Vektors bei jedem Schritt anzuzeigen. Sie können dies tun, indem Engine::draw()Sie einen Verweis auf einen Zahlenvektor ändern :

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

Und ein Sortieralgorithmus kann einfach zu einer einzigen Funktion werden:

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

Und dann main()könnte es so aussehen:

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

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

Der Vorteil dieses Ansatzes besteht darin, dass Sie Bedenken trennen. Das muss Enginejetzt nur noch einen Vektor visualisieren (er sollte wahrscheinlich in so etwas umbenannt werden Visualizer). Sie können problemlos neue Sortieralgorithmen hinzufügen, ohne Änderungen vornehmen zu müssen Engine.

Ein Problem mit dem oben genannten ist, dass SDL-Ereignisse nicht mehr verarbeitet werden. Sie können dies in tun draw()und eine draw()Rückgabe erhalten, die boolangibt, ob der Algorithmus fortgesetzt werden soll oder nicht.

Suchen Sie nach dem Lesen einer Datei nach Fehlern, nicht vorher

In Engine::loadFile()überprüfen Sie, ob die Datei korrekt geöffnet wurde, aber Sie überprüfen nie, ob beim Lesen ein Fehler aufgetreten ist. Ein möglicher Weg ist:

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

Hier verwenden wir die Tatsache, dass das eofbitnur festgelegt wird, wenn es das Ende der Datei erfolgreich erreicht hat. Es wird nicht festgelegt, wenn die Datei nicht geöffnet werden konnte oder wenn ein Fehler aufgetreten ist, bevor das Ende der Datei erreicht wurde.