Подтвердить что ты не робот

Как получить индекс значения в векторе, используя for_each?

У меня есть следующий код (компилятор: MSVС++ 10):

std::vector<float> data;
data.push_back(1.0f);
data.push_back(1.0f);
data.push_back(2.0f);

// lambda expression
std::for_each(data.begin(), data.end(), [](int value) {
     // Can I get here index of the value too?
});

В приведенном выше фрагменте кода я хочу получить индекс значения в векторе данных внутри выражения лямбда. Кажется, for_each принимает только одну функцию параметра. Есть ли альтернатива этому, используя for_each и лямбда?

4b9b3361

Ответ 1

Я не думаю, что вы можете захватить индекс, но вы можете использовать внешнюю переменную для индексирования, захватив ее в лямбда:

int j = 0;
std::for_each(data.begin(), data.end(), [&j](float const& value) {
            j++;
});
std::cout << j << std::endl;

Это печатает 3, как и ожидалось, и j содержит значение индекса.

Если вы хотите фактический итератор, вы можете сделать это аналогично:

std::vector<float>::const_iterator it = data.begin();
std::for_each(data.begin(), data.end(), [&it](float const& value) {
            // here "it" has the iterator
            ++it; 
});

Ответ 2

Что-то вроде этого:

template <typename IteratorT, typename FunctionT>
FunctionT enumerate(IteratorT first, 
                    IteratorT last, 
                    typename std::iterator_traits<IteratorT>::difference_type initial,
                    FunctionT func)
{
    for (;first != last; ++first, ++initial)
        func(initial, *first);
    return func;
}

Используется как:

enumerate(data.begin(), data.end(), 0, [](unsigned index, float val)
{
    std::cout << index << " " << val << std::endl;
});

Ответ 3

Я думаю, что самый простой способ - использовать std::accumulate:

std::accumulate(data.begin(), data.end(), 0, [](int index, float const& value)->int{
    ...
    return index + 1;
});

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

Ответ 4

В С++ 14 благодаря обобщенным лямбда-захватам вы можете сделать что-то вроде этого:

std::vector<int> v(10);
std::for_each(v.begin(), v.end(), [idx = 0] (int i) mutable {
    // your code...
    ++idx; // 0, 1, 2... 9
});

Ответ 5

В качестве альтернативы вы можете использовать & value - & data [0], хотя это может быть немного дороже.

std::for_each(data.begin(), data.end(), [&data](float const& value) {
    int idx = &value - &data[0];
});

Ответ 6

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

Эта оболочка итератора принимает форвардный итератор, тип значения которого T (называемый "внутренним итератором" ) и преобразует его в форвардный итератор, тип значения которого равен pair<int, T&>, где int - тип расстояния внутренний итератор.

Это было бы довольно просто, за исключением двух вещей:

  • Конструктор std::pair принимает свои аргументы по ссылке const, поэтому мы не можем инициализировать элемент данных типа T&; мы должны будем создать собственный тип пары для итератора.
  • Чтобы поддерживать правильную семантику для итератора, нам нужно lvalue (operator* необходимо вернуть ссылку, а operator-> - вернуть указатель), поэтому пара должна быть членом данных итератора, Поскольку он содержит ссылку, нам понадобится способ "reset", и нам понадобится его, чтобы он был лениво инициализирован, чтобы мы могли корректно обрабатывать концевые итераторы. boost::optional<T>, похоже, не нравится, если T не присваивается, поэтому мы напишем собственный простой lazy<T>.

Обертка lazy<T>:

#include <new>
#include <type_traits>

// A trivial lazily-initialized object wrapper; does not support references
template<typename T>
class lazy
{
public:

    lazy() : initialized_(false) { }
    lazy(const T& x) : initialized_(false) { construct(x); }

    lazy(const lazy& other)
        : initialized_(false)
    {
        if (other.initialized_)
            construct(other.get());
    }

    lazy& operator=(const lazy& other)
    {
        // To the best of my knowledge, there is no clean way around the self
        // assignment check here since T may not be assignable
        if (this != &other)
            construct(other.get());
        return *this;
    }

    ~lazy() { destroy(); }

    void reset() { destroy(); }
    void reset(const T& x) { construct(x); }

          T& get()       { return reinterpret_cast<      T&>(object_); }
    const T& get() const { return reinterpret_cast<const T&>(object_); }

private:

    // Ensure lazy<T> is not instantiated with T as a reference type
    typedef typename std::enable_if<
        !std::is_reference<T>::value
    >::type ensure_t_is_not_a_reference;

    void construct(const T& x) 
    {
        destroy();
        new (&object_) T(x); 
        initialized_ = true;
    }

    void destroy() 
    { 
        if (initialized_)
            reinterpret_cast<T&>(object_).~T();
        initialized_ = false;
    }

    typedef typename std::aligned_storage<
        sizeof T, 
        std::alignment_of<T>::value
    >::type storage_type;

    storage_type object_;
    bool initialized_;
};

enumerating_iterator:

#include <iterator>
#include <type_traits>

// An enumerating iterator that transforms an iterator with a value type of T
// into an iterator with a value type of pair<index, T&>.
template <typename IteratorT>
class enumerating_iterator
{
public:

    typedef IteratorT                              inner_iterator;
    typedef std::iterator_traits<IteratorT>        inner_traits;
    typedef typename inner_traits::difference_type inner_difference_type;
    typedef typename inner_traits::reference       inner_reference;

    // A stripped-down version of std::pair to serve as a value type since
    // std::pair does not like having a reference type as a member.
    struct value_type
    {
        value_type(inner_difference_type f, inner_reference s)
            : first(f), second(s) { }

        inner_difference_type first;
        inner_reference       second;
    };

    typedef std::forward_iterator_tag iterator_category;
    typedef inner_difference_type     difference_type;
    typedef value_type&               reference;
    typedef value_type*               pointer;

    explicit enumerating_iterator(inner_iterator it = inner_iterator(), 
                                  difference_type index = 0) 
        : it_(it), index_(index) { }

    enumerating_iterator& operator++() 
    {
        ++index_;
        ++it_;
        return *this;
    }

    enumerating_iterator operator++(int)
    {
        enumerating_iterator old_this(*this);
        ++*this;
        return old_this;
    }

    const value_type& operator*() const 
    { 
        value_.reset(value_type(index_, *it_));
        return value_.get();
    }

    const value_type* operator->() const { return &**this; }

    friend bool operator==(const enumerating_iterator& lhs,
                           const enumerating_iterator& rhs)
    {
        return lhs.it_ == rhs.it_;
    }

    friend bool operator!=(const enumerating_iterator& lhs,
                           const enumerating_iterator& rhs)
    {
        return !(lhs == rhs);
    }

private:

    // Ensure that the template argument passed to IteratorT is a forward
    // iterator; if template instantiation fails on this line, IteratorT is
    // not a valid forward iterator:
    typedef typename std::enable_if<
        std::is_base_of<
            std::forward_iterator_tag,
            typename std::iterator_traits<IteratorT>::iterator_category
        >::value
    >::type ensure_iterator_t_is_a_forward_iterator;

    inner_iterator it_;              //< The current iterator
    difference_type index_;          //< The index at the current iterator
    mutable lazy<value_type> value_; //< Pair to return from op* and op->
};

// enumerating_iterator<T> construction type deduction helpers
template <typename IteratorT>
enumerating_iterator<IteratorT> make_enumerator(IteratorT it)
{
    return enumerating_iterator<IteratorT>(it);
}

template <typename IteratorT, typename DifferenceT>
enumerating_iterator<IteratorT> make_enumerator(IteratorT it, DifferenceT idx)
{
    return enumerating_iterator<IteratorT>(it, idx);
}

Тест-заглушка:

#include <algorithm>
#include <array>
#include <iostream>

struct print_pair
{
    template <typename PairT> 
    void operator()(const PairT& p)
    {
        std::cout << p.first << ": " << p.second << std::endl;
    }
};

int main()
{
    std::array<float, 5> data = { 1, 3, 5, 7, 9 };

    std::for_each(make_enumerator(data.begin()), 
                  make_enumerator(data.end()), 
                  print_pair());
}

Это было минимально проверено; Comeau и g++ 4.1 оба принимают его, если я удаляю черты типа С++ 0x и aligned_storage (у меня нет новой версии g++ на этом ноутбуке для тестирования). Пожалуйста, дайте мне знать, если вы найдете какие-либо ошибки.

Мне очень интересны предложения о том, как улучшить это. В частности, мне бы хотелось знать, есть ли способ использовать lazy<T>, либо используя что-то из Boost, либо изменяя сам итератор. Надеюсь, я просто тупой и что на самом деле действительно простой способ реализовать это более чисто.

Ответ 7

Другой способ обернуть итераторы для перечисления:

Обязательные заголовки:

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

Итератор упаковки:

template<class Iter, class Offset=int>
struct EnumerateIterator : std::iterator<std::input_iterator_tag, void, void, void, void> {
  Iter base;
  Offset n;

  EnumerateIterator(Iter base, Offset n = Offset()) : base (base), n (n) {}

  EnumerateIterator& operator++() { ++base; ++n; return *this; }
  EnumerateIterator operator++(int) { auto copy = *this; ++*this; return copy; }

  friend bool operator==(EnumerateIterator const& a, EnumerateIterator const& b) {
    return a.base == b.base;
  }
  friend bool operator!=(EnumerateIterator const& a, EnumerateIterator const& b) {
    return !(a == b);
  }

  struct Pair {
    Offset first;
    typename std::iterator_traits<Iter>::reference second;

    Pair(Offset n, Iter iter) : first (n), second(*iter) {}

    Pair* operator->() { return this; }
  };

  Pair operator*() { return Pair(n, base); }
  Pair operator->() { return Pair(n, base); }
};

Перечислить перегрузки:

template<class Iter, class Func>
Func enumerate(Iter begin, Iter end, Func func) {
  typedef EnumerateIterator<Iter> EI;
  return std::for_each(EI(begin), EI(end), func);
}
template<class T, int N, class Func>
Func enumerate(T (&a)[N], Func func) {
  return enumerate(a, a + N, func);
}
template<class C, class Func>
Func enumerate(C& c, Func func) {
  using std::begin;
  using std::end;
  return enumerate(begin(c), end(c), func);
}

Скопированный тест от Джеймса:

#include <array>
#include <iostream>

struct print_pair {
  template<class Pair>
  void operator()(Pair const& p) {
    std::cout << p.first << ": " << p.second << "\n";
  }
};

int main() {
  std::array<float, 5> data = {1, 3, 5, 7, 9};
  enumerate(data, print_pair());
  return 0;
}

Здесь я не включаю предоставление смещения; хотя он полностью готов в EnumerateIterator, чтобы начать с значения, отличного от 0. Выбор слева - это какой тип сделать смещение и добавить ли лишние значения для дополнительного параметра или использовать значение по умолчанию. (Нет причин, по которым смещение должно быть типом разности итераторов, например, что, если вы сделали его связанным с датой типа, с каждой итерацией, соответствующей следующему дню?)

Ответ 8

Следуя стандартным соглашениям для C и С++, первый элемент имеет индекс 0, а последний элемент имеет размер индекса() - 1.

Итак, вы должны сделать следующее:

std::vector<float> data;
int index = 0;

data.push_back(1.0f);
data.push_back(1.0f);
data.push_back(2.0f);

// lambda expression
std::for_each(data.begin(), data.end(), [&index](float value) {
// Can I get here index of the value too?
   cout<<"Current Index :"<<index++; // gets the current index before increment
});

Ответ 9

возможно, в лямбда-функции, передайте ей int& вместо значения int, поэтому у вас будет адрес. и затем вы можете использовать это, чтобы вывести свою позицию из первого элемента.

будет работать? я не знаю, поддерживает ли for_each ссылки

Ответ 10

Вы также можете передать структуру как третий аргумент в std:: for_each и подсчитать индекс в нем следующим образом:

struct myStruct {
   myStruct(void) : index(0) {};
   void operator() (float i) { cout << index << ": " << i << endl; index++; }
   int index;
};

int main()
{

   std::vector data;
   data.push_back(1.0f);
   data.push_back(4.0f);
   data.push_back(8.0f);

   // lambda expression
   std::for_each(data.begin(), data.end(), myStruct());

   return 0;
}