Vector iterieren Verständnisproblem



  • Um beim Eingangsbeispiel zu bleiben:

    std::vector<int>TestVector{85,3,24,98,72};
    int array[5];
    int i=0;
    
    for (auto& v : TestVector)
    {
       array[i] = v;
       i++;
    }
    

    Ich hatte gehofft, dass es vielleicht möglich ist, über den Vector Iterator "v" einerseits den Wert der TestVector Felder und andererseits die fortlaufende Anzahl des Aufrufs von "v" in der Schleife (0,1,2,3 etc.) auszulesen.

    Ist das möglich? Oder ist der "i" Iterator schon der optimale Weg?


  • Mod

    Wenn ich das recht verstehe (ich bin mir in dieser Hinsicht nicht sicher, ob ich das genau verstanden habe) suchst du etwas in dieser Richtung?

    #include <vector>
    #include <set>
    #include <algorithm>
    #include <iterator>
    #include <iostream>
    
    using namespace std;
    
    struct Kombinierte_Werte {
       int  fortlaufende_nummer;
       int nummer_aus_set;
    };
    
    struct Kombinator
    {
    	int fortlaufende_nummer;
    	Kombinierte_Werte operator()(int nummer_aus_set)
    	{
    		return Kombinierte_Werte{fortlaufende_nummer++, nummer_aus_set};
    	}
    };
    
    int main()
    {
      set<int> my_set{14, 18, 16, 27};
    
      vector<Kombinierte_Werte> ergebnis;
      Kombinator kombinator = {0};
      // Dies ist die interessante Zeile:
      transform(my_set.begin(), my_set.end(), back_inserter(ergebnis), kombinator);
    
      for(auto element: ergebnis)
      	cout << element.fortlaufende_nummer << '\t' << element.nummer_aus_set << '\n';
    }
    

    http://ideone.com/bCjiVH
    Wobei man den Funktor Kombinator auch sehr knapp als Lambda innerhalb des transform-Aufrufs definieren könnte. Aber wenn du von copy noch nichts gehört hast, nehme ich auch mal an, das du noch nicht viel von lambdas gehört hast.

    Es gibt auch noch 1000 andere Möglichkeiten dieses oder ähnliches zu erreichen. Um zu entscheiden, was davon nun das beste ist, muss man den konkreten Fall betrachten. Deine bisherige Problembeschreibung ist ja eher abstrakt (was gut ist, aber man kann halt dadurch nicht sagen, welche Methode die "beste" ist). Allgemein bietet die Standardbibliothek sehr viele kleine Helferfunktionen an, mit denen man oft vorkommende Schleifenkonstrukte kompakt aufschreiben kann, was dann auch von vielen Leuten als "elegant" angesehen wird.



  • Hallo SeppJ,

    vielen Dank für das ausführliche Beispiel, das ich ausprobieren werde.

    Den Begriff "Funktor Kombinator" werde ich auffrischen müssen - habe ihn aber schon einmal im Zusammenhang mit dem "Lambda" Begriff und Sortierfunktionen in Klassen Vectoren benutzt.

    Danke und Grüße,
    Bernd


  • Mod

    Ein Funktor ist ein funktionsartiges Objekt. Etwas, das man aufrufen kann, wie eine Funktion, aber sich über mehrere Aufrufe hinweg Informationen merken kann. Ungefähr so wie statische Variablen in Funktionen (falls dir das was sagt), aber im objektorientierten Stil.

    Das habe ich hier gemacht, weil du mehrere Quellen für deine Daten hast: Einerseits die Daten aus dem Set und andererseits die fortlaufende Nummer, die du anscheinend wünscht. Mein Funktor ist bloß eine Funktion, die ein Element des Sets entgegen nimmt und dieses zusammen mit der fortlaufenden Nummer zusammen in ein struct des gewünschten Ergebnistyps packt. Dabei zählt mein Funktor eigenständig mit.

    Ein funktionsartiges Objekt muss das deswegen sein, weil transform (und viele andere Funktionen aus dem algorithms-Header) von der Art sind, dass man ihnen einen Bereich gibt und dann sagt, dass sie eine bestimmte Funktion auf eine bestimmte Art und Weise auf diesen Bereich anwenden sollen. Oder eben ein funktionsartiges Objekt, die sind da ganz flexibel. Hauptsache irgendwas, dass sich mittels runder Klammern aufrufen lässt.

    Ein Lambdaausdruck ist eine Möglichkeit, um kleine (oder theoretisch auch große) Funktionen, die man nur einmal benötigt, direkt vor Ort zu definieren. So könnte das in diesem Fall aussehen:

    #include <vector>
    #include <set>
    #include <algorithm>
    #include <iterator>
    #include <iostream>
    
    using namespace std;
    
    struct Kombinierte_Werte {
       int  fortlaufende_nummer;
       int nummer_aus_set;
    };
    
    int main()
    {
      set<int> my_set{14, 18, 16, 27};
    
      vector<Kombinierte_Werte> ergebnis;
      int counter = 0;
      transform(my_set.begin(), my_set.end(), back_inserter(ergebnis), 
                [&counter](int nummer_aus_set){return Kombinierte_Werte{counter++, nummer_aus_set};});
    
      for(auto element: ergebnis)
      	cout << element.fortlaufende_nummer << '\t' << element.nummer_aus_set << '\n';
    }
    

    http://ideone.com/ngibe6
    Und das ist dann doch schon eine recht elegante Art, das auszudrücken, wonach du gefragt hast. Das ganze spannende Zeug spielt sich in dem einem großen Ausdruck in den Zeilen 21-22 ab
    (Wobei es, wie gesagt, noch viele andere Methoden geben könnte, je nachdem, was du genau willst)

    Mit klassischen for-Schleifen geschrieben sähe das so aus:

    int counter = 0;
      for(auto nummer_aus_set : my_set)
      {
      	ergebnis.push_back(Kombinierte_Werte{counter++, nummer_aus_set});
      }
    

    Was, wie du siehst, auch nicht gerade schlechter ist, vielleicht sogar besser lesbar 😃 . Man kann argumentieren, dass transform aus einer Zeit kommt, in der es noch keine range-based for und ähnliches gab und die Schleife damals so ausgesehen hätte:

    int counter = 0;
      for(set<int>::const_iterator it = my_set.begin(); it != my_set.end(); ++it)
      {
      	Kombinierte_Werte kombinierter_wert = {counter++, *it};
      	ergebnis.push_back(kombinierter_wert);
      }
    

    Und das ist dann auch schon eine ganze Spur unschöner als die anderen hier gezeigten Methoden.



  • Mit klassischen for-Schleifen geschrieben sähe das so aus:

    int counter = 0;
      for(auto nummer_aus_set : my_set)
      {
        ergebnis.push_back(Kombinierte_Werte{counter++, nummer_aus_set});
      }
    

    Was, wie du siehst, auch nicht gerade schlechter ist, vielleicht sogar besser lesbar 😃

    Ja, mit Schleife finde ich es auch besser lesbar! Danke auch für die Erklärung zu Funktor und Lambda. 👍



  • Ich fürchte, die in der Frage gezeigte Art von for-Schleife ist für die Aufgabe nicht geeignet.

    Alternative:

    std::vector<int>TestVector{85,3,24,98,72};
    int array[5];
    
    for (int i=0; i < 5; ++i)
    {
       array[i] = TestVector[i];
    }
    

    Wobei man die "5" natürlich noch durch eine geeignete Konstante (constexpr) ersetzen muss. Das der Vector und das array genügend Elemente haben müssen, ist auch noch zu prüfen.

    Die in der Frage gezeigte Variante der for-Schleife ist nach meinem Verständnis nur dafür da, um über alle Elemente des Vektors zu iterieren und damit etwas zu machen. Sobald man wieder eine Laufvariable einfügen muss (für die Zuweisung wie in dem Beispiel), ist m. E. der Witz dieser neuen Variante von for weg. Ich würde deswegen bei der klassischen Variante bleiben.

    Bekanntlich führen viele Wege nach Rom, aber Funktor und Lambda ist wohl etwas übertrieben.

    Im übrigen gibt es jetzt eine Klasse std::array. Vielleicht hat die einen Konstruktor, dem man Iteratoren übergeben kann? Konnte das auf die schnelle jetzt nicht feststellen.



  • SeppJ schrieb:

    int counter = 0;
      transform(my_set.begin(), my_set.end(), back_inserter(ergebnis), 
                [&counter](int nummer_aus_set){return Kombinierte_Werte{counter++, nummer_aus_set};});
      
      for(auto element: ergebnis)
      	cout << element.fortlaufende_nummer << '\t' << element.nummer_aus_set << '\n';
    

    Streng genommen ist [&count] nicht nötig, da könnte auch eine Kopie genommen werden, aber in auto element wäre besser eine Referenz ( auto& element ).


  • Mod

    captured! schrieb:

    SeppJ schrieb:

    int counter = 0;
      transform(my_set.begin(), my_set.end(), back_inserter(ergebnis), 
                [&counter](int nummer_aus_set){return Kombinierte_Werte{counter++, nummer_aus_set};});
      
      for(auto element: ergebnis)
      	cout << element.fortlaufende_nummer << '\t' << element.nummer_aus_set << '\n';
    

    Streng genommen ist [&count] nicht nötig, da könnte auch eine Kopie genommen werden, aber in auto element wäre besser eine Referenz ( auto& element ).

    Beides falsch.



  • SeppJ schrieb:

    captured! schrieb:

    SeppJ schrieb:

    int counter = 0;
      transform(my_set.begin(), my_set.end(), back_inserter(ergebnis), 
                [&counter](int nummer_aus_set){return Kombinierte_Werte{counter++, nummer_aus_set};});
      
      for(auto element: ergebnis)
      	cout << element.fortlaufende_nummer << '\t' << element.nummer_aus_set << '\n';
    

    Streng genommen ist [&count] nicht nötig, da könnte auch eine Kopie genommen werden, aber in auto element wäre besser eine Referenz ( auto& element ).

    Beides falsch.

    Was meinst du mit falsch?


  • Mod

    captured! schrieb:

    Was meinst du mit falsch?

    Mit "falsch" meine ich "schlechter als mein Code". Damit ist deine Klugscheißerei also voll am Ziel vorbei, daher "falsch".



  • Was ist daran besser, von Kombinierte_Werte eine Kopie zu erstellen? Der Herr Meyers wäre voll dagegen.

    Btw, in C++14, benötigt man die counter-Variable nicht:

    vector<Kombinierte_Werte> ergebnis;
      transform(my_set.begin(), my_set.end(), back_inserter(ergebnis),
                [counter=0](int nummer_aus_set)mutable{return Kombinierte_Werte{counter++, nummer_aus_set};});
    

Anmelden zum Antworten