Print comma separated list from std::vector [duplicate]
Asked Answered
Z

7

7

I'm trying to print a comma separated list of a single detail from a std::vector<MyClass>. So far the simplest and cleverest way I have seen to do this is to use

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

That worked fine when I was printing a vector of strings. However, now I am trying to print a single detail about MyClass. I know in Python I could do something like

(x.specific_detail for x in vec)

to get a generator expression for the thing that I am interested in. I'm wondering if I can do something similar here or if I am stuck doing

for (auto it = vec.begin(); it != vec.end(); ++it) {
    // Do stuff here
}
Zoography answered 18/9, 2017 at 14:19 Comment(15)
Have you considered a range based for loop?Lysimachus
You could use std::transform very much like you use std::copy, but with a lambda.Hydrofoil
@rex Using a range based for you end up with some pretty awful hacks to prevent a trailing commaZoography
@NickChapman True, but how was the Python example different in that regard? Or the for loop example that you provide in the question for that matter?Lysimachus
@FredLarson you need to prevent the trailing comma.Zoography
@rex in Python I could easily slice things to get what I wanted. Also in the for loop example there will also be hacky junk. That's why I'm hoping a lambda can save the day here.Zoography
Ok, but it seems like you would always need a conditional check somewhere to avoid the comma. How would you slice things to avoid that?Lysimachus
@rex print((x.specific_detail + ", " for x in vec[:-1]), end=""); print(vec[-1].specific_detail)Zoography
@NickChapman Boost::join does this, including providing a user-defined predicate.Dance
@NickChapman: You're correct. I remembered it wrong.Hydrofoil
Some good answers below, but the question already seems to have the closest equivalent to the Python code. This is turning into a useful question along the lines of: "is there a better way to avoid the trailing comma?".Lysimachus
I did not see mention of: en.cppreference.com/w/cpp/experimental/ostream_joinerThemselves
@JeffGarrett nobody who writes software in a production environment would ever use anything in experimental or -std=c++1z. It's asking for breakage down the line. Boost certainly, but incomplete standards, never.Cirrus
@RichardHodges I claim that there exist production environments where it would be completely reasonable to rely on code that has gone through much ISO design process and been published by the committee, even though its interface may evolve later, likely in a different namespace. But I think it's worth mentioning as an option mostly as it is a standards-track solution for this problem.Themselves
@RichardHodges, thank you. It drives me nuts when people are like why aren't you using this thing from C++2050 that was just discussed on Tuesday?Zoography
B
25

One way of solving this I have seen is:

std::string separator;
for (auto x : vec) {
  ss << separator << x.specific_detail;
  separator = ",";
}
Burtie answered 18/9, 2017 at 14:49 Comment(4)
This wastes time reinitializing the separator variable.Menides
@Menides even if it's not optimised away by the compiler I suspect this is a very fast operation that doesn't break branch prediction etc. I suspect performance will be dominated by the streaming. But yes, you are right in theory, only way to know for sure is to measure.Burtie
Agreed. However, would you think that an if statement checking if the iterator is at the end of the vector might be better than reinitializing a variable?Menides
An if is exactly the sort of thing that will break branch prediction and impact performance significantly, plus you still have the operation to evaluate the if every loop.Burtie
C
4

A fairly easy and reusable way:

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

And another way which defines an extendable protocol for printing containers:

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

...of course we can go further...

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

}

expected output:

[ 1,2,3,4,5 ]
( 1 : 2 : 3 : 4 : 5 )
>>>1-2-3-4-5<<<
Cirrus answered 18/9, 2017 at 14:54 Comment(0)
H
3

Here's an example using 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';
}

Output:

Testing, One, Two, Three
7, 3, 3, 5
Hydrofoil answered 18/9, 2017 at 15:1 Comment(2)
@todo check that vector is not empty first.Cirrus
@RichardHodges: Done.Hydrofoil
P
2

Here is a tiny simple range library:

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

now we are ready.

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.

Now you can get fancier. boost transform iterators, together with boost range, let you do something similar to a list comprehension in python. Or Rangesv3 library for C++2a.

Writing a transform input iterator isn't amazingly hard, it is just a bunch of boilerplate. Simply look at the axioms of input iterator, write a type that stores an arbitrary iterator and forwards most methods to it.

It also stores some function. On * and ->, call the function on the dereferenced 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 example of transformer.

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

Note that std::prev requires a bidirectional iterator, which requires forward iterator concept, which requires that the transform iterator return an actual reference, which is a pain.

Penstock answered 18/9, 2017 at 14:37 Comment(0)
Z
1

Here's what was ultimately used

// 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;
Zoography answered 18/9, 2017 at 15:17 Comment(1)
@RichardHodges there are checks against that. When it's empty a different behavior is desired.Zoography
E
1

You can use the exact code you already have, just change the type you pass to std::ostream_iterator to restrict its output:

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

Eliciaelicit answered 18/9, 2017 at 16:0 Comment(0)
S
-3

You can simply the exact same code, but define a operator<< overload:

ostream &operator<<(ostream& out)
{
    out << m_detail;
}
Streamway answered 18/9, 2017 at 14:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.