• No se han encontrado resultados

Intercambiar el contenido de un contenedor con otro mediante swap() 13 Determinar cuando un contenedor es igual, menor que o mayor que otro.

In document C++ SOLUCIONES DE PROGRAMACION (página 166-176)

Técnicas básicas de contenedor asociativo

12. Intercambiar el contenido de un contenedor con otro mediante swap() 13 Determinar cuando un contenedor es igual, menor que o mayor que otro.

Análisis

La STL da soporte a dos sabores básicos de contenedor asociativo: mapas y conjuntos. En un mapa cada elemento consta de un par clave/valor y el tipo de clave puede diferir del tipo del valor. En un conjunto, la clave y el valor se incrustan en el mismo objeto. Aunque mapas y conjuntos operan, en esencia, de la misma manera, un map se usa en la solución porque demuestra mejor las técnicas esenciales requeridas para usar cualquier contenedor asociativo.

La especifi cación de plantilla para map se muestra a continuación: template <class Key, class T, class Comp = less<Key>

class Allocator = allocator<T> > class map

Aquí, Key es el tipo de datos de las claves y T es el tipo de valores que se está almacenando (asig- nando). La función que compara dos claves está especifi cada por Comp. Observe que las opciones predeterminadas usan el objeto de función less. El asignador está especifi cado por Allocator, cuya opción predeterminada es el asignador estándar.

Un aspecto central de un contenedor asociativo es que mantiene una colección ordenada de elementos basados en el valor de las claves. El orden específi co es determinado por la función de comparación, que es less como opción predeterminada. Esto signifi ca que, como opción prede- terminada, los elementos de un contenedor asociativo están almacenados en orden ascendente. Sin embargo, es posible especifi car un objeto de comparación que almacena de manera diferente los elementos.

La clase map da soporte a tres constructores. Aquí se muestran los dos usados en esta solución: explicit map(const Comp &fucomp = Comp(), const Allocator &asign = Allocator())

map(const map<Key, T, Componente, Allocator> &ob)

La primera forma construye un mapa vacío. La segunda forma construye un mapa que contiene los mismos elementos que ob y es el constructor de copia de map. El parámetro fucomp especifi ca la función de componente usada para ordenar el mapa. En casi todos los casos, puede permitir esto como opción predeterminada. El parámetro asign especifi ca el asignador, que también suele admitirse como opción predeterminada. Para usar un mapa, debe incluir el encabezado <map>.

El tipo de objeto contenido por un mapa es una instancia de pair, que es una struct que encap- sula dos objetos. Se declara así:

template <class TipoC, class TipoC> struct pair { typedef TipoC first_type;

typedef TipoV second_type;

TipoC first; //para elementos de mapa, contiene la clave TipoV second; //para elementos de mapa, contiene el valor

// Constructores pair();

pair(const TipoC &c, const TipoV &v);

template<class A, class B> pair(const pairA, B> &ob); }

La clase pair puede usarse para contener cualquier par de objetos. Sin embargo, cuando se usa para contener un par clave/valor, el valor en fi rst contiene la clave y el valor en second contiene el valor asociado con esa clave. La clase pair necesita el encabezado <utility>, que se incluye auto- máticamente en <map>.

Puede construir un pair al usar uno de los constructores de pair o empleando la función make_pair(), que también se declara en <utility>; construye un objeto pair basado en los tipos de datos usados como parámetros. La función make_pair es genérica y tiene este prototipo:

template <class TipoC, class TipoV)

pair<TipoC, TipoV) make_pair(const TipoC &c, const TipoV &v)

Como puede ver, devuelve un objeto pair que consta de valores de los tipos especifi cados por TipoC y TipoV. La ventaja de make_pair() es que los tipos del objeto que se está almacenando son determi- nados automáticamente por el compilador en lugar de que usted los especifi que en forma explícita.

Para map, el tipo value_type es un tipedef para pair<const Key, T>. Por tanto, un mapa contiene instancias de pair. Más aún, el tipo iterator defi nido por map señala a objetos de tipo pair<Key, T>. Por tanto, cuando una función map devuelve un iterador, la clave está disponible mediante el campo fi rst de pair y el valor se obtiene mediante el campo second del pair.

Después de que se ha creado un par, los objetos de pair pueden agregarse a él. Una manera de hacer esto que funciona para todos los contenedores asociativos consiste en llamar a insert(). Todos los contenedores asociativos dan soporte por lo menos a tres versiones de insert(). Éste es el usado aquí:

pair<iterator, bool> insert(const value_type &val)

Inserta val en el contenedor que invoca en un punto que mantiene el orden del contenedor asocia- tivo. (Recuerde que value_type es un type_def para pair<const Key, T>.) La función devuelve un objeto de pair que indica el resultado de la operación. Si val puede insertarse, el valor bool (que es el campo second) será true, y false, de otra manera. El valor iterator (que está en el campo fi rst) señalará al elemento insertado, si se tiene éxito, o al elemento ya existente que usa la misma clave. La operación de inserción fallará si se hace un intento por insertar un elemento en un contenedor que requiere claves únicas (como map o set) y el contenedor ya incluye la clave. Un contenedor asocia- tivo crecerá automáticamente a medida que se necesite cuando se le agreguen elementos.

Puede eliminar uno o más elementos de un contenedor asociativo al llamar a erase(). Tiene por lo menos tres formas. Aquí se muestra la usada en esta solución:

size_type erase(const key_type &tc)

Elimina del contenedor todos los elementos que tienen claves con el valor c. En el caso de conte- nedores asociativos que requieren claves únicas, una llamada a erase() elimina sólo un elemento. Devuelve el número de elementos eliminados, que podría ser cero o uno para un map.

Puede eliminar todos los elementos de un contenedor asociativo al llamar a clear(), que se muestra aquí:

void clear()

Puede obtener un iterador a un elemento en un contenedor asociativo que tiene una clave especifi cada al llamar a fi nd(), que se muestra aquí:

Aquí, c especifi ca la clave. Si el contenedor incluye un elemento que tiene una clave igual a c, fi nd() devuelve un iterador al primer elemento coincidente. Si la clave no se encuentra, entonces se devuelve end().

Puede determinar el número de elementos en un contenedor al llamar a size(). Para determi- nar si un contenedor está vacío, llame a empty(), como se muestra aquí.

bool empty() const size_type size() const

Puede obtener un iterador al primer elemento del contenedor al llamar a begin(). Debido a que los contenedores asociativos están ordenados, éste siempre será el primer elemento especifi cado por la función de comparación. Un iterador a una parte del último elemento en la secuencia se obtiene al llamar a end(). Aquí se muestran estas funciones:

iterator begin() iterator end()

Para declarar una variable que se usará como un iterador, debe especifi car el tipo de iterador del contenedor. Por ejemplo, esto declara un iterador que puede señalar a elementos dentro de map<string, int>:

map<string, int>::iterator itr;

Puede usar iteradores para recorrer en ciclo el contenido de un contenedor asociativo. El proceso es similar al usado para recorrer en ciclo el contenido de un contenedor de secuencias. La principal diferencia es que en contenedores asociativos que almacenan pares clave/valor, el objeto señalado por el iterador es un pair. Por ejemplo, suponiendo un iterador declarado de manera apropiada llamado itr, he aquí un bucle que despliega todas las claves y los valores en un map llamado mimapa:

for(itr=mimapa.begin(); itr != mimapa.end() ++itr)

cout << "Clave: " << itr->first << ", Valor:" << itr->second << endl;

El bucle se ejecuta hasta que itr es igual a mimapa.end(), lo que asegura, por tanto, que se desplie- guen todos los elementos. Recuerde que end() no devuelve un apuntador al último elemento de un contenedor. En cambio, devuelve un apuntador a uno después del último elemento. Por tanto, el último elemento de un contenedor es señalado por end()-1.

Como se explicó en la revisión general, un contenedor reversible es aquel en que los elementos pueden recorrerse en orden inverso (de atrás hacia adelante). Todos los contenedores asociativos integrados son reversibles. Cuando se usa un contenedor reversible, puede obtener un iterador inverso al fi nal del contenedor al llamar a rbegin(). Un iterador inverso a uno antes del primer elemento en el contenedor se obtiene al llamar a rend(). Aquí se muestran estas funciones:

reverse_iterator rbegin() reverse_iterator rend()

También hay versiones const de estas funciones. Un iterador inverso se declara como un iterador regular. Por ejemplo:

Puede usar un iterador inverso para recorrer en ciclo un mapa en orden inverso. Por ejemplo, dado un iterador inverso llamado ritr, he aquí un bucle que despliega las claves y los valores de un mapa llamado mimapa, de atrás al frente:

for(ritr=mimapa.rbegin(); ritr != mimapa.rend() ++ritr)

cout << "Clave: " << ritr->first << ", Valor:" << ritr->second << endl;

El iterador inverso ritr empieza en el elemento señalado por rbegin(), que es el último elemento del contenedor. Se ejecuta hasta que es igual a rend(), que señala a un elemento que está uno an- tes del inicio del contenedor. (En ocasiones es útil pensar que *rbegin() y rend() devuelven apunta- dores al inicio y el fi nal de un contenedor inverso.) Cada vez que se aumenta un iterador inverso, señala al elemento anterior. Cada vez que se disminuye, señala al siguiente elemento.

El contenido de dos contenedores asociativos puede intercambiarse al llamar a swap(). He aquí la manera en que se declara con map.

void swap(map<Key, T, Comp, Allocator> &ob)

El contenido del contenedor que invoca se intercambia con el especifi cado por ob.

Ejemplo

En el siguiente ejemplo se usa map para demostrar las técnicas básicas de contenedor asociativo: // Demuestra las operaciones básicas de contenedor asociativo.

//

// En este ejemplo se usa map, pero pueden aplicarse las mismas // técnicas básicas a cualquier contenedor asociativo.

#include <iostream> #include <string> #include <map> using namespace std;

void mostrar(const char *msj, map<string, int> mp); int main() {

// Declara un mapa vacío que contiene pares clave/valor // en que la clave es una cadena y el valor es un entero. map<string, int> m;

// Inserta caracteres en v. Se devuelve un iterador // al objeto insertado.

m.insert(pair<string, int>("Alfa", 100)); m.insert(pair<string, int>("Gamma", 300)); m.insert(pair<string, int>("Beta", 200)); // Declara un iterador a un map<string, itr>. map<string, int>::iterator itr;

// Despliega el primer elemento en m. itr = m.begin();

<< itr->first << ", " << itr->second << endl; // Despliega el último elemento en m.

itr = m.end(); --itr;

cout << "El \u00a3ltimo par clave/valor en m: " << itr->first << ", " << itr->second << "\n\n"; // Despliega todo el contenido de m.

mostrar("Todo el contenido de m: ", m);

// Muestra el tamaño de m, que es el número de // elementos contenidos por m.

cout << "El tama\u00a4o de m es " << m.size() << "\n\n"; // Declara un iterador inverso a un map<string, itr>. map<string, int>::reverse_iterator ritr;

// Ahora, muestra el contenido de m en orden inverso. cout << "El contenido de m invertido:\n";

for(ritr=m.rbegin(); ritr != m.rend(); ++ritr)

cout << " " << ritr->first << ", " << ritr->second << endl; cout << endl;

// Encuentra un elemento dada su clave. itr = m.find("Beta");

if(itr != m.end())

cout << itr->first << " tiene el valor " << itr->second << "\n\n"; else

cout << "Clave no encontrada.\n\n"; // Crea otro mapa que es igual al primero. map<string, int> m2(m);

mostrar("El contenido de m2: ", m2); // Compara dos mapas.

if(m == m2) cout << "m y m2 son equivalentes.\n\n"; // Inserta más elementos en m y m2.

cout << "Se insertan elementos adicionales en m y m2.\n"; m.insert(make_pair("Epsilon", 99));

m2.insert(make_pair("Zeta", 88));

mostrar("El contenido de m es ahora: ", m); mostrar("El contenido de m2 es ahora: ", m2); // Determina la relación entre m y m2. Es una // comparación lexicográfica. Por ello, el primer // elemento en el contenedor determina cuál // contenedor es menor que el otro.

if(m < m2) cout << "m es menor que m2.\n\n"; // Elimina Beta de m.

mostrar("m tras eliminar Beta: ", m);

if(m > m2) cout << "Ahora, m es mayor que m2.\n\n"; // Intercambia el contenido de m y m2.

cout << "Se intercambian m y m2.\n"; m.swap(m2);

mostrar("El contenido de m: ", m); mostrar("El contenido de m2: ", m2); // Limpia m.

m.clear();

if(m.empty()) cout << "m est\u00a0 vac\u00a1o."; return 0;

}

// Despliega el contenido de un map<string, int> al usar // un iterador.

void mostrar(const char *msj, map<string, int> mp) { map<string, int>::iterator itr;

cout << msj << endl;

for(itr=mp.begin(); itr != mp.end(); ++itr)

cout << " " << itr->first << ", " << itr->second << endl; cout << endl;

}

Aquí se muestra la salida:

El primer par clave/valor en m: Alfa, 100 El último par clave/valor en m: Gamma, 300 Todo el contenido de m: Alfa, 100 Beta, 200 Gamma, 300 El tamaño de m es 3 El contenido de m invertido: Gamma, 300 Beta, 200 Alfa, 100

Beta tiene el valor 200 El contenido de m2: Alfa, 100

Beta, 200 Gamma, 300

Se insertan elementos adicionales en m y m2. El contenido de m es ahora: Alfa, 100 Beta, 200 Épsilon, 99 Gamma, 300 El contenido de m2 es ahora: Alfa, 100 Beta, 200 Gamma, 300 Zeta, 88 m es menor que m2. m tras eliminar Beta: Alfa, 100

Épsilon, 99 Gamma, 300

Ahora, m es mayor que m2. Se intercambian m y m2. El contenido de m: Alfa, 100 Beta, 200 Gamma, 300 Zeta, 88 El contenido de m2: Alfa, 100 Épsilon, 99 Gamma, 300 m está vacío.

Gran parte del programa se explica por sí solo, pero hay unos cuantos aspectos que merecen un examen de cerca. En primer lugar, observe cómo se declara un objeto de map mediante la línea siguiente:

map<string, int> m;

Esto declara un mapa llamado m que contiene pares clave/valor en que la clave es de tipo string y el valor es de tipo int. Esto signifi ca que los tipos de objetos contenidos por m son casos de pair<string, int>. Observe que se usa la función de comparación predeterminada less. Esto signi- fi ca que los objetos se almacenan en el mapa en orden ascendente. Además, observe que se usa el asignador predeterminado.

A continuación, los pares clave/valor se insertan en m al llamar a insert(), como se muestra aquí:

m.insert(pair<string, int>("Alfa", 100)); m.insert(pair<string, int>("Gamma", 300)); m.insert(pair<string, int>("Beta", 200));

Debido a que m usa la función de comparación predeterminada, el contenido se ordena auto- máticamente de manera ascendente con base en las claves. Por tanto, el orden de las claves en el mapa después de las llamadas anteriores a insert() es Alfa, Beta, Gamma, como lo confi rma la salida.

A continuación, se declara un iterador al mapa mediante la línea siguiente: map<string, int>::iterator itr;

Debido a que el tipo de iterador debe coincidir exactamente con el tipo de contenedor, es necesario especifi car los mismos tipos de clave y valor. Por ejemplo, un iterador que contiene pares clave/ valor de tipo string/int no funciona con un mapa que contiene pares clave/valor de tipo ofstream/ string.

Luego, el programa usa el iterador para desplegar el primero y el último par clave/valor en el mapa al usar esta secuencia:

// Despliega el primer elemento en m. itr = m.begin();

cout << "El primer par clave/valor en m: "

<< itr->first << ", " << itr->second << endl; // Despliega el último elemento en m.

itr = m.end(); --itr;

cout << "El \u00a3ltimo par clave/valor en m: " << itr->first << ", " << itr->second << "\n\n";

Como se explicó, la función rbegin() devuelve un iterador al primer elemento en el contenedor y end() devuelve un iterador a uno después del último elemento. Por esto itr disminuye después de la llamada a end() para que pueda desplegarse el último elemento. Recuerde que el tipo de objeto señalado por un iterador map es una instancia de pair. La clave está contenida en el campo fi rst y el valor en el second. Además, observe cómo los campos de pair se especifi can al aplicar el ope- rador –> a itr de la misma manera en que usaría –> con un apuntador. En general, los iteradores funcionan como apuntadores y se manejan, en esencia, de la misma manera.

A continuación, todo el contenido de m se despliega al llamar a mostrar(), que despliega el contenido de map<string, int> que se pasa. Preste especial atención a la manera en que se desplie- gan los pares clave/valor mediante el siguiente bucle for:

for(itr=mp.begin(); itr != mp.end(); ++itr)

cout << " " << itr->first << ", " << itr->second << endl;

Debido a que end() obtiene un iterador que señala a uno después del fi nal del contenedor, el bucle se detiene automáticamente después de que se ha desplegado el último elemento.

Luego, el programa despliega el contenido de m invertido mediante el uso de un iterador in- verso y un bucle que ejecuta de m.begin() a m.rend(). Como se explicó, un iterador inverso opera en el contenedor de atrás hacia adelante. Por tanto, el incremento de un iterador inverso causa que señale al elemento anterior en el contenedor.

Preste especial atención a la manera en que se comparan dos contenedores mediante el uso de los operadores ==, < y >. En el caso de contenedores asociativos, la comparación se conduce empleando una comparación lexicográfi ca de los elementos, que en el caso de map son pares cla-

ve/valor. Aunque el término "lexicográfi co" signifi ca "orden de diccionario", su signifi cado suele generalizarse a lo que se relaciona con STL. En el caso de comparaciones entre contenedores, dos de éstos son iguales si contienen el mismo número de elementos, en el mismo orden, y todos los elementos correspondientes son iguales. En el caso de contenedores asociativos que contienen pa- res clave/valor, esto signifi ca que cada clave y valor del elemento deben coincidir. Si se encuentra una falta de coincidencia, el resultado de una comparación lexicográfi ca se basa en los primeros elementos que no coinciden. Por ejemplo, suponga que un mapa contiene el par:

prueba, 10 y otro contiene:

prueba, 20

Aunque las claves sean las mismas, debido a que los valores difi eren, estos dos elementos no son equivalentes. Por tanto, se juzgará que el primer mapa es menor que el segundo.

Otro tema interesante es la secuencia que encuentra un elemento dada su clave. Aquí se muestra: // Encuentra un elemento dada su clave.

itr = m.find("Beta"); if(itr != m.end())

cout << itr->first << " tiene el valor " << itr->second << "\n\n"; else

cout << "Clave no encontrada.\n\n";

La capacidad de encontrar un elemento dada su clave es uno de los aspectos defi nitorios de los contenedores asociativos. (¡Por esa razón se les denomina "contenedores asociativos"!) El método fi nd() busca el contenedor que invoca una clave que coincida con una especifi cada como argumen- to. Si se encuentra, se devuelve un iterador al elemento. De otra manera, se devuelve end().

Opciones

Puede contar el número de elementos en un contenedor asociativo que coincide con una clave especifi cada al llamar a count(), que se muestra aquí:

size_type count(const key_type &c) const

Devuelve el número de veces que ocurre c en el contenedor. En el caso de contenedores que re-

In document C++ SOLUCIONES DE PROGRAMACION (página 166-176)

Outline

Documento similar