Есть ли эквивалент цикла enumerate на основе диапазона из Python в C ++? Я представляю себе что-то подобное.

enumerateLoop (auto counter, auto el, container) {
    charges.at(counter) = el[0];
    aa.at(counter) = el[1];
}

Можно ли это сделать с помощью шаблонов или макросов?

Я знаю, что могу просто использовать старый цикл for-loop и повторять, пока не дойду до container.size(). Но мне интересно, как это решить с помощью шаблонов или макросов.

ИЗМЕНИТЬ

Я немного поигрался с итераторами повышения после подсказки в комментариях. У меня есть еще одно рабочее решение на C ++ 14.

template <typename... T>
auto zip(const T &... containers) -> boost::iterator_range<boost::zip_iterator<
decltype(boost::make_tuple(std::begin(containers)...))>> {
  auto zip_begin =
    boost::make_zip_iterator(boost::make_tuple(std::begin(containers)...));
  auto zip_end =
    boost::make_zip_iterator(boost::make_tuple(std::end(containers)...));
  return boost::make_iterator_range(zip_begin, zip_end);
}

template <typename T>
auto enumerate(const T &container) {
return zip(boost::counting_range(0, static_cast<int>(container.size())),
container);
} 

https://gist.github.com/kain88-de/fef962dc1c15437457a8

5
Max Linke 27 Фев 2015 в 18:56

9 ответов

Лучший ответ

Я кое-что написал для этого некоторое время назад.

По сути, вам нужно обернуть итератор и дать ему парную семантику.

AFAIK, в языке нет ничего подобного. И я не думаю, что у Boost это тоже есть. Вам в значительной степени придется кататься самостоятельно.

// Wraps a forward-iterator to produce {value, index} pairs, similar to
// python's enumerate()
template <typename Iterator>
struct EnumerateIterator {
private:
  Iterator current;
  Iterator last;
  size_t index;
  bool atEnd;

public:
  typedef decltype(*std::declval<Iterator>()) IteratorValue;
  typedef pair<IteratorValue const&, size_t> value_type;

  EnumerateIterator()
    : index(0), atEnd(true) {}

  EnumerateIterator(Iterator begin, Iterator end)
    : current(begin), last(end), index(0) {
    atEnd = current == last;
  }

  EnumerateIterator begin() const {
    return *this;
  }

  EnumerateIterator end() const {
    return EnumerateIterator();
  }

  EnumerateIterator operator++() {
    if (!atEnd) {
      ++current;
      ++index;

      atEnd = current == last;
    }

    return *this;
  }

  value_type operator*() const {
    return {*current, index};
  }

  bool operator==(EnumerateIterator const& rhs) const {
    return
      (atEnd && rhs.atEnd) ||
      (!atEnd && !rhs.atEnd && current == rhs.current && last == rhs.last);
  }

  bool operator!=(EnumerateIterator const& rhs) const {
    return !(*this == rhs);
  }

  explicit operator bool() const {
    return !atEnd;
  }
};

template<typename Iterable>
EnumerateIterator<decltype(std::declval<Iterable>().begin())> enumerateIterator(Iterable& list) {
  return EnumerateIterator<decltype(std::declval<Iterable>().begin())>(list.begin(), list.end());
}

template<typename ResultContainer, typename Iterable>
ResultContainer enumerateConstruct(Iterable&& list) {
  ResultContainer res;
  for (auto el : enumerateIterator(list))
    res.push_back(move(el));

  return res;
}
3
OmnipotentEntity 27 Фев 2015 в 16:12

Boost :: Range поддерживает это с 1.56.

#include <boost/range/adaptor/indexed.hpp>
#include <boost/assign.hpp>
#include <iterator>
#include <iostream>
#include <vector>


int main(int argc, const char* argv[])
{
    using namespace boost::assign;
    using namespace boost::adaptors;

    std::vector<int> input;
    input += 10,20,30,40,50,60,70,80,90;

//  for (const auto& element : index(input, 0)) // function version
    for (const auto& element : input | indexed(0))      
    {
        std::cout << "Element = " << element.value()
                  << " Index = " << element.index()
                  << std::endl;
    }

    return 0;
}
0
Catskul 24 Июл 2019 в 00:57

Тобиас Видлунд написал красивый заголовок в стиле Python, лицензированный MIT, только enumerate (хотя C ++ 17):

GitHub

Сообщение блога

Действительно приятно использовать:

std::vector<int> my_vector {1,3,3,7};

for(auto [i, my_element] : en::enumerate(my_vector))
{
    // do stuff
}
0
M. Ahnen 14 Фев 2019 в 09:07

Вот решение на основе макросов, которое, вероятно, превосходит большинство других по простоте, времени компиляции и качеству генерации кода:

#include <iostream>

#define fori(i, ...) if(size_t i = -1) for(__VA_ARGS__) if(i++, true)

int main() {
    fori(i, auto const & x : {"hello", "world", "!"}) {
        std::cout << i << " " << x << std::endl;
    }
}

Результат:

$ g++ -o enumerate enumerate.cpp -std=c++11 && ./enumerate 
0 hello
1 world
2 !
0
Forrest Voight 12 Янв 2019 в 03:56

Этот вопрос помечен как c ++ 14, но я хотел бы опубликовать ответ для c ++ 20, потому что есть действительно красивое решение, которое я опубликовал здесь

1
Björn Sundin 10 Окт 2020 в 11:33

В C ++ 17 и структурированных привязках это выглядит нормально - определенно лучше, чем некрасивые изменяемые лямбды с локальным [i = 0](Element&) mutable или что-то еще, что я сделал, прежде чем признать, что, вероятно, не все нужно впихивать в for_each() и др. - и чем другие решения, требующие счетчика с областью действия вне цикла for.

for (auto [it, end, i] = std::tuple{container.cbegin(), container.cend(), 0};
     it != end; ++it, ++i)
{
      // something that needs both `it` and `i`ndex
}

Вы можете сделать этот общий, если будете использовать этот шаблон достаточно часто:

template <typename Container>
auto
its_and_idx(Container&& container)
{
    using std::begin, std::end;
    return std::tuple{begin(container), end(container), 0};
}

// ...

for (auto [it, end, i] = its_and_idx(foo); it != end; ++it, ++i)
{
    // something
}

Стандартное предложение C ++ P2164 предлагает добавить views::enumerate, который предоставит представление диапазона, дающего как ссылку на элемент, так и индекс элемента для пользователя, повторяющего его.

Мы предлагаем представление enumerate, тип значения которого - struct с двумя членами index и value, представляющими соответственно положение и значение элементов в адаптированном диапазоне.

[. . .]

Эта функция в той или иной форме существует в Python, Rust, Go (с поддержкой языка) и во многих библиотеках C ++: ranges-v3, folly, boost::ranges (indexed).

Наличие или отсутствие этой функции является предметом повторяющихся вопросов о переполнении стека.

Эй смотри! Мы знамениты.

1
underscore_d 28 Май 2020 в 09:02

Вы также можете более элегантно использовать автоматические диапазоны, доступные с C ++ 11:

int i = 0;
for (auto& el : container){
    charges.at(counter) = el[0];
    aa.at(counter) = el[1];
    ++i;
}

Однако вам все равно придется пересчитывать i вручную.

2
steffen 16 Авг 2016 в 17:55

Для этого есть решение, предшествующее C ++ 11: boost.range.indexed. К сожалению, он не работает с циклами for на основе диапазона C ++ 11, только с подробными циклами старого стиля. Однако с C ++ 17 это должно быть (почти) таким же простым, как в python, используя структурированные привязки

Тогда должно быть возможно реализовать что-то, что работает следующим образом:

for (auto& [n,x] : enumerate(vec)) x = n;

Итак, немного подождем;)

3
André Bergner 6 Сен 2016 в 09:55

Перечисление нескольких переменных было идиомой, начиная с C. Единственная сложность заключается в том, что вы не можете объявить обе переменные в инициализаторе цикла for.

int index;
for (auto p = container.begin(), index = 0; p != container.end(); ++p, ++index)

Я не думаю, что это может быть проще (или мощнее), чем это.

4
QuestionC 11 Апр 2016 в 12:52