Reserva de places d'Avió ======================== Ens demanen dissenyar un sistema de reserves de les places d'un avió. Les reserves les efectuen un conjunt d'agències de viatges que atenen i gestionen les peticions que li arriben dels clients. Les dades de les reserves de seients de l'avió es guarden en una base de dades única i centralitzada. Cada agència pot accedir a aquesta base de dades de forma concurrent. Quan s'atén la petició d'un client es cerca una plaça lliure, i si se'n troba, es reserva la plaça guardant en la base de dades, el nom del client i la data i hora en que es fa la reserva. Per realitzar el sistema podrem comptar amb el disseny de la classe :py:class:`Avió`: La classe Avio ^^^^^^^^^^^^^^ .. py:module:: avio La classe :py:class:`Avió`, especificada a continuació, està implementada al fitxer :download:`avio.py `. Representa avions amb un cert nombre de files i sis seients per fila (lletres 'A' a 'F'). Cada seient està identificat pel seu número de fila (un enter entre 1 i el nombre de files totals de l'avió) i una lletra entre 'A' i 'F'. La referència d'un seient serà una etiqueta (:py:class:`str`) amb primer el número, i després la lletra. Per exemple, :code:`"3B"`, :code:`"25C"`, ... .. py:class:: Avió(identificador, nFiles=10) En el cas que no existeixi, crea una base de dades al disc amb el nom de l':code:`identificador` (:py:class:`str`) amb l'extensió :code:`.db`. En el cas que aquesta existeixi, no la toca. El paràmetre :code:`nFiles` és el nombre de files de 6 seients que tindrà el nou avió. En el cas que la base de dades existeixi, no es tindrà en compte :code:`nFiles`, atès que la base de dades té el seu nombre de files propi. .. rubric:: Atributs .. py:attribute:: nom Identificador de l'avió (:py:class:`str`) .. py:attribute:: nFiles Nombre de files de 6 seients de l'avió. .. py:method:: lliure(seient): Retorna True si la plaça de l'etiqueta *seient* (:py:class:`str` del tipus :code:`"NL"` i :math:`0 \leq N \lt nFiles` i :math:`'A' \leq L \leq 'F'`) està lliure. Retorna False en cas contrari. Un seient lliure és un seient que no té cap nom assignat. .. py:method:: places() Retorna el nombre total de seients de l'avió. Aquesta classe suporta les **operacions** següents: .. csv-table:: :header: "Operació", "Resultat" :widths: 15, 35 :delim: | ``a[s]``| Retorna un tuple essent el primer element el nom de la persona que ocupa el seient de l'etiqueta s (:py:class:`str`) del tipus :code:`"NL"` i :math:`0 \leq N \lt nFiles` i :math:`'A' \leq L \leq 'F'` ``a[s] = (nom, data)``| Assigna el tuple composat per un *nom* (:py:class:`str`) i una data (:py:class:`datetime.datetime`) al seient d'etiqueta s (:py:class:`str`) del tipus :code:`"NL"` i :math:`0 \leq N \lt nFiles` i :math:`'A' \leq L \leq 'F'` Aquesta classe suporta la funció :py:func:`iter` que retorna un iterador d'etiquetes de seients de l'avió (:code:`"1A", "1B", ..., "1F", "2A", "2B", ...`). Proveu d'utilitzar aquesta classe. Disposeu d'un doctest per a inspirar-vos a :download:`test-avio.txt `. Mireu la implementació (:download:`avio.py `) i observeu que la classe té atributs de classe, i altres mètodes per ús intern. La classe :py:class:`Avió` està connectada a una base de dades que guarda informació sobre els seus seients. La gestió de la base de dades es fa usant el mòdul :py:mod:`mòdul sqlite3 `. Cada operació de lectura, escriptura de l'estat d'un seient es guarda sempre a la base de dades que hi ha al disc per no perdre cap dada en el cas que hagi una fallida d'alimentació elèctrica. El plantejament del disseny serà el següent: #. Dissenyar en el fitxer :file:`agencia.py` un objecte fil anomenat Agència que donat un objecte de classe :py:class:`Avió` *avió*, una llista de noms dels clients procedirà a fer la reserva de seients de l'*avió*. Mantindrà un registre de les reserves que ha pogut satisfer. #. Dissenyar en el fitxer :file:`reserves.py` el programa principal que crearà un objecte de la classe :py:class:`Avió`. Donat un nombre d'agències crearà un fil Agencia per cada agència a considerar, i els engegarà i esperarà la finalització de tots els fils. Farà una estadística per comprovar que no ha hagut cap sobre-reserva comprovant els registres de cada fil i que cap fil ha estat en inanició (comprovant el nombre de reserves que ha fet). Finalment farà un llistat de tots els clients ordenat per data de reserva. Agencia ^^^^^^^ .. py:module:: agencia En el fitxer agència :file:`agencia.py` es dissenyarà la classe :py:class:`Agència` que heretarà de la classe :py:class:`threading.Thread` del mòdul :py:mod:`threading`: .. py:class:: Agència(nomAg, avió, peticions) .. rubric:: Atributs .. py:attribute:: nomAg Nom de l'agència de viatges (:py:class:`str`) .. py:attribute:: avió L'avió on s'han de fer les reserves de places (:py:class:`Avió`) .. py:attribute:: peticions Una llista (:py:class:`list`) de noms (:py:class:`str`) de clients que demanen plaça a l'avió. .. py:attribute:: reserva Diccionari (:py:class:`dict`) amb els noms de cliens com a claus, i la referència del seient (:py:class:`str`) com a valor. .. rubric:: Mètodes .. py:method:: run() Per cada petició de client, es cercarà si hi ha una plaça lliure en l'avió. En el cas que hagi la plaça lliure es reservarà la plaça a nom del client i es donarà d'alta el client en el registre *reserva* guardant també la referència del seient reservat. En el mètode :py:meth:`__init__` heu de tenir en compte cridar a l'init de :py:class:`Thread`. Reserves ^^^^^^^^ .. py:module:: reserves En el fitxer :file:`reserves.py` cal dissenyar la funció :py:func:`registreReserves`: .. py:function:: registreReserves(idAvio='Boeing714', nFiles=30, nAgencies=10) Donat la identificació d'un avió *idAvio* crearà un objecte *avió* de la classe :py:class:`Avió` amb *nFiles* de 6 seients. Engegarà *nAgencies* fils. A cada fil se li passarà com a paràmetres, el nom de l'agència, l'*avió* i, les peticios de clients que ha d'atendre.Un cop han finalitzat els fils, invocarà la funció :py:func:`estadisAgències`. .. py:function:: estadisAgències(avió, agències) Donat un avió (:py:class:`Avió`), i una llista de fils ja executats dek tipus :py:class:`Agència`, examina el registre de reserves de places efectuats, fent un comptatge per seient. Si troba que un seient s'ha reservat més d'un cop ho mostra per pantalla. (Amb això controlarem si ha hagut "overbooking"). Compta també el total de reserves dels fils i els compara amb el nombre de places de l'avió per saber si hi ha plena ocupació. També mostra una llista de nombre de reserves fetes per fil. (Amb això podrem controlar si ha hagut inanició d'algun fil). Finalment mostrarà una llista de clients ordenada per data de reserva (Amb això podrem veure com s'ha anat fent sequencialment les reserves concurrentment). Programa principal ^^^^^^^^^^^^^^^^^^ A més, cal que :file:`reserves.py` pugui invocar-se com un programa amb arguments en la línia de bash (No us oblideu del :code:`if __main__...` per poder proves en l'íntèrpret de python si cal). Per exemple, .. code-block:: bash python3 reserves.py Boeing417 50 15 Aquesta línia crearà una base de dades (si aquesta no existeix) :file:`Boeing417` de 50 files de 6 seients i competiran per omplir-lo 15 agències de viatge. .. code-block:: bash $ python3 reserves.py Boeing333 50 Aquesta línia crearà una base de dades (si aquesta no existeix) :file:`Boeing333.db` de 50 files de 6 seients i competiran per omplir-lo 10 (valor per omissió) agències de viatge. .. code-block:: bash $ python3 reserves.py Boeing333 Aquesta línia crearà una base de dades (si aquesta no existeix) :file:`Boeing333.db` de 30 (valor per omissió) files de 6 seients i competiran per omplir-lo 10 (valor per omissió) agències de viatge. .. code-block:: bash $ python3 reserves.py Aquesta línia crearà una base de dades (si aquesta no existeix) :file:`Boeing714.db` de 30 (valor per omissió) files de 6 seients i competiran per omplir-lo 10 (valor per omissió) agències de viatge. Si hi han més de tres arguments, mostrarà el missatge: .. code-block:: bash $ python3 reserves.py 1 2 3 4 Ús: python3 reserves.py [nomAvió] [nombre de files] [nombre d'agències] Notes ^^^^^ A més de l'aportació de la classe :py:class:`Avió`, aportem en el fitxer :download:`esborrany.py ` el disseny de la funció :py:func:`estadisAgències`. Generació automàtica de peticions """"""""""""""""""""""""""""""""" Exemple d'una llista de 10 fils amb generació automàtica de noms d'agències i llista de noms de clients (els noms de clients són números (:py:class:`str`), cada agència té prous clients per omplir l'avió): .. code-block:: python agencies = [Agència(f'age{i:03d}', avió, map(str, range(i*nPlaces , i*nPlaces + nPlaces))) for i in range(10)] Visualització de les bases de dades d'Avió """""""""""""""""""""""""""""""""""""""""" Disposeu del programa :download:`mostra.py ` per visualitzar el contingut de la base de dades de l'avió. Per exemple, per mostrar el contingut de la base de dades :file:`Boeing714.db`, feu: .. code-block:: bash python3 mostra.py Boeing714 Si us equivoqueu de nom us crearà una base de dades buida amb el nom equivocat. Proves """""" Per les proves, recordeu que la base de dades guarda els resultats produits en anteriors sessions. Per exemple, si en l'avió està tot reservat, les agències no podran reservar res. Cal esborrar la base de dades corresponent. Protecció de zones crítiques """""""""""""""""""""""""""" Si no heu posat cap mecanisme de protecció (panys, semàfors) de les zones crítiques en el disseny us sortirà que alguns seients estan reservats més d'un cop, iper tant, no teniu un disseny correcte de l'aplicació. Cal protegir la modificació de les dades de l'avió amb semàfors (:py:class:`threading.Semaphore`) o panys (:py:class:`threading.Lock`). Heu de vigilar que el mecanisme de protecció a més d'evitar que un seient sigui reservat més d'un cop, no deixi en inanició cap fil-agència. Les lectures o consultes d'un seient de forma concurrent no son cap problema. En canvi la modificació d'un seient lliure per fer una reserva ha de fer-se de forma atòmica. Un exemple de la comanda (executada sempre sense cap Boeing714.db existent) per eveure els efectes de protecció/no protecció de les zones crítiques: .. block-code:: bash python3 reserves.py Boeing714 10 10 produeix els següent resultat a pantalla .. copmentat .. panels:: per no saber com configurar .. dropdown:: sense protecció zona crítica .. dropdown:: amb protecció zona crítica .. literalinclude:: exemple3 sense protecció zona crítica: .. literalinclude:: exemple4 amb protecció zona crítica: .. literalinclude:: exemple3 .. Una solució: - :download:`agencia.py ` - :download:`reserves.py ` .. Noms agafats de https://ca.wiktionary.org/wiki/Viccionari:Llista_de_noms_propis_en_catal%C3%A0