2017-09-18 4 views
2

J'essaie d'imprimer une liste séparée par des virgules d'un seul détail à partir d'un std::vector<MyClass>. Jusqu'à présent, la plus simple et plus intelligent comme je l'ai vu faire est d'utiliserImprimer la liste séparée par des virgules de std :: vector

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

Cela a bien fonctionné quand je l'impression d'un vecteur de chaînes. Cependant, maintenant j'essaie d'imprimer un seul détail sur MyClass. Je sais en Python que je pouvais faire quelque chose comme

(x.specific_detail for x in vec) 

pour obtenir une expression de générateur pour la chose que je suis intéressé. Je me demande si je peux faire quelque chose de similaire ici ou si je suis coincé en train de faire

for (auto it = vec.begin(); it != vec.end(); ++it) { 
    // Do stuff here 
} 
+0

Avez-vous envisagé une [gamme à base de boucle ] (http://en.cppreference.com/w/cpp/language/range-for)? – wally

+2

Vous pouvez utiliser ['std :: transform'] (http://en.cppreference.com/w/cpp/algorithm/transform) tout comme vous utilisez' std :: copy', mais avec un lambda. –

+0

@rex L'utilisation d'une plage basée sur vous finit avec des hacks assez redoutables pour empêcher une virgule de fin –

Répondre

1

Voici une bibliothèque de gamme simple minuscule:

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

maintenant nous sommes prêts.

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 example.

Maintenant, vous pouvez obtenir un colombophile. booster les itérateurs de transformation, ainsi que la plage de boost, vous permettent de faire quelque chose de similaire à une compréhension de liste en python. Ou bibliothèque Rangesv3 pour C++ 2a.

L'écriture d'un itérateur d'entrée de transformation n'est pas incroyablement difficile, c'est juste un tas de passe-partout. Regardez simplement les axiomes de l'itérateur d'entrée, écrivez un type qui stocke un itérateur arbitraire et lui transmet la plupart des méthodes.

Il stocke également certaines fonctions. Sur * et ->, appelez la fonction sur l'itérateur déréférencé.

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 example of transformer.

And if we add -- we can even use ostream iterator.

Notez que std::prev requiert un itérateur bidirectionnel, qui nécessite un concept d'itération vers l'avant, qui nécessite que l'itérateur de transformation renvoie une référence réelle, ce qui est pénible.

3

Une façon de résoudre ce que j'ai vu est:

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

Vous pouvez simplement le même code, mais définir un opérateur < < surcharge:

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

Une façon assez facile et réutilisable :

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

Et une autre façon qui définit un p extensible rotocol pour l'impression de conteneurs:

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

... bien sûr, nous pouvons aller plus loin ...

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

} 

résultat attendu:

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

Voici un exemple en utilisant 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'; 
} 

Sortie:

Testing, One, Two, Three 
7, 3, 3, 5 
+1

@todo vérifie que le vecteur n'est pas vide en premier. –

+0

@RichardHodges: Terminé. –

0

est ici ce qui a finalement été utilisé

// 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; 
+0

Qui va bien sûr planter si vec est vide. –

+0

@RichardHodges il y a des vérifications contre cela. Quand il est vide, un comportement différent est souhaité. –

1

Vous pouvez utiliser le code exact que vous avez déjà, il suffit de changer le type que vous passez à std::ostream_iterator pour limiter sa production:

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