Каждый сталкивается с этой проблемой в какой-то момент:
for(const auto& item : items) {
cout << item << separator;
}
... и вы получите дополнительный разделитель, которого вы не хотите в конце. Иногда он не печатает, но, скажем, выполняет какое-то другое действие, но для того, чтобы последовательные действия одного и того же типа требовали какого-либо действия разделителя, но последнее не делает.
Теперь, если вы работаете со старой школой для циклов и массива, вы бы сделали
for(int i = 0; i < num_items; i++)
cout << items[i];
if (i < num_items - 1) { cout << separator; }
}
(или вы можете исключить последний элемент из цикла.) Если у вас есть что-то, что допускает неразрушающие итераторы, даже если вы не знаете его размер, вы можете сделать:
for(auto it = items.cbegin(); it != items.cend(); it++) {
cout << *it;
if (std::next(it) != items.cend()) { cout << separator; }
}
Мне не нравится эстетика последних двух, и, как правило, для петель. Могу ли я получить тот же эффект, что и с двумя последними, но используя более сильные конструкции С++ 11ish?
Чтобы расширить вопрос дальше (за пределами, скажем,
этого), я скажу, что также хотел бы, чтобы явным образом не имел особого случая первый или последний элемент. Это "деталь реализации", с которой я не хочу беспокоиться. Итак, в imaginary-future-С++ возможно что-то вроде:
for(const auto& item : items) {
cout << item;
} and_between {
cout << separator;
}
Ответ 1
Мой путь (без дополнительной ветки):
const auto separator = "WhatYouWantHere";
const auto* sep = "";
for(const auto& item : items) {
std::cout << sep << item;
sep = separator;
}
Ответ 2
Исключение конечного элемента из итерации - это то, что предлагает предложение Ranges для упрощения. (Обратите внимание, что есть более эффективные способы решения конкретной задачи объединения строк, отключение элемента от итерации просто создает больше особых случаев, о которых нужно беспокоиться, например, когда коллекция уже была пуста.)
Пока мы ожидаем стандартизованной парадигмы Ranges, мы можем сделать это с существующим диапазоном с небольшим вспомогательным классом.
template<typename T> struct trim_last
{
T& inner;
friend auto begin( const trim_last& outer )
{ using std::begin;
return begin(outer.inner); }
friend auto end( const trim_last& outer )
{ using std::end;
auto e = end(outer.inner); if(e != begin(outer)) --e; return e; }
};
template<typename T> trim_last<T> skip_last( T& inner ) { return { inner }; }
и теперь вы можете написать
for(const auto& item : skip_last(items)) {
cout << item << separator;
}
Демо: http://rextester.com/MFH77611
Для skip_last
, который работает с ranged-for, необходим двунаправленный итератор, для аналогичного skip_first
достаточно иметь итератор Forward.
Ответ 3
Знаете ли вы устройство Duff?
int main() {
int const items[] = {21, 42, 63};
int const * item = items;
int const * const end = items + sizeof(items) / sizeof(items[0]);
// the device:
switch (1) {
case 0: do { cout << ", ";
default: cout << *item; ++item; } while (item != end);
}
cout << endl << "I'm so sorry" << endl;
return 0;
}
(Live)
Надеюсь, я не испортил весь день. Если вы не хотите или никогда не использовать это.
(бормотать) Мне очень жаль...
Устройство, обрабатывающее пустые контейнеры (диапазоны):
template<typename Iterator, typename Fn1, typename Fn2>
void for_the_device(Iterator from, Iterator to, Fn1 always, Fn2 butFirst) {
switch ((from == to) ? 1 : 2) {
case 0:
do {
butFirst(*from);
case 2:
always(*from); ++from;
} while (from != to);
default: // reached directly when from == to
break;
}
}
Live test:
int main() {
int const items[] = {21, 42, 63};
int const * const end = items + sizeof(items) / sizeof(items[0]);
for_the_device(items, end,
[](auto const & i) { cout << i;},
[](auto const & i) { cout << ", ";});
cout << endl << "I'm (still) so sorry" << endl;
// Now on an empty range
for_the_device(end, end,
[](auto const & i) { cout << i;},
[](auto const & i) { cout << ", ";});
cout << "Incredibly sorry." << endl;
return 0;
}
Ответ 4
Я не знаю никаких специальных идиом для этого. Тем не менее, я предпочитаю специальный случай первым, а затем выполняю операцию над остальными элементами.
#include <iostream>
#include <vector>
int main()
{
std::vector<int> values = { 1, 2, 3, 4, 5 };
std::cout << "\"";
if (!values.empty())
{
std::cout << values[0];
for (size_t i = 1; i < values.size(); ++i)
{
std::cout << ", " << values[i];
}
}
std::cout << "\"\n";
return 0;
}
Выход: "1, 2, 3, 4, 5"
Ответ 5
Обычно я делаю это наоборот:
bool first=true;
for(const auto& item : items) {
if(!first) cout<<separator;
first = false;
cout << item;
}
Ответ 6
Мне нравятся простые структуры управления.
if (first == last) return;
while (true) {
std::cout << *first;
++first;
if (first == last) break;
std::cout << separator;
}
В зависимости от вашего вкуса вы можете поместить инкремент и тест в одну строку:
...
while (true) {
std::cout << *first;
if (++first == last) break;
std::cout << separator;
}
Ответ 7
Я не знаю, что вы можете найти где-нибудь в специальном случае... Например, Boost Библиотека алгоритмов строк имеет join. Если вы посмотрите на его реализацию, вы увидите специальный случай для первого элемента (без последующего разделителя), а затем разделитель будет добавлен перед каждым последующим элементом.
Ответ 8
Вы можете определить функцию for_each_and_join, которая в качестве аргумента принимает два функтора. Первый функтор работает с каждым элементом, второй работает с каждой парой смежных элементов:
#include <iostream>
#include <vector>
template <typename Iter, typename FEach, typename FJoin>
void for_each_and_join(Iter iter, Iter end, FEach&& feach, FJoin&& fjoin)
{
if (iter == end)
return;
while (true) {
feach(*iter);
Iter curr = iter;
if (++iter == end)
return;
fjoin(*curr, *iter);
}
}
int main() {
std::vector<int> values = { 1, 2, 3, 4, 5 };
for_each_and_join(values.begin(), values.end()
, [](auto v) { std::cout << v; }
, [](auto, auto) { std::cout << ","; }
);
}
Пример в реальном времени: http://ideone.com/fR5S9H
Ответ 9
int a[3] = {1,2,3};
int size = 3;
int i = 0;
do {
std::cout << a[i];
} while (++i < size && std::cout << ", ");
Вывод:
1, 2, 3
Цель состоит в том, чтобы использовать способ &&
. Если первое условие истинно, оно оценивает второе. Если это неверно, то второе условие пропускается.
Ответ 10
Я не знаю о "идиоматическом", но С++ 11 предоставляет функции std::prev
и std::next
для двунаправленных итераторов.
int main() {
vector<int> items = {0, 1, 2, 3, 4};
string separator(",");
// Guard to prevent possible segfault on prev(items.cend())
if(items.size() > 0) {
for(auto it = items.cbegin(); it != prev(items.cend()); it++) {
cout << (*it) << separator;
}
cout << (*prev(items.cend()));
}
}
Ответ 11
Мне нравится функция boost::join
. Поэтому для более общего поведения вам нужна функция, которая вызывается для каждой пары элементов и может иметь постоянное состояние. Вы использовали бы это как вызов funcgion с лямбдой:
foreachpair (range, [](auto left, auto right){ whatever });
Теперь вы можете вернуться к регулярному циклу for
на основе диапазона, используя фильтры диапазона!
for (auto pair : collection|aspairs) {
Do-something_with (pair.first);
}
В этой идее pair
задается пара смежных элементов исходного набора. Если у вас есть "abcde", то на первой итерации вам присваиваются первые = "a", а второй = "b"; в следующий раз через first = 'b' и second = 'c'; и др.
Вы можете использовать подобный подход фильтра для подготовки кортежа, который отмечает каждый элемент итерации перечислением для /first/middle/last/iteration, а затем выполняет переключатель внутри цикла.
Чтобы просто оставить последний элемент, используйте фильтр диапазона для всех-но-последних. Я не знаю, уже ли это в Boost.Range или что может быть поставлено с Rangev3 в процессе, но что общий подход к созданию регулярного цикла делает трюки и делает его "опрятным".
Ответ 12
Вот небольшой трюк, который мне нравится:
Для двунаправленных итерационных объектов:
for ( auto it = items.begin(); it != items.end(); it++ )
{ std::cout << *it << (it == items.end()-1 ? "" : sep); };
Используя тернарный оператор ?
, я сравниваю текущую позицию итератора с вызовом item.end()-1
. Поскольку итератор, возвращаемый item.end()
, ссылается на позицию после последнего элемента, мы уменьшаем его один раз, чтобы получить наш фактический последний элемент.
Если этот элемент не является последним элементом в итерируемом, мы возвращаем наш разделитель (определенный в другом месте), или если он является последним элементом, мы возвращаем пустую строку.
Для одноименных итераций (проверено с помощью std:: forward_list):
for ( auto it = items.begin(); it != items.end(); it++ )
{ std::cout << *it << (std::distance( it, items.end() ) == 1 ? "" : sep); };
Здесь мы заменяем предыдущее тернарное условие вызовом std:: distance, используя текущее местоположение итератора, и конец итерабельного.
Примечание. Эта версия работает как с двунаправленными итерами, так и с итераторами с одним направлением.
EDIT:
Я понимаю, что вам не нравятся итерации типа .begin()
и .end()
, но если вы хотите, чтобы счетчик LOC уменьшался, в этом случае вам, вероятно, придется избегать итерации на основе диапазона.
"Trick" просто обертывает логику сравнения в одном тройном выражении, если ваша логика сравнения относительно проста.