2010-09-02 24 views
39

¿Existe alguna implementación de iterador existente (quizás en impulso) que implemente algún tipo de iterador de acoplamiento?iterador de acoplamiento

Por ejemplo:

unordered_set<vector<int> > s; 

s.insert(vector<int>()); 
s.insert({1,2,3,4,5}); 
s.insert({6,7,8}); 
s.insert({9,10,11,12}); 

flattening_iterator<unordered_set<vector<int> >::iterator> it(...), end(...); 
for(; it != end; ++it) 
{ 
    cout << *it << endl; 
} 
//would print the numbers 1 through 12 
+3

Imprimirá los números del 1 al 12, pero no necesariamente en orden, ya que está utilizando un conjunto _unordered_ en el ejemplo, ¿verdad? –

+0

@James: Sí, en el ejemplo no me importa en qué orden están impresos. – George

Respuesta

40

No sé de cualquier aplicación en una biblioteca importante, pero parecía un problema interesante así que escribí una implementación básica. Solo lo he probado con el caso de prueba que presento aquí, por lo que no recomiendo usarlo sin más pruebas.

El problema es un poco más complicado de lo que parece porque algunos de los contenedores "internos" pueden estar vacíos y debe omitirlos. Esto significa que avanzar el flattening_iterator en una posición puede hacer avanzar el iterador en el contenedor "exterior" en más de una posición. Debido a esto, el flattening_iterator necesita saber dónde está el final del rango externo para que sepa cuándo debe detenerse.

Esta implementación es un iterador directo. Un iterador bidireccional también necesitaría hacer un seguimiento del comienzo del rango externo. Las plantillas de función flatten se utilizan para hacer que la construcción de flattening_iterator sea un poco más fácil.

#include <iterator> 

// A forward iterator that "flattens" a container of containers. For example, 
// a vector<vector<int>> containing { { 1, 2, 3 }, { 4, 5, 6 } } is iterated as 
// a single range, { 1, 2, 3, 4, 5, 6 }. 
template <typename OuterIterator> 
class flattening_iterator 
{ 
public: 

    typedef OuterIterator        outer_iterator; 
    typedef typename OuterIterator::value_type::iterator inner_iterator; 

    typedef std::forward_iterator_tag    iterator_category; 
    typedef typename inner_iterator::value_type  value_type; 
    typedef typename inner_iterator::difference_type difference_type; 
    typedef typename inner_iterator::pointer   pointer; 
    typedef typename inner_iterator::reference  reference; 

    flattening_iterator() { } 
    flattening_iterator(outer_iterator it) : outer_it_(it), outer_end_(it) { } 
    flattening_iterator(outer_iterator it, outer_iterator end) 
     : outer_it_(it), 
      outer_end_(end) 
    { 
     if (outer_it_ == outer_end_) { return; } 

     inner_it_ = outer_it_->begin(); 
     advance_past_empty_inner_containers(); 
    } 

    reference operator*() const { return *inner_it_; } 
    pointer operator->() const { return &*inner_it_; } 

    flattening_iterator& operator++() 
    { 
     ++inner_it_; 
     if (inner_it_ == outer_it_->end()) 
      advance_past_empty_inner_containers(); 
     return *this; 
    } 

    flattening_iterator operator++(int) 
    { 
     flattening_iterator it(*this); 
     ++*this; 
     return it; 
    } 

    friend bool operator==(const flattening_iterator& a, 
          const flattening_iterator& b) 
    { 
     if (a.outer_it_ != b.outer_it_) 
      return false; 

     if (a.outer_it_ != a.outer_end_ && 
      b.outer_it_ != b.outer_end_ && 
      a.inner_it_ != b.inner_it_) 
      return false; 

     return true; 
    } 

    friend bool operator!=(const flattening_iterator& a, 
          const flattening_iterator& b) 
    { 
     return !(a == b); 
    } 

private: 

    void advance_past_empty_inner_containers() 
    { 
     while (outer_it_ != outer_end_ && inner_it_ == outer_it_->end()) 
     { 
      ++outer_it_; 
      if (outer_it_ != outer_end_) 
       inner_it_ = outer_it_->begin(); 
     } 
    } 

    outer_iterator outer_it_; 
    outer_iterator outer_end_; 
    inner_iterator inner_it_; 
}; 

template <typename Iterator> 
flattening_iterator<Iterator> flatten(Iterator it) 
{ 
    return flattening_iterator<Iterator>(it, it); 
} 

template <typename Iterator> 
flattening_iterator<Iterator> flatten(Iterator first, Iterator last) 
{ 
    return flattening_iterator<Iterator>(first, last); 
} 

El siguiente es un talón de prueba mínima:

#include <algorithm> 
#include <iostream> 
#include <set> 
#include <vector> 

int main() 
{ 
    // Generate some test data: it looks like this: 
    // { { 0, 1, 2, 3 }, { 4, 5, 6, 7 }, { 8, 9, 10, 11 } } 
    std::vector<std::vector<int>> v(3); 
    int i(0); 
    for (auto it(v.begin()); it != v.end(); ++it) 
    { 
     it->push_back(i++); it->push_back(i++); 
     it->push_back(i++); it->push_back(i++); 
    } 

    // Flatten the data and print all the elements: 
    for (auto it(flatten(v.begin(), v.end())); it != v.end(); ++it) 
    { 
     std::cout << *it << ", "; 
    } 
    std::cout << "\n"; 

    // Or, since the standard library algorithms are awesome: 
    std::copy(flatten(v.begin(), v.end()), flatten(v.end()), 
       std::ostream_iterator<int>(std::cout, ", ")); 
} 

Como dije al principio, no he probado esto a fondo. Avíseme si encuentra algún error y estaré encantado de corregirlo.

+0

Muchas gracias por tomarse el tiempo para escribir eso arriba. No he hecho muchas pruebas todavía, pero el único problema que he tenido es que gcc se queja de "typedef typename OuterIterator" diciendo que debería ser "typedef OuterIterator". – George

+0

@George: Ah, gracias. Eso fue un error de copiar y pegar combinado con el cumplimiento de normas laxas en Visual C++ :-P. –

+1

@George: Heads-up: hubo un error en la sobrecarga 'operator ==' que causó que solo funcionara al comparar con un iterador final; Lo he corregido en una edición. –

6

Decidí "mejorar" un poco el concepto de iterador de aplanamiento, aunque como James señaló que está trabado con Rangos (excepto el contenedor más interno), utilicé rangos de principio a fin y obtuve una rango aplanado, con una profundidad arbitraria.

Primero usa un ladrillo de construcción:

template <typename C> 
struct iterator { using type = typename C::iterator; }; 

template <typename C> 
struct iterator<C const> { using type = typename C::const_iterator; }; 

Y luego define una (muy mínimo) ForwardRange concepto:

template <typename C> 
class ForwardRange { 
    using Iter = typename iterator<C>::type; 
public: 
    using pointer = typename std::iterator_traits<Iter>::pointer; 
    using reference = typename std::iterator_traits<Iter>::reference; 
    using value_type = typename std::iterator_traits<Iter>::value_type; 

    ForwardRange(): _begin(), _end() {} 

    explicit ForwardRange(C& c): _begin(begin(c)), _end(end(c)) {} 

    // Observers 
    explicit operator bool() const { return _begin != _end; } 

    reference operator*() const { assert(*this); return *_begin; } 
    pointer operator->() const { assert(*this); return &*_begin; } 

    // Modifiers 
    ForwardRange& operator++() { assert(*this); ++_begin; return *this; } 
    ForwardRange operator++(int) { ForwardRange tmp(*this); ++*this; return tmp; } 

private: 
    Iter _begin; 
    Iter _end; 
}; // class ForwardRange 

Este es nuestro ladrillo de construcción aquí, sin embargo, de hecho, podríamos hacer que hacer con el resto:

template <typename C, size_t N> 
class FlattenedForwardRange { 
    using Iter = typename iterator<C>::type; 
    using Inner = FlattenedForwardRange<typename std::iterator_traits<Iter>::value_type, N-1>; 
public: 
    using pointer = typename Inner::pointer; 
    using reference = typename Inner::reference; 
    using value_type = typename Inner::value_type; 

    FlattenedForwardRange(): _outer(), _inner() {} 

    explicit FlattenedForwardRange(C& outer): _outer(outer), _inner() { 
     if (not _outer) { return; } 
     _inner = Inner{*_outer}; 
     this->advance(); 
    } 

    // Observers 
    explicit operator bool() const { return static_cast<bool>(_outer); } 

    reference operator*() const { assert(*this); return *_inner; } 
    pointer operator->() const { assert(*this); return _inner.operator->(); } 

    // Modifiers 
    FlattenedForwardRange& operator++() { ++_inner; this->advance(); return *this; } 
    FlattenedForwardRange operator++(int) { FlattenedForwardRange tmp(*this); ++*this; return tmp; } 

private: 
    void advance() { 
     if (_inner) { return; } 

     for (++_outer; _outer; ++_outer) { 
      _inner = Inner{*_outer}; 
      if (_inner) { return; } 
     } 
     _inner = Inner{}; 
    } 

    ForwardRange<C> _outer; 
    Inner _inner; 
}; // class FlattenedForwardRange 

template <typename C> 
class FlattenedForwardRange<C, 0> { 
    using Iter = typename iterator<C>::type; 
public: 
    using pointer = typename std::iterator_traits<Iter>::pointer; 
    using reference = typename std::iterator_traits<Iter>::reference; 
    using value_type = typename std::iterator_traits<Iter>::value_type; 

    FlattenedForwardRange(): _range() {} 

    explicit FlattenedForwardRange(C& c): _range(c) {} 

    // Observers 
    explicit operator bool() const { return static_cast<bool>(_range); } 

    reference operator*() const { return *_range; } 
    pointer operator->() const { return _range.operator->(); } 

    // Modifiers 
    FlattenedForwardRange& operator++() { ++_range; return *this; } 
    FlattenedForwardRange operator++(int) { FlattenedForwardRange tmp(*this); ++*this; return tmp; } 

private: 
    ForwardRange<C> _range; 
}; // class FlattenedForwardRange 

Y aparentemente, it works

+0

¡Absolutamente genial! +1 – dudu

+0

Minit nitpick: Me parece un poco confuso el nombre Rango para un iterador. – Nobody

+0

@ Nadie: Bueno, eso es porque en realidad es un rango y no un iterador (aunque se puede usar como uno solo). Incrusta ambos "extremos" del rango que se iterarán en un solo objeto, por lo que es autosuficiente. En realidad, es desafortunado, pero muchos rangos interesantes no se pueden expresar fácilmente como pares de iteradores (o al menos, no sin redundancia). –

1

Llego un poco tarde aquí, pero acabo de publicar a library (multidim) para tratar ese problema. El uso es bastante simple: para usar su ejemplo,

#include "multidim.hpp" 

// ... create "s" as in your example ... 

auto view = multidim::makeFlatView(s); 
// view offers now a flattened view on s 

// You can now use iterators... 
for (auto it = begin(view); it != end(view); ++it) cout << *it << endl; 

// or a simple range-for loop 
for (auto value : view) cout << value; 

La biblioteca es solo de encabezado y no tiene dependencias. Requiere C++ 11 sin embargo.