Печать списков с запятыми C ++ - PullRequest
46 голосов
/ 17 августа 2010

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

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

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

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

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

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

Ненавижу, когда мелочи сбивают меня с толку.

РЕДАКТИРОВАТЬ: Спасибо всем. Вот почему я размещаю подобные вещи здесь. Так много хороших ответов, и решаются по-разному. После семестра Java и ассемблера (разные классы) необходимость выполнения C ++ проекта за 4 дня заставила меня задуматься. Мало того, что я получил свой ответ, я получил возможность подумать о различных способах решения такой проблемы. Потрясающе.

Ответы [ 24 ]

50 голосов
/ 17 августа 2010

Использовать infix_iterator:

// 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 "infix_iterator.h"

// ...
std::copy(keywords.begin(), keywords.end(), infix_iterator(out, ","));
24 голосов
/ 17 августа 2010

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

for (iter = keywords.begin(); iter != keywords.end(); iter++) {
  if (iter != keywords.begin()) cout << ", ";
  cout << *iter;
}
23 голосов
/ 15 марта 2016

В ближайшем к вам экспериментальном готовом компиляторе C ++ 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.0SVN и Clang 3.9 SVN

17 голосов
/ 17 августа 2010

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

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

РЕДАКТИРОВАТЬ: Вы также можете использовать цикл среднего тестирования, как предложено TED. Это будет что-то вроде:

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

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

15 голосов
/ 17 августа 2010

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

const char *padding = "";
for (auto iter = keywords.begin(); iter != keywords.end(); ++iter) {
    out << padding << *iter;
    padding = ", "
}
7 голосов
/ 17 августа 2010

Как то так?

while (iter != keywords.end())
{
 out << *iter;
 iter++;
 if (iter != keywords.end()) cout << ", ";
}
6 голосов
/ 14 февраля 2016

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

Очевидное решение относится к специальному случаюпервая итерация:

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

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

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

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

5 голосов
/ 17 августа 2010

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

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

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

3 голосов
/ 17 августа 2010

Попробуйте это:

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;
    }
}
2 голосов
/ 17 августа 2010

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

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

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

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

...