For_each, который дает два (или n) соседних элемента
Существует ли стандартная реализация for_each
, которая вызывает элемент и следующий в диапазоне?
Например, возьмите диапазон {0, 1, 2, 3, 4, 5}
, я хотел бы вызвать функцию f
с каждым элементом и его преемником: {f(0, 1), f(1, 2), f(2, 3), f(3, 4), f(4, 5)}
Обратите внимание, что последний элемент пропущен, потому что он не имеет преемника.
Также было бы хорошо, если бы это было обобщено на n преемников, которые передаются вместе с самим элементом.
До сих пор я всегда решал эту проблему с помощью рукописного цикла с итераторами. Тем не менее, я хотел бы пойти намного дальше в соответствии с диапазоном С++ 11, основанным на или std::for_each
чтобы избежать кода на уровне котла.
Примеры
// today: handwritten loop
for(Range::iterator current = range.begin(); current != range.end(); ++current)
f(*current, *std::next(current));
// near future: wrapped version
for_each_pair(range.begin(), range.end(), f);
// slightly further future: generalized version
for_each_tuple<n>(range.begin(), range.end(), f);
Дополнительный вопрос
Название функции может быть улучшено. Для меня for_each_pair
/tuple
звучит так, как будто должны быть возвращены все подмножества размера n диапазона (что само по себе является другой проблемой, которую я хотел бы решить). Поэтому я хотел бы получить несколько предложений о лучших именах, таких как:
for_each_adjacent<n>
Временное решение
Я разместил свое собственное решение в ЧР. Я не буду дублировать его здесь, потому что речь идет о стандартном решении, и уже есть достаточно ответов.
Ответы
Ответ 1
Простейшим было бы написать его как общий алгоритм, а затем применить его много раз.
template< typename FwdIter, typename Func >
Func for_each_pair( FwdIter iterStart, FwdIter iterEnd, Func func )
{
if( iterStart == iterEnd )
return func;
FwdIter iterNext = iterStart;
++iterNext;
for( ; iterNext != iterEnd; ++iterStart, ++iterNext )
{
func( *iterStart, *iterNext );
}
return func;
}
Как меня спрашивали, почему он возвращает func (а не void), это типично для for_each из-за того, что
- func может быть объектом
- Он передается по значению.
func может "накапливать" какое-то состояние, но это копия, которую мы сделали в этот алгоритм, который накапливает его, а не пользовательский исходный объект. Поэтому мы передаем им измененный объект "func".
Ответ 2
Вы можете на самом деле злоупотреблять std::unique
или std::adjacent_find
для этого: предикат вызывается с каждой последовательной парой в диапазоне итераторов, и до тех пор, пока предикат всегда возвращает false, он ничего не изменит и не вернет раньше.
Не считая этого конкретного взлома, я бы реализовал это как адаптер итератора, а не алгоритм. То есть я бы выполнил consecutive_tuple_iterator<N>
, который вернул бы все кортежи из N последовательных элементов. Тогда вы можете использовать его для таких вещей, как count_if
и includes
, а не только for_each
. (Однако это не подходит для большинства алгоритмов модификации.)
Ответ 3
С С++ 11 и новыми вспомогательными функциями итератора std:: next и std:: prev для итераторов, второй вариант стандартного алгоритма std:: transform можно использовать для итерации по соседним элементам.
Вот пример, который генерирует список смежных пар из списка:
std::vector<int> nums{3, 4, 2, 9, 15, 267};
std::vector<std::pair<int,int>> num_pairs;
if (!nums.empty()) {
std::transform(
std::begin(nums), std::prev(std::end(nums)),
std::next(std::begin(nums)),
std::back_inserter(num_pairs),
std::make_pair<
decltype(nums)::const_reference,
decltype(nums)::const_reference
>
);
}
Ответ 4
Не совсем то, что вы хотите, но посмотрите cpplinq.
int numbers[] = {0, 1, 2, 3, 4, 5};
auto pairs = cpplinq::from_array(numbers)
>> cpplinq::pairwise()
>> cpplinq::to_vector(); // yields (0,1), (1,2), (2,3), (3,4), (4,5)
for(auto p : pairs)
f(p.first, p.second);