2012-11-01 11 views
8

Python itertools ha tee per iterables n-plicating:equivalente itertools.tee in Boost :: Range?

def tee(iterable, n=2): 
    it = iter(iterable) 
    deques = [collections.deque() for i in range(n)] 
    def gen(mydeque): 
     while True: 
      if not mydeque:    # when the local deque is empty 
       newval = next(it)  # fetch a new value and 
       for d in deques:  # load it to all the deques 
        d.append(newval) 
      yield mydeque.popleft() 
    return tuple(gen(d) for d in deques) 

non riuscivo a trovare l'equivalente in Boost::Range. Mi sto perdendo qualcosa o dovrei semplicemente rotolare il mio?

+3

Forse aggiungere una descrizione del testo in chiaro migliore di ciò che si desidera, non tutti potrebbero capire quali espressioni di resa o di lista fanno in python. – inf

+0

Hai un caso d'uso per questo? – filmor

+0

È passato un po 'di tempo, ma l'idea era di n-plicare un iteratore a un insieme di utenti (ad esempio thread diversi) che applicassero diverse operazioni. Simile al comando 'tee' in Unix. Ho finito per utilizzare un approccio completamente diverso, in cui invece di duplicare l'input, ho applicato le operazioni in modo sequenziale (cioè multiplexing). –

risposta

1

itertools.tee è adatto per i single pass iterables che sono onnipresenti in Python. Ad esempio, Generators sono single pass e vengono spesso utilizzati.

Ma se si dispone già di lista/deque non utilizzerà itertools.tee per esso, perché comporterebbe la duplicazione superflua - si può semplicemente iterare originale lista/deque più e più volte.

C++ ha anche il concetto di intervalli a passaggio singolo, ad esempio Input Iterator, ma non sono così onnipresenti. È una conseguenza di un altro insieme di obiettivi del tipico programma C++: dare all'utente il massimo possibile mantenendo le migliori prestazioni. È un'altra mentalità se lo desideri.

Per illustrare questo confrontiamo boost::transformed e itertools.imap (o generator expressions):

entrambi forniscono vista sequenza di input tramite dato "prisma". itertools.imap rendimenti singolo passaggio iterabile, mentre boost :: trasformato rendimenti variano vista che ha stessa categoria di ingresso - vale a dire, se si desidera passare Random Access Range come input si otterrebbe Random Access Gamma come risultato.

Un altro fatto è che C++ impiega value semantics per impostazione predefinita, mentre python ha semantica del puntatore. Significa che se si copia l'iteratore in C++ e si "sbatte" più volte - l'iteratore originale non verrà modificato (sebbene possa essere invalidato se si tratta di un intervallo a passaggio singolo, ma non è il punto).

Ma, a volte, si desidera accumulare valori dal raggio pass singolo e guardarli più volte. In tal caso, la soluzione più comune è quella di accumulare valori in alcuni contenitori esplicitamente, a mano. Per esempio:

vector<int> cache(first,last); 

Tuttavia, involucri T-simili sono ancora possibili in C++, qui è proof-of-concept. L'uso è:

auto forward_range = tee_range(first,last); 

tee_range richiederà gamma singolo passaggio come argomento e restituisce gamma avanti (che è multi-pass) (c'è anche make_tee_iterator, che opera a livello iterator). Quindi, si può estrarre copia di ciascuna linea e iterare più volte:

auto r = forward_range; 
auto out = ostream_iterator<int>(cout," "); 
copy(forward_range,out); 
copy(forward_range,out); 
copy(r,out); 

Therer è anche improvenment oltre itertools.tee - internamente, solo una deque viene utilizzato per i valori di cache.

live demo:

#include <boost/range/adaptor/transformed.hpp> 
#include <boost/iterator/iterator_facade.hpp> 
#include <boost/smart_ptr/make_shared.hpp> 
#include <boost/range/iterator_range.hpp> 
#include <boost/smart_ptr/shared_ptr.hpp> 
#include <boost/container/vector.hpp> 
#include <boost/container/deque.hpp> 
#include <boost/range/algorithm.hpp> 
#include <algorithm> 
#include <iterator> 
#include <cassert> 
#include <limits> 

template<typename InputIterator> 
class tee_iterator : public boost::iterator_facade 
    < 
     tee_iterator<InputIterator>, 
     const typename std::iterator_traits<InputIterator>::value_type, 
     boost::forward_traversal_tag 
    > 
{ 
    typedef typename std::iterator_traits<InputIterator>::value_type Value; 
    typedef unsigned Index; 
    struct Data 
    { 
     boost::container::deque<Value> values; 
     boost::container::vector<tee_iterator*> iterators; 
     InputIterator current,end; 
     Index min_index, current_index; 
     Index poped_from_front; 
     // 
     Data(InputIterator first,InputIterator last) 
      : current(first), end(last), min_index(0), current_index(0), poped_from_front(0) 
     {} 
     ~Data() 
     { 
      assert(iterators.empty()); 
     } 
    }; 
    boost::shared_ptr<Data> shared_data; 
    Index index; 
    static Index get_index(tee_iterator *p) 
    { 
     return p->index; 
    } 
public: 
    tee_iterator() 
     : index(std::numeric_limits<Index>::max()) 
    {} 
    tee_iterator(InputIterator first,InputIterator last) 
     : shared_data(boost::make_shared<Data>(first,last)), index(0) 
    { 
     shared_data->iterators.push_back(this); 
    } 
    tee_iterator(const tee_iterator &x) 
     : shared_data(x.shared_data), index(x.index) 
    { 
     if(shared_data) 
      shared_data->iterators.push_back(this); 
    } 
    friend void swap(tee_iterator &l,tee_iterator &r) 
    { 
     using std::swap; 
     *boost::find(l.shared_data->iterators,&l) = &r; 
     *boost::find(r.shared_data->iterators,&r) = &l; 
     swap(l.shared_data,r.shared_data); 
     swap(l.index,r.index); 
    } 
    tee_iterator &operator=(tee_iterator x) 
    { 
     swap(x,*this); 
    } 
    ~tee_iterator() 
    { 
     if(shared_data) 
     { 
      erase_from_iterators(); 
      if(!shared_data->iterators.empty()) 
      { 
       using boost::adaptors::transformed; 
       shared_data->min_index = *boost::min_element(shared_data->iterators | transformed(&get_index)); 
       Index to_pop = shared_data->min_index - shared_data->poped_from_front; 
       if(to_pop>0) 
       { 
        shared_data->values.erase(shared_data->values.begin(), shared_data->values.begin()+to_pop); 
        shared_data->poped_from_front += to_pop; 
       } 
      } 
     } 
    } 
private: 
    friend class boost::iterator_core_access; 
    void erase_from_iterators() 
    { 
     shared_data->iterators.erase(boost::find(shared_data->iterators,this)); 
    } 
    bool last_min_index() const 
    { 
     return boost::count 
     (
      shared_data->iterators | boost::adaptors::transformed(&get_index), 
      shared_data->min_index 
     )==1; 
    } 
    Index obtained() const 
    { 
     return Index(shared_data->poped_from_front + shared_data->values.size()); 
    } 
    void increment() 
    { 
     if((shared_data->min_index == index) && last_min_index()) 
     { 
      shared_data->values.pop_front(); 
      ++shared_data->min_index; 
      ++shared_data->poped_from_front; 
     } 
     ++index; 
     if(obtained() <= index) 
     { 
      ++shared_data->current; 
      if(shared_data->current != shared_data->end) 
      { 
       shared_data->values.push_back(*shared_data->current); 
      } 
      else 
      { 
       erase_from_iterators(); 
       index=std::numeric_limits<Index>::max(); 
       shared_data.reset(); 
      } 
     } 
    } 
    bool equal(const tee_iterator& other) const 
    { 
     return (shared_data.get()==other.shared_data.get()) && (index == other.index); 
    } 
    const Value &dereference() const 
    { 
     if((index==0) && (obtained() <= index)) 
     { 
      shared_data->values.push_back(*(shared_data->current)); 
     } 
     assert((index-shared_data->poped_from_front) < shared_data->values.size()); 
     return shared_data->values[index-shared_data->poped_from_front]; 
    } 
}; 

template<typename InputIterator> 
tee_iterator<InputIterator> make_tee_iterator(InputIterator first,InputIterator last) 
{ 
    return tee_iterator<InputIterator>(first,last); 
} 

template<typename InputIterator> 
boost::iterator_range< tee_iterator<InputIterator> > tee_range(InputIterator first,InputIterator last) 
{ 
    return boost::iterator_range< tee_iterator<InputIterator> > 
    (
     tee_iterator<InputIterator>(first,last), 
     tee_iterator<InputIterator>() 
    ); 
} 
// _______________________________________________________ // 

#include <iostream> 
#include <ostream> 
#include <sstream> 

int main() 
{ 
    using namespace std; 
    stringstream ss; 
    ss << "1 2 3 4 5"; 
    istream_iterator<int> first(ss /*cin*/),last; 
    typedef boost::iterator_range< tee_iterator< istream_iterator<int> > > Range; // C++98 
    Range r1 = tee_range(first,last); 
    Range r2 = r1, r3 = r1; 
    boost::copy(r1,ostream_iterator<int>(cout," ")); 
    cout << endl; 
    boost::copy(r2,ostream_iterator<int>(cout," ")); 
    cout << endl; 
    boost::copy(r2,ostream_iterator<int>(cout," ")); 
} 

uscita è:

1 2 3 4 5 
1 2 3 4 5 
1 2 3 4 5 

Boost.Spirit ha Multi Pass iterator che ha obiettivi simili.

L'iteratore multipass converte qualsiasi iteratore di input in un iteratore di inoltro adatto per l'uso con Spirit.Qi. multi_pass eseguirà il buffer dei dati quando necessario e scarterà il buffer quando il suo contenuto non sarà più necessario. Ciò accade se esiste una sola copia dell'iteratore o se non si può verificare alcun backtracking.