Domanda C'è un modo per iterare al massimo N elementi usando loop per loop?


Mi piacerebbe sapere se c'è un modo carino per scorrere al massimo N elementi in un contenitore usando l'intervallo basato per ciclo e / o algoritmi dalla libreria standard (questo è il punto, so che posso usare solo "vecchio" "per il ciclo con una condizione).

Fondamentalmente, sto cercando qualcosa che corrisponda a questo codice Python:

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

40
2018-06-11 13:11


origine


risposte:


Come personalmente userò entrambi Questo o Questo rispondi (+1 per entrambi), solo per aumentare le tue conoscenze - ci sono degli adattatori boost che puoi usare. Per il tuo caso - il affettato sembra il più appropriato:

#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: N è richiesta da sliced non essere più grande di distance(range) - La versione più sicura (e più lenta) è la seguente:

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

Quindi, ancora una volta, userei un approccio C / C ++ più semplice e vecchio (questo volevi evitare nella tua domanda;)


35
2018-06-11 13:48



Ecco la soluzione di salvataggio più economica che funziona per tutti gli iteratori avanzati che potrei inventare:

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

Questo potrebbe attraversare il range quasi due volte, ma non vedo altro modo per ottenere la lunghezza dell'intervallo.


13
2018-06-11 13:31



Puoi usare il buon vecchio break per interrompere manualmente un ciclo quando necessario. Funziona anche con loop basato sulla gamma.

#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
2018-06-11 13:23



Il C ++ è fantastico, dal momento che puoi codificarlo da solo orrendo soluzioni e nascondile sotto uno strato di astrazione

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

Esempio dal vivo

Il codice sopra ovviamente manca di alcuni errori di controllo e altre regolazioni, ma volevo solo esprimere chiaramente l'idea.

Funziona da allora range-based per loops produrre codice simile al seguente

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

cfr. begin_expr e end_expr


7
2018-06-11 14:11



Se il tuo contenitore non ha (o potrebbe non avere) RandomAccessIterator, c'è ancora un modo per skinare questo gatto:

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

Almeno per me, è molto leggibile :-). E ha complessità O (N) indipendentemente dal tipo di contenitore.


6
2018-06-11 13:34



Questo è un indice iteratore. Per lo più si tratta di una spiegazione, lasciandolo fuori, perché sono pigro.

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

avvolge un tipo scalare Te via * restituisce una copia. Funziona anche su iteratori, in modo divertente, che è utile qui, poiché ci consente di ereditare efficacemente da un puntatore:

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

L'iteratore di indicizzazione include due iteratori, il secondo dei quali deve essere ad accesso casuale. Usa il primo iteratore per ottenere un indice, che usa per cercare un valore dal secondo.

Successivamente, abbiamo un tipo di intervallo. Uno più perfezionato di SFINAE può essere trovato in molti posti. Fa scorrere su un intervallo di iteratori in a for(:) loop facile:

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

Qui ci sono aiutanti per rendere il lavoro con intervalli di indexT facile:

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, quasi lì.

index_filter_it prende un intervallo di indici e un iteratore di accesso casuale e crea una gamma di iteratori indicizzati in dati di iteratore di accesso casuale:

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 prende un index_range e un contenitore di accesso casuale, interseca i loro indici, quindi le chiamate 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) );
}

e ora abbiamo:

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

e viola, abbiamo un grande strumento musicale.

esempio dal vivo

I filtri Fancier sono possibili.

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

visiterà 1, 3, 0, 18, 22, 2, 4 pollici arr. Tuttavia, non controlla i limiti, a meno che arr.begin()[] Bounds controlli.

Ci sono probabilmente errori nel codice sopra, e probabilmente dovresti semplicemente usarlo boost.

Se implementi - e [] sopra indexT, puoi anche collegare a catena questi intervalli.


5
2018-06-11 18:56



Questa soluzione non va oltre end(), ha O(N) complessità per std::list (non usa std::distance) lavora con std::for_eache richiede solo 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 */ });

L'unica cosa che non fa è lavorare con InputIterator ad esempio std::istream_iterator - Dovrai usare un contatore esterno per questo.


2
2018-06-11 13:51



Per prima cosa scriviamo un iteratore che si ferma su un dato indice:

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

Ora possiamo scrivere un algoritmo per fare una rabbia di questo iteratore da un intervallo dato:

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
2018-06-12 08:02