简体   繁体   中英

Join vector of strings to std::ostream (like boost::join)

I have a vector of strings and I want output it to stream (file stream, actually). And I want to have a delimiter between vector elements. There is a way to use standard ostream_iterator

std::vector <std::string> strs;
std::ostream_iterator<std::string> out_file_iterator ( out_file, delim );
std::copy ( strs.begin(), strs.end(), out_file_iterator );

I didn't like this way because there is a delim text after each element, but I don't need to have a delim after last element. I'd like to use something like boost::join . However boost::join returns string and my vector too large to output it to string.

What is most elegant way to achieve my goal?

One way that works is to handle the last one separately. But don't think it's very elegant . Of course, you could wrap the ugliness in your own join function.

assert(strs.size() > 0);
std::ostream_iterator<std::string> out_file_iterator ( out_file, delim );
std::copy ( strs.begin(), strs.end()-1, out_file_iterator );
out_file << strs.back();

The most elegant would be to write your own loop. Or a seperate function.

template<class Stream, class InIt>
void print_range(Stream& s, InIt first, InIt last, char const* delim = "\n"){
  if(first == last)
    return;
  s << *first++;
  for(; first != last; ++first){
    s << delim << *first;
  }
}

For a general solution (untested):

template<class T>
class ostream_join_iterator {
public:

    // Construct like an ostream_iterator.
    ostream_join_iterator(std::ostream& stream,
        const std::string& delimiter = "")
        : stream(stream), delimiter(delimiter), first(true) {}

    // Behave like an output iterator.
    ostream_join_iterator& operator++()    { return *this; }
    ostream_join_iterator& operator++(int) { return *this; }
    ostream_join_iterator& operator*()     { return *this; }

    // Output a delimiter before all but the first element.
    template<class T>
    ostream_join_iterator& operator=(const T& value) {
        if (!first) {
            stream << delimiter;
        } else {
            first = false;
        }
        stream << value;
        return *this;
    }

private:

    std::ostream& stream;
    const std::string delimiter;
    bool first;

};

You can use it like a regular std::ostream_iterator :

std::copy(strings.begin(), strings.end(),
    ostream_join_iterator<std::string>(file, delimiter));

This is an idea with a functor

using namespace std;

struct add_delim_t {
  add_delim_t(const char *_delim) : delim_(_delim), is_first_(true) {}
  string operator () (const string &_val) {
    if (is_first_) { is_first_ = false; return _val; } else return delim_ + _val;
  }
private:
  const string delim_;
  bool is_first_;
};

transform(s.begin(), s.end(), ostream_iterator<string>(cout), add_delim_t(" , "));

The problem with this solution that it uses statefull predicate. Theoretically it means UB.

There is an idea with Boost Function Input Iterator

using namespace std;

struct generator {
  typedef string result_type;
  generator(result_type _delim) : delim_(_delim), is_first_(true) {}
  result_type operator () () { 
    if (!is_first_)
      return delim_; 
    is_first_ = false; 
    return ""; 
  }
private:
  result_type delim_;
  bool is_first_;
};

template<class T>
struct reverse_plus : public binary_function<T, T, T> {
  T operator()(const T& _lhs, const T& _rhs) const { return (_rhs + _lhs); }
};

// output to file stream
transform
( strs.begin()
, strs.end()
, boost::make_function_input_iterator(generator(" , "), boost::infinite())
, ostream_iterator<string> out_file_iterator(out_file)
, reverse_plus<string>()
);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM