13

Is there a way to use a std::ostream_iterator (or similar) such that the delimiter isn't placed for the last element?

#include <iterator> #include <vector> #include <algorithm> #include <string> using namespace std; int main(int argc, char *argv[]) { std::vector<int> ints = {10,20,30,40,50,60,70,80,90}; std::copy(ints.begin(),ints.end(),std::ostream_iterator<int>(std::cout, ",")); } 

Will print

10,20,30,40,50,60,70,80,90,

I'm trying to avoid the trailing the delimiter. I want to print

10,20,30,40,50,60,70,80,90

Sure, you could use a loop:

for(auto it = ints.begin(); it != ints.end(); it++){ std::cout << *it; if((it + 1) != ints.end()){ std::cout << ","; } } 

But given C++11 range based loops this is cumbersome to track position.

int count = ints.size(); for(const auto& i : ints){ std::cout << i; if(--count != 0){ std::cout << ","; } } 

I'm open to using Boost. I looked into boost::algorithm::join() but needed to make a copy of the ints to strings so it was a two-liner.

std::vector<std::string> strs; boost::copy(ints | boost::adaptors::transformed([](const int&i){return boost::lexical_cast<std::string>(i);}),std::back_inserter(strs)); std::cout << boost::algorithm::join(strs,","); 

Ideally I'd just like to use a std::algorithm and not have the delimiter on the last item in the range.

Thanks!

3
  • 1
    Does infix_iterator answer this? Commented Jun 28, 2013 at 2:39
  • Iterate to the penultimate value in your std::copy line, rather than ::end, then print the last item. Commented Jun 28, 2013 at 2:57
  • @Cubbi [infix_iterator][stackoverflow.com/a/3497021/273767] does indeed work. It's nice that it's a drop in replacement for std::ostream_iterator in code. Commented Jun 28, 2013 at 16:30

4 Answers 4

6

@Cubbi pointed out in a comment that is is exactly what infix_iterator does

// infix_iterator.h // // Lifted from Jerry Coffin's 's prefix_ostream_iterator #if !defined(INFIX_ITERATOR_H_) #define INFIX_ITERATOR_H_ #include <ostream> #include <iterator> template <class T, class charT=char, class traits=std::char_traits<charT> > class infix_ostream_iterator : public std::iterator<std::output_iterator_tag,void,void,void,void> { std::basic_ostream<charT,traits> *os; charT const* delimiter; bool first_elem; public: typedef charT char_type; typedef traits traits_type; typedef std::basic_ostream<charT,traits> ostream_type; infix_ostream_iterator(ostream_type& s) : os(&s),delimiter(0), first_elem(true) {} infix_ostream_iterator(ostream_type& s, charT const *d) : os(&s),delimiter(d), first_elem(true) {} infix_ostream_iterator<T,charT,traits>& operator=(T const &item) { // Here's the only real change from ostream_iterator: // Normally, the '*os << item;' would come before the 'if'. if (!first_elem && delimiter != 0) *os << delimiter; *os << item; first_elem = false; return *this; } infix_ostream_iterator<T,charT,traits> &operator*() { return *this; } infix_ostream_iterator<T,charT,traits> &operator++() { return *this; } infix_ostream_iterator<T,charT,traits> &operator++(int) { return *this; } }; #endif #include <vector> #include <algorithm> #include <string> #include <iostream> using namespace std; int main(int argc, char *argv[]) { std::vector<int> ints = {10,20,30,40,50,60,70,80,90}; std::copy(ints.begin(),ints.end(),infix_ostream_iterator<int>(std::cout,",")); } 

Prints:

10,20,30,40,50,60,70,80,90

Sign up to request clarification or add additional context in comments.

Comments

4

copy could be implement as:

template<class InputIterator, class OutputIterator> OutputIterator copy (InputIterator first, InputIterator last, OutputIterator result) { while (first!=last) { *result = *first; ++result; ++first; } return result; } 

The assignment to the ostream_iterator (output iterator) could be implemented as:

ostream_iterator<T,charT,traits>& operator= (const T& value) { *out_stream << value; if (delim!=0) *out_stream << delim; return *this; } 

So the delimiter will be appended on every assignment to the output iterator. To avoid the delimiter being appended to the last vector element, the last element should be assigned to an output iterator without delimiter, for example:

#include <iostream> #include <vector> #include <algorithm> #include <iterator> int main() { std::vector<int> ints = {10,20,30,40,50,60,70,80,90}; std::copy(ints.begin(), ints.end()-1, std::ostream_iterator<int>(std::cout, ",")); std::copy(ints.end()-1, ints.end(), std::ostream_iterator<int>(std::cout)); std::cout << std::endl; return 0; } 

Results in:

10,20,30,40,50,60,70,80,90 

1 Comment

On second thoughts, there's really no point in the 2nd call to copy.
3

this would be easier. Dunno this's what you want

#include<iostream> #include<algorithm> #include<vector> #include<iterator> int main() { std::vector<int> ints={10,20,30,40,50,60,70,80,90}; std::copy(ints.begin(),ints.end(),std::ostream_iterator<int> (std::cout,",")); std::cout<<(char)8; } 

1 Comment

I can't argue with it being easier but I don't like how it's a two-liner to insert the back space into the stream. When this is called more than once it becomes error prone.
0

Use the erase method of std::string:

 string join (const vector< vector<int> > data, const char* separator){ vector< vector<int> > result(data[0].size(), vector<int>(data.size())); stringstream rowStream; vector<string> rowVector; for (size_t i = 0; i < data.size(); i++ ){ copy(data[i].begin(), data[i].begin() + data[i].size(), ostream_iterator<int>(rowStream, " ")); rowVector.push_back(rowStream.str().erase(rowStream.str().length()-1)); rowStream.str(""); rowStream.clear(); } copy(rowVector.begin(), rowVector.begin() + rowVector.size(), ostream_iterator<string>(rowStream, separator)); return rowStream.str().erase(rowStream.str().length()-3); } 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.