Печать списков с запятыми С++

Я знаю, как это сделать на других языках, но не на С++, который я вынужден использовать здесь.

У меня есть набор строк, которые я распечатываю в списке, и им нужна запятая между каждой, но не конечная запятая. Например, в java, я бы использовал строковый конструктор и просто удалял запятую с конца после того, как я построил свою строку. Как это сделать на С++?

auto iter = keywords.begin();
for (iter; iter != keywords.end( ); iter++ )
{

    out << *iter << ", ";
}
out << endl;

Сначала я попытался вставить этот блок, чтобы сделать это (перемещая здесь запятую)

if (iter++ != keywords.end())
    out << ", ";
iter--;

Я ненавижу, когда маленькие вещи меня трогают.

EDIT: Спасибо всем. Вот почему я публикую такие вещи здесь. Так много хороших ответов и решаются по-разному. После семестра Java и сборки (разные классы) необходимость выполнения проекта С++ за 4 дня бросила меня на цикл. Я не только получил свой ответ, я получил возможность подумать о различных способах решения такой проблемы. Высокий.

Ответ 1

Используйте infix_iterator:

// infix_iterator.h 
// 
// Lifted from Jerry Coffin  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 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 "infix_iterator.h"

// ...
std::copy(keywords.begin(), keywords.end(), infix_iterator(out, ","));

Ответ 2

В экспериментальном готовом компиляторе С++ 17, который скоро появится для вас, вы можете использовать std::experimental::ostream_joiner:

#include <algorithm>
#include <experimental/iterator>
#include <iostream>
#include <iterator>

int main()
{
    int i[] = {1, 2, 3, 4, 5};
    std::copy(std::begin(i),
              std::end(i),
              std::experimental::make_ostream_joiner(std::cout, ", "));
}

Примеры в реальном времени с использованием GCC 6.0 SVN и Clang 3.9 SVN

Ответ 3

Поскольку все решили сделать это с помощью циклов while, я приведу пример для циклов.

for (iter = keywords.begin(); iter != keywords.end(); iter++) {
  if (iter != keywords.begin()) cout << ", ";
  cout << *iter;
}

Ответ 4

Один общий подход заключается в том, чтобы напечатать первый элемент до цикла и выполнить цикл только над остальными элементами, PRE-распечатать запятую перед каждым оставшимся элементом.

В качестве альтернативы вы можете создать свой собственный поток, который поддерживает текущее состояние строки (до конца) и помещает запятые в нужное место.

EDIT: Вы также можете использовать цикл с промежуточным тестированием, предложенный T.E.D. Это будет что-то вроде:

if(!keywords.empty())
{
    auto iter = keywords.begin();
    while(true)
    {
        out << *iter;
        ++iter;
        if(iter == keywords.end())
        {
            break;
        }
        else
        {
            out << ", ";
        }
    }
}

Сначала я упомянул метод "сначала напечатать первый элемент перед циклом", потому что он сохраняет тело цикла очень простым, но любой из подходов работает нормально.

Ответ 5

Предполагая смутно нормальный выходной поток, поэтому запись пустой строки в него действительно ничего не делает:

const char *padding = "";
for (auto iter = keywords.begin(); iter != keywords.end(); ++iter) {
    out << padding << *iter;
    padding = ", "
}

Ответ 6

Что-то вроде этого?

while (iter != keywords.end())
{
 out << *iter;
 iter++;
 if (iter != keywords.end()) cout << ", ";
}

Ответ 7

Есть много умных решений, и слишком много, которые калечат код, кроме надежды на спасение, не позволяя компилятору выполнять свою работу.

Очевидное решение - это особый случай первой итерации:

bool first = true;
for (auto const& e: sequence) {
   if (first) { first = false; } else { out << ", "; }
   out << e;
}

Это мертвый простой шаблон, который:

  • Не искажает цикл: все еще очевидно с первого взгляда, что каждый элемент будет повторяться.
  • Позволяет больше, чем просто помещать разделитель или фактически печатать список, поскольку блок else и тело цикла могут содержать произвольные утверждения.

Это может быть не самый эффективный код, но потенциальная потеря производительности одной хорошо предсказанной ветки, скорее всего, будет омрачена массивным бегемотом, который std::ostream::operator<<.

Ответ 8

Мой типичный метод для выполнения разделителей (на любом языке) заключается в использовании цикла с промежуточным тестированием. Код С++:

for (;;) {
   std::cout << *iter;
   if (++iter == keywords.end()) break;
   std::cout << ",";
}

(примечание: перед циклом требуется дополнительная проверка if, если ключевые слова могут быть пустыми)

Большинство других показанных решений в конечном итоге выполняют весь дополнительный тест на каждой итерации цикла. Вы делаете I/O, поэтому время, затраченное на это, не является большой проблемой, но это оскорбляет мою чувствительность.

Ответ 9

Попробуйте следующее:

typedef  std::vector<std::string>   Container;
typedef Container::const_iterator   CIter;
Container   data;

// Now fill the container.


// Now print the container.
// The advantage of this technique is that ther is no extra test during the loop.
// There is only one additional test !test.empty() done at the beginning.
if (!data.empty())
{
    std::cout << data[0];
    for(CIter loop = data.begin() + 1; loop != data.end(); ++loop)
    {
        std::cout << "," << *loop;
    }
}

Ответ 10

Существует небольшая проблема с оператором ++, который вы используете.

Вы можете попробовать:

if (++iter != keywords.end())
    out << ", ";
iter--;

Таким образом, ++ будет оцениваться до сравнения итератора с keywords.end().

Ответ 11

В питоне мы просто пишем:

print ", ".join(keywords)

так почему бы и нет:

template<class S, class V>
std::string
join(const S& sep, const V& v)
{
  std::ostringstream oss;
  if (!v.empty()) {
    typename V::const_iterator it = v.begin();
    oss << *it++;
    for (typename V::const_iterator e = v.end(); it != e; ++it)
      oss << sep << *it;
  }
  return oss.str();
}

а затем просто используйте его как:

cout << join(", ", keywords) << endl;

В отличие от примера python, где " " - это строка, а keywords - итерабельность строк, здесь в этом примере на С++ разделитель и keywords могут быть любыми потоковыми, например

cout << join('\n', keywords) << endl;

Ответ 12

чтобы избежать размещения if внутри цикла, я использую это:

vector<int> keywords = {1, 2, 3, 4, 5};

if (!keywords.empty())
{
    copy(keywords.begin(), std::prev(keywords.end()), 
         std::ostream_iterator<int> (std::cout,", "));
    std::cout << keywords.back();
}

Это зависит от типа вектора, int, но вы можете удалить его с помощью некоторого помощника.

Ответ 13

Следующее следует делать: -

 const std::vector<__int64>& a_setRequestId
 std::stringstream strStream;
 std::copy(a_setRequestId.begin(), a_setRequestId.end() -1, std::ostream_iterator<__int64>(strStream, ", "));
 strStream << a_setRequestId.back();

Ответ 14

Я предлагаю вам просто переключить первый символ с помощью лямбда.

std::function<std::string()> f = [&]() {f = [](){ return ","; }; return ""; };                  

for (auto &k : keywords)
    std::cout << f() << k;

Ответ 15

Если значения являются std::string вы можете написать это красиво в декларативном стиле с помощью range-v3

#include <range/v3/all.hpp>
#include <vector>
#include <iostream>
#include <string>

int main()
{
    using namespace ranges;
    std::vector<std::string> const vv = { "a","b","c" };

    auto joined = vv | view::join(',');

    std::cout << to_<std::string>(joined) << std::endl;
}

Для других типов, которые должны быть преобразованы в строку, вы можете просто добавить преобразование, вызывающее to_string.

#include <range/v3/all.hpp>
#include <vector>
#include <iostream>
#include <string>

int main()
{
    using namespace ranges;
    std::vector<int> const vv = { 1,2,3 };

    auto joined = vv | view::transform([](int x) {return std::to_string(x);})
                     | view::join(',');
    std::cout << to_<std::string>(joined) << std::endl;
}

Ответ 16

Я думаю, что это должно работать

while (iter != keywords.end( ))
{

    out << *iter;
    iter++ ;
    if (iter != keywords.end( )) out << ", ";
}

Ответ 17

Может быть так.

bool bFirst = true;
for (auto curr = keywords.begin();  curr != keywords.end(); ++curr) {
   std::cout << (bFirst ? "" : ", ") << *curr;
   bFirst = false;
}

Ответ 18

Я использую для этого небольшой вспомогательный класс:

class text_separator {
public:
    text_separator(const char* sep) : sep(sep), needsep(false) {}

    // returns an empty string the first time it is called
    // returns the provided separator string every other time
    const char* operator()() {
        if (needsep)
            return sep;
        needsep = true;
        return "";
    }

    void reset() { needsep = false; }

private:
    const char* sep;
    bool needsep;
};

Чтобы использовать его:

text_separator sep(", ");
for (int i = 0; i < 10; ++i)
    cout << sep() << i;

Ответ 19

Использование boost:

std::string add_str("");
const std::string sep(",");

for_each(v.begin(), v.end(), add_str += boost::lambda::ret<std::string>(boost::lambda::_1 + sep));

и вы получите строку, содержащую вектор, разделенный запятой.

EDIT: для удаления последней запятой просто введите:

add_str = add_str.substr(0, add_str.size()-1);

Ответ 20

Вы можете использовать цикл do, переписать условие цикла для первой итерации и использовать оператор короткого замыкания && и тот факт, что допустимый поток true.

auto iter = keywords.begin();
if ( ! keywords.empty() ) do {
    out << * iter;
} while ( ++ iter != keywords.end() && out << ", " );
out << endl;

Ответ 21

Я бы пошел с чем-то вроде этого, легким решением и должен работать для всех итераторов.

int maxele = maxele = v.size() - 1;
for ( cur = v.begin() , i = 0; i < maxele ; ++i)
{
    std::cout << *cur++ << " , ";
}
if ( maxele >= 0 )
{
  std::cout << *cur << std::endl;
}

Ответ 22

Другое возможное решение, позволяющее избежать if

Char comma = '[';
for (const auto& element : elements) {
    std::cout.put(comma) << element;
    comma = ',';
}
std::cout.put(']');

Зависит от того, что вы делаете в своем цикле.

Ответ 23

Этот перегружает оператор потока. Да, глобальные переменные являются злыми.

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iterator>

int index = 0;
template<typename T, template <typename, typename> class Cont>
std::ostream& operator<<(std::ostream& os, const Cont<T, std::allocator<T>>& vec)
{
    if (index < vec.size()) {
        if (index + 1 < vec.size())
            return os << vec[index++] << "-" << vec;
        else
            return os << vec[index++] << vec;
    } else return os;
}

int main()
{
    std::vector<int> nums(10);
    int n{0};
    std::generate(nums.begin(), nums.end(), [&]{ return n++; });
    std::cout << nums << std::endl;
}

Ответ 24

Можно использовать функторы:

#include <functional>

string getSeparatedValues(function<bool()> condition, function<string()> output, string separator)
{
    string out;
    out += output();
    while (condition())
        out += separator + output();
    return out;
}

Пример:

if (!keywords.empty())
{
    auto iter = keywords.begin();
    cout << getSeparatedValues([&]() { return ++iter != keywords.end(); }, [&]() { return *iter; }, ", ") << endl;
}

Ответ 25

Сочетание лямбды и макроса в c ++ 11:

#define INFIX_PRINTER(os, sep)([&]()->decltype(os)&{static int f=1;os<<(f?(f=0,""):sep);return os;})()

Использование:

for(const auto& k: keywords)
    INFIX_PRINTER(out, ", ") << k;