Перебор нечетных (четных) элементов только в цикле на основе диапазона - PullRequest
0 голосов
/ 24 февраля 2019

Предположим, у нас есть простой массив (или другой контейнер, который поддерживает циклы на основе диапазона):

const int N = 8;
int arr[N] = {0, 1, 2, 3, 4, 5, 6, 7};

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

for (int i = 0; i < N; i+=2)
{
   std::cout << arr[i] << std::endl;
}

Как получить аналогичный результат, используя цикл на основе диапазона и избегая использования явных итераторов / индексов или пропуска итераций?Что-то вроде:

for (const auto& v: odd_only(arr))
{
   std::cout << v << std::endl;
}

Какое простое и элегантное решение вы видите?Стандартная библиотека содержит что-то подобное?

Ответы [ 4 ]

0 голосов
/ 27 февраля 2019

Это на самом деле не ответ на вопрос, но - чего бы это ни стоило - всякий раз, когда я сталкиваюсь с ограниченным диапазоном, я ищу стандартное решение для алгоритма.Нравится ...

#include <algorithm>
#include <iostream>
#include <iterator>
#include <utility>

int main()
{
    int arr[] {0, 1, 2, 3, 4, 5, 6, 7};
    std::copy_if(
        std::begin(arr), std::end(arr),
        std::ostream_iterator<int>(std::cout, "\n"),
        [is_odd_element = true](int n) mutable {
            return std::exchange(is_odd_element, not is_odd_element);
        });
}
0 голосов
/ 25 февраля 2019

Что касается того, что вы сейчас спрашиваете;Я не верю, что еще ничего не существует.Теперь, что касается перебора контейнера по некоторому целому числу N, мы можем сделать следующее;мы можем написать наш собственный тип функции for_each.Я написал один ниже, и он работает как драгоценный камень!Вы также можете захотеть взглянуть на функцию std::advance, так как она может быть другой возможной реализацией.Я проверял это сам, когда писал эту функцию.Тем не мение;Что касается массивов c, я не уверен, что многое можно обойтись без кучки дополнительного кода, такого как шаблоны классов, оболочки и т. д. Вот моя функция.

#include <array>
#include <vector>
#include <iterator>

template<typename Container, typename Function>
void for_each_by_n( Container&& cont, Function f, unsigned increment_by = 1) {
    if ( increment_by == 0 ) return; // must check this for no op

    using std::begin;
    auto it = begin(cont);

    using std::end;
    auto end_it = end(cont);

    while( it != end_it ) {
        f(*it);
        for ( unsigned n = 0; n < increment_by; ++n ) {
            if ( it == end_it ) return;
            ++it;
        }
    }
}

int main() {
    std::array<int,8> arr{ 0,1,2,3,4,5,6,7 };
    std::vector<double> vec{ 1.2, 1.5, 1.9, 2.5, 3.3, 3.7, 4.2, 4.8 };

    auto l = [](auto& v) { std::cout << v << ' '; };

    for_each_by_n(arr, l); std::cout << '\n';
    for_each_by_n(vec, l); std::cout << '\n';

    for_each_by_n(arr, l, 2); std::cout << '\n';
    for_each_by_n(arr, l, 4); std::cout << '\n';

    for_each_by_n(vec, l, 3); std::cout << '\n';
    for_each_by_n(vec, l, 5); std::cout << '\n';

    for_each_by_n(arr, l, 8); std::cout << '\n';
    for_each_by_n(vec, l, 8); std::cout << '\n';

    // sanity check to see if it doesn't go past end.
    for_each_by_n(arr, l, 9); std::cout << '\n';
    for_each_by_n(vec, l, 9); std::cout << '\n';

    return 0;
}

-Output-

 0 1 2 3 4 5 6 7
 1.2 1.5 1.9 2.5 3.3 3.7 4.2 4.8
 0 2 4 6 
 0 4
 1.2 2.5 4.2
 1.2 3.7
 0
 1.2
 0
 1.2

Что мне нравится в этом примере выше, так это то, что вы можете не только увеличивать цикл на целое число N;вышеупомянутая функция также принимает function pointer, function object, functor или lambda и выполняет требуемое действие.

В вашем случае вы пытались перебрать свой контейнер на 2 для каждого нечетного или каждого четного индекса, а внутри цикла вы печатали результаты.Вот в моем примере;Я печатаю результаты в виде лямбды, которая передается этой функции.

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

В настоящее время мы должны согласиться с тем, что могут предложить C ++ 11 - C ++ 17.В ближайшем будущем у нас должно быть много новых и мощных функций с выпуском C ++ 20.

0 голосов
/ 27 февраля 2019

Существует готовое решение для этой проблемы в Range-v3 .Я думаю, что это может быть полезно, если вы не хотите писать свою собственную реализацию или вам нужна большая гибкость (например, произвольный шаг)

#include <range/v3/all.hpp>

void example()
{
    int data[8] = {0, 1, 2, 3, 4, 5, 6, 7};
    for (auto i : ranges::view::stride(data, 2))
    {
        std::cout << i << std::endl;
    }
}

(скопировано из комментария @hlt)

0 голосов
/ 24 февраля 2019

Нет поддержки для того, что вы запрашиваете - но вы можете написать свои собственные реализации even_only и odd_only.

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

template <typename C, bool IsOdd>
class even_odd_only
{
    C& c;
public:
    class iterator
    {
    public:
        // all the definitions required for iterator!
        // most if not all might simply be derived from C::iterator...

        // copy/move constructor/assignment as needed

        // core of the wrapper: increment twice internally!
        // just doing += 2 is dangerous, though, we might increment beyond
        // the end iterator (undefined behaviour!)additionally, += 2 only
        // is possible for random access iterators (so we limit usability)
        void operator++() { ++b; if(b != e) ++b; }

        // operator* and operator-> (both return *b), post-increment
        // (defined in terms of pre-increment), etc...
        // comparison: only needs to compare b iterators!

    private:
        C::iterator b;
        C::iterator e; // needed for comparison to avoid incrementing beyond!
        iterator(C::iterator b, C::iterator e) : b(b), e(e) { }
    };
    // const_iterator, too; possibly make a template of above
    // and derive const and non-const iterators from?

    even_odd_only(C& c) : c(c) { }

    iterator begin()
    {
        using std::begin;
        using std::end;
        using std::empty;
        auto b = begin(c);
        // should be self-explanatory:
        // skip first element in odd variant (if there is)
        if constexpr(IsOdd) { if(!empty(c)) { ++b; } }
        return iterator(b, end(c));
    };
    iterator end()
    {
        using std::end;
        return iterator(end(c), end(c));
    }
};

template <typename T>
using even_only = even_odd_base<T, false>;
template <typename T>
using odd_only = even_odd_base<T, true>;

Как есть, это будет работать даже с итераторами без произвольного доступа и даже с двунаправленными адресами.Но особенно для RA-итераторов, он менее эффективен, чем классический цикл (из-за промежуточного, если в operator++).

Определение итераторов сравнения: всегда operator== и operator!=, только для операторов произвольного доступавы можете дополнительно иметь operator[<|>|<=|>=] (→ std::enable_if).

Более подробную информацию о том, как написать итератор, вы можете найти здесь - имейте в виду, что, когда вы столкнетесь,std::iterator сам по себе устарел.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...