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:

conjuntos_meses

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:

conjuntos_main

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.

Descargar

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.

conjuntos_ejecucion

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.

4 comentarios sobre “El mundo en un conjunto…

  1. 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

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s