Un pequeño resumen, por favor…

junio 28, 2009 en Delphi, Enlace interesante, Entrada Diario, Noticias, Noticias Delphi

Ayer haciendo pruebas, dejé en la barra lateral, embebido, uno de los iframes de google, del servicio egoogle.books y que contenía una vista parcial de un libro que hablaba de sql server. Concretamente ,de Sql Server 2000 (“SQL Server 2000 Fast Answers for dbas and developers” de Joseph Sack). Y por error, olvide eliminar el frame, por lo que hace un rato he visto con asombro que todavía estaba. :-)

El caso es que estaba viendo que tal quedaba y comprobando si existían libros que hablaran de Delphi o de cualquier otro tema que pudiera ser de interés, y facilitar un acceso desde una entrada, o bien desde la barra lateral. Y sí, existen algunos (bastantes mas recortados que esté que finalmente he dejado y que habla del servidor).

No está de más saberlo, por si en algún momento nos pueden sacar de un apuro.  Pero dado que algunos estan muy recortados y pertenecen a versiones anteriores de Delphi, y tampoco hay demasiados, he desistido en la idea de ir subiendolos al blog (tal y como lo he hecho en la barra lateral). De todas formas, siempre podeis hacer una busqueda dentro de ese servicio para ver si existe alguna vista completa de libros que puedan ayudaros en un determinado momento.

http://books.google.com/books

Creo que hoy se puede percibir un cierto pesimismo en mi estado de ánimo, por un par de comentarios que he dejado en Twibes. No iba a escribir esta entrada pero al final aquí estoy, como muchas noches.

Esta semana que ha acabado ha tenido algunos apuntes interesantes. Mi amigos, con los que comparto correo que habitualmente no tiene nada que ver con la programación, suelen repetir esa frase. Cuando alguien inserta un comentario de esos pesones, que da pereza leer, alguien comenta por lo bajini:

- A ver… ¿un resumen?.

Esta estrategia obliga al que entrega el comentario  tostón a resumirlo en cuatro o cinco palabras, para ver si a alguien le puede interesar… :-)

¡Nos hemos hecho comodos hasta para eso!

Estas son algunas de las referencias que hacía.

Pero quedaron en el tintero, hacer referencia al video de Marco Cantú, dentro de las sesiones que organizó en Delphi Live y que creo que tenían un título similar a “La cara divertida de Delphi”. La sesión transcurre en un ambiente relajado y desenfadado. Y los asistentes, pudieron conocer aspectos de unicode, dentro de un ejemplo que va incrementando en complejidad y que se vale del servicio de igoogle para personalizar títulos y etiquetas. Está en inglés.

http://blog.marcocantu.com/blog/video_fun_side_delphi.html

Tambien os recomiendo que le sigais la pista a los comentarios que va aportando Zarko Gajic. Mas que noticias, lo que aporta son lo que aqui podemos llamar “trucos” con la diferencia de que no se limita a expresar como se hace tal o cual cosa sino que dedica las entradas que hagan falta para explicarlo.

Respecto a los enlaces de velneo que se puede ver en mi pagina, dentro del seguimiento de los  Twitter diarios, ya sabeis que salió la primera “release” de la V7 y que la compañía está volcada en darle vida a esa primera versión. Todavía es muy pronto para tener una visión real y justa de lo que puede aportar esa filosofía.  Fijaos que no digo producto. Los productos se van mejorando y se van haciendo mejores con el tiempo, sobretodo  si la filosofía sobre la que subyacen es buena y solida. Una empresa que no sabe lo que quiere es como un barco sin timon y sin rumbo, a la deriva de las modas y los intereses económicos. Una sola vez he hablado con Alfonso Gutierrez, CEO de Velneo, personalmente, y reconozco que se tiene muy claro cual es la filosofia de Velneo y que esperan de sus productos. Tuve una muy buena impresión durante el transcurso de esa conversación distendida y muy cordial.

En fin, creo que es justo reconocerlo, con independencia de que la V7 sea realmente un punto de partida y no un punto de llegada, que puede ser lo que algunos programadores pueden pensar. Saben qué quieren: Esa es la impresión que siempre he tenido de ellos, como empresa.

Y bueno, ya para acabar, comentar que estoy haciendo un esfuerzo por acercar la comunidad de Delphi de habla inglesa a la nuestra, y no se que tal se está percibiendo esto desde las personas que leen el blog. Me ayudaría saber vuestra opinión. La cantidad de recursos que disponen es muy grande  pero existe el “inconveniente” de que están en inglés y muchos compañeros, como yo mismo, tienen dificultades para seguirlos. Yo me puse las pilas y estoy en ello, mejorando mi inglés, con la única intención de poder ir incorporando estos recursos en el blog. Es uno de los motivos por los que dispongo de menos tiempo para escribir.

Vereis… esta tarde estaba desanimado y lo estaba porque creo que nos faltan recursos en castellano y parece que a todo el mundo le da igual. Veo mucha apatía y eso me desanima en ocasiones. Yo no quiero que vengan a Madrid a venderme un producto… no… lo que quiero es poder sacarle el 200 %. ¿Cuántas publicaciones escritas se han editado de las nuevas versiones (libros, revistas, etc…)? ¿Cuantos nuevos recursos se van sumando y cuantos estan desapareciendo espantados hacia el mundo de punto net de la mano de Visual Studio y similares?. ¿Por qué cada vez los foros y web hispanos son mas escasos mientras crecen exponencialmente las publicaciones de microsoft en castellano de sus productos, de sus MVPs.? etc. etc.

Seguro que mañana veo las cosas de otra forma.

El mundo en un conjunto…

junio 17, 2009 en Artículos, ¿Sabías que...?, ¿Sabías que...? [Delphi], Delphi, Entrada Diario, Sintaxis

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. :-D

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.

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.

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.

Lo mas destacado… :-)

junio 14, 2009 en Advertencia, ¿Sabías que...?, ¿Sabías que...? [Delphi], Consejo, Delphi, Enlace interesante, Entrada Diario

Suelo reservar la tarde del domingo, un rato al menos, para hacer un repaso mental de lo que me ha parecido mas interesante a nivel de enlaces durante toda la semana. Quizás, antes -hablo de bastantes meses atrás- dedicaba mas tiempo a buscar paginas nuevas,  o entradas nuevas, si fuera el caso de que fueran conocidas y ya las hubiera incluido en mis enlaces, pero ahora, desde que empecé a incorporar los breves mensajes de Twitter (o Twibes) la situación cambió, y en lugar de mi busqueda tradicional, me he concentrado en seleccionar los comentarios que tuvieran mas interés.

El domingo anterior ya lo hice. Y si os dais cuenta, de no hacerlo así, muchos de esos comentarios pueden llegar a perderse ya que quedaran sepultados por los nuevos mensajes y por desgracia, algo como “voy a pasear al perro” en respuesta de “que es lo que estas haciendo” o “voy a trabajar duramente”, será inevitablemente el ejecutor de que no acaben siendo visibles y se oculten por los siglos de los siglos. Eso si algun dia NO deja de existir ese invento de las url cortas y nos dejan sin una referencia real a la que apuntar (ni forma de acceder) :-)

Huele que hay algo en esta nueva concepción de internet que no me acaba de gustar del todo. Recuerdo cuando en los foros tradicionales, donde recibiamos un correo en respuesta de la pregunta de un compañero, el moderador -yo en las ocasiones en las que lo he sido- era capaz de “recriminar” que un compañero X se fuera por los cerros de ubeda y nos contara su vida. Y eso era así porque los foros, en aquellos momentos, tenían un cariz mas “profesional” o al menos se entendía así. Era otro momento, y entiendase el recriminar, sin connotación negativa puesto que el principal objetivo de aquellos foros era basicamente apoyar al programador en sus tareas cotidianas.

Sin embargo, los años y la necesidad de marcar nuevas directrices y de llamar de otra forma a lo que ya existía, nos han hecho necesitar virtualmente conocer algo mas de los interlocutores y así, podemos llegar a saber que nuestro autor favorito un día se rompió el brazo o detalles mas escatológicos sobre su vida privada (dicho esto entre comillas). :-) Estamos casi obligados a tener un perfil repartido entre el facebock, twitter, los spaces de microsoft, blogger, wordpress, tuenti, etc… para existir en el mundo virtual.

Ya se que me repito. Pero son cosas que nos afectan. La servidumbre de asimilar estas nuevas ideas dentro de los blogs tienen costes adicionales. Yo por ejemplo, pagué ayer con una hora de sueño, cuando a las 2 de la mañana, acabando la entrada del blog y en el momento de hacerla publica, tras varias horas editandola, subiendo al servidor las imagenes, el codigo, revisando los errores… me di cuenta de que no se podía visualizarla. ¿Y por qué razon? Pues porque uno de los complementos o pluggins de wordpress generaba un error e impedía que se cargara correctamente la pagina. Así que me costó una hora descubrir que lo que realmente fallaba era el pluggin y localizarlo para intentar resolver el problema.

Hace poco, no fue una hora sino una tarde y por otro motivo similar…

Todo no es malo. No. Muchos de los mensajes contienen referencias a entradas que posiblemente nos pasarían inadvertidas, no porque no tuvieran importancia sino porque uno al final visita las paginas que conoce y dificilmente sale de su entorno.

Podriamos llamar a esta sección “And the winner is…”. Esta semana me quedo con la entrada http://alex.ciobanu.org/?p=232 de  Alexandru Ciobanu, en la que abordaba la posibilidad de extender la instancia de TObject y referenciar a una estructura o a otro objeto, que pueda tener un sentido dentro del dominio de nuestra aplicación.

Y me he decantado por esta (habia otra que hacia referencia al futuro de Delphi http://www.theregister.co.uk/2009/06/12/embarcadero_codegear_tools_future/) porque me ha permitido descubrir algunos detalles que desconocía.

* Que el tamaño en bytes de las instancias de TObject, fue modificado en Delphi 2009, pasando a 8 bytes en lugar de los 4 tradicionales. Sobre este punto encontre un artículo que introducía muy bien el tema y lo explicaba, y de paso, me permitía conocer la existencia de un puntero escondido a una nueva instancia de la clase TMonitor (que supuestamente era un tipo clase para sincronizar y facilitar los accesos concurrentes de los hilos de ejecución). O al menos entendí algo similar.

Así que una entrada me llevó a la otra: http://blogs.teamb.com/craigstuntz/2009/03/25/38138/ (Craig Stuntz) donde la información era mucho mas detallada sobre este punto, que habia levantado preguntas en algunos foros sobre el paso de 4 bytes a 8. (http://stackoverflow.com/questions/679022/what-data-does-a-tobject-contain)

Y tambien accidentalmente descubrí al hilo de todas estas investigaciones de sabueso, algunas entradas que comentaban aspectos de la VMT (la tabla de métodos virtuales)

Un del año 2000:  http://oreilly.com/catalog/delphi/chapter/ch02.html

y otra mas actual, que contiene unas imagenes muy directas para comprender que es lo que realmente está sucediendo al instaciarse TObject, entre bastidores.

http://pages.cs.wisc.edu/~rkennedy/vmt

Nada más.

Que tengais una buena semana.

:-)

Experimentos, reflexiones y otros artefactos (y V)

junio 14, 2009 en Artículos, Código, Consejo, Delphi, Entrada Diario, Sintaxis

He preferido que esta quinta entrega cerrara el título “Experimentos, reflexiones y otros artefactos”,  y despedir estos mini artículos o mini serie, si es que se pueden llamar así, con un ejemplo un tanto más avanzado, pero en la linea de los anteriores. Así que me habeis tenido aperreado toda la semana, dándole vueltas a la cabeza sobre cómo iba a finalizar estas entradas. :-)

No. No creais que es sencillo elegir el “experimento” ya que debe cumplir a priori algunas condiciones en cuanto a la extensión, a su complejidad, al uso de componentes que puedan ser compatibles en varias versiones y casi siempre en cuanto a que sea verdaderamente didáctico. Por lo que no vale cualquier idea que te venga a la cabeza, sino que tienes que pelearte con ella y ver si realmente te vale. Y os confieso cuando uno llega a casa tras la jornada diaria quedan pocas ganas de pelearse con nada (os pasará tambien a vosotros casi con seguridad).  :-)

En fin… Yo creo que sí que vale la pena el esfuerzo, y creo muchos de vosotros lo agradecereis.

¿Teneis ganas de trabajar un rato?

Venga, vamos allá.

Este era el ejemplo que se me ocurrió:  Imaginaba que pudieramos tener la necesidad de calcular el modo más óptimo de recorrer una cantidad de puntos indefinido, eso sí, recorriendolos todos, desde un origen cualquiera hasta un destino cualquiera. Esa era la primera idea que me me vino a la mente. La idea además era vistosa, porque podíamos dibujar los puntos y las lineas sobre el formulario, lo cual no era demasiado difícil. Además, nos permitía seguir abordando el uso de clases muy básicas como las listas de punteros (TList), que muchos compañeros que dan sus primeros pasos pudieran no estar demasiado familiarizados.

El problema es que tras esa idea, existe una complejidad inherente que puede ser tan grande como uno quiera, por lo que decidí intentar abordarlo de una forma sencilla, aun a pesar de que pueda no ser optima. Y el criterio que se sigue para ordenar el camino que nos conduce desde el nodo (rojo) hasta el nodo (azul) sigue una pauta que se rige en calcular la menor distancia con respecto al nodo anterior, una vez ordenado el vector.

De hecho, si os fijais he puesto lineas mas arriba “¿Teneis ganas de trabajar un rato?” y lo he hecho porque os dejo para vosotros si quereis adaptarlo para que no considere todos los nodos sino el camino mínimo (fijado previamente una numero de puntos por los que haya que pasar)  y no considere en la ruta todos, sino esa cantidad.

Al crearse el formulario, recrea una cantidad de nodos (he puesto  10 por poner una cifra) en puntos aleaotorios de un recuadro del formulario. El punto rojo es la salida (es otro nodo pero no es del vector). El punto azul el destino(tambien otro nodo que no existe en el vector). Ambos puntos, se enlazan a cada uno de los nodos del vector simplemente para que se pudiera ver una idea que voy a comentar posteriormente (*).

Esta es un imagen del formulario:

Asi que, como comentaba en lineas anteriores, vamos a recorrer el vector de nodos y reordenarlo sucesivamente hasta que se cumpla la precondición, que es que la ruta quede ordenada en función de la distacia al nodo que nos sirve de pivote, que va recorriendo la ruta que parte desde el punto de origen. Iniciamos en el punto de salida y buscamos el nodo mas cercano.  Luego el más cercano a este. Y asi sucesivamente hasta llegar al punto de destino. En la imagen que figura tras el código se puede apreciar bastante bien.

Vamos a ver el módulo que hemos escrito para expresar esta idea:

unit UNodo;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, Math;

type
   TNodo = class;
   TLinea = class;

   TOrdenacion = procedure(Sender: TObject; ANodo:TNodo) of Object;

   TMalla = Class(TObject)
   private
    FPivote: TNodo;
    FLineas: TList;
    FNodos: TList;
    FZona: TForm;
    FSalida: TNodo;
    FDestino: TNodo;
    procedure SetSalida(const Value: TNodo);
    function GetLineas(Indice: Integer): TLinea;
    function GetNodos(Indice: Integer): TNodo;
    procedure InternalOrdenaNodos(Sender: TObject; ANodo: TNodo);
    procedure VaciarLineas;
    procedure VaciarNodos;
    procedure DibujaRuta;
    function CalculaMetricaRuta: Double;
   protected
    procedure DoOrdenaNodos(ANodo: TNodo; AOrdenacion: TOrdenacion); virtual;
    procedure DoDibujarMalla; virtual;
    procedure OrdenarNodos(ANodo: TNodo; ADestino: TNodo);overload; virtual;
   public
    FCambios: TStrings;
    Constructor Create(AForm: TForm); virtual;
    Destructor Destroy; override;
    procedure InicializarMalla;
    procedure OrdenarNodos; overload;
    Function CountLineas: Integer;
    Function CountNodos: Integer;
    procedure ActualizarMalla;
    function AddNodo: Integer;
    function AddLinea: Integer;
    function GenerarRuta: Double;
    property Salida : TNodo read FSalida;
    property Destino: TNodo read FDestino;
    property Lineas[Indice: Integer]: TLinea read GetLineas;
    property Nodos[Indice: Integer]: TNodo read GetNodos;

   End;

   TLinea = Class(TObject)
   private
    FMalla: TMalla;
    FAlfa: TNodo;
    FOmega: TNodo;
    FDistancia: Double;
    FIndice: Integer;
    procedure SetAlfa(const Value: TNodo);
    procedure SetOmega(const Value: TNodo);
    procedure SetIndice(const Value: Integer);
   protected
   public
    function GetDistancia: Double;
    Constructor Create(AMalla: TMalla); virtual;
    Destructor Destroy; override;
    procedure Enlaza(AAlfa,AOmega: TNodo); overload;
    procedure DibujarLinea;
    property Alfa: TNodo read FAlfa write SetAlfa;
    property Omega: TNodo read FOmega write SetOmega;
    property Indice: Integer  read FIndice write SetIndice;
   End;

  TNodo = Class(TObject)
  private
    FMalla: TMalla;
    FShape: TShape;
    FPoint: TPoint;
    FPeso: Integer;
    FTag: Integer;
    function GetX: Integer;
    procedure SetX(const Value: Integer);
    function GetY: Integer;
    procedure SetY(const Value: Integer);
    procedure SetPeso(const Value: Integer);
    procedure SetTag(const Value: Integer);
   protected
   public
    function GetDistancia: Double;
    function GetIndice: Integer;
    function AddLinea(ANodoTarget: TNodo): Integer;
    Constructor Create(AMalla: TMalla); virtual;
    Destructor Destroy; override;
    procedure DibujarNodo;
    property X: Integer read GetX write SetX;
    property Y: Integer read GetY write SetY;
    property Peso: Integer read FPeso write SetPeso;
    property Distancia: Double read GetDistancia;
    property Tag: Integer read FTag write SetTag;
  End;

implementation

function CalcularDistanciaNodos(AAlfa, AOmega: TNodo): Double;

        //Necesitamos dos puntos (x1,y1) (x2,y2)
        // [x1,y1] [x2,y2]
        // Formula Raiz de ( ((x2-x1) al cuadrado) + ((y2-y1) al cuadrado) )

      function CalcularDistancia(X1, Y1, X2, Y2: Integer): Double;
      begin
         Result:= SQrt(Power((X2-X1), 2) + Power((Y2-Y1), 2));
      end;
begin
  if (AAlfa = Nil) or (AOmega = Nil) then Result:= 0
  else Result:= CalcularDistancia(AAlfa.X, AAlfa.Y, AOmega.X, AOmega.Y);
end;

{ TMalla }

procedure TMalla.ActualizarMalla;
begin
  DoDibujarMalla;
end;

function TMalla.AddLinea: Integer;
begin
   Result:= FLineas.Add(TLinea.Create(Self));
   TLinea(FLineas[Result]).Indice:= Result;
end;

function TMalla.AddNodo: Integer;
begin
   Result:= FNodos.Add(TNodo.Create(Self));
end;

constructor TMalla.Create(AForm: TForm);
begin
  if AForm = Nil then
    Raise Exception.Create('Atención: La referencia no es valida');
  FZona:= AForm;

  FSalida:= TNodo.Create(Self);
  FDestino:= TNodo.Create(Self);

  FNodos:= TList.Create;
  FLineas:= TList.Create;
end;

destructor TMalla.Destroy;
begin
  //limpiamos la lista de nodos
  VaciarNodos;
  FreeAndNil(FNodos);
  //limpiamos la lista de lineas
  VaciarLineas;
  FreeAndNil(FLineas);
  //nos desvinculamos del objeto padre
  //sobre el que nos mostramos
  FZona:= Nil;

  FreeAndNil(FSalida);
  FreeAndNil(FDestino);

  inherited Destroy;
end;

procedure TMalla.DibujaRuta;
var
  i: Integer;
  FNodo: TNodo;
begin
  VaciarLineas;

  fNodo:= Nil;
  for i := 0 to CountNodos - 1 do begin
    if FNodo <> Nil then begin
       FNodo.AddLinea(Nodos[i]);
    end
    else FSalida.AddLinea(Nodos[i]);
    FNodo:= Nodos[i];
  end;
  FNodo.AddLinea(FDestino);
end;

procedure TMalla.DoDibujarMalla;
var
  i: Integer;
begin
  if Assigned(FZona) then begin
     for i := 0 to CountLineas - 1 do Lineas[i].DibujarLinea;
     for i := 0 to FNodos.Count - 1 do Nodos[i].DibujarNodo;
     Salida.DibujarNodo;
     Destino.DibujarNodo;
  end;
end;

procedure TMalla.DoOrdenaNodos(ANodo: TNodo; AOrdenacion: TOrdenacion);
begin
  AOrdenacion(Self, ANodo);
end;

function TMalla.GenerarRuta: Double;
begin
  if (CountNodos = 0) or (FSalida = Nil) or (FDestino = Nil) then
    Raise Exception.Create('Error: No existe suficente información en la ruta a generar');
  OrdenarNodos;
  DibujaRuta;
  Result:= CalculaMetricaRuta;
end;

function TMalla.GetLineas(Indice: Integer): TLinea;
begin
   if (Indice < 0) or (Indice > FLineas.Count-1) then
      Raise Exception.Create('Error en el valor del indice de la matriz');
  Result:= FLineas[Indice];
end;

function TMalla.GetNodos(Indice: Integer): TNodo;
begin
   if (Indice < 0) or (Indice > FNodos.Count-1) then
      Raise Exception.Create('Error en el valor del indice de la matriz');
   Result:= FNodos[Indice];
end;

procedure TMalla.InicializarMalla;
begin
   VaciarLineas;
   VaciarNodos;
   FPivote:= Nil;
end;

procedure TMalla.InternalOrdenaNodos(Sender: TObject; ANodo: TNodo);

   procedure OrdenaQuickSort(L,R: Integer);
   var
     i,j: Integer;
     piv, aux: TNodo;
   begin
     i:= L;
     j:= R;
     piv:= Nodos[(L+R) div 2]; //pivote
     Repeat
       while (Nodos[i].GetDistancia < piv.GetDistancia) do Inc(i);
       while (piv.GetDistancia < Nodos[j].GetDistancia) do Dec(j);
       if i<= j then begin
          Aux:= Nodos[i];
          FNodos.Exchange(i,j);
          FNodos[j]:= Aux;
          Inc(i);
          Dec(j);
       end;
       if L<j then OrdenaQuickSort(L,j);
       if i<R then OrdenaQuickSort(i,R);
     Until i>j;
   end;  

begin
   OrdenaQuickSort(ANodo.GetIndice, CountNodos-1);
end;

procedure TMalla.OrdenarNodos;
var
  i: Integer;
begin
  FPivote:= FSalida;
  for i := 0 to CountNodos - 1 do
     OrdenarNodos(Nodos[i], FDestino);
end;

procedure TMalla.OrdenarNodos(ANodo: TNodo; ADestino: TNodo);
var
  i: Integer;
begin
  //guardamos el indice del vector para que calcule la distancia
  //correctamente ya que el pivote debe avancar
  i:= ANodo.GetIndice;
  DoOrdenaNodos(ANodo, InternalOrdenaNodos);
  //en este punto el nodo puede haber cambiado
  FPivote:= Nodos[i];
end;

procedure TMalla.SetSalida(const Value: TNodo);
begin
  if FSalida = nil then
     FSalida := Value;
end;

procedure TMalla.VaciarLineas;
var
  i: Integer;
begin
  for i := 0 to CountLineas - 1 do begin
    TLinea(FLineas[i]).Free;
    FLineas[i]:= Nil;
  end;
  FLineas.Clear;
end;

procedure TMalla.VaciarNodos;
var
  i: Integer;
begin
  for i := 0 to CountNodos - 1 do begin
    TNodo(FNodos[i]).Free;
    FNodos[i]:= Nil;
  end;
  FNodos.Clear;
end;

function TMalla.CalculaMetricaRuta: Double;
var
  i: Integer;
begin
  Result:= 0;
  for i := 0 to CountLineas - 1 do begin
    Result:= Result + TLinea(FLineas[i]).GetDistancia;
  end;
end;

function TMalla.CountLineas: Integer;
begin
   Result:= FLineas.Count;
end;

function TMalla.CountNodos: Integer;
begin
  Result:= FNodos.Count;
end;

{TNodo}

procedure TNodo.DibujarNodo;
begin
  if not Assigned(Self) then Exit;

  if (Self = FMalla.Salida) or
     (Self = FMalla.FDestino) then begin
    if (Self = FMalla.Salida) then begin
      FShape.Brush.Color:= clRed;
      FShape.Pen.Color:= clRed;
    end
    else begin
      FShape.Brush.Color:= clBlue;
      FShape.Pen.Color:= clBlue;
    end;
  end
  else begin
    FShape.Brush.Color:= clLime;
    FShape.Pen.Color:= clGreen;
    FMalla.FZona.Canvas.Font.Size:= 7;
    FMalla.FZona.Canvas.TextOut(X-12,Y-12,'('+InTToStr(GetIndice)+')')
  end;
end;

function TNodo.AddLinea(ANodoTarget: TNodo): Integer;
var
  fLin: TLinea;
begin
   if ANodoTarget = Nil then
     Raise Exception.Create('Atención: La referencia al nodo no es valida');
   Result:= FMalla.AddLinea;
   if Result >= 0 then
      FMalla.Lineas[Result].Enlaza(Self, ANodoTarget);
end;

constructor TNodo.Create(AMalla: TMalla);
begin
  if AMalla = Nil then
     Raise Exception.Create('Atención: La referencia no es valida');
  FMalla:= AMalla;

  FShape:= TShape.Create(Nil);
  FShape.Parent:= AMalla.FZona;
  FShape.Shape:= stCircle;
  FShape.Height:= 8;
  FShape.Width:= 8;
  if Self = FMalla.Salida then begin
    FShape.Brush.Color:= clRed;
    FShape.Pen.Color:= clRed;
  end
  else begin
    FShape.Brush.Color:= clLime;
    FShape.Pen.Color:= clGreen;
  end;

  FPeso:= 0;

  x:= 0;
  y:= 0;

end;

destructor TNodo.Destroy;
begin
  FMalla:= Nil;
  FreeAndNil(FShape);
  inherited;
end;

function TNodo.GetDistancia: Double;
begin
  Result:= CalcularDistanciaNodos(FMalla.FPivote, Self);
end;

function TNodo.GetIndice: Integer;
var
  i: Integer;
begin
   i:= 0;
   while i < FMalla.CountNodos do begin
     if FMalla.Nodos[i] = Self then begin
       Result:= i;
       Exit;
     end;
     Inc(i);
   end;
   Result:= -1;
end;

function TNodo.GetX: Integer;
begin
  Result:= FPoint.X;
end;

procedure TNodo.SetTag(const Value: Integer);
begin
  FTag := Value;
end;

procedure TNodo.SetPeso(const Value: Integer);
begin
  FPeso := Value;
end;

procedure TNodo.SetX(const Value: Integer);
begin
  FPoint.X := Value;
  FShape.Left:= FPoint.X;
end;

function TNodo.GetY: Integer;
begin
  Result:= FPoint.Y;
end;

procedure TNodo.SetY(const Value: Integer);
begin
  FPoint.Y := Value;
  FShape.Top:= FPoint.Y;
end;

{ TLinea }

constructor TLinea.Create(AMalla: TMalla);
begin
  FMalla:= AMalla;
  FAlfa:= Nil;
  FOmega:= Nil;
end;

destructor TLinea.Destroy;
begin
  FMalla:= Nil;
  inherited;
end;

procedure TLinea.DibujarLinea;
begin
   if Assigned(FMalla) then begin
      FMalla.FZona.Canvas.MoveTo(FAlfa.X, FAlfa.Y);
      FMalla.FZona.Canvas.LineTo(FOmega.X, FOmega.Y);
   end;
end;

function TLinea.GetDistancia: Double;
begin
  Result:= FDistancia;
end;

procedure TLinea.Enlaza(AAlfa, AOmega: TNodo);
begin
   if AAlfa = AOmega then
      Raise Exception.Create('No se pueden enlazar dos nodos iguales');

   if AAlfa <> FAlfa then FAlfa:= AAlfa;
   if AOmega <> FOmega then FOmega:= AOmega;

   FDistancia:= CalcularDistanciaNodos(FAlfa, FOmega);
end;

procedure TLinea.SetAlfa(const Value: TNodo);
begin
  if Value <> FOmega then FAlfa := Value;
  FDistancia:= CalcularDistanciaNodos(FAlfa, FOmega);
end;

procedure TLinea.SetIndice(const Value: Integer);
begin
  FIndice := Value;
end;

procedure TLinea.SetOmega(const Value: TNodo);
begin
  if Value <> FAlfa then FOmega := Value;
  FDistancia:= CalcularDistanciaNodos(FAlfa, FOmega);
end;

end.

Y esta es la imagen, una vez generada la ruta:

Descargar

El asterisco (*) venía a cuento cuando comentaba que podíais modificar facilmente el ejemplo para que calculara la ruta no en función de recorrer todos los nodos sino aquellos que pudieran hacer menor la distancia. Para ello, quizás bastaría modificar el calculo de la distancia, sobrescribiendo la función virtual

DoOrdenaNodos(ANodo: TNodo; AOrdenacion: TOrdenacion); virtual;

Y entregando como parámetro una nueva función que ademas, tuviera en cuenta no solo la distancia de los dos nodos sino también el calculo con respecto al punto de destino. Y una vez obtenido ese recalculo, compararlo en cada uno de los avances del vecto con el punto final. Podría ser una idea.

Como hicimos en la entrada anterior vamos a intentar destacar algunas ideas importantes que os pueden servir de guia:

* Este es un ejemplo y no deja de serlo aunque calcule un resultado. Os comento ésto porque este resultado puede ser correcto para mi, en el dominio de una aplicación concreta y no ser para otros requerimientos, puesto que no existe realmente una optimización al generar la ruta, ni existen caminos que precondicionen optar por una ruta o por  otra. Aqui hemos cogido puntos al azar y nos hemos imaginado la forma de recorrerlos todos.

* Otro punto que parece interesante y por eso lo he incluido así, es el paso de una función como parámetro, que nos permite reutilizar el metodo ante la necesidad de tener en cuenta nuevas condiciones. Para ello, nos basta sobrescribir el método, que es algo realmente sencillo. En este caso concreto, el método tiene un tipo declarado en el interfaz de la unidad UNodos.pas (TOrdenacion = procedure(Sender: TObject; ANodo:TNodo) of Object;)

* Y puede seros de curiosidad, por ejemplo el algoritmo QuickSort, que necesité modificar un tanto para que se reordenara en función de la distancia, como he explicado en lineas anteriores.

* El tratamiento de las listas de punteros (TList) y de las listas de cadenas y como podemos valernos de estas clases para almacenar y manipular referencias a objetos. Es un punto que se reitera continuamente en buena parte del código que podais encontrar, ya que son clases muy básicas.

* Y como siempre, teniendo en cuenta la reutilización. Aunque no lo he comentado, creo que incluso podríamos haber hecho uso del generador de códigos (visto en la entrada anterior), haciendo una ligera modificiación para que nos pudiera calcular distintas combinaciones de nodos y ofrecer a nuestro usuario, la posibilidad de elegir entre varias rutas alternativas.

Con un poco de imaginación quizás no hablariamos de distancia. Es una de las razones que me han hecho añadir la propiedad Peso en el Nodo, aunque luego no le he dado ninguna utilidad. ¿Una combinación de factores, distancia y peso y otros que pudieramos considerar? ¡Hay tantas posibilidades que en nuestro pequeño ejemplo nos hemos intentado quedar con la más didáctica y no enrevesar el código con mayor complejidad que no nos iba a aporatar realmente nada.

Yo creo que podemos dejarlo en este punto. La idea era compartir con vosotros las ventajas de razonar en términos de clases, de intentar en la medida que nos sea posible abstraernos y hacer que nuestro código sea lo mas reutilizable posible. Si bien es cierto que a corto plazo puede ser un inconveniente porque implica un mayor esfuerzo, a largo plazo es rentable, tanto a nivel de reusabilidad como de adaptabilidad a nuevos requerimientos.

Espero que os puedan ayudar estas lineas.


Cerrando la semana

junio 8, 2009 en Enlace interesante, Entrada Diario, Noticias, Noticias Delphi

Si alguno ha seguido los comentarios de twitter que he ido dejando en el grupo de Delphi español, hay algunas ideas que parecen interesantes y que han ido apareciendo en algunos perfiles del grupo principal de Delphi en twibes. Así que estás líneas nos pueden ayudar, si no a comentarlas, al menos a dejar una referencia a las mismas.

Os copio los ultimos comentarios de mi twitter a lo largo de la semana:

  1. La programación y la solidaridad pueden ir de la mano.http://tinyurl.com/pcoaot (KENN HUSSEY) ¿y por que no…? viahttp://twib.es/5NM

  2. Resalta @civeljahim el video que publica Devexpress sobre el uso de sus ExpressBars (Ribbon Control). http://twurl.nl/lk7g46

  3. ¿Todo el mundo es feliz al pulsar F1? ¡NO ME LO CREO!. :-D Un sistema de ayuda debe ser eficaz. Esa es la palabra. Lo demás son cuentos.

  4. ¿Nadie se dio cuenta en su momento de que precisamente es todo lo que no debería ser un sistema de ayuda en linea? viahttp://twib.es/5NM

  5. Siempre se puede dar un paso hacia atrás… El sistema de ayuda a partir de D7 creo que es un ejemplo de eso. Ineficaz. Lioso. Lento…

  6. Coincido con Zarko que es una expresión de creatividad.Ademas tenéis disponible el código fuente.¿Algo así para el aprendizaje de un niño??

  7. ZarkoGajic nos deja en sus paginas algo mas que un juego. Visitad y comentad que os perece: http://tinyurl.com/pjkk6z :-)

  8. Interesante no… imprescindible :-) via http://twib.es/5NM

  9. Cary Jensen nos deja en http://tinyurl.com/n86dwq un zip con las combinaciones de atajos de teclado del ide. Muy interesante descargarlo.

  10. El artículo de A. Bauer entre otras cosas nos habla sobre los contructores de clase. http://tinyurl.com/neagj2 Muy recomendable su lectura

  11. Tenia mucha razon Marco Cantú al recomendar el articulo de Allen Bauer en embarcadero http://tinyurl.com/neagj2 viahttp://twib.es/5NM

  12. Ver lista de correcciones Updates 3 y 4 para D2009:http://edn.embarcadero.com/… http://edn.embarcadero.com/…

De todos los putnos, hay algunos que son muy interesantes. A mi me ha parecido interesante el punto 10 que aborda los constructores de clase, algo que pertenece a las novedades de la ultima versión de Delphi. Muy útil el comentario de los atajos de teclado de Cary Jensen. El punto 7 es para verlo con tranquilidad. Resulta curioso y sobretodo didáctico, el pequeño juego que habíamos podido encontrar tras el link de Zarko Gajic.

Por cierto, las paginas de este programador, no tienen desperdicio. Apunte el link en el blog hacia About.com que es el dominio desde el que nos escribe.

Y ya para finalizar, el link de @civeljahim sobre el video de Devexpress nos augura mas videos sobre el uso de sus componentes. Siempre he pensado, y la verdad es que muchos compañeros han estado de acuerdo en eso, que estos componentes son magnificos, muy buenos. Su punto flaco quizás es que son complicados de entender y el tiempo que necesitamos para darles una usabilidad razonable es bastante mayor que otros componentes mas sencillos. Así que en esa tesitura, esperar nuevos videos puede ser una buena noticia para bastantes desarrolladores que los usan.

Respecto al grupo de Delphi español en Twibes, sigue en marcha. ¿se nota? Como comenté en otra entrada anterior, se pueden recoger enlaces interesantes que de otra forma podrían pasar desapercibidos pues no tienen entidad suficiente quizás para una entrada de blog. Esa es un poco la idea. Que siga creciendo el numero de personas que lo integran y que todos tengan libertad para aportar lo que quieran.

Por hoy si que doy por acabado el día. Mañana otro…  :-)

Experimentos, reflexiones y otros artefactos (IV)

junio 7, 2009 en Advertencia, Artículos, Ayuda, Código, Delphi, Entrada Diario

Hace una tarde calurosa y bastante veraniega. En una tarde así, no se puede estar en casa escribiendo una entrada como esta… :-) El sol cae a plomo a las 6 de la tarde, un día de verano, y parece que todo invita a salir a disfrutar de un rato de deporte. Así que con con vuestro permiso :-) inicio la entrada ahora, hasta donde llegue, y la prosigo esta noche si quedara algo por decir.

¿Por donde empiezo? Ummmm…

Lo habíamos dejado en la parte en la que haciamos un pequeño boceto de las dos clases que ibamos a considerar. La clase TGenerador y la clase TAtributo. Eso nos permitía ver como se podían relacionar y a modo de prueba, imaginábamos un método para permitirnos alterar el orden de los mismos, en caso de ser necesario.

Durante esta semana he podido sacer algunos minutos para adelantarme y darle forma a esas dos ideas. Pero no teneis que perder de vista, por favor, que lo importante no es el código en si mismo, que no es mas que un ejemplo cualquiera y puede contener alguna que otra errata al analizarlo con profundidad, sino el hecho mismo de solicitar de cada uno, un ejercicio de análisis previo y reflexión, intentando razonar en términos de clases, en la medida que nos sea posible.

Una imagen vale mas que mil palabras. Este es el ejemplo que preparé para probar la implementación de ambas clases ya con una funcionalidad mayor.

exercicio2

La imagen muestra en la parte superior, 4 atributos que representan los tipos posibles que hemos adminitido. Un atributo combinable (Atributo 2), capaz de seleccionar multiples valores. Un atributo dependiente (Atributo 3), tambien combinable pero que va a permitirnos descartar alguna combinaciones en funcion de una relación imaginaria. Es decir, el caso de que un tipo de atributo solo pueda combinarse con determinados valores de otro atributo. Decirlo es mas complicado que razonarlo.  En el ejemplo, el color ’18′ solo puede ser combinado con los tipos ‘AA’ y ‘AB’.

Nos queda el atributo dependiente, representado en el Atributo 1, que toma siempre el mismo valor. Y el Atributo rellenable de, que toma valores respecto a otro atributo cualquiera combinable y no rellenable. Es el Atributo 4.

Así que al pulsar el boton “Solución”, nos presenta en los dos componentes TMemo, las combinaciones correctas y las descartadas. En este ejemplo, se aceptan 8 combinaciones y se descartan 16, del total de 24 combinaciones posibles. Las ventanas superiores unicamente existen con el motivo de permitirnos visualizar la información que contiene el generador y los atributos de cara a seguir mejor el ejemplo.

Ahora mismo, el desarrollo del ejemplo se va alejando progresivamente de los bocetos de las dos primeras entradas, representadas en el bucle anidado for, intimamente ligado al interfaz.

Podemos ver la declaración de tipos del módulo que contiene las clases protagonistas:


type

  TEstructura = Class(TObject)
    FIndice: Integer;
    FSiguiente: TObject;
  private
    procedure SetIndice(const Value: Integer);
    procedure SetSiguiente(const Value: TObject);
  published
  public
  constructor Create;
  destructor Destroy; override;
  function Situar(AIndice: Integer): TEstructura;
  property Indice: Integer read FIndice write SetIndice;
  property Siguiente: TObject read FSiguiente write SetSiguiente;
  end;

  TAtributo = class;

  TGenerador = Class(TObject)
  private
    FAtributos: TList;
    FCombinaciones: TStringList;
    function GetAtributos(Indice: Integer): TAtributo;
    function GetDescartado(Indice: Integer): Boolean;
    procedure SetDescartado(Indice: Integer; const Value: Boolean);
    function GetCombinaciones(Indice: Integer): String;
   protected
    procedure DoCombinar; virtual;
    procedure DoLimpiarCombinaciones; virtual;
    procedure AssignCombinaciones; virtual;
  public
    constructor Create;
    destructor Destroy; override;
    function Count: Integer;
    procedure SetUpPosition(AAtributo: TAtributo);
    procedure SetDownPosition(AAtributo: TAtributo);
    function GetPosicion(AAtributo: TAtributo): Integer;
    function GetMultiplicador(AAtributo: TAtributo): Integer;
    function GetTotalCombinaciones: Integer;
    function AddAtributo: TAtributo;
    procedure Combinar; virtual;
    procedure VerificarDependencias;
    property Atributos[Indice: Integer]: TAtributo read GetAtributos;
    property Descartado[Indice: Integer]: Boolean read GetDescartado write SetDescartado;
    property Combinaciones[Indice: Integer]: String read GetCombinaciones;
  end;

  TAtributo = class(TObject)
  private
    FGenerador: TGenerador;
    FListaValores: TStrings;
    FCombinaciones: TStrings;
    FCombinable: Boolean;
    FCaption: String;
    FRellenableDe: TAtributo;
    FDependienteDe: TAtributo;
    procedure SetCombinable(const Value: Boolean);
    procedure SetCaption(const Value: String);
    function GetValues(Indice: Integer): String;
    procedure SetValues(Indice: Integer; const Value: String);
    function GetCombinacion(Indice: Integer): String;
    procedure SetRellenableDe(const Value: TAtributo);
    procedure SetDependienteDe(const Value: TAtributo);
    function GetDependencia(Indice: Integer): TEstructura;
    function GetPosicion: Integer;
    { Private declarations }
  protected
    function DoGetMultiplicador: Integer; virtual;
  public
    { Public declarations }
    procedure LimpiarCombinaciones;
    procedure Combinar;
    function GetMultiplicador: Integer;
    function ValuesCount: Integer;
    procedure UpPosicion;
    procedure DownPosicion;
    constructor Create(AGenerador: TGenerador); virtual;
    destructor Destroy; override;
    function AddValue(const AValue: String): Integer; overload;
    function AddValue(const AValue, ADependencia: String): Integer; overload;
    procedure DeleteValue(const AValor: String);
    procedure VerificarDependencias;
    property Combinable: Boolean read FCombinable write SetCombinable;
    property Posicion: Integer read GetPosicion;
    property Caption: String read FCaption write SetCaption;
    property Values[Indice: Integer]: String read GetValues write SetValues;
    property Dependencia[Indice: Integer]: TEstructura read GetDependencia;
    property Combinacion[Indice: Integer]: String read GetCombinacion;
    property RellenableDe: TAtributo read FRellenableDe write SetRellenableDe;
    property DependienteDe: TAtributo read FDependienteDe write SetDependienteDe;
  end;

Descargar

No nos es posible en una entrada explicar todas las relaciones, por varios motivos razonables, entre los que considero el tiempo y el espacio. Así que lo mejor es que os descargueis el codigo fuente y le deis un vistazo.

Se me ocurren algunas ideas que pueden ser interesantes:

* A pesar de funcionar correctamente, o al menos eso creo, el hecho mismo de declarar las 3 clases en la parte pública del interfaz, hacen que queden disponibles para el uso, métodos que por ejemplo pertenen a TAtributo y que tan solo deberían estar disponibles para la instancia de TGenerador, puesto que va a ser esta quien quede intimamente ligada a ella, dado que conoce como deben ser ejecutados. De hecho, para ser formalmente correcto, el “usuario”, que en este caso es el formulario, debería batallar tanto con TGenerador como con la clase TAtributo, pero parece claro que nunca debería poder ejecutar por ejemplo el metodo VerificarDependencias en el atributo.  Y sin embargo, la disposición de las clases y la publicación de los métodos lo facilitan.

* En ocasiones, al pensar en clases aparecen puntos de vista que inicialmente no considerabamos. De hecho, lo que inicialmente era un bucle for anidado, ha sido sustituido por otro razonamiento. ¿Es posible que la clase Generador le pueda decir a cada atributo: Oye majo, que quiero que te combines? Ese es el motivo de que exista un metodo virtual DoCombinar, que se encarga de que cada atributo ejecute su propia expansión de combinaciones…

A eso me refería al comentar que a veces surgen nuevos puntos de vista, mejores o peores, pero distintos. Se me ocurrío razonar al estilo de la cuenta de la vieja. Y generar una lista de las disposiciones individuales de cada uno de los atributos en las combinaciones.

Este es el razonamiento de la vieja.

Supongamos los siguentes pares (Atributos, valores)

Atributo1  > A, B          Atributo2 >  C, D           Atributo3 > E, F

Podemos seguir un patron para combinarlos (el mismo que seguiría el bucle for)

ACE, ACF, ADE, ADF, BCE, BCF, BDE, BDF

Nos permite individualmente obtener una lista de valores

Atributo1 > A,A,A,A,B,B,B,B     Atributo2 > C,C,D,D,C,C,D,D   Atributo3 > E,F,E,F,E,F,E,F

Por eso aparece por algun lado el multiplicador, que no es mas que las veces que tiene que repetirse el valor en la cadena. En este ejemplo en concreto, el Atributo1 tiene un multiplicador de 4, el Atributo2, un multiplicador de 2, y el atributo3 un multiplicador de 1. Y dichos valores, son factibles de ser implementados mediante una sencilla función.

function TGenerador.GetMultiplicador(AAtributo: TAtributo): Integer;
var
  i,j: Integer;
begin
   if (AAtributo.Combinable) and (not Assigned(AAtributo.RellenableDe)) then
   begin
      Result:= 1;
      i:= FAtributos.IndexOf(AAtributo);
      if  (i < 0) then Result:= -1
      else begin
         for j := i+1 to FAtributos.Count - 1 do
            if (TAtributo(FAtributos[j]).ValuesCount = 0) or
                            (not TAtributo(FAtributos[j]).Combinable)    then
               Result:= Result * 1
            else Result:= Result * TAtributo(FAtributos[j]).ValuesCount;
      end;
   end
   else begin
      Result:= 1;
   end;
end;

* La tercera reflexión que ya se desprende de todo lo comentado, es el tiempo que dedicamos a implementar lo mismo. Visto de la primera forma, como razonabamos en las dos primeras entradas, la ganancia de tiempo podría ser considerable (menor tiempo). Este módulo, el que vemos en la entrada actual, tiene mayor trabajo para hacer exactamente lo mismo y quizás fuera una buena razón para considerar que perdemos el tiempo. Eso que deciamos de: -acaba como sea pero acaba (El jefe dixit) suele ser pan para hoy y hambre para mañana. Así que de cuando en cuando, deberíais dar un buen puñetazo en la mesa :-) y poner una silla trabando la entrada de vuestro despacho de forma que sea imposible que nadie os distraiga. ;-)  Por paradojas de la vida, esa misma persona que hoy os pide que acabeis ya el desarrollo como sea, es la misma que mañana os atará de los pulgares en el mástil mayor, si dentro de 6 meses se complica el desarrollo.

Vale… es una exageración bastante malvada pero yo creo que se me entiende. Y el peor enemigo que puede tener vuestro trabajo es que seais vuestro propio jefe… :-)

* Otra cosa que puede extrañar en el codigo, es que aparezca un tercero en discordia (la clase TEstructura). Es algo bastante habitual y ya  lo referia en alguna entrada anterior al hablar de aquel componente Buscador, que dejabamos atras al hablar de los hilos de ejecución. En aquel caso, el tercero en discorida era una implementación del patron iterador, que permitía recorrer la estructura y avanzar. En este caso concreto, me encontré que para poder trabajar con la dependencias, siendo posible que existiera una relación múltiple entre los valores: un color podia pertenecer a varios tipos, pensé que me era necesaria una forma sencilla de recorrerla. En eso me ayuda la clase TEstructura, que apunta a otra Estructura con el valor de relación. De esa forma, puedo buscar si un color pertenece a un tipo y saber si la combinación debe ser descartada o no.

* Y la ultima reflexión que cierra la entrada: la reutilización de nuestro codigo. Ligarnos a nuestros interfaces nos puede permitir reutilizarlos en forma de herencia y en eso ya nos ayuda el IDE de Delphi, al habilitar nuestros formularios en el repositorio de objetos. Sin embargo, al hacerlo así nos obliga a arrastrar no solo las ideas sino las apariencias y eso no es demasiado bueno en todos los casos.

Creo que os lo puedo demostrar… Dadme unos minutos… Pongamos por caso que dos días despues, vamos a formar una peña con el gabinete de programadores para jugar a las quinielas y poder hacernos ricos y dejar de trabajar. :-) WOWWWWW…  Y como somos previsores recordamos que nuestro programa hacía algo similar… De haberme ligado al interfaz, me hubiera sido imposible en este caso concreto reutilizar esa idea de forma sencilla. Sin embargo, nuestro nuevo planteamiento sí me permite mediante controles distintos y buscando una finalidad que no tiene porque coincidir con la original, reutilizar el código escrito.

Esta es la imagen de nuestro nuevo interfaz de ejemplo:

Dadle un vistazo:

azar1x2

Este es el código que ha permitido ampliar la funcionalidad original (por si teneis curiosidad)

:-)

unit main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ButtonGroup, ExtCtrls, StdCtrls, CheckLst, uAtributos;
const

RestriccionFicticia = 100000;

type
  TForm1 = class(TForm)
    chbPartidos: TCheckListBox;
    chb1: TCheckListBox;
    chbx: TCheckListBox;
    chb2: TCheckListBox;
    Label1: TLabel;
    btnCombinar: TButton;
    chbCombinables: TCheckListBox;
    lbxResultados: TListBox;
    Label2: TLabel;
    labCombinaciones: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure chbPartidosClickCheck(Sender: TObject);
    procedure btnCombinarClick(Sender: TObject);
    procedure chb1ClickCheck(Sender: TObject);
    procedure chbCombinablesClickCheck(Sender: TObject);
  private
    { Private declarations }
    FGenerador: TGenerador;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.btnCombinarClick(Sender: TObject);
var
  i: Integer;
begin
  fGenerador.Combinar;
  lbxResultados.Clear;
  for i:= 0 to fGenerador.GetTotalCombinaciones-1 do begin
    if not fGenerador.Descartado[i] then
      lbxResultados.Items.Add(fGenerador.Combinaciones[i]);
  end;
  labCombinaciones.Caption:= ' Total: ' + IntToStr(lbxResultados.Items.Count);
end;

procedure TForm1.chb1ClickCheck(Sender: TObject);
begin

  case (Sender as TCheckListBox).Tag of
    1: begin
             if (Sender as TCheckListBox).Checked[(Sender as TCheckListBox).ItemIndex] then
                FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].AddValue('1')
             else FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].DeleteValue('1');

          if not FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].Combinable then begin
             chbx.Checked[(Sender as TCheckListBox).ItemIndex]:= False;
             chb2.Checked[(Sender as TCheckListBox).ItemIndex]:= False;
          end;
       end;
    2: begin
          if (Sender as TCheckListBox).Checked[(Sender as TCheckListBox).ItemIndex] then
             FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].AddValue('X')
          else FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].DeleteValue('X');

          if not FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].Combinable then begin
             chb1.Checked[(Sender as TCheckListBox).ItemIndex]:= False;
             chb2.Checked[(Sender as TCheckListBox).ItemIndex]:= False;
          end;
       end;
    3: begin
             if (Sender as TCheckListBox).Checked[(Sender as TCheckListBox).ItemIndex] then
                FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].AddValue('2')
             else FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].DeleteValue('2');

          if not FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].Combinable then begin
             chb1.Checked[(Sender as TCheckListBox).ItemIndex]:= False;
             chbX.Checked[(Sender as TCheckListBox).ItemIndex]:= False;
          end;
       end;
  end;

  if fGenerador.GetTotalCombinaciones > RestriccionFicticia then begin
    (Sender as TCheckListBox).Checked[(Sender as TCheckListBox).ItemIndex]:= False;
    case (Sender as TCheckListBox).Tag of
       1: FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].DeleteValue('1');
       2: FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].DeleteValue('X');
       3: FGenerador.Atributos[(Sender as TCheckListBox).ItemIndex].DeleteValue('2');
    end;

    Raise Exception.Create('No se puede exceder de '+
                           IntToStr(RestriccionFicticia)+' combinaciones...');
  end;

  labCombinaciones.Caption:= 'Total combinaciones ' +
                             IntToStr(fGenerador.GetTotalCombinaciones);

end;

procedure TForm1.chbCombinablesClickCheck(Sender: TObject);
begin
   if not chbCombinables.Checked[chbCombinables.ItemIndex] then begin
     if chb1.Checked[chbCombinables.ItemIndex] then begin
        chb1.Checked[chbCombinables.ItemIndex]:= False;
        FGenerador.Atributos[chbCombinables.ItemIndex].DeleteValue('1');
     end;

     if chbX.Checked[chbCombinables.ItemIndex] then begin
       chbX.Checked[chbCombinables.ItemIndex]:= False;
       FGenerador.Atributos[chbCombinables.ItemIndex].DeleteValue('X');
     end;

     if chb2.Checked[chbCombinables.ItemIndex] then begin
       chb2.Checked[chbCombinables.ItemIndex]:= False;
       FGenerador.Atributos[chbCombinables.ItemIndex].DeleteValue('2');
     end;
   end;

  FGenerador.Atributos[chbCombinables.ItemIndex].Combinable:=
                               chbCombinables.Checked[chbCombinables.ItemIndex];
  labCombinaciones.Caption:= 'Total combinaciones ' +
                              IntToStr(fGenerador.GetTotalCombinaciones);
end;

procedure TForm1.chbPartidosClickCheck(Sender: TObject);
begin
  chbPartidos.Checked[chbPartidos.ItemIndex]:= True;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  i: Integer;
  fAtrib: TAtributo;
begin
  FGenerador:= TGenerador.Create;

  for i := 0 to chbPartidos.Count - 1 do begin
     chbPartidos.Checked[i]:= True;
     fAtrib:= FGenerador.AddAtributo;
     fAtrib.Caption:= IntToStr(i);
     fAtrib.Combinable:= True;
  end;

  for i := 0 to chbCombinables.Count - 1 do begin
     chbCombinables.Checked[i]:= True;
  end;

end;

end.

Descargar

Creo que por hoy ya esta bien.

Hace un día estupendo. Y también hay que sacar tiempo para el deporte. No todo puede ser trabajo, ¿no? Además, no se si alguna vez comenté que suelo correr con frecuencia, y a lo largo de la temporada intento participar en alguna que otra carrera popular, casi siempre en la distancia Media Maratón. Creo que es importante el equilibrio personal y que debemos buscarlo en nuestro interior.

Quedan más ideas en el tintero. Las compartiremos en próximas entradas.