Los mapas o diccionarios en programación son listas de datos, que se relacionan con el par clave/valor. Esto es que cada valor tiene una clave, algo así como un diccionario. Que da entrada o clave tiene una descripción o valor.
Este tipo listas se pueden implementar en Free Pascal o Moderm Pascal (ahora este nuevo nombre se está poniendo de moda) usando la clase TFPGMap, La clase TFPGMap es una especialización de las clases genéricas.
Contenido
Introducción
La programación genérica centra en el algoritmo, y no en el tipo de datos, para ello usa tios de datos genéricos. Una buena introducción a la programación con datos genéricos podría ser esta:
Los genéricos, a veces llamados parámetros genéricos, un nombre que permite presentarlos mucho mejor. A diferencia del parámetro de una función (argumento), que tiene un valor, el parámetro genérico es un tipo. Y un parámetro genérico parametrizado una clase, un interfaz, un record, o, con menor frecuencia, un método.
https://ftp-developpez.com/sjrd/tutoriels/delphi-generiques/delphi-genericos.pdf
Declaración y uso
Antes de seguir, hay que tener en cuenta que la implementación de genéricos en Free Pascal es diferente que en Delphi. Free Pascal implementó el uso de genéricos antes que Delphi, y para mantener la compatibilidad con este, se añadió la unidad Generics.Collections. Más información sobre esto en la wiki de FreePascal.
La clase TFPGMap nos permite crear una lista de pares clave/valor, tal como comenté antes. Y la clave y valor puede ser de cualquier tipo de dato, incluso diferentes entre ellos. Aunque con fines didácticos, voy a usar cadenas (string).
1 2 3 4 5 6 7 8 9 |
//Clave tipo string type TKey = string; //Valor tipo string; type TData = string; type TMiMapa = specialize TFPGMap<TKey, TData>; |
Cómo se puede ver en este código, debemos usar la unidad fgl, y la declaración del tipo usamos string para la clave y lo mismo para el valor.
Observa el código siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
program maps; uses SysUtils, fgl; type TMiMapa = specialize TFPGMap<string, string>; var MiMapa: TMiMapa; begin MiMapa := TMiMapa.Create; //Crear el objeto MiMapa.AddOrSetData('Nombre', 'Jorge'); //Añadir clave valor MiMapa.AddOrSetData('Telefono', '123456789'); MiMapa.AddOrSetData('Email', 'email@email.com'); MiMapa.AddOrSetData('Borrarme', 'Borrado'); Writeln('Número de elementos: ', MiMapa.Count); //Mostrar cuantos elementos //No te olvides de liberar el mapa FreeAndNil(MiMapa); end. |
En la línea 12 creamos el mapa. Tras ello, añadimos diferentes pares clave/valor. En la línea 17 mostramos el número de elementos que hay, por supuesto, antes de terminar liberamos la memoria.
Búsqueda de un elemento
Ahora vamos modificar el código anterior para localizar una clave y su valor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
//No borres el código que hay aquí var MiMapa: TMiMapa; Valor: string; begin //No borres el código que hay aquí MiMapa.AddOrSetData('Borrarme', 'Borrado'); //inserta esto Writeln('Número de elementos: ', MiMapa.Count); if MiMapa.TryGetData('Telefono', Valor) then begin Writeln('Encontró teléfono', Valor); end; Writeln('Número de elementos: ', MiMapa.Count); //No te olvides de liberar el mapa FreeAndNil(MiMapa); end. |
Con el método TryGetData podemos obtener el valor de una clave. En este caso buscamos la clave Telefono. La función devuelve true si encontró la clave indicada. Por el parámetro Valor, nos devuelve el valor que corresponde con la clave buscada.
También existe la posibilidad de buscar una clave y obtener el índice esta. El código siguiente lo haría.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//La lista debe estar ordenada si queremos obtener el indice MiMapa.Sorted := True; Writeln('Buscar el indice de nombre'); if MiMapa.Find('Nombre', Indice) = True then begin WriteLn('Encontrado en ', indice); WriteLn(MiMapa.Keys[Indice]); //Devuelve la clave Writeln(MiMapa.Data[Indice]); //Devuelve el valor end else begin Writeln('No existe'); end; |
Fíjate que para usar la búsqueda y obtener el índice, la lista debe estar ordenada. La método Find, nos devuelve true si ha tenido éxito en la búsqueda. Y usando los métodos Keys y Data, indicándoles el índice, podemos obtener la clave y el valor de dicha clave respectivamente.
Probando algunas maldades
Observa el siguiente código
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
begin MiMapa := TMiMapa.Create; // MiMapa.OnKeyCompare := @Comparar; MiMapa.AddOrSetData('Nombre', 'Jorge'); MiMapa.AddOrSetData('Telefono', '123456789'); MiMapa.AddOrSetData('Email', 'email@email.com'); MiMapa.AddOrSetData('Borrarme', 'Borrado'); Writeln('Número de elementos: ', MiMapa.Count); if MiMapa.TryGetData('Telefono', Valor) then begin Writeln('Encontró teléfono', Valor); end; //La lista debe estar ordenada si queremos obtener el indice MiMapa.Sorted := True; Writeln('Buscar el indice de nombre'); if MiMapa.Find('Nombre', Indice) = True then begin WriteLn('Encontrado en ', indice); WriteLn(MiMapa.Keys[Indice]); //Devuelve la clave Writeln(MiMapa.Data[Indice]); //Devuelve el valor end else begin Writeln('No existe'); end; Writeln('Añadimos un repetido'); MiMapa.AddOrSetData('Email', 'email@email.com'); //Como es repetido no lo añade Writeln('Número de elementos: ', MiMapa.Count); //Substituye el valor de la clave MiMapa.AddOrSetData('Email', 'email1@email.com'); WriteLn(MiMapa.KeyData['Email']); //Borrar un elemento. MiMapa.Remove('Borrarme'); //hay uno menos Writeln('Número de elementos: ', MiMapa.Count); MiMapa.Remove('Borrarme'); //Sino existe no pasa nada. Writeln('Número de elementos: ', MiMapa.Count); //No te olvides de liberar el mapa FreeAndNil(MiMapa); |
En este código, realizamos algunas «maldades», como añadir una clave y valor que ya existe (línea 27). Al hacer esto no ocurre ningún error ni aviso, etc. Simplemente no se añade. Esto se comprueba que el número de elementos no ha variado
En el caso de que la clave exista, y el valor se diferente (línea 31) se modifica el contenido del valor. Tal como se muestra en la línea siguiente.
Para borrar un elemento, simplemente llamando al método Remove, indicando su clave, este es eliminado (línea 34). Si tratamos de borrar una clave que no existe, simplemente no ocurre nada, tal como muestra la línea 37.
Gestionar el orden.
En este ejemplo, simplemente, hemos añadido y borrado elementos. Pero también tenemos la posibilidad de gestionar el orden de los elementos que se añaden. Esto es interesante, sobre todo, cuando la clave, o el valor es un tipo de dato como un objeto.
La clase TMap, dispone de dos eventos, los cuales podemos usar en nuestro programa. Estos OnDataCompare y OnKeyCompare. Estos eventos se dispara cada vez que hay que comparar se compara un valor, o una clave. Para hacer uso de alguna de estos eventos, debemos asignarlo en el momento de crear el objeto.
1 2 |
MiMapa.OnKeyCompare := @Compararkeys; MiMapa.OnDataCompare := @CompararValores; |
Y declarar las correspondientes funciones.
1 2 3 4 5 6 7 8 9 |
function Compararkeys(const Key1, Key2: TKey): integer; begin Result := CompareStr(Key1,Key2); end; function CompararValores(const Data1, Data2: TData): integer; begin Result := CompareStr(Data1,Data2); end; |
El valor que deben devolver es un entero cuyo valor debe ser uno de los siguientes:
- Si el resultado de esta función es negativo, se considera que la primera clave (key1) es ‘menor’ que la segunda clave (key2) y se moverá antes que la segunda en la lista.
- Si el resultado de la función es positivo, se considera que la primera clave (key1) es ‘mayor que’ la segunda clave (key2) y se moverá después de la segunda en la lista.
- Si el resultado de la función es cero, se considera que las claves son ‘iguales’ y no se realizará ningún movimiento.
Te dejo aquí abajo el código completo. Échale un vistazo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
program maps; uses SysUtils, fgl; type TKey = string; type TData = string; type TMiMapa = specialize TFPGMap<TKey, TData>; var MiMapa: TMiMapa; Indice: integer; Valor: string; function Compararkeys(const Key1, Key2: TKey): integer; begin Result := CompareStr(Key1,Key2); end; function CompararValores(const Data1, Data2: TData): integer; begin Result := CompareStr(Data1,Data2); end; begin MiMapa := TMiMapa.Create; MiMapa.OnKeyCompare := @Compararkeys; MiMapa.OnDataCompare := @CompararValores; MiMapa.AddOrSetData('Nombre', 'Jorge'); MiMapa.AddOrSetData('Telefono', '123456789'); MiMapa.AddOrSetData('Email', 'email@email.com'); MiMapa.AddOrSetData('Borrarme', 'Borrado'); Writeln('Número de elementos: ', MiMapa.Count); if MiMapa.TryGetData('Telefono', Valor) then begin Writeln('Encontró teléfono', Valor); end; //La lista debe estar ordenada si queremos obtener el indice MiMapa.Sorted := True; Writeln('Buscar el indice de nombre'); if MiMapa.Find('Nombre', Indice) = True then begin WriteLn('Encontrado en ', indice); WriteLn(MiMapa.Keys[Indice]); //Devuelve la clave Writeln(MiMapa.Data[Indice]); //Devuelve el valor end else begin Writeln('No existe'); end; Writeln('Añadimos un repetido'); MiMapa.AddOrSetData('Email', 'email@email.com'); //Como es repetido no lo añade Writeln('Número de elementos: ', MiMapa.Count); //Substituye el valor de la clave MiMapa.AddOrSetData('Email', 'email1@email.com'); WriteLn(MiMapa.KeyData['Email']); //Borrar un elemento. MiMapa.Remove('Borrarme'); //hay uno menos Writeln('Número de elementos: ', MiMapa.Count); MiMapa.Remove('Borrarme'); //Sino existe no pasa nada. Writeln('Número de elementos: ', MiMapa.Count); //No te olvides de liberar el mapa FreeAndNil(MiMapa); end. |
Saludos