Los conjuntos son de esos tipos que muchas veces nos pasan desapercibidos. 🙂 Se podría incluso decir que pasan sin pena ni gloria. No tienen la importancia de una clase o de un registro. Y se situan dentro del ranking de popularidad entre los Enumerados y los Subrango, que es casi lo mas bajo que uno puede caer 🙂 Al menos siempre tuve esa impresión. 😀
Hablando en serio, ya que hemos acabado las cinco entradas donde todo giraba en torno a pensar en clases, parece que se hacía apropiado comentar algo de los tipos personalizados. Y digo lo de apropiado, porque los tipos personalizados no son imprescindibles puesto que al final los identificadores son representaciones de un valor ordinal. Pero pensar en términos de clases incluye un premisa que hasta ahora habia quedado un poco en el aire, y es simplemente que el código debe ser también lo mas claro posible. Y en eso, sin duda, puede ayudarnos la existencia de los tipos personalizados, tanto enumeraciones, subrangos, conjuntos o registros, (éstos últimos, con un peso mucho mayor lógicamente).
Realmente, ese pensamiento, que mi código sea claro, sencillo, limpio, nos debería acompañar siempre. Nuestra «supervivencia» depende un poco de eso. Nuestros razonamientos deben seguir caminos similares. No supone un rasgo de inteligencia enrevesar nuestro código adrede y hacerlo ilegible incluso para nosotros mismos, (a poco que pasen algunos meses desde que lo escribimos, puede convertirse involuntariamente en una realidad, y en un momento posterior en una pequeña pesadilla). Y eso, a menudo, pasa mas de lo que quisiéramos. 🙂
Y entre algunos de esos «problemas», de refilón puede haberse colado no haber hecho uso de los tipos personalizados: ¿Métodos que recibían parámetros de tipo Integer y que escondían un tipo personalizado?. Puede ser uno de los ejemplos que me vienen a la mente:
-¡Ahí la cagaste, amigo!-, pensaba para mí…- ¡podías haber utilizado un tipo enumerado!-.
Y así podríamos enumerar muchos mas. Es cierto que es muy subjetivo pero creo que todos estaremos de acuerdo en que resulta mas legible algo como
function ElUsuarioEligio: TDia;
siendo TDia =(dLunes, dMartes, dMiercoles, dJueves, dViernes))
que
function ElUsuarioEligio: Integer;
Pero empecemos por el principio, y es entender que es una Enumeración: Una enumeración es un tipo ordinal, definido como un conjunto ordenado de valores en el que cada valor tiene un unico antecesor y un unico sucesor, a excepción del primer valor o del último, dado que el primer valor no tiene antecesor y el ultimo carece de sucesor, por definición.
¿No es demasiado intuitiva, no?
Pero es eso: Un conjunto de valores ordenado. No hay mucho mas.
Si tuviéramos que identificar los dias de las semana para poder referirnos a ellos en nuestro código, siempre podríamos apoyarnos en las constantes. Tal que así:
const klunes = 1; kmartes = 2; kmiercoles = 3; kjueves = 4; kviernes = 5; ksabado=6; kdomingo=7
Lo cual puede ser claro, pero no nos seria demasiado útil en algunos casos, ya que no tengo un indice ínterno que me permita recorrer todos los valores y hacer algo para cada día, salvo que me pueda crear también «algo» que haga la misma función, mediante una variable entera y un bucle que recorra los valores desde 1 hasta 7.
Por lo que, resulta natural que el lenguaje me ofrezca la forma de agrupar esos valores en un tipo (enumerado) de forma que dicho tipo pueda representar a cualquiera de ellos. El tipo TDiaSemana se representaría:
TDiaSemana = (seLunes, seMartes, seMiercoles, seJueves, seViernes, seSabado, seDomingo)
Desde ese momento, contando con los tipos enumerados, ya resulta mas fácil en un procedimiento cualquiera hacer uso de un parámetro de tipo TDiaSemana en lugar del mismo de tipo Integer. Ofrece mas claridad al revisar el código y menor posibilidad de errar al invocarlo, evaluando un valor que no se establece en la precondición.
Por defecto, la enumeración se iniciaría en el valor 0 (cero) pero eso tampoco es obligatorio, como podemos observar en el ejemplo que he añadido. Podríamos iniciar la semana en el valor 1, por eso de que es el primer día… 🙂
TDiaSemana = (seLunes = 1, seMartes = 2, seMiercoles = 3, seJueves =4 , seViernes = 5, seSabado = 6, seDomingo = 7)
Y ya casi para acabar con los tipos enumerados, al igual que sucede con los tipos ordinales, disponen de un índice que permite ser recorrido, y existen a nuestra disposición algunas funciones predefinidas que podemos usar para manipularlos dentro de nuestro código:
- Ord (Valor de una expresión ordinal).
- Pred (Valor antecesor del citado por la expresión)
- Succ (Valor sucesor del citado por la expresión)
- High (Valor máximo que puede tomar el tipo)
- Low ( Valor mínimo que puede tomar el tipo)
Eso con respecto a los tipos enumerados.
Otro tipo personalizado, es el Subrango, que representa un subconjunto de un tipo ordinal (de cualquiera de ellos). Y se construye mediante un valor minimo y un mayor maximo separados por dos puntos, siendo ese mínimo y ese máximo expresiones de un tipo de valor ordinal. Admite un máximo de 256 valores
TMisFavoritos = seViernes...seDomingo;
Pero bueno… también podemos hacer:
TMiRangoPorcentajes = 0..199; TAlfabeto = 'a'..'z';
Lo mas habitual es que definamos variables de tipo y, como comentábamos lineas mas arriba, hagamos uso de las funciones predefinidas para movernos por el rango de valores que especifica el tipo.
Y finalmente, llegamos a los conjuntos, que era lo que me movía a iniciar la entrada. Puede ser que fuera porque, pienso yo, al igual que muchos compañeros, si bien usamos habitualmente los tipos enumerados y los subrangos, no lo hacemos con las misma frecuencia que los conjuntos y siempre quedan como mas relegados. Y eso a pesar que los vemos presentes en propiedades de clases tan habituales como la clase TFont, en lla propiedad Style[] . Por citar una de las que me parecen mas intuitivas para citar.
¿Que es un conjunto?
Pues un conjunto es una colección de valores ordinales, con un máximo de 256 elementos (1 byte). Se utliza la sintaxis:
Type TMiConjunto = Set of TMiTipoOrdinal;
Olvidaba comentar que al admitir el valor nulo como posible valor de dominio, es decir, el conjunto vacío, no tenemos disponibles 256 valores sino 255 distintos de este valor nulo.
En el caso que comentabamos, el de los días de la semana, podríamos establecer un conjunto que representara los días laborables de semana y utilizarlo para indicar que días había trabajado de la misma.
type TDiasTrabajo = seLunes..seViernes; TMiJornada = Set of TDiasTrabajo; var MiSemana: TMiJornada; begin //la variable no contiene ningun elemento MiSemana:= [];
Es decir. Esa semana no hemos dado palo al algua… 🙂
Estas son las funciones que tenemos disponibles y que podeis ampliar información consultando en la ayuda del entorno: (las pongo en función del ejemplo para que se van mas claras)
Si incluimos un elemento, podemos valernos del operador (+) o de la función Include
MiSalida:= MiSemana + [seLunes]; o bien Include(MiSemana, seLunes)
Y si deseo excluir un elemento del conjunto, podemos hacer uso del operador (–) o de la función Exclude
MiSalida:= MiSemana - [seLunes]; o bien Excude(MiSemana, seLunes)
En el caso de los conjuntos, tenemos formas de saber si un elemento pertenece a un conjunto. ¿como?
Muy fácil, haciendo uso del operador (in), que nos permite evaluar en una expresion booleana la pertenencia del elemento.
if (seLunes in MiSemana) then
También, desde Delphi 2005 es posible recorrer los valores que pertenecen a un conjunto haciendo uso de for (ValorOrdinal) in (Conjunto). Lineas mas abajo lo podréis ver.
Bueno… Lo mejor, es que lo veamos en un ejemplo muy sencillo. Vamos a usar una ventana modal para preguntar al usuario que meses va a elegir, suponiendo que este proceso sea parte de otro mas general del que valernos para obtener una consulta (ej: estadísticas de ventas en los meses deseados, etc.). Al cerrar la ventana de dialogo, nos copiará la selección deseada en el listbox, permitiendo que al volver a ser llamada se restablezca la selección anterior.
He intentado que el ejemplo contenga las expresiones mas generales. Esta es la imagen de la ventana modal, que va a ser llamada para que el usuario pueda elegir que meses desea incluirl modulo principal:
Y este es el código que se ejecutará:
unit UMeses; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Buttons; type TMeses = (meEnero = 1, meFebrero = 2, meMarzo = 3, meAbril = 4, meMayo = 5, meJunio = 6, meJulio = 7, meAgosto = 8, meSeptiembre = 9, meOctubre = 10, meNoviembre = 11, meDiciembre = 12); TAnual = Set of TMeses; TDialogoMeses = class(TForm) grbMeses: TGroupBox; chbEnero: TCheckBox; chbFebrero: TCheckBox; chbMarzo: TCheckBox; chbJulio: TCheckBox; chbMayo: TCheckBox; chbJunio: TCheckBox; chbAgosto: TCheckBox; chbSeptiembre: TCheckBox; chbOctubre: TCheckBox; chbAbril: TCheckBox; chbNoviembre: TCheckBox; chbDiciembre: TCheckBox; BitBtn1: TBitBtn; BitBtn2: TBitBtn; procedure FormCreate(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); private FSeleccion: TAnual; procedure SetSeleccion(const Value: TAnual); { Private declarations } public { Public declarations } property Seleccion: TAnual read FSeleccion write SetSeleccion; end; var DialogoMeses: TDialogoMeses; implementation {$R *.dfm} procedure TDialogoMeses.FormClose(Sender: TObject; var Action: TCloseAction); var i: Integer; begin for i := 0 to grbMeses.ControlCount - 1 do if TCheckBox(grbMeses.Controls[i]).Checked then Include(FSeleccion, TMeses(grbMeses.Controls[i].Tag)); end; procedure TDialogoMeses.FormCreate(Sender: TObject); begin Seleccion:= []; end; procedure TDialogoMeses.SetSeleccion(const Value: TAnual); var i: Integer; begin FSeleccion := Value; for i := 0 to grbMeses.ControlCount - 1 do if TMeses(grbMeses.Controls[i].Tag) in Seleccion then TCheckBox(grbMeses.Controls[i]).Checked:= True; end; end.
Como podéis ver es muy sencillo y claro. Creamos el conjunto con un valor vacio y tan solo si es asignado a través de la propiedad Selección, reponemos la elección previa que hizo el usuario.
Y esta es la imagen del formulario principal, donde llamaremos a la ventana modal y recogeremos los valores elegidos:
unit UMain; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TMain = class(TForm) btnsiguiente: TButton; lbxSeleccion: TListBox; labPaso1: TLabel; Label1: TLabel; btnCerrar: TButton; procedure btnsiguienteClick(Sender: TObject); procedure btnCerrarClick(Sender: TObject); private { Private declarations } public { Public declarations } end; var Main: TMain; implementation uses UMeses; {$R *.dfm} procedure TMain.btnCerrarClick(Sender: TObject); begin Close; end; procedure TMain.btnsiguienteClick(Sender: TObject); const MESES: array [TMeses] of String = ('Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'); var i: Integer; fMes: TMeses; begin DialogoMeses:= TDialogoMeses.Create(nil); try //vamos a restaurar la seleccion previa en la ventana modal //y solo lo hacemos si existen items de una seleccion anterior if lbxSeleccion.Items.Count > 0 then for i := 0 to lbxSeleccion.Items.Count - 1 do DialogoMeses.Seleccion:= DialogoMeses.Seleccion + [TMeses(Integer(lbxSeleccion.Items.Objects[i]))]; //lanzamos la ventana modal if DialogoMeses.ShowModal = mrOk then begin lbxSeleccion.Clear; for fMes in DialogoMeses.Seleccion do begin lbxSeleccion.AddItem(MESES[fMes], TObject(fMes)); end; end; finally FreeAndNil(DialogoMeses); end; end; end.
Tambien hubiera podido ser correcto, en el interior de un bucle mas general que recorriera los valores que puede tomar la variable fMes:
If (fMes in DialogoMeses.Seleccion) then begin lbxSeleccion.AddItem(MESES[fMes], TObject(fMes));
Hay un detalle que os puede ser de interes y es el uso de la constante MESES, que permite recuperar el valor literal. Es muy habitual hacer uso de un array de dimensión el Tipo ordinal (en este caso TMeses).
const MESES: array [TMeses] of String
Esta es una imagen del ejemplo en tiempo de ejecución.
Nada mas por hoy. La idea era compartir con vosotros la necesidad de que el código sea lo mas intuitivo posible. Y los tipos personalizados, tanto las enumeraciones, los subrangos o los conjuntos, enriquecen nuestro código aportando información adicional, semántica, que permite lo que hemos escrito sea mas fácil de comprender, tanto para nosotros como para otras personas que lo compartan con nosotros. Ese es, un poco el resumen.
No puedo evitar mandarte mi mas sincera admiración por tu sitio web.
Es un punto de referencia ineludible para mi y nunca dejas de sorprenderme
Un saludo
Me gustaMe gusta
Saludos también para ti, Javier.
Me alegra recibir tu comentario. 🙂
Gracias.
Salvador.
Me gustaMe gusta
Muy buen aporte Salvador, gracias por la información.
Me gustaMe gusta