using namespace std; // Esta macro permite obtener el minimo pero no verifica tipos lo que es inadecuado // Las macros del preprocesador permite hacer metaprogramacion en C/C++ #define min(a,b) ((a) < (b) ? (a) : (b)) // Esta es una funcion libre plantilla. No es realmente una funcion, sino un molde // para construir funciones. C++ utilizara esta plantilla para construir funciones // a demanda conforme se usen en el programa; esto en tiempo de compilacion templateTipoDato minimo(TipoDato a, TipoDato b) { return a < b ? a : b; } // El 'template ' declara TipoDato como un tipo de datos // generico. C++ lo reemplazara por el tipo adecuado cuando se utilice la // plantilla para construir una funcion particular. Notese que la declaracion // de TipoDato solo esta disponible para la funcion que le sigue. Es decir el // TipoDato declarado anteriormente solo esta disponible para la plantilla // minimo() y maximo() no puede utilizarla; sino que maximo debe definir su // propio TipoDato template TipoDato maximo(TipoDato a, TipoDato b) { return a > b ? a : b; } // Prueba una de las plantillas para construir funciones int main() { long long a = 0; cout << "a: "; cin >> a; long long b = 0; cout << "b: "; cin >> b; // Cuando C++ ve minimo(a,b) no puede invocar a la plantilla // minimo(TipoDato, TipoDato). Una plantilla no es una funcion. Es solo una // plantilla para construir funciones con diferentes tipos de datos. C++ // entonces genera una funcion libre minimo(double, double) con esa plantilla // automaticamente y la utiliza en la siguiente invocacion cout << "minimo(" << a << ", " << b << ") = " << minimo(a,b) << endl; // Si usamos la plantilla con otro tipo de datos (double) C++ creara otra // funcion, en este caso, minimo(double, double) e invoca esa funcion generada // en la siguiente linea cout << "minimo(" << a << ", " << b << ") = " << minimo((double)a,(double)b) << endl; // Esto no generara una nueva funcion minimo(double, double), sino que C++ // reutilizara la que ya se creo previamente cout << "minimo(-0.01, -0.1) = " << minimo(-0.01, -0.1) << endl; // A este punto nunca se utilizo la funcion maximo(), por lo que C++ nunca // expandio la plantilla maximo(TipoDato, TipoDato) return 0; } ]]>
#include "Array.h"
using std::cout;
using std::endl;
using std::cin;
int main()
{
Array arr;
Array arr2;
char value = char();
while ( cin >> value )
arr.add(value);
for (size_t i = 0; i < arr.count(); ++i )
cout << i << ": " << arr[i] << endl;
return 0;
}
]]>
const size_t incrementFactor = 2; const size_t initialCapacity = 10; templateclass Array { private: /// The count of elements stored in the array size_t size; /// The quantity of elements that can be stored in the array /// without asking for more memory size_t capacity; /// A C-type array of elements DataType* arr; public: /// Default constructor and conversion constructor explicit Array(size_t capacity = initialCapacity); /// Destructor ~Array(); /// Adds a copy of the given value to the ending position of the array /// @return true if the element was added, false if no enough memory is available bool add(const DataType& element); /// Returns the number of elements stored in the array inline size_t count() const { return size; } /// Gets read and write access to the i-th element inline DataType& operator[](size_t i) { return arr[i]; } /// Gets read-only access to the i-th element inline const DataType& operator[](size_t i) const { return arr[i]; } private: /// Copy constructor Array(const Array& other); /// Assignment operator const Array& operator=(const Array& other); /// Makes room for more values bool incrementArray(); }; #include template Array ::Array(size_t capacity) : size(0) , capacity(capacity ? capacity : initialCapacity) , arr( new DataType[capacity] ) { assert(arr); } template Array ::~Array() { delete [] arr; } template bool Array ::add(const DataType &element) { if ( size == capacity ) if ( ! incrementArray() ) return false; arr[size++] = element; return true; } template bool Array ::incrementArray() { size_t newCapacity = capacity * incrementFactor; DataType* newArr = new DataType[newCapacity]; if ( ! newArr ) return false; for ( size_t i = 0; i < size; ++i ) newArr[i] = arr[i]; capacity = newCapacity; delete [] arr; arr = newArr; return true; } #endif // ARRAY_H ]]>
El siguiente listado implementa un contenedor cola simplemente enlazada. Las inserciones al inicio o final de la cola son muy eficientes. Pero para acceder a un valor, se requiere recorrer todos los elementos previos. Se provee una clase Iterator para poder hacer este recorrido.
#include "Queue.h"
using namespace std;
int main()
{
Queue cola;
double data = 0.0;
while ( cin >> data )
cola.add(data);
cout << cola << endl;
for ( Queue::ConstIterator itr = cola.begin(); itr != cola.end(); ++itr)
cout << *itr << ", ";
cout << endl;
#if 0
cin.clear();
Queue colaStr;
string text;
while ( cin >> text )
colaStr.add(text);
cout << colaStr << endl;
#endif
return 0;
}
]]>
const int null = 0; templateclass Queue { private: /// For each element stored in the queue, a Node is created to keep pointers to /// the next element. Note Node is for Queue class use exclusively (private) struct Node { /// A copy of the original element to be stored in the queue DataType data; /// A pointer to the next node in the queue Node* next; public: /// Conversion constructor explicit Node(const DataType& data) : data(data), next(null) { } }; private: /// A pointer to the first node in the queue. Null if queue is empty Node* head; /// A pointer to the last node in the queue. Null if queue is empty Node* tail; /// The count of elements currently stored in the queue size_t size; public: /// Default constructor Queue() : head(null), tail(null), size(0) { } /// Destructor: when the queue is destroyed, all its values are deleted also ~Queue() { clear(); } /// Returns true if queue is empty inline bool empty() const { return head == null; } /// Adds a copy of the given element into the queue /// @return true on success, false if not enough memory bool add(const DataType& data) { Node* node = new Node(data); if ( ! node ) return false; if ( empty() ) tail = head = node; else tail = tail->next = node; return ++size; } /// Remove all elements from the queue void clear() { for ( Node* node = head; head; node = head) { head = head->next; delete node; } tail = null; size = 0; } /// Prints all elements of the queue to the given output stream /// @param output File or stream to print /// @param separator Each element will be separated from another with this text std::ostream& print(std::ostream& output, const char* separator = " ") const { for ( Node* node = head; node; node = node->next) output << node->data << separator; return output; } public: /// Allows traversing the queue or pointing to a specific value in the queue class ConstIterator { private: /// An iterator mimics a pointer to this element which is stored in the queue const Node* node; public: /// Default constructor and conversion constructor explicit ConstIterator(const Node* node = null) : node(node) { } /// Returns true if this iterator points to the same element than other iterator inline bool operator!=(const ConstIterator& other) const { return node != other.node; } /// Get read-only access to the pointed element in array inline const DataType& operator*() const { return node->data; } /// Pre-increment operator: ++itr const ConstIterator& operator++() { node = node->next; return *this; } /// Post-increment operator: itr++ ConstIterator operator++(int) { ConstIterator previous(node); node = node->next; return previous; } }; /// Returns an iterator to the first element in the queue inline ConstIterator begin() const { return ConstIterator(head); } /// Returns an iterator to an invalid position in the queue inline ConstIterator end() const { return ConstIterator(); } private: Queue(const Queue& other); const Queue& operator=(const Queue& other); }; /// Allows std::cout << myqueue1 << myqueue2 << ... ; template std::ostream& operator<<(std::ostream& output, const Queue & queue) { return queue.print(output); } #endif // QUEUE_H ]]>
En un árbol binario de búsqueda cada nodo conoce cuales son sus nodos hijos: uno a la izquierda y otro a la derecha, de tal forma que cualquier nodo que se encuentre a la izquierda tiene un valor menor que el nodo y a la derech a un valor mayor o igual al nodo. El siguiente implementa algunos métodos de un árbol binario de búsqueda no balanceado.
int main()
{
BinarySearchTree tree;
double valor;
while ( true )
{
std::cout << "Valor " << tree.count() + 1 << ": ";
std::cin >> valor;
if ( valor == 0.0 )
break;
tree.insert(valor);
};
size_t i = 0;
for ( typename BinarySearchTree::ConstIterator itr = tree.begin(); itr != tree.end(); ++itr )
std::cout << ++i << ": " << *itr << std::endl;
std::cout << std::endl;
return 0;
}
]]>
#includetemplate class less { public: bool operator()(const DataType& a, const DataType& b) const { return a < b ? a : b; } }; template > class BinarySearchTree { private: class Node { friend class BinarySearchTree; private: DataType data; Node* parent; Node* left; Node* right; public: Node(const DataType& data, Node* parent = null, Node* left = null, Node* right = null) : data(data), parent(parent), left(left), right(right) { } ~Node() { delete left; delete right; } }; private: Node* root; size_t elementCount; public: BinarySearchTree() : root(null), elementCount(0) { } ~BinarySearchTree() { delete root; } inline size_t count() const { return elementCount; } /// @return true if the value was successfully inserted in the tree, /// false if not enough memory bool insert(const DataType& value) { Comparator comp; Node **node = &root, *parent = null; for ( ; *node; node = comp(value, (*node)->data) ? &(*node)->left : &(*node)->right ) parent = *node; *node = new Node(value, parent); return *node ? ++elementCount : 0; } void clear() { delete root; root = null; elementCount = 0; } void print() const { print(root); } private: void print(Node* node) const { if ( ! node ) return; print(node->left); std::cout << node->data << ' '; print(node->right); } static Node* minimum(Node* node) { while ( node && node->left ) node = node->left; return node; } static Node* findNextNode(Node* node) { if ( node->right ) return minimum(node->right); while ( node->parent && node->parent->right == node ) { node = node->parent; if ( ! node->parent ) return null; } if ( node->parent && node->parent->left == node ) node = node->parent; return node; } public: class ConstIterator { private: Node* ptr; public: explicit ConstIterator(Node* ptr) : ptr(ptr) { } inline const DataType& operator*() const { return ptr->data; } inline bool operator==(const ConstIterator& other) const { return ptr == other.ptr; } inline bool operator!=(const ConstIterator& other) const { return ptr != other.ptr; } ConstIterator& operator++() { return moveToNextNode(); } ConstIterator operator++(int) { ConstIterator result = *this; moveToNextNode(); return result; } private: ConstIterator& moveToNextNode() { if ( ! ptr ) return *this; Node* next = BinarySearchTree::findNextNode(ptr); ptr = ptr == next ? (Node*)null : next; return *this; } }; ConstIterator begin() const { return ConstIterator( minimum(root) ); } ConstIterator end() const { return ConstIterator(null); } }; #endif // BINARYSEARCHTREE_H ]]>
Un mapa, también llamado arreglo asociativo, hash o diccionario; es una estructura de datos que permite asociar una llave con un valor. Tanto las llaves como sus valores asociados pueden ser de cualquier tipo de datos. Por ejemplo, se puede asociar una palabra con su definición, o una palabra con su frecuencia de aparición en un texto, o un nivel de educación (un número) contra las personas que la han alcanzado (una lista enlazada de objetos), etc. El siguiente ejemplo muestra el contenedor std::map<Key, Value> de la Biblioteca Estándar de Plantillas (STL) de C++.
#include
El siguiente ejemplo es un contador de palabras. Recibe una lista de nombres de archivo por parámetro. Cada palabra del archivo es cargada a un mapa (implementado como un árbol binario de búsqueda balanceado). Cada palabra es asociada con un entero largo, el cual indica la cantidad de veces que se ha encontrado la palabra en el archivo. El operador [] del mapa recibe una llave y retorna una referencia a su valor asociado. Cada vez que se encuentra una palabra, se incrementa su contador (con el operador ++). Finalmente el programa imprime cada una de las palabras y su conteo en la salida estándar.
#include#include
Ejercicio. Modifique el ejemplo del contador de palabras únicas para que ignore caracteres especiales que se cargan al inicio o final de las palabras. Indague sobre los métodos de la clase std::string.
Ejercicio. Modifique el programa anterior, de tal forma que para cada palabra almacene las posiciones en el archivo donde la palabra aparece. Es decir, en su mapa cada palabra debe estar asociada con una lista enlazada de enteros (streampos). Indague sobre el método ifstream::tellg(). Su programa debe imprimir cada palabra única, la cantidad de veces que aparece en el archivo, y la lista de posiciones donde se encuentra.
Ejercicio. Modifique el contador de palabras únicas para que imprima las palabras en orden descendente de aparición. Sugerencia: cree otro mapa donde asocie la frecuencia de aparición con una lista de palabras. Recorra el mapa original para llenar el segundo. Luego imprima el segundo mapa de tal forma que las palabras más frecuentes aparezcan primero.