/* lab17.h (C) 2006 adolfo@di-mare.com */ /** \file lab17.h \brief Lista implementada usando el Rep de la biblioteca STL. \author Adolfo Di Mare \date 2006 */ #ifndef STL_list_h #define STL_list_h ///< Evita la inclusión múltiple #include #include // #include using namespace std; namespace ADH { // declaraciones hacia adelanta para que el compilador no se enrede template class list; // para definir check_ok( const list & ); template class list_node; // para definir punteros list_node * prev, * next; template class list_node_base; template class test_list; /// Clase base para el nodo de la lista STL. /// - Esta clase un truco para ahorrarse el campo \c m_val /// del nodo cabezal de la lista. template class list_node_base { template friend class list; template friend class list_node; template friend class test_list; private: // Todo el Rep es privado list_node_base() { } ///< Constructor public: list_node * prev; ///< Puntero al nodo anterior list_node * next; ///< Puntero al siguiente nodo /* NOTA - Esta es una clase privada porque el programador cliente nunca tiene necesidad de usar nodos, pues para accesar los valores de la lista puede usar iteradores y las operaciones de inserción y borrado de la lista. - El constructor no inicializa los campos "next+prev" porque al crear un nodo siempre es necesario cambiarles el valor. - Esta clase no está definida dentro de las clase list porque al usar plantillas el compilador no entiende bien las clases de plantilla anidadas. Por eso, el truco es usar una clase "externa" a list, pero dejando privado su constructor para que el programador cliente no pueda usar esta clase directamente. - No se han usado los identificadores "m_next" y "m_prev" para los campos "next" y "prev" para que el código se parezca más al que aparece en los libros de texto. - Los campos "next" y "prev" son públicos por la misma razón por la que es público el campo "m_val" de la clase list_node. */ }; // list_node_base /// Nodos de la clase \c "list". /// - Esta es una clase privada para implementar la lista. template class list_node : public list_node_base { template friend class list; template friend class list_node; template friend class test_list; template friend bool check_ok( const list & L ); private: // Todo el Rep es privado /// Constructor para \c "v" list_node(const T& v) : m_val(v) {} public: T m_val; ///< Valor almacenado en el nodo /* NOTA - El constructor no inicializa los campos "next+prev" porque al crear un nodo siempre es necesario cambiarles el valor. - El campo "m_val" es público porque el compilador no permite declarar "friend" a la clase "iterator": template friend class list::iterator; // NO COMPILA Al ser "m_val" un campo público, en el implementación de list::iterator::operator*() es posible acceder a "m_val" sin que el compilador emita un error de violación de campo privado. - Como el constructor de list::iterator es privado un programador que trate de crear nodos recibirá un error del compilador. - Lo mismo ocurrirá si el programador tratar de usar el campo "next" o "prev" a través de un iterador pues para llegar a cualquiera de esos campos antes necesita entrar al campo "m_p" que es un campo privador de la clase list::iterator. */ }; // list_node /// Lista implementada usando el Rep de la biblioteca STL. /// - Esta clase tiene varias de las operaciones más importantes /// de la clase \c "list" de la biblioteca estándar de C++. template class list : public list_node_base { template friend class test_list; public: typedef T value_type; ///< Nombre estándar del objeto contenido typedef value_type& reference; ///< Referencia al objeto contenido typedef const value_type& const_reference; ///< Referencia constante al objeto contenido typedef unsigned size_type; ///< Nombre estándar del tipo retornado por \c size() public: /** Iteradores bidireccionales para la clase \c list. \dontinclude test_STL_list.cpp \skipline test::iterator() \until }} \see ADH::test_list::test_iterator() */ class iterator { template friend class list; template friend class test_list; public: iterator() : m_p(0) { /* inicializo, como los de STL */ } ///< Constructor /// Constructor de copia iterator( const iterator& o ) : m_p(o.m_p) { } // copia el Rep directo iterator& operator=( const iterator& o ) { m_p = o.m_p; // copia el Rep directo return *this; } /// ¿ p == q ? friend bool operator == ( const iterator& p, const iterator& q ) { return ( p.m_p == q.m_p ); } /// ¿ p != q ? friend bool operator != ( const iterator& p, const iterator& q ) { return !( p == q ); // return operator == (p, q); } iterator operator++() { m_p = m_p -> next; return *this; } ///< \c ++p iterator operator++(int) { iterator q = (*this); m_p = m_p -> next; return q; } ///< \c p++ iterator operator--() { m_p = m_p -> prev; return *this; } ///< \c --p iterator operator--(int) { iterator q = (*this); m_p = m_p -> prev; return q; } ///< \c p-- value_type& operator*() { return m_p -> m_val ; } ///< \c *p value_type* operator->() { return &(m_p -> m_val); } ///< \c p-> private: /// Verifica la invariante de la clase. bool ok( ) const; /// Verifica la invariante de la clase. template friend bool check_ok( const iterator & I ) { return I.ok(); } private: list_node * m_p; ///< Puntero al nodo de \c "list" }; // list::iterator public: /** Constructor de vector. \dontinclude test_STL_list.cpp \skipline test::constructor() \until }} \see ADH::test_list::test_constructor() */ list() { this->next = this->prev = static_cast< list_node* >((void*) this); } /// Constructor de copia. \see ADH::list::list() list(const list& L) { this->next = this->prev = static_cast< list_node* >((void*) this); *this = L; } ~list() { clear(); } ///< Destructor void clear(); void push_front( const value_type& v ); void push_back( const value_type& v ); void pop_front(); void pop_back(); /** Retorna "true" si la lista está vacía. \dontinclude test_STL_list.cpp \skipline test::constructor() \until }} \see ADH::test_list::test_constructor() */ bool empty() const { return this->next == static_cast< list_node* >((void*) this); } size_type size() const; ///< Cantidad de valores almacenados en \c "*this". \see ADH::list::list() /// Referencia al primer valor de la lista. \pre ! empty() /// \see ADH::list::push_front() const_reference front() const { return this->next->m_val; } /// Referencia al último valor de la lista. \pre ! empty() /// \see ADH::list::push_back() const_reference back() const { return this->prev->m_val; } /** Denota al primer valor de la lista. \see ADH::list::front(). \dontinclude test_STL_list.cpp \skipline test::iterator() \until }} \see ADH::test_list::test_iterator() */ iterator begin() const { iterator p; p.m_p = this->next; return p; } /// Denota el valor que ya está fuera del contenedor. \see ADH::list::begin(). iterator end () const { iterator p; p.m_p = static_cast< list_node* >((void*) this); return p; } list& operator=(const list& LO); /// Sinónimo de L = LO. \see ADH::list::operator=(). void copy(const list& LO) { *this = LO; } void move(list& L); void swap(list& L); // Intercambia *this <==> L void insert(iterator p, const value_type& v) { list_node *q; q = new list_node (v); q->next = p.m_p; q->prev = p.m_p->prev; q->prev->next = q; p.m_p->prev = q; } void erase (iterator p) { p.m_p->prev->next = p.m_p->next; p.m_p->next->prev = p.m_p->prev; delete p.m_p; } void remove(const value_type& v); void splice( iterator p, list& L, iterator from ); template friend bool operator == (const list&, const list&); template friend bool check_ok( const list & L ); private: // Rep // Como esta clase deriva directamente de la clase "list_node_base" // contiene todos los campos de esa clase. O sea, el Rep contiene: // list_node * prev; ///< Puntero al último nodo de la lista // list_node * next; ///< Puntero al primer nodo de la lista }; // list /** Verifica la invariante de la clase \c "list". - Regresa \c "true" si \c "L" contiene un valor correcto. - Podría retornar \c "true" aún cuando \c "L" tenga su valor corrupto. - Podría no retornar si \c "L" tiene su valor corrupto. \par Rep Diagrama de la clase \code <=====================================================> <=============> | next prev | | next prev | | +-+-+ +-+-+ +-+-+ +-+-+ +-+-+ | | +-+-+ | <===>|*|*|<===>|*|*|<===>|*|*|<===>|*|*|<===>|*|*|<===> <===>|*|*|<===> +-+-+ +-+-+ +-+-+ +-+-+ +-+-+ +-+-+ | 1 | | 2 | | 3 | | 4 | L L +---+ +---+ +---+ +---+ ^ Lista vacía ^ m_val ^ /|\ | | | L.front() L.back() L.end() \endcode \code Diagrama de herencia - Como tanto list como list_node derivan de +----------------+[]-next list_node_base, ambas clases contienen los | list_node_base | campos "next" y "prev". +----------------+[]-prev / \ - Por esto, la lista "*this" se ve como un nodo más +------+ +-----------+ - Sin embargo, la lista no contiene el campo "m_val" | list | | list_node | que sí aparece en los nodos list_node. +------+ +-----------+ - ¡Todo esto ahorra el campo "m_val" en la lista! \endcode \par Descripción en palabras - Una lista es una secuencia de nodos enlazados del primero al último. - En esta implementación la lista es doblemente enlazada. - El último nodo de la lista apunta al nodo cabeza, de manera que si se siguen los punteros \c "next" en una lista que se obtendrían esta secuencia valores: 1->2->3->4-> end() ->1->2->3 ... - El puntero hacia atrás del primer nodo de la lista apunta hacia el nodo cabeza de la lista, de manera que si se siguen los punteros \c "prev" en una lista que se obtendrían esta secuencia valores: 4->3->2->1-> end() ->4->3->2 ... - En lugar de crear un nodo cabeza, el truco que se usó aquí es poner los campos \c "next" y \c "prev" como los campos del Rep de la lista. Para hacerlo, se usó herencia, derivando de la clase \c list_node_base<>. Por eso el Rep de la lista es un nodo cabeza que sólo contiene los punteros de enlace. - La clase "nodo" está partida en 2 pedazos para que en el Rep de la lista no aparezca el campo "m_val". De esta manera se logran 2 mejoras: - No aparece el campo \c "m_val" en el Rep de la \c list. - No hace falta crear en memoria dinámica el nodo cabeza de la lista porque sus valores están almacenados directamente en el Rep de la \c list. - Para que el valor de la lista sea correcto es necesario que cada uno de los valores almacenados en la lista sea correcto. - El campo "next" debiera llamarse "m_next", como lo manda la convención para nombrar los campos de una clase. - En este caso, se ha mantenido el nombre "next" sin el prefijo "m_" porque en la mayor parte de los libros de texto, el nombre de este campo es "next", de manera que tiene un valor mnemónico-pedagógico usar "next" en lugar de "m_next". - El campo \c "next" no se llama \c "m_next" porque en los libros de texto el nombre que aparece siempre es \c "next". Lo mismo ocurre con el campo \c "prev". \pre Obliga al programador cliente a implementar la función \c bool check_ok( const value_type & ); */ template bool check_ok( const list & L ) { if ( ! (&L != 0) ) { return false; /// - Invariante: la lista no puede estar almacenada en una posición nula. } if ( !( (L.prev != 0) && (L.next != 0 ) ) ) { return false; /// - Invariante: los campos \c next y \c prev siempre deben ser no nulos, aún si la lista está vacía. } if ( L.prev == L.next ) { if ( L.next == static_cast< list_node* >(& L) ) { return true; /// - Invariante: la lista vacía siempre se representa como el nodo enlazado hacia sí mismo } else { return false; /// - Invariante: si la lista está vacía tanto \c next como \c prev debe estar /// enlazados hacia el mismo nodo. } } list_node * p = L.next; while ( p->next != static_cast< list_node* >(& L) ) { p = p->next; if ( !( (p->next->prev == p) && (p->prev->next == p) ) ){ return false; /// - Invariante: Cada nodo debe estar enlazado con su nodo siguiente y anterior. } if ( ! check_ok( p->m_val ) ) { return false; /// - Invariante: El valor \c "m_val" almacenado en un nodo de la lista /// siempre debe ser un valor correcto. } p = p->next; } return true; } #undef NO_COMPILA #ifdef NO_COMPILA template bool list::iterator ok( ) const { if (m_p == 0) { return true; // "I.m_p" es un puntero nulo } list TempL; TempL.m_first = m_p; // simula una lista a partir de "I.m_p" bool ok = check_ok( TempL ); TempL.m_first = 0; // evita que el destructor de "TempL" destruya el resto if (! ok) { return false; // lista mala ==> iterador malo } return true; } #endif // NO_COMPILA /** Agrega una copia del valor \c "v" al principio de la lista \c "*this". \par Complejidad - O ( \c 1 ) \dontinclude test_STL_list.cpp \skipline test::push_front_back() \until }} \see ADH::test_list::test_push_front_back() */ template inline void list::push_front( const T& v ) { // void list::push_front( const list::value_type& ); // NO COMPILA insert( begin(), v ); } // list::push_front() /** Agrega una copia del valor \c "v" al final de la lista \c "*this". \par Complejidad - O ( \c 1 ) \see ADH::list::push_front() */ template inline void list::push_back( const T& v ) { insert( end(), v ); } // list::push_back() /** Elimina el valor del principio de la lista. \pre \c !empty() \dontinclude test_STL_list.cpp \skipline test::pop_front_back() \until }} \see ADH::test_list::test_pop_front_back() */ template inline void list::pop_front() { erase( begin() ); } // list::pop_front() /** Elimina el valor del final de la lista. \pre \c !empty() \par Complejidad - O ( \c 1 ) \see ADH::list::pop_front() */ template inline void list::pop_back() { erase( --end() ); // erase( end()-- ); NO funciona /* NOTA Aunque en esta implementación sí ocurre que el "nodo" previo a "end()" es el último nodo de la lista, en general no se puede asegurar que sea cierto: assert( C.end() == ((C.end()--) ++) ); // "C" es un contenedor STL. */ } // list::pop_back() // list::size_type list::size( ) const { // NO COMPILA template unsigned list::size( ) const { unsigned n = 0; iterator p = begin(); while (p != end()) { n++; p++; } return n; } /** Elimina los valores del contenedor y lo deja vacío. \dontinclude test_STL_list.cpp \skipline test::clear() \until }} \see ADH::test_list::test_clear() */ template void list::clear() { #if 0 while ( ! empty() ) { // No se le meta al Rep pop_front(); // - es "menos" eficiente } #endif #if 1 // SI se le mete al Rep while ( this->next != static_cast< list_node* >((void*) this) ) { list_node *p = this->next; // lo sostiene this->next = this->next->next; // avanza delete p; // lo mata } // Reconecta circular al puntero previo del Rep de list this->prev = static_cast< list_node* >((void*) this); #endif } // list::clear() /** Copia ==> L = LO. \dontinclude test_STL_list.cpp \skipline test::assign() \until }} \see ADH::test_list::test_assign() */ template list& list::operator=(const list& LO) { if (&LO == this) { return *this; // evita autocopia } // borra todo clear(); // copia iterator p = LO.begin(); iterator L0_end = LO.end(); while (p != L0_end) { push_back( *p ); ++p; } return *this; } // list::operator=() /// Copia el valor de \c Lsrc sobre \c Ldst. /// - El valor anterior de \c Ldst se pierde. template void copy( list& Ldst, const list& Lsrc ) { if ( &Ldst == &Lsrc ) { return; // evita autocopia } // copia sobre los valores actuales de Ldst typename list::iterator pDst = Ldst.begin(); typename list::iterator endDst = Ldst.end(); typename list::iterator pSrc = Lsrc.begin(); typename list::iterator endSrc = Lsrc.end(); while ( (pSrc != endSrc) && (pDst != endDst) ) { *pDst = *pSrc; // reutiliza los valores ya almacenados en Ldst ++pDst; ++pSrc; } if ( (pSrc == endSrc) && (pDst == endDst) ) { return; // ya no hay nada que copiar pues ambas listas son del mismo largo } else if (pDst == endDst) { // Ldst es una lista más corta while (pSrc != endSrc) { Ldst.push_back( *pSrc ); pSrc++; } } else { assert( (pSrc == endSrc) ); // list Ltemp; Ltemp.splice( Ltemp.end(), Lsrc, pSrc, endSrc ); typename list::iterator pTmp; while (pDst != endDst) { pTmp = pDst; ++pTmp; // avanza pTmp antes de Ldst.erase(pDst); // matar a pDst pDst = pTmp; } } } /** Intercambia *this <==> L \dontinclude test_STL_list.cpp \skipline test::move_swap() \until }} \see ADH::test_list::test_move_swap() */ template void list::swap(list& L) { #if 1 list tmp; // menos eficiente pero más claro tmp.move( L ); // tmp <== L L.move( *this ); // L <== *this this->move( tmp ); // *this <== tmp #else assert( true && false && "list::swap() IMPLEMENTACION INCORRECTA" ); if (this->next==this->prev && L.next==L.prev) { return; // ambas listas están vacías } else if (this->next==this->prev && L.next != L.prev) { // sólo *this está vacía list_node * p = end().m_p->next; list_node * q = L.end().m_p; this->next = L.next; this->prev = L.prev; this->next->prev = p; this->prev->next = p; L.next = L.prev = q; } else if (this->next!=this->prev && L.next == L.prev) { // sólo L está vacía list_node * p = L.end().m_p->next; L.next = this->next; L.prev = this->prev; L.next->prev = p; L.prev->next = p; this->next = this->prev = static_cast< list_node* >((void*) this); } else if (this->next != this->prev && L.next != L.prev) { list_node * tmp = L.next; list_node * final = end().m_p; list_node * finalL = end().m_p; L.next = this->next; this->next = tmp; L.next->prev = finalL; this->next->prev = final; tmp = L.prev; L.prev = this->prev; this->prev = tmp; L.prev->next = finalL; this->prev->next = final; } #endif } /// Traslada el valor de \c "LO" a \c "*this" template void list::move(list& LO) { assert( true && "list::move() NO IMPLEMENTADO" ); if (&LO == this) { return; // evita autocopia } clear(); // borra todos lo valores de la lista actual if (LO.next == static_cast< list_node* >( (void*)(& LO) )) { return; // se va porque LO está vacía } #if 1 // "end_this" es "this" y apunta al "cabús" de la lista list_node * end_this = static_cast< list_node* >((void*) this); end_this->next = LO.next; end_this->prev = LO.prev; end_this->next->prev = end_this; end_this->prev->next = end_this; LO.next = LO.prev = static_cast< list_node* >( (void*)(& LO) ); #else list_node * p = end().m_p->next; // le roba a LO todos sus valores list_node * q = LO.end().m_p; this->next = LO.next; this->prev = LO.prev; this->next->prev = p; this->prev->next = p; LO.next = LO.prev = q; #endif } /** \fn template inline void ADH::list< T >::insert(iterator p, const value_type &v) \brief Agrega el valor \c "v" al contenedor. - El valor agregado queda antes de la posición \c "p" en el contenedor. - Si p==this->end() el valor queda al final de la lista. \par Complejidad - O ( \c 1 ) */ /** \fn template inline void ADH::list::erase(iterator p) \brief Elimina el valor \c "*p" del contenedor. - El valor agregado queda antes de la posición \c "p" en el contenedor. - Si p==this->end() el valor queda al final de la lista. \par Complejidad - O ( \c 1 ) */ /// Elimina del contenedor cada uno de los valores iguales a \c "v" template void list::remove(const T& v) { assert( true && "list::remove() NO IMPLEMENTADO" ); if ( empty() ) { return; } typename list::iterator i = begin(); typename list::iterator q; while (i != end()) { if (*i == v) { q = i; // como lo encontró ++i; erase(q); // lo borra } else { i++; // se lo brinca } } // list::remove() } /** \fn template inline void list::splice( iterator p, list& L, iterator from ) \brief Traslada el valor \c "from" de la lista \c "L" y lo coloca antes de \c "p". - El valor \c "from" deja de ser parte de \c "L" y pasa a estar almacenado dentro de \c "*this". - Todos los iteradores que denotan \c "from" quedan con su valor invalidado. \pre - El valor \c "p" debe denotar una posición en \c "*this" o \c "end()". - El valor \c "from" debe denotar una posición en \c "L". \remark - Al trasladar el valor se evita copiarlo. \par Complejidad - O ( \c size() ) */ template void list::splice( iterator p, list& L, iterator from ) { assert( false && "list::splice() NO IMPLEMENTADO" ); if ( L.empty() || L.empty() ) { return ; } else { from.m_p->prev = from.m_p->next; from.m_p->next->prev = from.m_p->prev; p.m_p->prev->next = from.m_p; from.m_p->prev = p.m_p->prev; from.m_p->next = p.m_p->next; p.m_p->next->prev = from.m_p; } } /// ¿L == LL? template bool operator == (const list& L, const list& LL) { if ( L.empty() && LL.empty() ) { return true; } if ( L.empty() || LL.empty() ) { return false; } typename list::iterator p = L.begin(); typename list::iterator q = LL.begin(); do { if ( *p != *q ) { return false; } ++p; ++q; } while ( p != L.end() && q != LL.end() ); return ( p == L.end() && q == LL.end() ); } /// ¿L != LL? template inline bool operator != (const list& L, const list& LL) { return ! (L==LL); } /** Retorna un iterador que denota al último valor de a lista \c "L". - Retorna \c L.end() si la lista está vacía. - Es ineficiente porque ejecuta en tiempo O ( L.size() ). - El argumento \c "L" no es \c "const" porque el iterador retornado no es un \c "const". \dontinclude test_STL_list.cpp \skipline test::last() \until }} \see ADH::test_list::test_last() */ template typename list::iterator last( list & L ) { typename list::iterator p,q; q = L.end(); p = L.begin(); while ( p != L.end() ) { q = p; // "q" está "antes" de "p" ++p; } // es ineficiente porque NO se le mete al "Rep" return q; } /** Graba el valor de \c "L" en el flujo \c "COUT". - En particular, este es el operador que se invoca cuando se usa, por ejemplo, este tipo de instrucción: \code COUT << L << LO; \endcode - Los valores quedan separados por comas. */ template std::ostream& operator<< (std::ostream &COUT, const ADH::list& L) { #if 1 { typename list::iterator p; // recorre la lista typename list::iterator endL; // después de la lista COUT << "("; { p = L.begin(); // p.operator= ( Lista.begin() ); endL = L.end(); for ( ; p != endL; ++p ) { COUT << ( *p ); if (p != last( L ) ) { COUT << ","; } } } COUT << ")"; } #endif #if 0 { COUT << "("; const list_node *p = L.next; while ( p != static_cast< const list_node* >( (void*)&L ) ) { COUT << p ->m_val; if ( p->next != static_cast< const list_node* >( (void*)&L ) ) { COUT << ","; // no es el último node de la lista } p = p->next; } COUT << ")"; } #endif #if 0 { if ( L.empty() ) { COUT << "()"; } else { list LO; LO = L; // Hace una copia COUT << "("; while (! LO.empty()) { typename list::value_type val = *LO.front(); COUT << val; if ( LO.size() > 1 ) { COUT << ','; } LO.pop_front(); } COUT << ")"; } } #endif #if 0 { COUT << "("; for (list LO = L; ! LO.empty(); LO.pop_front() ) { typename list::value_type val = LO.front(); COUT << val; if ( LO.size() > 1 ) { COUT << ','; } } COUT << ")"; } #endif return COUT; } // operator << /// Lee del flujo de texto \c "CIN" el valor de \c "L". /// - Se presume que el valor fue grabado con \c operator<<(). /// - "()" /// - "(1 2 3)" /// - "(1 2 3 77 888 999)" template std::istream& operator >> (std::istream &CIN, ADH::list& L) { L.clear(); // borra todo el valor anterior char ch; do { ch = CIN.get(); // lee el '(' de la pura izquierda } while ( ch != '(' ); assert( '(' == ch ); typename list::value_type v; // temporal para leer cada valor de la lista for (;;) { ch = CIN.peek(); // lee el delimitador o el ')' if (ch == ')') { ch = CIN.get(); // lee el ')' del final break; } else { CIN >> v; L.push_back( v ); } } return CIN; } // operator >> } // namespace ADH #endif // STL_list_h /* /---------<-----------------<-----------------<-----------\ | | | /------>-------\ /------>-------\ /------>-------\ | | | | | | | | | v | v | v | v / +-->+==/+=====+ /-->+==/+=====+ /-->+==/+=====+ /-->+==/+ | | * | | | | * | | | | * | | | | * | next | +---+ [1] | | +---+ [2] | | +---+ [3] | | +---+ | | * | | | | * | | | | * | | | | * | prev | +=|=+=====+ | +=|=+=====+ | +=|=+=====+ | +=|=+ | | m_val | | | | | | +-----|-------<---|----/ \-----|-------<---|-----/ | | | | | \-----------------------/ | \------->----------------->----------------->---/ /\ || last_node(L) */ // EOF: lab17.h