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

Есть ли способ перебрать не более N элементов, используя цикл для цикла?

Я хотел бы знать, есть ли хороший способ перебрать не более N элементов в контейнере, используя диапазон, основанный на цикле и/или алгоритмах из стандартной библиотеки (что в целом, я знаю, что могу просто использовать "старый" цикл с условием).

В принципе, я ищу что-то, что соответствует этому коду Python:

for i in arr[:N]:
    print(i)
4b9b3361

Ответ 1

Как я лично использовал бы этот или этот ответ (+1 для обоих), только для увеличивая ваши знания - есть дополнительные адаптеры, которые вы можете использовать. Для вашего случая - sliced представляется наиболее подходящим:

#include <boost/range/adaptor/sliced.hpp>
#include <vector>
#include <iostream>

int main(int argc, const char* argv[])
{
    std::vector<int> input={1,2,3,4,5,6,7,8,9};
    const int N = 4;
    using boost::adaptors::sliced;
    for (auto&& e: input | sliced(0, N))
        std::cout << e << std::endl;
}

Одно важное замечание: N требуется sliced не больше distance(range) - поэтому более безопасная (и более медленная) версия выглядит следующим образом:

    for (auto&& e: input | sliced(0, std::min(N, input.size())))

Итак - еще раз - я бы использовал более простой, старый подход на C/С++ (этого вы хотели избежать в своем вопросе;)

Ответ 2

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

auto begin = std::begin(range);
auto end = std::end(range);
if (std::distance(begin, end) > N)
    end = std::next(begin,N);

Это может проходить через диапазон почти в два раза, но я не вижу другого способа получить длину диапазона.

Ответ 3

Вы можете использовать старый добрый break, чтобы вручную разбить цикл, когда это необходимо. Он работает даже с циклом, основанным на диапазонах.

#include <vector>
#include <iostream>

int main() {
    std::vector<int> a{2, 3, 4, 5, 6};
    int cnt = 0;
    int n = 3;
    for (int x: a) {
       if (cnt++ >= n) break;
       std::cout << x << std::endl;
    }
}

Ответ 4

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

#include <vector>
#include <iostream>

//~-~-~-~-~-~-~- abstraction begins here ~-~-~-~-~-//
struct range {
 range(std::vector<int>& cnt) : m_container(cnt),
   m_end(cnt.end()) {}
 range& till(int N) {
     if (N >= m_container.size())
         m_end = m_container.end();
     else
        m_end = m_container.begin() + N;
     return *this;
 }
 std::vector<int>& m_container;
 std::vector<int>::iterator m_end;
 std::vector<int>::iterator begin() {
    return m_container.begin();
 }
 std::vector<int>::iterator end() {
    return m_end;
 }
};
//~-~-~-~-~-~-~- abstraction ends here ~-~-~-~-~-//

int main() {
    std::vector<int> a{11, 22, 33, 44, 55};
    int n = 4;

    range subRange(a);        
    for ( int i : subRange.till(n) ) {
       std::cout << i << std::endl; // prints 11, then 22, then 33, then 44
    }
}

Живой пример

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

Это работает, поскольку диапазон для циклов создает код, похожий на следующий

{
  auto && __range = range_expression ; 
  for (auto __begin = begin_expr,
       __end = end_expr; 
       __begin != __end; ++__begin) { 
    range_declaration = *__begin; 
    loop_statement 
  } 
} 

ЧФР. begin_expr и end_expr

Ответ 5

Если ваш контейнер не имеет (или может не иметь) RandomAccessIterator, есть еще способ обмануть этого кота:

int cnt = 0;
for(auto it=container.begin(); it != container.end() && cnt < N ; ++it,++cnt) {
  //
}

По крайней мере, для меня это очень читаемо:-). И он имеет сложность O (N) независимо от типа контейнера.

Ответ 6

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

template<class T>
struct indexT
 //: std::iterator< /* ... */ > // or do your own typedefs, or don't bother
{
  T t = {};
  indexT()=default;
  indexT(T tin):t(tin){}
  indexT& operator++(){ ++t; return *this; }
  indexT operator++(int){ auto tmp = *this; ++t; return tmp; }
  T operator*()const{return t;}
  bool operator==( indexT const& o )const{ return t==o.t; }
  bool operator!=( indexT const& o )const{ return t!=o.t; }
  // etc if you want full functionality.
  // The above is enough for a `for(:)` range-loop
};

он обертывает скалярный тип T, а на * возвращает копию. Он также работает на итераторах, забавно, что полезно здесь, поскольку это позволяет нам эффективно наследовать от указателя:

template<class ItA, class ItB>
struct indexing_iterator:indexT<ItA> {
  ItB b;
  // TODO: add the typedefs required for an iterator here
  // that are going to be different than indexT<ItA>, like value_type
  // and reference etc.  (for simple use, not needed)
  indexing_iterator(ItA a, ItB bin):ItA(a), b(bin) {}
  indexT<ItA>& a() { return *this; }
  indexT<ItA> const& a() const { return *this; }
  decltype(auto) operator*() {
    return b[**a()];
  }
  decltype(auto) operator->() {
    return std::addressof(b[**a()]);
  }
};

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

Далее мы имеем тип диапазона. Улучшенный SFINAE можно найти во многих местах. Он делает итерацию по целому ряду итераторов в цикле for(:) легким:

template<class Iterator>
struct range {
  Iterator b = {};
  Iterator e = {};
  Iterator begin() { return b; }
  Iterator end() { return e; }
  range(Iterator s, Iterator f):b(s),e(f) {}
  range(Iterator s, size_t n):b(s), e(s+n) {}
  range()=default;
  decltype(auto) operator[](size_t N) { return b[N]; }
  decltype(auto) operator[] (size_t N) const { return b[N]; }\
  decltype(auto) front() { return *b; }
  decltype(auto) back() { return *std::prev(e); }
  bool empty() const { return begin()==end(); }
  size_t size() const { return end()-begin(); }
};

Вот помощники для работы с диапазонами indexT easy:

template<class T>
using indexT_range = range<indexT<T>>;
using index = indexT<size_t>;
using index_range = range<index>;

template<class C>
size_t size(C&&c){return c.size();}
template<class T, std::size_t N>
size_t size(T(&)[N]){return N;}

index_range indexes( size_t start, size_t finish ) {
  return {index{start},index{finish}};
}
template<class C>
index_range indexes( C&& c ) {
  return make_indexes( 0, size(c) );
}
index_range intersect( index_range lhs, index_range rhs ) {
  if (lhs.b.t > rhs.e.t || rhs.b.t > lhs.b.t) return {};
  return {index{(std::max)(lhs.b.t, rhs.b.t)}, index{(std::min)(lhs.e.t, rhs.e.t)}};
}

ok, почти там.

index_filter_it принимает ряд индексов и итератор случайного доступа и делает ряд индексированных итераторов в данные данных итератора произвольного доступа:

template<class R, class It>
auto index_filter_it( R&& r, It it ) {
  using std::begin; using std::end;
  using ItA = decltype( begin(r) );
  using R = range<indexing_iterator<ItA, It>>;
  return R{{begin(r),it}, {end(r),it}};
}

index_filter принимает index_range и контейнер произвольного доступа, пересекает их индексы, а затем вызывает index_filter_it:

template<class C>
auto index_filter( index_range r, C& c ) {
  r = intersect( r, indexes(c) );
  using std::begin;
  return index_filter_it( r, begin(c) );
}

и теперь мы имеем:

for (auto&& i : index_filter( indexes(0,6), arr )) {
}

и альта, у нас есть большой музыкальный инструмент.

живой пример

Возможны фильтры Fancier.

size_t filter[] = {1,3,0,18,22,2,4};
using std::begin;
for (auto&& i : index_filter_it( filter, begin(arr) ) )

посетит 1, 3, 0, 18, 22, 2, 4 в arr. Однако он не проверяет границы, если arr.begin()[] не проверяет границы.

Вероятно, в приведенном выше коде есть ошибки, и вы, вероятно, должны просто использовать boost.

Если вы реализуете - и [] на indexT, вы можете даже объединить эти диапазоны.

Ответ 7

Это решение не прошло end(), сложность O(N) для std::list (не использует std::distance) работает с std::for_each и требует только ForwardIterator:

std::vector<int> vect = {1,2,3,4,5,6,7,8};

auto stop_iter = vect.begin();
const size_t stop_count = 5;

if(stop_count <= vect.size())
{
    std::advance(stop_iter, n)
}
else
{
    stop_iter = vect.end();
}

std::for_each(vect.vegin(), stop_iter, [](auto val){ /* do stuff */ });

Единственное, чего он не делает, это работать с InputIterator, например std::istream_iterator, для этого вам придется использовать внешний счетчик.

Ответ 8

Сначала мы пишем итератор, который останавливается при заданном индексе:

template<class I>
class at_most_iterator
  : public boost::iterator_facade<at_most_iterator<I>,
                  typename I::value_type,
                  boost::forward_traversal_tag>
{
private:
  I it_;
  int index_;
public:
  at_most_iterator(I it, int index) : it_(it), index_(index) {}
  at_most_iterator() {}
private:
  friend class boost::iterator_core_access;

  void increment()
  {
    ++it_;
    ++index_;
  }
  bool equal(at_most_iterator const& other) const
  {
    return this->index_ == other.index_ || this->it_ == other.it_;
  }
  typename std::iterator_traits<I>::reference dereference() const
  {
    return *it_;
  }
};

Теперь мы можем написать алгоритм для создания ярости этого итератора из заданного диапазона:

template<class X>
boost::iterator_range<
  at_most_iterator<typename X::iterator>>
at_most(int i, X& xs)
{
  typedef typename X::iterator iterator;
  return std::make_pair(
            at_most_iterator<iterator>(xs.begin(), 0),
            at_most_iterator<iterator>(xs.end(), i)
        );
}

Использование:

int main(int argc, char** argv)
{
  std::vector<int> xs = {1, 2, 3, 4, 5, 6, 7, 8, 9};
  for(int x : at_most(5, xs))
    std::cout << x << "\n";
  return 0;
}