¿Hay una manera de iterar sobre a lo sumo N elementos usando range-based for loop?


Me gustaría saber si hay una buena manera de iterar sobre a lo sumo N elementos en un contenedor utilizando el rango basado en bucle for y/o algoritmos de la biblioteca estándar (ese es el punto, sé que puedo usar el bucle for "antiguo" con una condición).

Básicamente, estoy buscando algo que corresponda a este código Python:

for i in arr[:N]:
    print(i)
Author: PiotrNycz, 2015-06-11

8 answers

Como yo personalmente usaría this o this answer (+1 para ambos), solo para aumentar su conocimiento, hay adaptadores boost que puede usar. Para su caso-el rebanado parece el más apropiado:

#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;
}

Una nota importante: sliced requiere que N no sea mayor que distance(range), por lo que la versión más segura(y más lenta) es la siguiente:

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

Así que, una vez más, usaría un enfoque más simple y antiguo de C/C++ (esto quería evitar en su pregunta ;)

 35
Author: PiotrNycz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:25:22

Aquí está la solución de guardado más barata que funciona para todos los iteradores forward que se me ocurrió:

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

Esto podría correr a través del rango casi dos veces, pero no veo otra manera de obtener la longitud del rango.

 13
Author: Baum mit Augen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-11 14:13:17

Puede usar el viejo break para romper manualmente un bucle cuando sea necesario. Funciona incluso con bucle basado en rango.

#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;
    }
}
 8
Author: Petr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-11 13:26:05

C++ es genial ya que puedes codificar tus propias soluciones horribles y ocultarlas bajo una capa de abstracción

#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
    }
}

Ejemplo En Vivo

El código anterior obviamente carece de alguna comprobación de errores y otros ajustes, pero quería expresar la idea claramente.

Esto funciona desde range-based for loops produce código similar al siguiente

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

Cfr. begin_expr y end_expr

 7
Author: Marco A.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-12 10:51:11

Si su contenedor no tiene (o podría no tener) RandomAccessIterator, todavía hay una manera de pelar este gato:

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

Al menos para mí, es muy legible :-). Y tiene complejidad O(N) independientemente del tipo de contenedor.

 6
Author: No-Bugs Hare,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-11 13:34:58

Este es un iterador de índice. Casi todo repetitivo, dejándolo fuera, porque soy perezoso.

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
};

Envuelve un tipo escalar T, y en * devuelve una copia. También funciona en iteradores, de manera divertida, lo cual es útil aquí, ya que nos permite heredar efectivamente de un puntero:

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()]);
  }
};

El iterador de indexación envuelve dos iteradores, el segundo de los cuales debe ser de acceso aleatorio. Utiliza el primer iterador para obtener un índice, que utiliza para buscar un valor de la segundo.

A continuación, tenemos un tipo de rango. Un SFINAE mejorado se puede encontrar en muchos lugares. Hace que iterar sobre un rango de iteradores en un bucle for(:) sea fácil:

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(); }
};

Aquí hay ayudantes para hacer que trabajar con rangos de indexT sea fácil:

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, ya casi estamos.

index_filter_it toma un rango de índices y un iterador de acceso aleatorio, y hace un rango de iteradores indexados en los datos de ese iterador de acceso aleatorio:

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 toma un index_range y un contenedor de acceso aleatorio, cruza sus índices, luego llama 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) );
}

Y ahora tenemos:

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

Y viola, tenemos un gran instrumento musical.

Ejemplo en vivo

Filtros más elegantes son posibles.

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

Visitará 1, 3, 0, 18, 22, 2, 4 en arr. Sin embargo, no verifica los límites, a menos que arr.begin()[] verifique los límites.

Probablemente haya errores en el código anterior, y probablemente debería usar boost.

Si implementa - y [] en indexT, incluso puede encadenar estos rangos.

 5
Author: Yakk - Adam Nevraumont,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-11 21:34:05

Esta solución no pasa de end(), tiene O(N) complejidad para std::list (no usa std::distance) funciona con std::for_each, y solo requiere 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 */ });

Lo único que no hace es trabajar con InputIterator como std::istream_iterator - tendrás que usar contador externo para eso.

 2
Author: Alexander Revo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-11 13:51:51

Primero escribimos un iterador que se detiene en un índice dado:

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_;
  }
};

Ahora podemos escribir un algorítmo para hacer furor de este iterador desde un rango dado:

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)
        );
}

Uso:

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;
}
 2
Author: ysdx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-06-12 08:02:28