Plantillas (templates)


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
template 
TipoDato 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;
}
]]>
Funciones libres plantilla. C++ las expandirá con cualquier tipo de datos con que se usen en tiempo de compilación. Obtener código fuente.

Clase Array


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

template 
class 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
]]>
Una clase plantilla que implementa un arreglo dinámico. Obtener código fuente.

Clase Queue

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;

template 
class 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
]]>
Una clase plantilla que implementa una cola enlazada. Obtener código fuente.

Clase BinarySearchTree

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

#include 

template 
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
]]>
Una clase plantilla que implementa un árbol binario de búsqueda no balanceado. Obtener código fuente.

Mapas (Arreglos asociativos, Hash o Diccionarios)

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 

using namespace std;

int main()
{
	map dicc;

	dicc["a"] = "Primera letra del alfabeto. Preposicion...";
	dicc["abaco"] = "Instrumento para contar";
	dicc["asilo"] = "Lugar de refugio para personas";
	// ...
	dicc["zurqui"] = "Tunel ubicado...";

	string palabra;
	map::const_iterator itr;
	while ( cin >> palabra )
		if ( ( itr = dicc.find(palabra) ) != dicc.end() )
			cout << itr->second << endl;

	return 0;
}
]]>
Ejemplo minimalista de implementación de un diccionario de definiciones. Obtener código fuente.

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 

using namespace std;

typedef map word_count_map;

// Unique word counter command
int main(int argc, char* argv[])
{
	for ( int i = 1; i < argc; ++i )
	{
		ifstream source( argv[i] );
		if ( ! source )
		{
			cerr << "Could not open " << argv[i] << endl;
			return 2;
		}

		string word;
		word_count_map countof;
		while ( source >> word )
			++countof[word];

		for ( word_count_map::const_iterator itr = countof.begin(); itr != countof.end(); ++itr)
			cout << itr->first << ": " << itr->second << endl;

		source.close();
	}

	return 0;
}
]]>
Un eficiente contador de palabras únicas en archivos de texto. Obtener código fuente. Puede probar con el texto completo del Quijote de La Mancha.

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.