Drucken Komma getrennte Liste von std::vector

Ich versuche, drucken Sie eine durch Kommas getrennte Liste von einem einzigen detail von einem std::vector<MyClass>. So weit die einfachste und cleverste Art und Weise, die ich gesehen habe, dies zu tun ist zu verwenden

std::ostringstream ss;
std::copy(vec.begin(), vec.end() - 1, std::ostream_iterator<std::string>(ss, ", "))
ss << vec.back();

Dass geklappt, wenn ich Druck war ein Vektor von Zeichenketten. Aber jetzt bin ich versucht zu drucken, ein einziges detail über MyClass. Ich kenne Python, ich könnte etwas tun, wie

(x.specific_detail for x in vec)

um einen generator-Ausdruck für die Sache, die mich interessiert. Ich Frage mich, ob ich etwas tun kann, ähnlich wie hier, oder wenn ich bin stecken tut

for (auto it = vec.begin(); it != vec.end(); ++it) {
    //Do stuff here
}
  • Haben Sie als range-basierte for-Schleife?
  • Sie könnten std::transform sehr ähnlich wie Sie verwenden std::copy, aber ein lambda-Ausdruck.
  • Mit einem Bereich aus, für den Sie am Ende mit ein paar ziemlich schreckliche hacks zu verhindern, dass eine nachgestellte Komma
  • Stimmt, aber wie war das Python-Beispiel anders in dieser Hinsicht? Oder die for-Schleife Beispiel, dass Sie in der Frage für diese Angelegenheit?
  • Sie müssen verhindern, dass das nachgestellte Komma.
  • in Python könnte ich einfach slice Dinge zu bekommen, was ich wollte. Auch in der for-Schleife Beispiel gibt es auch hacky-junk. Das ist, warum ich bin der Hoffnung, einen lambda-Ausdruck kann den Tag retten hier.
  • Ok, aber wie es scheint, würden Sie brauchen immer eine bedingte prüfen Sie irgendwo, um zu vermeiden das Komma. Wie würden Sie schneiden die Dinge zu vermeiden?
  • print((x.specific_detail + ", " for x in vec[:-1]), end=""); print(vec[-1].specific_detail)
  • Boost::join tut dies, einschließlich der Bereitstellung eines Benutzer-definierten Prädikat.
  • Du hast Recht. Ich erinnerte mich, dass es falsch ist.
  • Einige gute Antworten, aber die Frage scheint schon zu haben, die am ehesten entspricht dem Python-code. Dies ist drehen in eine nützliche Frage, die entlang der Linien von: „gibt es einen besseren Weg, um zu vermeiden, das nachgestellte Komma?“.
  • Ich habe nicht erwähnt: en.cppreference.com/w/cpp/experimental/ostream_joiner
  • niemand, der schreibt die software in einer Produktionsumgebung würde immer alles verwenden, was in der experimentellen oder -std=c++1z. Es ist zu Fragen, für Bruch auf der ganzen Linie. Steigern sicherlich, aber unvollständig standards, nie.
  • Ich behaupte, dass es existiert Produktionsumgebungen, in denen es wäre völlig vernünftig, verlassen Sie sich auf code, der mitgemacht hat, viel ISO-design-Prozess und veröffentlicht durch den Ausschuss, obwohl seine Oberfläche kann sich später, wahrscheinlich in einem anderen namespace. Aber ich denke, es ist erwähnenswert als option meist, wie es ist ein standards-track-Lösung für dieses problem.
  • danke. Es treibt mich in den Wahnsinn, wenn Leute wie, warum bist du nicht mit diesem Ding von C++2050, das wurde gerade besprochen am Dienstag?

InformationsquelleAutor Nick Chapman | 2017-09-18



7 Replies
  1. 12

    Einen Weg, dies zu lösen, die ich gesehen habe ist:

    std::string separator;
    for (auto x : vec) {
      ss << separator << x.specific_detail;
      separator = ",";
    }
  2. 3

    Eine ziemlich einfache und wiederverwendbare Weise:

    #include <vector>
    #include <iostream>
    
    template<class Stream, class T, class A>
    Stream& printem(Stream&os, std::vector<T, A> const& v)
    {
        auto emit = [&os, need_comma = false](T const& x) mutable
        {
            if (need_comma) os << ", ";
            os << x;
            need_comma = true;
        };
    
        for(T const& x : v) emit(x);
        return os;
    }
    
    
    int main()
    {
        auto v = std::vector<int> { 1, 2, 3, 4 , 5 };
    
        printem(std::cout, v) << std::endl;
    }

    Und einen anderen Weg definiert ein erweiterbares Protokoll für die Druck-Behälter:

    #include <vector>
    #include <iostream>
    
    template<class Container>
    struct container_printer;
    
    //specialise for a class of container
    template<class T, class A>
    struct container_printer<std::vector<T, A>>
    {
        using container_type = std::vector<T, A>;
    
        container_printer(container_type const& c) : c(c) {}
    
        std::ostream& operator()(std::ostream& os) const 
        {
            const char* sep = "";
            for (const T& x : c) {
                os << sep << x;
                sep = ", ";
            }
            return os;
        }
    
        friend std::ostream& operator<<(std::ostream& os, container_printer const& cp)
        {
            return cp(os);
        }
    
        container_type c;
    };
    
    template<class Container>
    auto print_container(Container&& c)
    {
        using container_type = typename std::decay<Container>::type;
        return container_printer<container_type>(c);
    }
    
    
    int main()
    {
        auto v = std::vector<int> { 1, 2, 3, 4 , 5 };
    
        std::cout << print_container(v) << std::endl;
    }

    …wir können natürlich auch weiter gehen…

    #include <vector>
    #include <iostream>
    
    template<class...Stuff>
    struct container_printer;
    
    //specialise for a class of container
    template<class T, class A, class Separator, class Gap, class Prefix, class Postfix>
    struct container_printer<std::vector<T, A>, Separator, Gap, Prefix, Postfix>
    {
        using container_type = std::vector<T, A>;
    
        container_printer(container_type const& c, Separator sep, Gap gap, Prefix prefix, Postfix postfix) 
        : c(c)
        , separator(sep)
        , gap(gap)
        , prefix(prefix)
        , postfix(postfix) {}
    
        std::ostream& operator()(std::ostream& os) const 
        {
            Separator sep = gap;
            os << prefix;
            for (const T& x : c) {
                os << sep << x;
                sep = separator;
            }
            return os << gap << postfix; 
        }
    
        friend std::ostream& operator<<(std::ostream& os, container_printer const& cp)
        {
            return cp(os);
        }
    
        container_type c;
        Separator separator;
        Gap gap;
        Prefix prefix;
        Postfix postfix;
    };
    
    template<class Container, class Sep = char, class Gap = Sep, class Prefix = char, class Postfix = char>
    auto print_container(Container&& c, Sep sep = ',', Gap gap = ' ', Prefix prefix = '[', Postfix postfix = ']')
    {
        using container_type = typename std::decay<Container>::type;
        return container_printer<container_type, Sep, Gap, Prefix, Postfix>(c, sep, gap, prefix, postfix);
    }
    
    
    int main()
    {
        auto v = std::vector<int> { 1, 2, 3, 4 , 5 };
    
        //json-style
        std::cout << print_container(v) << std::endl;
    
        //custom
        std::cout << print_container(v, " : ", " ", "(", ")") << std::endl;
    
        //custom
        std::cout << print_container(v, "-", "", ">>>", "<<<") << std::endl;
    
    }

    erwartete Ausgabe:

    [ 1,2,3,4,5 ]
    ( 1 : 2 : 3 : 4 : 5 )
    >>>1-2-3-4-5<<<
  3. 2

    Hier ist ein Beispiel für die Verwendung std::transform:

    #include <vector>
    #include <string>
    #include <iterator>
    #include <algorithm>
    #include <iostream>
    
    int main()
    {
        std::vector<std::string> strs = {"Testing", "One", "Two", "Three"};
    
        if (!strs.empty())
        {
            std::copy(std::begin(strs), std::prev(std::end(strs)), std::ostream_iterator<std::string>(std::cout, ", "));
            std::cout << strs.back();
        }
        std::cout << '\n';
    
        if (!strs.empty())
        {
            std::transform(std::begin(strs), std::prev(std::end(strs)), std::ostream_iterator<size_t>(std::cout, ", "),
                           [](const std::string& str) { return str.size(); });
            std::cout << strs.back().size();
        }
        std::cout << '\n';
    }

    Ausgabe:

    Testing, One, Two, Three
    7, 3, 3, 5
    • überprüfen Sie, dass der Vektor ist nicht leer ersten.
    • Getan.
  4. 2

    Hier ist eine kleine einfache Reihe Bibliothek:

    template<class It>
    struct range_t {
      It b, e;
      It begin() const { return b; }
      It end() const { return e; }
      bool empty() const { return begin()==end(); }
      std::size_t size() const { return std::distance( begin(), end() ); }
      range_t without_front( std::size_t n = 1 ) const {
        n = (std::min)(size(), n);
        return {std::next(b, n), e};
      }
      range_t without_back( std::size_t n = 1 ) const {
        n = (std::min)(size(), n);
        return {b, std::prev(e, n)};
      }
      range_t only_front( std::size_t n = 1 ) const {
        n = (std::min)(size(), n);
        return {b, std::next(b, n)};
      }
      range_t only_back( std::size_t n = 1 ) const {
        n = (std::min)(size(), n);
        return {std::prev(end(), n), end()};
      }
    };
    template<class It>
    range_t<It> range(It s, It f) { return {s,f}; }
    template<class C>
    auto range(C&& c) {
      using std::begin; using std::end;
      return range( begin(c), end(c) );
    }

    nun sind wir bereit.

    auto r = range(vec);
    for (auto& front: r.only_front()) {
      std::cout << front.x;
    }
    for (auto& rest: r.without_front()) {
      std::cout << "," << rest.x;
    }

    Live-Beispiel.

    Bekommen Sie jetzt schicker. boost-Transformation Iteratoren, zusammen mit boost-Bereich, lassen Sie Sie tun etwas ähnliches wie eine list comprehension in python. Oder Rangesv3-Bibliothek für C++2a.

    Schreiben eine Transformation input-iterator ist nicht unglaublich schwer, es ist nur eine Ansammlung von Textbausteinen. Einfach mal auf die Axiome der input-iterator, schreiben einen Typ, der speichert einen beliebigen iterator und leitet die meisten Methoden, um es.

    Außerdem werden einige Funktion. Auf * und ->, rufen Sie die Funktion auf der dereferenziert den iterator.

    template<class It, class F>
    struct transform_iterator_t {
      using reference=std::result_of_t<F const&(typename std::iterator_traits<It>::reference)>;
      using value_type=reference;
      using difference_type=std::ptrdiff_t;
      using pointer=value_type*;
      using iterator_category=std::input_iterator_tag;
    
      using self=transform_iterator_t;
      It it;
      F f;
      friend bool operator!=( self const& lhs, self const& rhs ) {
        return lhs.it != rhs.it;
      }
      friend bool operator==( self const& lhs, self const& rhs ) {
        return !(lhs!=rhs);
      }
      self& operator++() {
        ++it;
        return *this;
      }
      self operator++(int) {
        auto r = *this;
        ++*this;
        return r;
      }
      reference operator*() const {
        return f(*it);
      }
      pointer operator->() const {
        //dangerous
        return std::addressof( **this );
      }
    };
    
    template<class F>
    auto iterator_transformer( F&& f ) {
      return [f=std::forward<F>(f)](auto it){
        return transform_iterator_t<decltype(it), std::decay_t<decltype(f)>>{
          std::move(it), f
        };
      };
    }
    
    template<class F>
    auto range_transfromer( F&& f ) {
      auto t = iterator_transformer(std::forward<F>(f));
      return [t=std::move(t)](auto&&...args){
        auto tmp = range( decltype(args)(args)... );
        return range( t(tmp.begin()), t(tmp.end()) );
      };
    }

    Live-Beispiel des Transformators.

    Und wenn wir hinzufügen -- wir können sogar mit ostream iterator.

    Beachten Sie, dass std::prev erfordert einen bidirektionalen iterator, die erfordert forward-iterator-Konzept, die verlangt, dass die Transformation iterator zurückgeben, eine wirkliche Referenz, das ist ein Schmerz.

  5. 1

    Können Sie den genauen code, den Sie bereits haben, ändern Sie einfach den Typ, den Sie übergeben, um std::ostream_iterator zu beschränken, Ihre Ausgabe:

    class MyClassDetail {
        const MyClass &m_cls;
    public:
        MyClassDetail(const MyClass &src) : m_cls(src) {}
        friend std::ostream& operator<<(std::ostream &out, const MyClassDetail &in) {
            return out << in.m_cls.specific_detail;
        }
    };
    
    std::copy(vec.begin(), vec.end()-1, std::ostream_iterator<MyClassDetail>(ss, ", "));
    ss << MyClassDetail(vec.back());

    Live-demo

  6. 0

    Hier ist, was wurde schließlich

    //assume std::vector<MyClass> vec
    std::ostringstream ss;
    std::for_each(vec.begin(), vec.end() - 1,
        [&ss] (MyClass &item) {
            ss << item.specific_detail << ", ";
        }
    );
    ss << vec.back().specific_detail;
    • Das wird natürlich Abstürzen, wenn vec leer ist.
    • es sind Prüfungen, gegen, die. Wenn es leer ist, ein anderes Verhalten erwünscht ist.
  7. -3

    Können Sie einfach den exakt gleichen code, sondern definieren einen operator<< überlast:

    ostream &operator<<(ostream& out)
    {
        out << m_detail;
    }

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.