C++ Wie kann man effizient 10 % aus einer List mit vielen Elementen ziehen?

Du vergleichst auch Äpfel mit Birnen, weil du lediglich Pointer (vector<int*> ptrs;) hereingibst - der ganze Rest fehlt ja noch.

BeBur schrieb:
Menschen sind schlecht im generieren von Zufall weil sie u.a. versuchen das was sie für Muster halten zu vermeiden.
Nein, das ist keine Halluzination (täuschende Wahrnemung), weil, ich glaube, im Beispiel der "Zufall" nicht geseedet wurde und deshalb unbrauchbar ist.
Ergänzung ()

... Genau so ist es... Habe das Beispiel mal ausgeführt und erhalte die gleiche Ausgabe:

1760860800897.png


Zeile 23 müsste angepasst werden:

C++:
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <random>
#include <vector>
 
void print(auto const& rem, auto const& v)
{
    std::cout << rem << " = [" << std::size(v) << "] { ";
    for (auto const& e : v)
        std::cout << e << ' ';
    std::cout << "}\n";
}
 
int main()
{
    const auto in = {1, 2, 3, 4, 5, 6};
    print("in", in);
 
    std::vector<int> out;
    const int max = in.size() + 2;
    auto gen = std::default_random_engine{std::random_device{}()};
 
    for (int n{}; n != max; ++n)
    {
        out.clear();
        std::ranges::sample(in, std::back_inserter(out), n, gen);
        std::cout << "n = " << n;
        print(", out", out);
    }
}

Dann ist alles schön. ;)
 
Zuletzt bearbeitet:
kali-hi schrieb:
Du vergleichst auch Äpfel mit Birnen, weil du lediglich Pointer (vector<int*> ptrs;) hereingibst - der ganze Rest fehlt ja noch.
Ich hatte die Deklarationen testweise rausgezogen, hatte aber das selbe Ergebnis. Oder was meinst du mit "Rest"? Ich habe deine Implementierung verwendet. Du kannst meinen Code ja anpassen, wenn du da tiefer einsteigen möchtest, ich wollte nur mal ein konkretes Beispiel geben wie man sowas mit im wesentlichen einer einzelnen Codezeile (= Aufruf von std::ranges::sample) macht.

kali-hi schrieb:
Nein, das ist keine Halluzination (täuschende Wahrnemung), weil, ich glaube, im Beispiel der "Zufall" nicht geseedet wurde und deshalb unbrauchbar ist.
Das hat mit unbrauchbar nichts zu tun und hat auch nichts mit deiner originären Aussage
Das Beispiel sieht auch nicht zufällig aus, wenn die 5 (und 6) in 6 von 7 Fällen gezogen wird...
zu tun, sondern ist die Frage nach Determinismus. Ob man den haben will oder nicht hängt komplett vom - hier nicht definierten - Anwendungsfall ab.

Davon abgesehen irrst du dich. Der seed wird im Beispiel mithilfe von std::random_device{} gesetzt und dein Ergebnis ist auch nicht identisch mit dem im Beispiel. Das was du da verwendest std::default_random_engine würde ich prinzipiell nie verwenden, da die Wahl des Generators damit Implementierungsabhängig ist und es gibt keinen Grund einfach den gut etablierten mersenne-twister zu wählen, wenn man keine besonderen Anforderungen hat.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: kuddlmuddl, kali-hi und simpsonsfan
Die ganzen grundlegenden Fragestellungen zum Thema Zufall hatten wir doch schon.

Und auch ohne diff zu bemühen, sehe ich schnell, dass
Code:
n = 2, out = [2] { 4 5 }
und
Code:
n = 2, out = [2] { 1 3 }
nicht identisch sind.

Dass die letzten zwei Zeilen der Ausgabe immer alle Elemente enthalten, ist wenig überraschend und konsistent mit der Beschreibung der Funktion.

An der Stelle möchte ich dann auch noch darauf hinweisen, dass die Funktion für bestimmte Eingabewerte Undefined Behaviour definiert, d.h. es darf rauskommen, was will (nasal demons.) Daher sollte man die Spezifikation lesen und berücksichtigen.

Und dass es einen Unterschied macht, ob Werte von einer Stelle im Speicher an eine andere kopiert werden oder nicht (und man bspw. nur eine View darauf hat) sollte einem hoffentlich auch bewusst sein, wenn man auf dem Level unterwegs ist.
 
  • Gefällt mir
Reaktionen: kali-hi und BeBur
@BeBur teste doch mal (vorsichtshalber) einmal hiermit die Zeiten (von select_n_elements_a und select_n_elements_b):

C++:
#include <cstdint>
#include <iostream>
#include <vector>
#include <set>
#include <algorithm>
#include <numeric>
#include <random>

using namespace std;

template < typename E >
    void select_n_elements_a(const vector < E > & in, vector < E > & out, uint32_t n) {
        static auto gen = default_random_engine {
            random_device {}()
        };
        vector < uint32_t > indexes(in.size());
        iota(begin(indexes), end(indexes), 0);
        ranges::shuffle(indexes, gen);
        sort(indexes.begin(), indexes.begin() + n);
        for (auto it = indexes.begin(); it != indexes.begin() + n; it++) {
            out.push_back(in [ * it]);
        }
    }

template < typename E >
    void select_n_elements_b(const vector < E > & in, vector < E > & out, uint32_t n) {
        static auto gen = default_random_engine {
            random_device {}()
        };
        ranges::sample(in, back_inserter(out), n, gen);
    }

// test until the first match
template < typename F >
    void test1(F select_n_elements) {
        vector < int > in(10);
        iota(begin(in), end(in), 0);
        for (int i = 0; i < in.size();) {
            vector < int > out = {};
            select_n_elements(in, out, 1);
            for (auto & e: out) cout << e << ' ';
            cout << endl;

            if (out[0] == i) {
                i++;
                cout << endl;
            }
        }
    }

// test until missing the first element
template < typename F >
    void test9(F select_n_elements) {
        vector < int > in(10);
        iota(begin(in), end(in), 0);
        for (int i = 0; i < in.size();) {
            vector < int > out = {};
            select_n_elements(in, out, 9);
            for (auto & e: out) cout << e << ' ';
            cout << endl;

            if (find(out.begin(), out.end(), i) == out.end()) {
                i++;
                cout << endl;
            }
        }
    }

int main() {
    test1(select_n_elements_a < int > );
    test9(select_n_elements_a < int > );
    cout << "---\n" << endl;
    test1(select_n_elements_b < int > );
    test9(select_n_elements_b < int > );
}

Eigentlich sollte es dabei keinen signifikanten Zeitunterschied geben...
Ergänzung ()

BeBur schrieb:
Das was du da verwendest std::default_random_engine würde ich prinzipiell nie verwenden, da die Wahl des Generators damit Implementierungsabhängig ist und es gibt keinen Grund einfach den gut etablierten mersenne-twister zu wählen,
( ) Du weißt, dass im Beispiel immer die gleichen Kandidaten gezogen werden, und damit die Anforderungen an die Zufälligkeit (für mich) nicht erfüllt sind?
 
Zuletzt bearbeitet:
kali-hi schrieb:
@BeBur teste doch mal (vorsichtshalber) einmal hiermit die Zeiten (von select_n_elements_a und select_n_elements_b):
Ne, habe ich keine Lust drauf auf so ein Ping Pong, erstelle doch einfach selber Benchmarks, wenn es dich interessiert. Außerdem ist das Thema für mich soweit abgeschlossen, da es mit std::sample bereits Funktionalität gibt, die den Anforderungen 1:1 entspricht.

kali-hi schrieb:
( ) Du weißt, dass im Beispiel immer die gleichen Kandidaten gezogen werden
Wie schon von zwei Personen geschrieben wurde ist das falsch. Habe ich sogar im Satz direkt vor dem geschrieben, den du in deinem Beitrag zitiert hast.
 
  • Gefällt mir
Reaktionen: kuddlmuddl
simpsonsfan schrieb:
Und dass es einen Unterschied macht, ob Werte von einer Stelle im Speicher an eine andere kopiert werden oder nicht (und man bspw. nur eine View darauf hat) sollte einem hoffentlich auch bewusst sein, wenn man auf dem Level unterwegs ist.
Es geht nur um flache Kopien (Views) von in. Wenn in in zum Beispiel Personen-Objekte (Instanzen) sind, und ich von einem Element in out zum Beispiel den Namen ändere, dann soll dies auch Auswirkungen auf das entsprechende Element in in haben. ;)
Ergänzung ()

BeBur schrieb:
Wie schon von zwei Personen geschrieben wurde ist das falsch. Habe ich sogar im Satz direkt vor dem geschrieben, den du in deinem Beitrag zitiert hast.
Probiere es doch aus und schaue dir die Ergebnisse an. Der Seed ist, so weit ich weiß, immer derselbe.
 
kali-hi schrieb:
Es geht nur um flache Kopien (Views)
Views erstellen keine Kopien.
Ergänzung ()

kali-hi schrieb:
Probiere es doch aus und schaue dir die Ergebnisse an. Der Seed ist, so weit ich weiß, immer derselbe.
Habe ich. Ist nicht identisch. Du hast es auch ausprobiert und auch bei dir waren die Ergebnisse nicht identisch, schau dir dein hier hochgeladenes Bild nochmal genauer an. @simpsonsfan hat dir sogar eine konkrete Zeile gezeigt, wo es unterschiedlich ist. Keine Ahnung, wieso man dir das 4mal sagen muss.
 
BeBur schrieb:
schau dir dein hier hochgeladenes Bild nochmal genauer an
Ja ok, jetzt sehe ich es auch... :grr: Sorry
Ergänzung ()

BeBur schrieb:
Views erstellen keine Kopien.
Die Frage wäre doch...

ob
out.push_back(in [ * it]);
identisch ist zu
back_inserter(out)

ich vermute: ja.
Ergänzung ()

Ich glaube, ihr habt beide recht, @BeBur und @simpsonsfan ... Das Rad braucht hierbei nicht neu erfunden werden. Mir war erst nicht bewusst, dass algorithm in C++ schon so eine (ähnliche) Funktion anbietet.

Case closed?
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: kuddlmuddl und BeBur
Ok, dann halt ich... Es gibt nämlich zeitlich gesehen gar keinen Unterschied zwischen "meiner" Implementierung und ranges::sample. Mitunter ist meine Implementierung sogar schneller. ;)

C++:
#include <cstdint>

#include <cmath>

#include <chrono>

#include <iostream>

#include <vector>

#include <set>

#include <algorithm>

#include <numeric>

#include <random>

using namespace std;

template < typename E >
    void select_n_elements_a(const vector < E > & in, vector < E > & out,
        const uint32_t n) {
        static auto gen = default_random_engine {
            random_device {}()
        };
        vector < uint32_t > indexes(in.size());
        iota(begin(indexes), end(indexes), 0);
        ranges::shuffle(indexes, gen);
        sort(indexes.begin(), indexes.begin() + n);
        for (auto it = indexes.begin(); it != indexes.begin() + n; it++) {
            out.push_back(in [ * it]);
        }
    }

template < typename E >
    void select_n_elements_b(const vector < E > & in, vector < E > & out,
        const uint32_t n) {
        static auto gen = default_random_engine {
            random_device {}()
        };
        ranges::sample(in, back_inserter(out), n, gen);
    }

// test until the first match
template < typename F >
    void test1(F select_n_elements,
        const uint32_t n,
            const uint32_t type) {
        vector < int > in(n);
        iota(begin(in), end(in), 0);
        for (int i = 0; i < in.size();) {
            vector < int > out = {};
            select_n_elements(in, out, type == 0 ? 1 : n - 1);
            //for (auto & e: out) cout << e << ' ';
            //cout << endl;

            if (type == 0 && out[0] == i || type == 1 && find(out.begin(), out.end(), i) == out.end()) {
                i++;
                //cout << endl;
            }
        }
    }

int main() {
    for (int i = 2; i <= 5; i++) {
        const uint32_t n = pow(10, i);
        auto start = chrono::system_clock::now();
        test1(select_n_elements_a < int > , n, 0);
        auto end = chrono::system_clock::now();
        chrono::duration < double > elapsed = end - start;
        cout << "select_n_elements_a 1. n=" << n << ":  " << elapsed.count() << endl;

        start = chrono::system_clock::now();
        test1(select_n_elements_a < int > , n, 1);
        end = chrono::system_clock::now();
        elapsed = end - start;
        cout << "select_n_elements_a 2. n=" << n << ":  " << elapsed.count() << endl;

        start = chrono::system_clock::now();
        test1(select_n_elements_b < int > , n, 1);
        end = chrono::system_clock::now();
        elapsed = end - start;
        cout << "select_n_elements_b 1. n=" << n << ":  " << elapsed.count() << endl;

        start = chrono::system_clock::now();
        test1(select_n_elements_b < int > , n, 1);
        end = chrono::system_clock::now();
        elapsed = end - start;
        cout << "select_n_elements_b 2. n=" << n << ":  " << elapsed.count() << endl;

        cout << endl;
    }
}

Code:
select_n_elements_a 1. n=100:  0.00490246
select_n_elements_a 2. n=100:  0.0311453
select_n_elements_b 1. n=100:  0.00539372
select_n_elements_b 2. n=100:  0.00498923

Ich hatte aber keine Zeit, um das bis n=100000 durchzuexorzieren... weil mir der Online-Compiler nur 5 Sekunden Ausführungszeit zugesteht und lokal kein Compiler da.
Ergänzung ()

Edit: Zeile 81 sollte test1(select_n_elements_b < int > , n, 0); sein
 
Dein Testcode timed irgendwas, aber nicht die Ausführungszeit der beiden Funktionen select_n_elements_a und select_n_elements_b. Ein Profiler würde dir das auch gleich mitteilen. Leider habe ich grade keinen Profiler auf C++23 zur Hand (sondern mache das Ganze in VS Code.)
Ich kann dir aber sagen, dass select_n_elements_a bei mir um einiges langsamer ist als select_n_elements_b, wenn ich 50 000 Elemente aus 100 000 000 ziehen möchte. Das Teure dabei ist übrigens auch nicht das Sortieren der 50 000 Indizes*, sondern das Durchmischen aller 100 000 000 Indizes (mittels ranges::shuffle.)
Da ich keinen grade keinen Profiler da habe, habe ich das übrigens mittels Test mit und ohne shuffle gemacht.

Die beispielhafte Implementierung von ranges::sample erklärt auch, warum das schneller gehen kann. Denn dort wird letzlich etwas Ähnliches getan, wie in #33 vorgeschlagen: Es wird über die Ursprungsdaten iteriert und für jedes Element anhand einer Zufallsverteilung entschieden, ob es gewählt wird, oder nicht. Dieser Vorgang kann dann nach 50 000 gezogenen Elementen auch abgebrochen werden.

Die Details der verwendeten uniform distribution und warum das dann so mathematisch sauber ist, sind mir heute abend aber zu hoch, da müsste man schon nochmal in Ruhe drüberschauen.

Edit: *Wobei das auf die genauen Parameter ankommt. Erhöhe ich die Anzahl der Samples in die Richtung der Elementzahl des Ausgangsvektors, dann wird das Sortieren ähnlich teuer wie das Shuffle, sprich es nimmt dann ca. 50% Gesamtlaufzeit in Anspruch.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: kuddlmuddl, BeBur und kali-hi
simpsonsfan schrieb:
Es wird über die Ursprungsdaten iteriert und für jedes Element anhand einer Zufallsverteilung entschieden,
Ach so... Na, dagegen kann ich mit meinem Ansatz (verständlicherweise) nicht "anstinken"...

Für mich wäre das aber nicht mehr "fair zufällig"...

Beweisen kann ich es nicht, aber hierbei wird bestimmt mit dem Erwartungswert o. Ä. argumentiert werden...
 
Auch hier wieder nicht falsch verstehen, es werden für große Anzahlen in den meisten Fällen nahezu alle Elemente der Ausgangsdaten durchlaufen. Es ist also nicht so, dass bei 50 000 aus 100 000 000 jedes Mal eine Münze geworfen wird und dann bspw. bereits nach 100 000 durchlaufenen Elementen gestoppt wird (obwohl es zuällig natürlich passieren kann, dass man gerade 50 000 Elemente aus den ersten 100 000 zieht. Nur die Wahrscheinlichkeit, dass bei einer zufälligen Stichprobe ein Element aus den letzten 900 000 Elementen dabei ist, liegt halt bei (1-1e-50000), also nahezu 1.
Aber mit dem Ansatz müssen die Daten eben maximal einmal durchiteriert werden.

Ich denke jedenfalls, man kann davon ausgehen, dass tatsächliche Implementierungen bei gängigen Compilern mathematisch sauber sind (such that each possible sample has equal probability of appearance). Aber wie gesagt, ist mir heute zu mühsam, ich bin auch kein Mathematiker.

Und man muss halt keine exakte Permutation von 100 000 000 Elementen erstellen, es reicht, die richtigen Wahrscheinlichkeiten zu haben.
 
  • Gefällt mir
Reaktionen: kali-hi
Welche Verteilungsfunktion (https://cplusplus.com/reference/random/#:~:text=generator (class)-,Distributions,-Uniform:) würde man denn wählen, um die ... "Distanz" zum nächsten Element zu erhalten, wenn man beim Index i ist, also dieses Element zuletzt gewählt wurde? Oder ginge man hier gar nicht iterativ/deterministisch durch?

Und ja, es ist schon spät. Ich bin jetzt auch vorm tv ... man sollte sich nicht so übermäßig in so etwas hineinsteigern.
 
kali-hi schrieb:
Welche Verteilungsfunktion (https://cplusplus.com/reference/random/#:~:text=generator (class)-,Distributions,-Uniform:) würde man denn wählen, um die ... "Distanz" zum nächsten Element zu erhalten, wenn man beim Index i ist, also dieses Element zuletzt gewählt wurde? Oder ginge man hier gar nicht iterativ/deterministisch durch?

Und ja, es ist schon spät. Ich bin jetzt auch vorm tv ... man sollte sich nicht so übermäßig in so etwas hineinsteigern.
Auf das Thema CPU-Verbrauch vs. guter oder schlechter Zufall hatte ich schon hier hingewiesen: #53
Zum Vergleichen sollte sollte man also eigenen Zufall mitbringen um Schwankungen zwischen verschiedenen Laufzeitumgebungen auszugleichen, ansonsten kann man den PRNG auch alle 100K (o.ä.) Ziehungen mit "echten Zufall" neu befruchten.

"Echten" Zufall zum befruchten gibt es hier: https://linux.die.net/man/4/urandom
 
Zuletzt bearbeitet:
@foofoobar Jetzt noch auf meine Frage antworten... dann wäre mir geholfen ;)
 
Welche Frage? Du hast viele Fragen gestellt.

BTW: (Dieser Code https://www.computerbase.de/forum/t...elementen-ziehen.2254960/page-4#post-31006359)

Code:
$ make foo
g++     foo.cc   -o foo
foo.cc: In function ‘void select_n_elements_a(const std::vector<_RealType>&, std::vector<_RealType>&, uint32_t)’:
foo.cc:29:9: error: ‘ranges’ has not been declared
   29 |         ranges::shuffle(indexes, gen);
      |         ^~~~~~
foo.cc: In function ‘void select_n_elements_b(const std::vector<_RealType>&, std::vector<_RealType>&, uint32_t)’:
foo.cc:42:9: error: ‘ranges’ has not been declared
   42 |         ranges::sample(in, back_inserter(out), n, gen);
      |         ^~~~~~
make: *** [<builtin>: foo] Error 1
$
 
Zuletzt bearbeitet:
kali-hi schrieb:
Welche Verteilungsfunktion eingesetzt wird.
Darüber sind unendlich viele Doktorarbeiten geschrieben worden. Per Definition ist z.b. radioaktiver Zerfall "zufällig".
Letztendlich hängt das von den Anforderungen ab.

Hier ist was zum Einstieg: (Wikipedia hat auch was dazu)

https://nvlpubs.nist.gov/nistpubs/legacy/sp/nistspecialpublication800-22r1a.pdf
https://github.com/terrillmoore/NIST-Statistical-Test-Suite
https://csrc.nist.gov/projects/random-bit-generation/documentation-and-software

Der Mensch hinter Wireguard hat auch was dazu:


https://www.zx2c4.com/projects/linux-rng-5.17-5.18/
https://www.google.com/search?q=Jason+Donenfeld+random
kali-hi schrieb:
Ah, thx.
Code:
$ CXXFLAGS="-std=c++23 -O" make foo
g++ -std=c++23 -O    foo.cc   -o foo
$
Ich habe gestern mal mit stricken angefangen, dann habe ich jetzt was zum vergleichen.
kali-hi schrieb:
Ich hatte aber keine Zeit, um das bis n=100000 durchzuexorzieren... weil mir der Online-Compiler nur 5 Sekunden Ausführungszeit zugesteht und lokal kein Compiler da.
Cygwin oder eine Linux Live-CD.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: kali-hi
BeBur schrieb:
Im Regelfall sollte man die Funktionen aus std verwenden. Die interagieren konsistent mit anderen Teilen von std, die Wahrscheinlichkeit von Bugs ist um ein vielfaches geringer und die Laufzeit ist für den alllgemeinen Fall üblicherweise optimal oder nahezu optimal. Mal ganz davon abgesehen, dass es sehr ineffizient ist, solche allgemeinen Funktionalitäten zu implementieren, die schon in hoher Qualität existieren.
So ist es. Wenn immer die Standardbibliothek bereits eine Funktionalität bereitstellt, sollte praktisch immer diese genutzt werden. Denn diese ist höchstwahrscheinlich bug-frei, effizient, allgemein bekannt (andere kennen sich im Code sofort aus, und man selber nach 6 Monaten Pause ebenfalls !) und noch dazu gratis (sensu: Arbeitsstunden-frei). Win-win-win-win. Die Chance, was besseres selber zu basteln, ist entsprechend gering und eher nur in ganz konkreten Spezialfällen zu finden.

Und über das letzte bißchen an Performancen sollte man sich nur Gedanken machen nachdem gezieltes Profiling für einen ganz konkreten Anwendungsfall tatsächlich ein nennenswertes Problem identifiziert hat. Und das ist selten der Fall (meine Erfahrung: Das Performance-Thema wird überbewertet, und selbst wenn die Performance eine Rolle spielt, liegt man sowieso falsch wo das wirkliche Bottleneck herkommt).

kali-hi schrieb:
int main() {
const auto in = {1, 2, 3, 4, 5, 6};
print("in", in);

std::vector<int> out;
const int max = in.size() + 2;
auto gen = std::default_random_engine{std::random_device{}()};

for (int n{}; n != max; ++n)
{
out.clear();
std::ranges::sample(in, std::back_inserter(out), n, gen);
std::cout << "n = " << n;
print(", out", out);
}
}[/CODE];)
Wenn dir Performance so wichtig ist, dann setze dich mal mit den grundlegenden Eigenschaften der Standard-Container (bzw. der Standardbibliothek generell) auseinander. Wie schon in einem Vorpost von mir erwähnt haben die Container alle ihre jeweiligen Eigenschaften bzw. Stärken und Schwächen, und damit auch unterschiedliche "ideale" Einsatzzwecke. Den eierlegenden Wollmilchsau-Container gibt es nicht.
In Scott Meyers Buch "Effective STL" ist das z.B. gut aufgearbeitet (stammt aus 2008; als komplett veraltet würde ich es aber nicht auffassen, denn an den Prinzipien der Container hat sich nix geändert; vermutlich findet man auch was gutes aktuelleres. Und wenn ich dieses Buch erwähne ist hoffentlich auch die von @kali-hi vor ein paar Tagen gestellte Frage beantwortet, ob ich für meine Antworten bzw. Wissen hier ChatGPT heranziehe ...).
EDIT: gilt selbstverständlich nicht nur für die Container, sondern für alle Aspekte der Standardbibliothek.

Zum konkreten Anwendungsfalls: Ein std::vector ist besonders effizient wenn Elemente nur am Ende eingefügt (oder entfernt) werden (was hier im Code mit dem std::back_inserter der Fall ist) und weiters nochmals deutlich effizienter nutzbar wenn seine finale Größe bereits zum Zeitpunkt der Erstellung bekannt ist. Denn dann setzt man diese Größe explizit via std::vector::reserve() bevor noch Elemente hineingeschrieben werden (was also im obigen Code ergänzt werden sollte, gleich nach dem Constructor): denn dann muss der Vektor im Zuge seines Wachsens kein neues memory alloziieren und die enthaltenen Elemente vom alten memory ins neue rüberkopieren. Wird v.a. bei entsprechend großen Vektor-sizes und teuren Kopier-Operationen der Elemente eine tragende Rolle spielen, und maximal memory-efficient ist das ganze auch noch.

Fun fact: Scott Meyers empfiehlt, standardmäßig std::deque zu verwenden sofern der genaue Einsatzzweck des Containers nicht näher bekannt ist (wo und wie oft Elemente hinzugefügt/entfernt werden, finale Größe, Kosten der Kopieroperation der Elemente, etc.; siehe das oben genannte Buch zum Nachlesen der Details). Ein in der Praxis oft unterschätzter bzw. wenig genutzter Container, der noch am ehesten die positiven und negativen Eigenschafter der (sequentiellen) Container ausbalanciert, quasi der Mittelweg.

(Im hiesigen Bsp. ist aber std::vector besser).
 
  • Gefällt mir
Reaktionen: kuddlmuddl und kali-hi
firespot schrieb:
Für den wahlfreien Zugriff (mischen) (... also index-basiert) ist std::vector hierbei sicher besser ;)

Es gibt eigentlich gar nicht so viele Fälle, wo man wirklich nur einen Stack oder eine Queue benötigt (imho)

firespot schrieb:
ob ich für meine Antworten bzw. Wissen hier ChatGPT heranziehe ...
War nicht so gemeint ...

Nur leider antworten manche wirklich damit, ohne die ChatGpt-Antwort entsprechend zu kennzeichnen. Aber das wäre auch gegen die Regeln

foofoobar schrieb:
Hier ist was zum Einstieg: (Wikipedia hat auch was dazu)
Lektüre für später mal ... 🫡
 
Zurück
Oben