Ir al contenido


Foto

Formulas en un dbgrid o stringgrid


  • Por favor identifícate para responder
11 respuestas en este tema

#1 red_dragon

red_dragon

    Newbie

  • Miembros
  • Pip
  • 6 mensajes

Escrito 16 abril 2013 - 04:42

Buenas tardes a todos los intregantes de este foro.

Quisiera hacerles una pregunta, y es como consigo en un dbgrid o stringgrid insertar formulas formulas y que se realicen calculos segun las condiciones que tengan, estas formulas que estan almacenadas en una tabla, una muestra en las imagenes de abajo.

Imagen Enviada

Imagen Enviada

Imagen Enviada

Imagen Enviada

Imagen Enviada

Desde ya gracias por su ayuda.
  • 0

#2 Jose_Augusto

Jose_Augusto

    Member

  • Miembros
  • PipPip
  • 35 mensajes

Escrito 16 abril 2013 - 04:54

Hola red_dragon,

se me ocurre en un dbgrid, ocupes el evento DrawColumnCell o le agregues al componente un procedimiento que al momento que se dibuje, interprete la formula y te ponga el resultado.
  • 0

#3 Fenareth

Fenareth

    Advanced Member

  • Administrador
  • 3.486 mensajes
  • LocationMexico City

Escrito 16 abril 2013 - 04:55

Hola amigo red_dragon, bienvenido a DelphiAccess (y)

Con respecto a tu consulta, yo hace tiempo me tomé con una necesidad similar. Requería leer fórmulas e interpretarlas para obtener equivalencias entre varias unidades de medida, peso, volúmen, etc.

Lo que en ese momento pensé es que no hay manera de que Delphi lea el dato de fórmula desde un campo en una base de datos y la interprete como fórmula y la aplique. Lo que sí se podría hacer era generar un analizador de fórmulas, de tal manera que interpretara la cadena que se le estaba enviando, separara operadores de operandos y aplicara la fórmula para que regresara el resultado.

Como para lo que yo lo necesitaba se antojaba muy complejo, en mi caso, me fue suficiente con generar factores de conversión y únicamente recibir un dato para aplicarle el factor.

Por ejemplo:

Si la fórmula decía:  x * (1/2 / 4), lo que hice fue convertir la fórmula en: x * 2. Almacenaba el 2 como factor de conversión y recibía el dato (el valor de x) como parámetro del usuario. A mí me funcionó de ésta manera pero al parecer las fórmulas que deseas implementar son un poco más complejas así que no se me ocurre otra manera más que como te comentaba al inicio de mi respuesta.

Saludox ! :)
  • 0

#4 red_dragon

red_dragon

    Newbie

  • Miembros
  • Pip
  • 6 mensajes

Escrito 16 abril 2013 - 05:10

Esas imagenes son de un programa, que almacena una sintaxis propia en una tabla y las lee de alli para hacer el calculo segun los datos que tenga cada persona, busque informacion y en sitios en ingles sugerian crear un lenguaje propio pero eso esta chungo, por eso les pedia vuestra ayuda para saber como le hizo el programador de ese programa para que se calculen las formulas que estaban almacenadas en esa base de datos. 
  • 0

#5 Fenareth

Fenareth

    Advanced Member

  • Administrador
  • 3.486 mensajes
  • LocationMexico City

Escrito 16 abril 2013 - 05:19

No claro, generar un nuevo lenguaje es demasiado pedir, pero el generar como te decimos, un analizador pues es la solución que debes seguir.

Por ejemplo si obtienes la cadena: = a + b

Como paso primero tienes que discriminar los espacios en blanco, en Delphi con un simple Trim lo logras.
Después ver que la cadena inicie con un signo de = para poder validar que se trata de una fórmula.
Separar operadores de operandos.
Considerar la prioridad de operadores (/ y * son prioritarios sobre + y -, pero no sobre los signos de agupación (, ), [, ], {, }...)
Sustituir variables por valores reales.
Y devolver un resultado...

Suena complejo ???... No lo es tanto, pero si requiere de análisis y algo de trabajo duro, pero imposible no es, aquí podemos darte un par de ideas una vez que comiences a atacar al enemigo  :D...

Saludox ! :)


  • 0

#6 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 16 abril 2013 - 06:25

Lo primero, lo primero: ANÁLISIS.
¿Que tan variadas y/o complejan podrán, o debieran, ser dichas fórmulas? ¿Se espera demasiada variedad?
¿Cuántos parámetros necesitarían? ¿De donde sacarán los datos? ¿Se tiene pensado contemplar que se indique el campo como parámetro en lugar del valor? Es decir algo como:

OPERAR(ConElCampo) vs OPERAR(5)

¿De donde provienen los datos, de sus propios atributos del registro? ¿Desde otras tablas?

Es posible en cierto modo implementar de mecanismos que no lleven hacia el desarrollo de un lenguaje propiamente. Mi pensamiento, y propuesta, me lleva a esto:

1) Diseñar una Abstracta TOperation que cuente con un método Calculate() virtual y abstracto que devuelva el valor calculado.

2) Luego defino una clase concreta que implemente a su modo el método Calculate() que ésta necesite.

Esto permite que se puedan definir cientos de clases concretas, cada una con su propia implementación del algoritmo adecuado. Así por ejemplo entonces es que puedo tener clases como TLog10, TLog2, TPower...

Ya es cuestión entonces de indicar entonces que tipo aceptar:
A. El valor directamente
B. El nombre del campo

O bien, se podría incluso considerar la posibilidad de que existan métodos Calculate() sobrecargados y cuente con ambas posibilidades, y hasta que puedan devolver sobre diferentes tipos.

En caso de aceptar el nombre del campo se puede prever un diseño basado en algo como esto:



delphi
  1. function TConcreteOperationXXX.Calculate(FieldName: string, DataSet: TDataSet): double;
  2. var value: double;
  3. begin
  4.   value := DataSet.FieldByName(FieldName).AsDouble;
  5.   result := // hacer algo con value...
  6. end;



De este modo la clase sabe encontrar sobre que campo y que dataset buscar el valor con el cual operar.

Este diseño básico puede expandirse. De hecho nada impide que incluso una clase concreta de operación haga uso de otra. Por ejemplo digamos que se tiene pensado hacer una operación del tipo: (Valor1^5) * Log10(Valor2). Suponiendo que ya existieran las clases TPower y TLog10 entonces es posible hacer una clase TPowerLog10 algo como:



delphi
  1. type
  2.   TPowerLog10 = class
  3.   private
  4.     FPower: TPower;
  5.     FLog10: TLog10;
  6.     function GetValue2FromDataSet(FieldByName: string; DataSet: TDataSet): double;
  7.   public
  8.     function Calculate(FieldByName: string; DataSet: TDataSet): double; override;
  9.     constructor Create;
  10.     destructor Destroy;
  11.   end;
  12.  
  13. // ...
  14.  
  15. function TPowerLog10.function Calculate(FieldName: string; DataSet: TDataSet): double;
  16. var Value1, Value2: double;
  17. begin
  18.   Value1 := DataSet.FieldByName(FieldName, DataSet);
  19.   Value2 := GetValue2FromDataSet('ElCampo2', DataSet);
  20.   result := FPower.Calculate(Value1, 5) * FLog10.Calculate(Value2);
  21. end;



Naturalmente hay que hacer un buen análisis... el tema de la cantidad de parámetros es vital.

3) Luego defino una clase TCalculator que actúe de factoría y cree la clase apropiada según la operación leída del campo. Entonces cuenta con un método GetOperation() que le indica la clase de operación y los parámetros.

Si suponemos y definimos que como estándar se "almacene en el DBGrid" un texto con la nomeclatura:

<NameClass>(<ParamList>)
<ParamList> = <OneParam> | <MoreParams>
MoreParams = <Param>, | <MoreParams> | <Param>

Entonces supogamos que almacenamos: ClaseImpuesto(CampoTasa, CampoDescuento). Le pasamos este texto al método GetOperation() de la clase TCalculator y esta descompone al texto en sus partes a fin de detectar la clase a crear, y luego a los parámetros que necesita.

La implementación la dejo a debate. En términos abstractos lo que hará es buscar dentro de una lista de sus clases operaciones concretas la que cumpla con dicho nombre (puede aprovecharse TStrings que cuenta con el método AddObject() y agregar un objeto y asociado a este una "clave" alfanumérica).
Cuando obtiene la clase correspondiente, crea un objeto de la misma. Luego simplemente le envía el mensaje Operate() con los parámetros leídos.

Este diseño no es de lo más sencillo. Como es de esperar conlleva su debido tiempo, y por supuesto análisis.

Es una de las maneras de encararlo. Todo dependerá del análisis y la complejidad. En caso de un escenario más dinámico y complejo (y por sobre todo multiparamétrico) es muy posible que deba aprovecharse de patrones como Composite y Strategy.

Recomiendo una super lectura sobre los patrones aquí expuestos: Factory, Composite y Strategy.

Sea cual fuese la solución a la que llegues, requiere de MUCHO trabajo. De eso estoy seguro y no te salvas. Mantente lo más simple posible, y cuanto más restrictivo seas al momento de definir sobre las posibilidades de operaciones y combinaciones de operaciones y parámetros porque sino tu trabajo será de no acabar.

Las otras posibilidades que pueden barajarse es la de emplear bibliotecas de expresiones regulares... algunas de ellas aceptan cosas al estilo de fórmulas y son capaces de interpretarlas. Pero no esperes que sean tan abiertas y dinámicas como para llegar a entender cosas como:
SI(Campo1 < Campo2, SUMA(Campo1 * Campo3)  - INTERES(RANDOM(100) MOD 15), 0)

Que todo tiene su límite.

Saludos,
  • 0

#7 red_dragon

red_dragon

    Newbie

  • Miembros
  • Pip
  • 6 mensajes

Escrito 17 abril 2013 - 07:36

De hecho la persona que programo si pudo lograr que el programa aceptara formulas un poco complejas como esta de la imagen, los datos lo obtiene de tablas, toda la informacion de hecho esta en tablas una que otra informacion se obtiene del teclado como datos variables.

Imagen Enviada

En la imagen "BUSCAR("TABLA_COM")" saca datos de la tabla "TABLA_COM" segun el trabajador si el valor extraido es 1, la celda tomara valor 50 y sino pues 0.

Ahora yo estaba pensando en hacer un evaluador de expresiones, que me evalue la formula y de la validez o error de formula, ejecutar las formulas matematicas luego de que hayan sido evaluadas y sean correctas no va a ser tan dificil, pero aun no tengo bien definido como hacer en caso de las condiciones (haber si alguien tiene idea de esto).

Por cierto los datos se recogeran de tablas y una que otra de teclado.

Hace poco encontre este componente (http://artsoft.nm.ru...ks.html#formula) se llama ArtFormula, lo probe con el demo que trae me parece bueno pero aun no encuentro como llamar datos de una base de datos y que sean evaluados para el calculo respectivo por ejemplo en formulas que contengan condiciones (como el IF), este componente en un inicio pense que era la solucion hasta que me tropece con esa traba a ver si lo miran y me dan su opinion al respecto, gracias por su interes y por las respuestas a esta inquietud.
  • 0

#8 Jose_Augusto

Jose_Augusto

    Member

  • Miembros
  • PipPip
  • 35 mensajes

Escrito 17 abril 2013 - 08:32

Hola,

no te sirve tener un archivo temporal en excel?, agregas un libro de excel donde puedas meter todo lo de la consulta y lo muestras en pantalla.

visualmente tendrias un stringgrid, pero internamente una hoja de excel, que ya tiene su evaluador de expresiones y me parece que esta muy completa, solo te preocuparias por el manejo de las celdas.

generas la consulta, pasas los valores de los registros a la hoja de excel para que interprete y una vez interpretado pasas valores al stringgrid. si vas a modificar valores, entonces haces lo contrario y al finar guardas en la tabla.

es una solución muy burda, pero realizar un interprete de formulas no es de tres patadas.

Saludos.
  • 0

#9 red_dragon

red_dragon

    Newbie

  • Miembros
  • Pip
  • 6 mensajes

Escrito 17 abril 2013 - 10:16

Hace tiempo trate de hacer eso solo con informacion simple, normal pero la cosa se complica cuando se trabaja con bases de datos y hay calculo de por medio y ademas es poco elegante por eso consulto en el foro a ver si hay otra mejor o mas factible, gracias nuevamente a todos.
  • 0

#10 Sergio

Sergio

    Advanced Member

  • Moderadores
  • PipPipPip
  • 1.092 mensajes
  • LocationMurcia, España

Escrito 17 abril 2013 - 11:11

Yo hago exactamente eso en mis programas!

Uso un componente llamado dws o dwscript o "delphi web script" (https://code.google.com/p/dwscript/), pero en su versión "antigua" (1.3 creo recordar, antes de que fuese orientada a objeto, no la tienen en su web para descargar, si la necesitas te la envío, no se si valdrá para delphis modernos, en D7 va de lujo), porque desde que empece a usarlo, el lenguaje interno que usan ha evolucionando demasiado para mi.

Básicamente dws se usa así: creo un objeto dws, le cargo el script en una propiedad string, le pido que compile, me devuelve errores de compilación si los hay, y si no, lo ejecuto y al terminar, si de nuevo no hay errores, pregunto al objeto dws por el valor de las variables de "salida".

El objeto dws permite añadirle antes de nada una librería de funciones creadas por ti en delphi "normal", de forma que en mi caso tengo una función Si(boolean, string, string) y sus overloads para integers y float, con lo que el código tuyo podría usar la función sin que de error, porque es una función que yo he añadido al objeto dws.

Creando funciones para cada cosa que veas en el código podrías emular el lenguaje original bastante bien.

El problema con las versiones actuales de dws es que, por un lado, necesitas declarar todas las variables antes de usarlas, mientras que en mi versión el primer uso de una variable la inicializa a ese tipo -muy cómodo en scripts simples- y por otro lado, ahora se precisa de más "código de inicialización" para que el script compile -cosas de objetos y esas zarandajas-

En mi versión, algo así compila y ejecuta sin problemas:

x:= sqrt(2.3); x:= x*2;

Y tu luego le preguntas al objeto por el valor final de x.

Como en tu caso veo que las fórmulas no incluyen nada de esto (lo mismo que ocurre en mis aplicaciones) es porque el autor esperaba "añadirle" algo como "Valor:= "+MyFormula de forma que siempre sabía que tenía que rescatar el valor de la variable "Valor" al terminar.

Esta técnica la desarrolle porque el programa delphi era un clon mejorado de otro hecho en clipper, y clipper permitía ejecutar bloques de texto como si fueran código clipper sin mayor problema, por lo que incrustar fórmulas en bases de datos era algo muy normal.

También veo que las funciones hacen uso de busquedas en tablas, eso no tiene problema: defines una funcion BUSCAR() nueva para dws, con sus parámetros de tipo string (la tabla, la condicion, etc., todo como texto), que a su vez realice las queries que precisa para devolver el resultado pedido de nuevo como texto. Lo único es que una funcion dws no sabe de bases de datos, así que precisas de algo más: tendrás que añadir una conexion a base de datos en tu libreria supongo.
  • 0

#11 red_dragon

red_dragon

    Newbie

  • Miembros
  • Pip
  • 6 mensajes

Escrito 17 abril 2013 - 02:36

Estimado, Sergio gracias por tu respuesta, ya baje el componente dwscript actual para la version de Delphi que tengo, sin embargo no lo puedo probar sino hasta la noche y es que no tengo Delphi en el trabajo, sin embargo no se si puedas hacerme el favor de enviarme la version que tu tienes y un source demo si es que tuvieras me seria de mucha utilidad. Apenas llegue a casa me lanzare a bucear en los codigos que hasta en sueños no se alejan de mi mente.
  • 0

#12 Sergio

Sergio

    Advanced Member

  • Moderadores
  • PipPipPip
  • 1.092 mensajes
  • LocationMurcia, España

Escrito 18 abril 2013 - 02:18

Aqui te envio la V1.3 como adjunto, contiene ejemplos y ayuda, creo que trae componentes para d4 y d5, aunque yo uso el de D5 en D7, supongo que instalaran en otras versiones.

En tu código pondrias en los uses dwsComp y dwsUtilsLib, que es una libreria de funciones adicionales (las que trae el lenguaje por defecto son las mínimas).

Ejemplo de uso:



delphi
  1.   dws:= TDelphiWebScript.create(Nil);
  2.   //Creo la libreria de funciones adicionales y la "conecto" a dws:
  3.   dwsUtil:= TdwsUtilsLib.create(Nil);
  4.   dwsUtil.script:= dws;
  5.   // Esta es mi proipia libreria de funciones adicionales:
  6.   dwsHCLib:= TdwsHCLib.create(Nil);
  7.   dwsHCLib.script:= dws;
  8.  
  9.   //Puedes "inyectar" variables en dws antes de ejecutar código y usarlas en tu
  10.   //codigo:
  11.   dws.SetGlobal('MyInteger', 0);
  12.   dws.SetGlobal('MyFloat', VarAsType(MyFloatEnDelphi, varDouble));
  13.  
  14.   //Puedes enviar un TDataBase como integer pasando el puntero a tu objeto:
  15.   dws.SetGlobal('MyDB', integer(Addr(MyDataBase)));
  16.   //Dentro de tu funcion en dws lo rescatas asi:
  17.   //  var MyDB: ^TDataBase;
  18.   //  MyDB:= Ptr(round(args[0].Eval)); //aqui leo de un argumento de la funcion, en tu caso seria algo diferente
  19.  
  20.   //Cargas el código a ejecutar:
  21.   dws.Text:= Codigo;
  22.   //Ejecutas el código:
  23.   if EjecutaDWS(dws) then begin
  24.     //Recupero las variables que necesite:
  25.     MyVariableDelphi:=  dws.GetGlobal('MyVariableDws').FValue;
  26.   end;
  27.   //Libero objeto:
  28.   dws.free;



Y esta es la función que encapsula todo lo de ejecutar el script y avisar de errores:



delphi
  1. function EjecutaDWS(dws: TDelphiWebScript): boolean;
  2. var i: integer;
  3.     error: string;
  4. begin
  5.   result:= true;
  6.   if debug then ShowSource('Codigo fuente creado (debug)', dws.Text);
  7.   //Compilo el script...
  8.   try dws.Compile;
  9.   except
  10.     on Exception do dws.blocking:= true;
  11.   end;
  12.   //Errores de compilacion?
  13.   if dws.Msgs.count > 0 then begin
  14.     error:= 'Error en formula'+#10;
  15.     for i:= 0 to dws.Msgs.count-1 do
  16.       error:= error+#10+dws.Msgs[i].AsInfo;
  17.     ShowSource('Errores de compilacion en las formulas.',dws.Text, error);
  18.     result:= false;
  19.     exit;
  20.   end;
  21.   //Ejecuto el script...
  22.   dws.timeout:= 120; //120 segundos maximo...
  23.   try dws.Execute;
  24.   except on Exception do dws.timeout:= 120; //Por decir algo...
  25.   end;
  26.   //Error al ejecutar? Voy a mostrarlo en pantalla...
  27.   if dws.Msgs.count > 0 then begin
  28.     error:= 'Error al calcular una formula'+#10;
  29.     for i:= 0 to dws.Msgs.count-1 do
  30.       error:= error+#10+dws.Msgs[i].AsInfo;
  31.     ShowSource('Error al calcular una formula.',dws.Text,error);
  32.     result:= false;
  33.     exit;
  34.   end;
  35.   if debug then begin
  36.     error:= '';
  37.     for i:= 0 to dws.GlobalsCount-1 do error:= error+#10+dws.Globals[i].Name;
  38.     ShowMessageHC('Variables globales '+IntToStr(dws.GlobalsCount)+#10+error);
  39.   end;
  40. end;



No se si te faltara algo, para crerar tu libreria mira el dwsUtilsLib.pas, y ya me cuentas.

Archivos adjuntos


  • 0




IP.Board spam blocked by CleanTalk.