Konsolenoutput in Tabellen formatieren [gelöst]



  • Moin zusammen,

    ich würde gerne folgenden Array, schön sauber und geordnet in 2-4 Tabellen nebeneinander darstellen:

    #include <iostream>
    
    int main()
    {
        std::string test[30] = {"test", "testtestingtest", "testtestingtesttesttestingtest", "testtest", "testing", "testingtest", "testtesting",
                              "test", "test", "test", "testtestingtest", "testtestingtesttesttestingtest", "testtest", "testing", "testingtest",
                              "test", "test", "test", "testtestingtest", "testtestingtesttesttestingtest", "testtest", "testing", "testtestingtest", 
                              "testtestingtesttesttestingtest", "testtest", "testing", "testingtest", "testtesting", "test", "test"};
                                
        for (int i = 0; i<30; i++){
            std::cout << i << " " <<test[i] <<std::endl;
        }
    
        return 0;
    }
    

    Wenn ich nun aber std::cout <<i << " " <<test[i] <<"\t" versuche, kommt nur totaler Unsinn bei raus. Ich denke, dass schwierige daran ist, dass alle Worte nicht gleich lang sind aber trotzdem in "gleichen" Tabellen dargestellt werden sollen - natürlich numerisch passend. Quasi so, nur nebeneinander:

    1. test
    2. testing
    3. testingtest
    4. testtesting

    1. testing
    2. test
    3. testest
    4. testingtestest

    Gruß,



  • @Richard_Wunsch

    Hallo,

    schau dir mal std::setfill und std::setw an.



  • @Jockelx

    Hab ich schon. Das sind doch nur fixe Lückenfüller oder?

    Edit: Und fixe Abstandshalter...

    Edit2:

    for (int i = 0; i<30; i++){
            std::cout << i << " " <<test[i] <<std::setw (4);
            if (i % 4 == 0){
                std::cout << std::endl;
            }
        }
    

    Die erste Tabelle stimmt, danach wird es aber relativ chaotisch. Wie gleiche ich die Wortlängen aus? Man könnte natürlich, die jeweilige Wortlänge errechnen und dann mit std::setfill gegenarbeiten, aber das wäre ein bisschen sehr kompliziert.



  • @Richard_Wunsch sagte in Konsolenoutput formatieren:

    Was meinst du mit Wortlänge ausgleichen? Dafür nutzt du doch std::setw.

    Irgendwie so meinte ich:

    #include <iostream>
    #include <algorithm>
    #include <array>
    #include <iomanip>
    
    int main()
    {
        std::array<std::string, 30> v = {"test", "testtestingtest", "testtestingtesttesttestingtest", "testtest", "testing", "testingtest", "testtesting",
                              "test", "test", "test", "testtestingtest", "testtestingtesttesttestingtest", "testtest", "testing", "testingtest",
                              "test", "test", "test", "testtestingtest", "testtestingtesttesttestingtest", "testtest", "testing", "testtestingtest", 
                              "testtestingtesttesttestingtest", "testtest", "testing", "testingtest", "testtesting", "test", "test"};
                                
    	auto result = std::max_element(v.begin(), v.end(), [](const std::string& s1, const std::string& s2){
    		return s1.size() < s2.size();
    	});
    	auto max = result->size() +2;
            for (int i = 0; i<30; i++){
                std::cout << i << std::setw(max) <<v[i] <<std::endl;
        }
    
        return 0;
    }
    


  • @Jockelx

    Danke für deine Mühe! Wenn ich deinen Code compiliere, sieht es schon ganz gut aus, wird aber immer noch untereinander ausgegeben.

    Bei meinem Versuch:

        for (int i = 0; i<30; i++){
            std::cout << i << " " <<test[i] <<std::setw (15);
            if (i % 4 == 0){
                std::cout << std::endl;
            }
        }
    

    Kommt das raus:

    https://www.directupload.net/file/d/5286/rwpscbmp_png.htm

    Die erste Tabelle stimmt (abgesehen von der numerischen Reihenfolge), aber alle folgenden sind durcheinander, weil die Wörter in der ersten Tabelle unterschiedlich lang sind. Gibt es eine einfach Möglichkeit das auszugleichen?



  • @Richard_Wunsch
    Ach, so hab in deinem ersten Code gar nix von mehreren Spalten gesehen 😉

    Dann halt so (rest wie oben):

    auto max = result->size() +1;
    for (int i = 0; i<30; i++){
        std::cout << std::setw(2) << i << ' ' << v[i] << std::setw(max-v[i].size()) << ' ';
        if(i % 2)
           std::cout << '\n';
    }
    


  • @Jockelx

    Vielen Dank! Funktioniert hervorragend. Also muss man doch den Abstand zwischen zwei Wörtern errechnen, um es vernünftig darzustellen! Wenn die Tabellen jetzt noch chronologisch kongruent wären, wäre es Perfektion. Aber so geht es auch schon sehr gut.



  • @Richard_Wunsch sagte in Konsolenoutput formatieren:

    @Jockelx

    Also muss man doch den Abstand zwischen zwei Wörtern errechnen, um es vernünftig darzustellen!

    Ja, sorry, hatte dich da am Anfang falsch verstanden.



  • @Jockelx

    Habe mich auch nicht gerade deutlich ausgedrückt ^^

    Edit: Ah, außerdem vergleicht man auch nicht zwei Wörter, sondern sucht nur innerhalb des arrays (bzw. vectors) nach dem längsten Wort und setzt das als standard wert für std::setw, richtig?

    Warum vergleichst du noch den zweitgrößten String? Würde das nicht reichen?:

    auto result = std::max_element(v.begin(), v.end(), [](const std::string& s1){
    		return s1.size();
    	});
    
    


  • Falls es noch jemand interessiert: einen bestehenden array kann man mit std::copy in einen fixen array kopieren und mit if (i % 3 == 2) bzw. if (i % 4 == 3) 3 oder 4 Tabellenspalten erzeugen. Siehe Code:

    #include <iostream>
    #include <algorithm>
    #include <array>
    #include <iomanip>
    
    int main()
    {
        std::string test[30] = {"test", "testtestingtest", "testtestingtesttesttestingtest", "testtest", "testing", "testingtest", "testtesting",
                              "test", "test", "test", "testtestingtest", "testtestingtesttesttestingtest", "testtest", "testing", "testingtest",
                              "test", "test", "test", "testtestingtest", "testtestingtesttesttestingtest", "testtest", "testing", "testtestingtest", 
                              "testtestingtesttesttestingtest", "testtest", "testing", "testingtest", "testtesting", "test", "test"};
        
        std::array<std::string, 30> v = {};
        std::copy(std::begin(test),std::end(test),std::begin(v))  ;
        
        auto result = std::max_element(v.begin(), v.end(), [](const std::string& s1, const std::string& s2){
    		return s1.size() < s2.size();
        });
        auto max = result->size() +2;
        for (int i = 0; i<30; i++){
            std::cout << std::setw(2) << i << ' ' << v[i] << std::setw(max-v[i].size()) << ' ';
        if(i % 3 == 2)
           std::cout << '\n';
        }
        return 0;
    }                       
    

    Edit: Den Verweis auf std::copy bitte ignorieren -> siehe unten



  • @Richard_Wunsch

    Aus welchem Grund sollte man das array umkopieren?



  • @manni66

    Falls er bereits vorhanden ist. Oder gibt es die begin() Funktion auch für "einfache" arrays?



  • @Richard_Wunsch sagte in Konsolenoutput in Tabellen formatieren [gelöst]:

    @manni66

    Falls er bereits vorhanden ist. Oder gibt es die begin() Funktion auch für "einfache" arrays?

    Was benutzt du denn in deinem copy?



  • @manni66

    Hahaha danke für den Hinweis. Wie kann man nur solche Tomaten auf den Augen haben?



  • Noch 2 Fragen hierzu - Warum ist es nicht möglich, die Arraylänge mit einem Integer zu beschreiben?

    #include <iostream>
    #include <algorithm>
    #include <array>
    #include <iomanip>
    
    int main()
    {
        int testArrayLength = 30;
        std::string test[testArrayLength  = {"test", "testtestingtest", "testtestingtesttesttestingtest", "testtest", "testing", "testingtest", "testtesting",
                              "test", "test", "test", "testtestingtest", "testtestingtesttesttestingtest", "testtest", "testing", "testingtest",
                              "test", "test", "test", "testtestingtest", "testtestingtesttesttestingtest", "testtest", "testing", "testtestingtest", 
                              "testtestingtesttesttestingtest", "testtest", "testing", "testingtest", "testtesting", "test", "test"};
        
        auto result = std::max_element(std::begin(test), std::end(test), [](const std::string& s1, const std::string& s2){
    		return s1.size() < s2.size();
        });
        auto max = result->size() +2;
        for (int i = 0; i<30; i++){
            std::cout << std::setw(2) << i << ' ' << test[i] << std::setw(max-test[i].size()) << ' ';
        if(i % 3 == 2)
           std::cout << '\n';
        }
        return 0;
    }                       
    
    main.cpp:14:51: error: no matching function for call to ‘begin(std::string [testArrayLength])’
    

    Und warum ist es nicht möglich den Inhalt des Arrays erst im Nachhinein zu definieren?

    #include <iostream>
    #include <algorithm>
    #include <array>
    #include <iomanip>
    
    int main()
    {
        std::string test[] = {};
        test [0] = "test";
        auto result = std::max_element(std::begin(test), std::end(test), [](const std::string& s1, const std::string& s2){
    		return s1.size() < s2.size();
        });
        auto max = result->size() +2;
        for (int i = 0; i<30; i++){
            std::cout << std::setw(2) << i << ' ' << test[i] << std::setw(max-test[i].size()) << ' ';
        if(i % 3 == 2)
           std::cout << '\n';
        }
        return 0;
    }                       
    
    main.cpp:10:51: error: no matching function for call to ‘begin(std::string [0])’
    

    Wenn ich stattdessen std::string test[] = {""}; benutze funktioniert es - warum?



  • @Richard_Wunsch Benutze std::vector!

    Bei Arrays muss die Länge bereits während der Compilezeit feststehen. Bei deinem ersten Beispiel fehlt dir mindestens eine eckige schließende Klammer hinter std::string test[testArrayLength und außerdem muss testArrayLength dafür eine Konstante sein. Also const int testArrayLength = 30; und es wäre möglich.

    Wenn du variabel viele Strings hast oder erst im Programmverlauf die Strings füllen willst, nimm std::vector<std::string>.

    Bei Arrays, die du gleich zuweist wie hier: std::string test[] = {""}; wird die Länge des Arrays automatisch anhand der rechten Seite ermittelt. Dort befindet sich 1 Element. Der Code entspricht also: std::string test[1] = {""};

    Das hier ist Blödsinn: std::string test[] = {};. Was soll das sein - ein Array der Länge 0? Also wirst du in test hier niemals etwas speichern können. Das folgende test [0] = "test"; aus deinem Beispiel geht aber davon aus, dass das Array mindestens die Länge 1 hat. Boom!

    Vergiss fürs erste Arrays und sieh dir vector an!



  • @wob Auch hier vielen Dank für deine Antwort!! In meinem Kopf waren Vektoren immer mehrdimensional, weshalb ich nie darauf zurückgegriffen habe, um nur eine Zahl oder einen String zu speichern.

    Die eckige Klammer war einfach nur schlampig kopiert...

    Mir war nicht bewusst, dass die Arraylänge bereits beim compilen konstant feststehen muss - danke für den Hinweis. Dann macht es natürlich auch Sinn, dass ein einfacher Integer als Beschreibung nicht reicht.