Печать списков с запятыми С++
Я знаю, как это сделать на других языках, но не на С++, который я вынужден использовать здесь.
У меня есть набор строк, которые я распечатываю в списке, и им нужна запятая между каждой, но не конечная запятая. Например, в 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;