/* */

30 de septiembre de 2007

Problema 6.2

.
Enunciado
Una municipalidad necesita procesar las infracciones de tránsito que levantaron sus agentes. Para esto dispone de los siguientes archivos.

MOVIMIENTOS.dat

  • patente - string[6]
  • fecha - 13 dígitos
  • id_infraccion - 2 dígitos
  • observaciones - string[200]
Corresponde a las infracciones levantadas. El archivo tiene a lo sumo 10000 registros y está desordenado. El campo fecha es un entero de 13 o 14 dígitos que indica la cantidad de milisegundos transcurridos entre el momento en que se registró la infracción y el 1 de enero de 1970.

INFRACCIONES.dat
  • id_infraccion - 2 dígitos
  • descripcion - string[50]
  • importe - real
  • dias_promo - integer
  • dto_promo - integer
Define las posibles infracciones tipificadas por la municipalidad. Por ejemplo: "Pasar un Semasforo en Rojo" o "Invadir Senda Peatonal". Tiene a lo sumo 100 registros y está desordenado.

Cada infracción tiene un valor (importe) que corresponde a la multa que debe pagar el infractor. Sin embargo la municipalidad tiene un plan de promoción para incentivar el pronto pago de las mismas. Por este motivo cada infracción también tiene una cantidad de días de promoción y un descuento a aplicar. Es decir: si el infractor se presenta a pagar la infracción antes de los dias_promo días de cometida la infracción entonces se le aplicará un descuento del dto_promo porciento sobre el importe.

Se pide desarrollar un programa interactivo que permita ingresar una patente muestre el estado de deuda relacionado con la patente ingresada según el siguiente formato:



Si la patente no registra infracciones entonces debe mostrar un mensaje indicando esto. Así hasta que el operador ingresa una patente 'ZZZZZZ' para finalizar.

Nota: para manejo de fechas se dispone de la unit uFechas cuya interface se expone a continuación.

uFechas.pas (interface)
   1:
2:unit uFechas;
3:
4:interface
5: // retorna la fecha del sistema (fecha actual)
6: // en formato timestamp (milisegundos)
7: function today(): comp;
8:
9: // dado un timestamp retorna el dia del mes
10: function getDay(f: comp): integer;
11:
12: // dado un timestamp retorna el mes
13: // 1=>enero, 2=>febrero,...
14: function getMonth(f: comp): integer;
15:
16: // dado un timestamp retorna el anio
17: function getYear(f: comp): integer;
18:
19: // dados dos timestamp retorna la cantidad
20: // de dias de diferencia entre ambos
21: function daysBetween(f1,f2: comp): integer;
22:
23: // dado un timestamp retorna un string
24: // con la representacion de la fecha contenida
25: // en dicho timestamp, por ejemplo: '25/9/2007'
26: function dateToString(f:comp): string[10];
27:


Análisis

Este es un problema de consultas sobre archivos. Si el archivo de movimientos estuviese ordenado por patente entonces el problema consistiría en posicionarnos en la primer infracción de la patente por la cual están consultando y luego recorrer con corte de control para mostrar las infracciones cometidas y totalizar los importes. Por otro lado, por cada infracción solo tenemos el id_infraccion por lo tanto tendremos que ir consultando el archivo de infracciones para obtener la descripción, el importe y los días y descuento de la promoción.

Para agilizar el acceso y las búsquedas sobre el archivo de infracciones lo subiremos a un array en memoria. Sabemos que a lo sumo existen 100 tipos de infracciones diferentes porque el campo id_infraccion tiene 2 dígitos. Por lo tanto con un array de 100 posiciones estará bien.

El verdadero problema está dado en poder acceder y recorrer con corte de contról el archivo de movimientos. La mejor solución para esto será indexar el archivo. Esto es: un array en el que guardaremos (por cada registro) la patente, la fecha y la posición relativa del registro dentro del archivo. El array lo cargaremos ordenado por patente y luego por fecha, por lo tanto con este índice podremos ver al archivo de movimientos como si estuviese ordenado por dichos campos.

Veamos la sección type.
   1:
2:const
3: // cantidad maxima de registros en los archivos
4: MAX_MOVIMIENTOS = 10000;
5: MAX_INFRACCIONES = 100;
6:
7:type
8:
9: // registro del archivo
10: RMovimiento = record
11: patente: string[6];
12: fecha: extended;
13: id_infraccion: byte;
14: observaciones: string[200];
15: end;
16:
17: // tipo de archivo
18: FMovimientos = file of RMovimiento;
19:
20: // registro del archivo
21: RInfraccion = record
22: id_infraccion: byte;
23: descripcion: string[50];
24: importe: real;
25: dias_promo: integer;
26: dto_promo: byte;
27: end;
28:
29: // tipo de archivo
30: FInfracciones = file of RInfraccion;
31:
32: // tipo de array para infracciones
33: AInfracciones = array[1..MAX_INFRACCIONES]
34: of RInfraccion;
35:
36: // registro para el indice
37: RIndice = record
38: patente: string[6];
39: fecha: extended;
40: pos: integer;
41: end;
42:
43: // tipo de array para el indice
44: AIndice = array [1..MAX_MOVIMIENTOS] of RIndice;
45:

Como la fecha está expresada con tanta presición (incluye hasta el milisegundo) podemos asegurar que la combinación patente+fecha es única ya que un mismo auto nunca podría comenter dos infracciones en un mismo momento (salvo que el conductor cruze un semásforo, a toda velocidad, hablando por celular y con exceso de alcohol en sangre, pero en este caso serían cuatro infracciones consecutivas registradas en diferentes milisegundos).

Veamos el programa principal.


En el programa principal subimos el archivo de infracciones al array arrInfr. Este archivo solo lo utilizaremos para cargar su información en el array. Por lo tanto el archivo lo abriremos y lo cerreraremos dentro del procedimiento subirArchInfracciones. En cambio, el archivo de movimientos lo utilizaremos constantemente por lo tanto lo abrimos al inicio del programa y lo cerramos al final.

Antes de continuar con el análisis del programa principal veamos el desarrollo del procedimiento subirArchInfracciones.



Volviendo al programa principal, utilizamos la función indexarMov. Esta función crea el índice para el archivo de movimientos cargando adecuadamente el array arrIdx. Retorna la cantidad de registros que fueron indexados (o lo que es lo mismo: cuantos elementos útiles se cargaron en el array: len).

Con el archivo de infracciones subido y el archivo de movimientos indexado, el programa comienza a interactuar con el usuario. Este ingresa la patente por la cual quiere consultar. La buscamos en el índice con la función buscar que retorna la posición dentro del array en la que se encontró la primer infracción de la patente o un valor negativo si la patente no registra ninguna infracción. En este caso (no hay infracciones) mostramos un mensaje indicando la situación.

Si la patente ingresada tiene infracciones entonces emitimos el listado en el procedimiento emitirInforme cuyo diagrama veremos a continuación.





En emitirInforme nos preocupamos por obtener toda la información que nos piden en el listado de deuda de la patente.

Obtenemos la patente accediendo a arrIdx[posArr].patente. La fecha de hoy (o fecha del sistema) la obtenemos con la función today de la librería uFechas y la convertimos a string para mostrar la primera línea del listado.

Luego inicializamos un acumulador (tot) para sumar los importes que el infractor deberá abonar y una variable i para recorrer el array índice con corte de control.

Dentro del while (recorremos el array mientras sean infracciones de la misma patente) posicionamos el puntero en el archivo de movimientos en la posición arrIdx[i].pos y leemos. Asignamos el id_infraccion en una variable id (para simplificar) y luego obtenemos todos los datos que debemos mostrar en el listado.

La única complejidad está dada en poder calcular el monto que debería abonar el infractor en función de la fecha actual y la fecha de la infracción. Es decir: si los días transcurridos entre la fecha actual y la fecha de la infracción es menor a los días estipulados en la promoción entonces se le aplicará el descuento. Para calcular los días transcurridos utilizamos la función daysBetween de la unit uFechas.


Veamos ahora la función indexarMov que recorre el archivo de movimientos para generar el array índice arrIdx.



La función indexarMov recorre el archivo hasta el eof. Por cada registro del archivo inserta (ordenado) un registro tipo RIndice en el array. El registro regIdx lo carga con la patente y la fecha de la infracción y la posición relativa del registro reg dentro del archivo de movimientos. Recordemos que los registros se numeran desde cero por lo tanto la posición relativa coincidirá con len-1. También vemos el desarrollo del procedimiento insertarOrdenado que inserta el registro regIdx en el array arr manteniendo el orden por patente+fecha.

Notemos que en insertarOrdenado, en el while iteramos mientras i sea menor o igual que len y mientras la función esMenor(arr[i],regIdx) retorne true. Es decir: mientras arr[i] sea menor que regIdx. Como tenemos que comparar dos campos lo más conveniente resulta hacerlo mediante una función. Esta función retorna true si la patente del primer registro es menor (alfabeticamente) que la del segundo o bien si ambas son iguales pero la fecha del primero es menor que la del segundo.









.