# ALS Examen final Julio (c) 2023 Baltasar MIT License <baltasarq@uvigo.gal>
import io
import csv
import unittest
import random as rnd
from collections import defaultdict
"""
1. (3 pts.) Cree la clase Empresa. Esta clase representa a empresas
que operan en bolsa. Se creará con un identificador
que se convertirá a partir del nombre que se le pase,
convirtiendo a mayúsculas y limitándolo a cuatro caracteres,
y el valor de la empresa.
Por ejemplo, Apple podría representarse con Empresa(“Apple”, 12000).
Estos dos datos se podrán recuperar con las propiedades de solo lectura
id y valor. El método para convertir a texto devolverá
la información como “<id> (<valor>pts.)”.
Por ejemplo, Microsoft con 2000 puntos de valor
sería “MICR (2000pts.)”.
Cree las clases Op (operación), Compra y Venta.
La clase Op permite agrupar compras y ventas.
En cuanto a las clases Compra y Venta, son muy parecidas:
ambas se crean con tres parámetros:
dos objetos Empresa, la empresa “origen” (la que realiza la operación),
la empresa “destino” (sobre la que se realiza la operación),
y un valor de la operación en euros.
Estos datos pueden recuperarse mediante
tres propiedades de solo lectura: empresa_org, empresa_dest y valor.
Cuando se convierte a texto,
el formato es: “<empresa_org_id> -> <empresa_dest_id>: <valor>€”,
de forma que valor presenta siempre dos decimales.
Por ejemplo, si Apple comprara 42€ de Microsoft,
se crearía como Compra(Empresa(“Apple”, 12000),
Empresa(“Microsoft”, 9000), 42), y se representaría como
“bidd APPL -> MICR: 42.00€”.
La única diferencia entre Compra y Venta es que
en el caso de la primera,
al texto se le antepone “bidd”, y a la venta se le antepone “sell”.
"""
class Empresa:
def __init__(self, id: str, valor: float):
self.__id = id.strip().upper()[:4]
self.__valor = valor
@property
def id(self):
return self.__id
@property
def valor(self):
return self.__valor
def __str__(self):
return f"{self.id} ({self.valor}pts.)"
class Op:
def __init__(self, empresa_org: Empresa, empresa_dest: Empresa, valor: float):
self.__empresa_org = empresa_org
self.__empresa_dest = empresa_dest
self.__valor = valor
@property
def empresa_org(self):
return self.__empresa_org
@property
def empresa_dest(self):
return self.__empresa_dest
@property
def valor(self):
return self.__valor
def __str__(self):
return f"{self.empresa_org.id} -> {self.empresa_dest.id}: {self.valor: 5.2f}€"
class Compra(Op):
def __init__(self, empresa_org: Empresa, empresa_dest: Empresa, valor: float):
return super().__init__(empresa_org, empresa_dest, valor)
def __str__(self):
return "bidd " + super().__str__()
class Venta(Op):
def __init__(self, empresa_org: Empresa, empresa_dest: Empresa, valor: float):
return super().__init__(empresa_org, empresa_dest, valor)
def __str__(self):
return "sell " + super().__str__()
"""
2. (3 pts.) Cree la clase Bolsa,
que representa los movimientos en una bolsa de valores.
No necesita ningún parámetro para ser creada.
Internamente, lleva cuatro estructuras de datos sincronizadas:
una colección con todas las operaciones,
una estructura de datos con todas las operaciones por empresa,
una estructura de datos con todas las compras por empresa,
y una última estructura de datos con todas las ventas por empresa.
Estas cuatro colecciones son accesibles mediante las propiedades
de solo lectura:
ops, ops_por_empresa, compras_por_empresa, y ventas_por_empresa,
respectivamente.
En estas propiedades no se realiza ningún tipo de búsqueda o filtrado.
Nótese que “por empresa” se refiere siempre a la empresa de destino
de la operación.
El método Bolsa.inserta(op: Op) actualiza
todas las estructuras de datos con la nueva operación op.
El método que convierte la información en texto devuelve
para cada empresa (de destino) una lista de sus operaciones.
Ejemplo:
e1 = Empresa("msft", 5000)
e2 = Empresa("apple", 9000)
b1 = Bolsa()
b1.inserta(Venta(e1, e2, 10))
b1.inserta(Compra(e2, e1, 4))
print(b1)
Salida:
Bolsa
APPL:
sell MSFT -> APPL: 10.00€
MSFT:
bidd APPL -> MSFT: 4.00€
"""
class Bolsa:
def __init__(self):
self.__ops = []
self.__e_ops = {}
self.__e_ventas = {}
self.__e_compras = {}
@property
def ops(self):
return list(self.__ops)
@property
def ops_por_empresa(self):
return dict(self.__e_ops)
@property
def ventas_por_empresa(self):
return dict(self.__e_ventas)
@property
def compras_por_empresa(self):
return dict(self.__e_compras)
def inserta(self, op: Op):
nombre_empresa = op.empresa_dest.id
# General
self.__ops.append(op)
# General por empresa
l_ops = self.__e_ops.get(nombre_empresa)
if not l_ops:
l_ops = []
self.__e_ops[nombre_empresa] = l_ops
l_ops.append(op)
# Compras por empresa
if isinstance(op, Compra):
l_ops = self.__e_compras.get(nombre_empresa)
if not l_ops:
l_ops = []
self.__e_compras[nombre_empresa] = l_ops
l_ops.append(op)
# Ventas por empresa
if isinstance(op, Venta):
l_ops = self.__e_ventas.get(nombre_empresa)
if not l_ops:
l_ops = []
self.__e_ventas[nombre_empresa] = l_ops
l_ops.append(op)
def __str__(self):
toret = ""
for (k, vals) in self.ops_por_empresa.items():
toret += k + ":\n" + str.join("\n", [str(val) for val in vals])
toret += "\n\n"
return toret
"""
3. (4 pts.)Cree el método Bolsa.get_compras_de(e: Empresa)
y Bolsa.get_ventas_de(e: Empresa), que devuelven,
respectivamente, una lista de compras en las que
la empresa de destino es la empresa e,
y una lista de ventas en las que la empresa de destino es la empresa e.
En caso de no existir operaciones, devolverá la lista vacía.
Cree el método Bolsa.copia(),
que devuelve un nuevo objeto Bolsa idéntico.
Reescribe el método necesario para que al ejecutar len(b),
siendo b un objeto Bolsa, devuelva el número total de
operaciones almacenadas.
"""
def _Bolsa_get_compras_de(self, empresa: str) -> list:
ops = self.__e_compras.get(empresa)
return list(ops if ops else [])
def _Bolsa_get_ventas_de(self, empresa: str) -> list:
ops = self.__e_ventas.get(empresa)
return list(ops if ops else [])
def _Bolsa_copia(self):
toret = Bolsa()
for old_op in self.ops:
toret.inserta(old_op)
return toret
def _Bolsa_len_(self):
return len(self.ops)
Bolsa.get_compras_de = _Bolsa_get_compras_de
Bolsa.get_ventas_de = _Bolsa_get_ventas_de
Bolsa.copia = _Bolsa_copia
Bolsa.__len__ = _Bolsa_len_
# Preguntas exclusivas Parcial 2 (pregunta 1 era común)-----
"""
2. (4 pts.) Modifica la clase Op de forma que ofrezca
la siguiente funcionalidad sin crear nuevos métodos ni propiedades.
Siendo op un objeto de la clase Op, al ejecutar op.dts[0] se debe devolver
la empresa de origen en la operación.
Con op.dts[1] se debe devolver la empresa de destino de la operación.
Finalmente, con op.dts[2] se retorna el valor de la operación.
Así, si c1 = Compra(Empresa(“Apple”, 12000), Empresa(“Microsoft”, 9000), 42))
entonces c1.dts[0].id == “APPL”, c1.dts[1].id == “MICR”,
mientras que c1.dts[2] == 42.
"""
def _Op_get_attr(self, item):
if item == "dts":
return [self.empresa_org, self.empresa_dest, self.valor]
raise AttributeError(item)
Op.__getattr__ = _Op_get_attr
"""
3. (3 pts.) Modifica la clase Op de forma que soporte el método to_csv(f),
siendo f un archivo ya abierto.
Guardará dos líneas de texto con cuatro campos separados por comas,
la primera con una cabecera y la segunda con los datos de la operación,
precedida de “bidd” si es una compra, y “sell” si es una venta.
De las empresas solo se guarda su id.
Por ejemplo,
si c1 = Venta(Empresa(“Apple”, 12000), Empresa(“Microsoft”, 9000), 42),
entonces obtendríamos:
Salida:
C/V, Empresa org., Empresa dest., Valor
sell, APPL, MICR, 42
"""
def _Op_to_csv(self, f):
wr = csv.writer(f)
es_compra = "bidd" if type(self) == Compra else "sell"
wr.writerow(["Compra/Venta", "Empresa org.", "Empresa dest.", "valor"])
wr.writerow([es_compra, self.empresa_org.id, self.empresa_dest.id, self.valor])
Op.to_csv = _Op_to_csv
# Tests ----------------------------------------------------
class TestEmpresa(unittest.TestCase):
def setUp(self):
self.ids = ["MICR", "APPL"]
self.vals = [2000, 1000]
self.empresas = []
for (i, id) in enumerate(self.ids):
self.empresas.append(Empresa(id.title() + (" " * rnd.randint(0, 10)), self.vals[i]))
def test_creacion(self):
for (i, id) in enumerate(self.ids):
self.assertEqual(id, self.empresas[i].id)
self.assertEqual(self.vals[i], self.empresas[i].valor)
def test_str(self):
dstrs = ["MICR (2000pts.)", "APPL (1000pts.)"]
for (i, dstr) in enumerate(dstrs):
self.assertEqual(dstr, str(self.empresas[i]))
class TestOps(unittest.TestCase):
def setUp(self):
self.e1 = Empresa("Microsoft", 1000)
self.e2 = Empresa("Apple", 2000)
self.val_compras = [50, 90]
self.val_ventas = [20, 45]
self.empresas_compras = [(self.e1, self.e2), (self.e2, self.e1)]
self.empresas_ventas = [(self.e2, self.e1), (self.e1, self.e2)]
self.compras = []
self.ventas = []
for (i, empresa) in enumerate(self.empresas_compras):
empresas = self.empresas_compras[i]
self.compras.append(Compra(empresas[0], empresas[1], self.val_compras[i]))
for (i, empresa) in enumerate(self.empresas_ventas):
empresas = self.empresas_ventas[i]
self.ventas.append(Venta(empresas[0], empresas[1], self.val_ventas[i]))
def test_creacion_str(self):
for (i, compra) in enumerate(self.compras):
empresas = self.empresas_compras[i]
self.assertEqual(empresas[0], compra.empresa_org)
self.assertEqual(empresas[1], compra.empresa_dest)
self.assertEqual(f"bidd {empresas[0].id} -> {empresas[1].id}: {self.val_compras[i]}.00€", str(compra))
for (i, venta) in enumerate(self.ventas):
empresas = self.empresas_ventas[i]
self.assertEqual(empresas[0], venta.empresa_org)
self.assertEqual(empresas[1], venta.empresa_dest)
self.assertEqual(f"sell {empresas[0].id} -> {empresas[1].id}: {self.val_ventas[i]}.00€", str(venta))
class TestBolsa(unittest.TestCase):
def setUp(self):
self.b = Bolsa()
self.e1 = Empresa("Microsoft", 1000)
self.e2 = Empresa("Apple", 2000)
self.val_compras = [50, 90]
self.val_ventas = [20, 45]
self.empresas_compras = [(self.e1, self.e2), (self.e2, self.e1)]
self.empresas_ventas = [(self.e2, self.e1), (self.e1, self.e2)]
self.compras = []
self.ventas = []
for (i, empresa) in enumerate(self.empresas_compras):
empresas = self.empresas_compras[i]
self.compras.append(Compra(empresas[0], empresas[1], self.val_compras[i]))
for (i, empresa) in enumerate(self.empresas_ventas):
empresas = self.empresas_ventas[i]
self.ventas.append(Venta(empresas[0], empresas[1], self.val_ventas[i]))
def test_creacion(self):
self.assertEqual(0, len(self.b))
def test_carga(self):
self.assertEqual(0, len(self.b))
all_ops = self.compras + self.ventas
for op in all_ops:
self.b.inserta(op)
self.assertEqual(len(all_ops), len(self.b))
self.assertEqual(len(all_ops), len(self.b.ops))
self.assertEqual(len(self.b.ops), len(self.b))
all_ops_set = set(all_ops)
for op in self.b.ops:
all_ops_set.remove(op)
self.assertEqual(0, len(all_ops_set))
all_ops_por_empresa_set = set(all_ops)
for k in self.b.ops_por_empresa.keys():
for op in self.b.ops_por_empresa[k]:
all_ops_por_empresa_set.remove(op)
self.assertEqual(0, len(all_ops_por_empresa_set))
compras_set = set(self.compras)
for ks in self.b.compras_por_empresa.keys():
for op in self.b.compras_por_empresa[ks]:
compras_set.remove(op)
self.assertEqual(0, len(compras_set))
ventas_set = set(self.ventas)
for ks in self.b.ventas_por_empresa.keys():
for op in self.b.ventas_por_empresa[ks]:
ventas_set.remove(op)
self.assertEqual(0, len(ventas_set))
def test_carga_por_empresa(self):
self.assertEqual(0, len(self.b))
compras_por_empresa = defaultdict(list)
for op in self.compras:
self.b.inserta(op)
compras_por_empresa[op.empresa_dest.id].append(op)
for kid in compras_por_empresa:
bolsa_ops = set(self.b.compras_por_empresa[kid])
test_compras = set(compras_por_empresa[kid])
self.assertEqual(len(test_compras), len(bolsa_ops))
for op in test_compras:
bolsa_ops.remove(op)
self.assertEqual(0, len(bolsa_ops))
class TestListOp(unittest.TestCase):
def setUp(self):
self.e1 = Empresa("ibm", 10000)
self.e2 = Empresa("microsoft", 25000)
self.c1 = Compra(self.e1, self.e2, 45)
def test_dts(self):
self.assertEqual(self.e1, self.c1.dts[0])
self.assertEqual(self.e2, self.c1.dts[1])
self.assertEqual(self.c1.valor, self.c1.dts[2])
def test_csv(self):
escrito = ""
with io.StringIO() as out_str:
self.c1.to_csv(out_str)
escrito = out_str.getvalue().strip()
for (i, linea) in enumerate(escrito.split('\n')):
componentes = linea.split(',')
self.assertEqual(4, len(componentes))
componentes = [campo.strip() for campo in componentes]
if i == 0:
self.assertEqual("valor", componentes[3])
if i == 1:
self.assertEqual("bidd", componentes[0])
self.assertEqual(componentes[1], self.e1.id)
self.assertEqual(componentes[2], self.e2.id)
self.assertEqual(str(self.c1.valor), componentes[3])
def simple_chk_parcial1():
e1 = Empresa("msft", 5000)
e2 = Empresa("apple", 9000)
print(e1)
print(e2)
print("\nBolsa")
bolsa = Bolsa()
bolsa.inserta(Venta(e1, e2, 10))
bolsa.inserta(Compra(e2, e1, 4))
print(bolsa)
print("\nCopia de bolsa")
b2 = bolsa.copia()
b2.inserta(Compra(e2, e1, 400))
print(b2)
print("\nBolsa")
print(bolsa)
print("\nVentas de MSFT")
print(str.join(", ", [str(v) for v in bolsa.get_ventas_de("MSFT")]))
print("\nVentas de Apple")
print(str.join(", ", [str(v) for v in bolsa.get_ventas_de("APPL")]))
print("\nCompras de MSFT")
print(str.join(", ", [str(v) for v in bolsa.get_compras_de("MSFT")]))
print("\nCompras de Apple")
print(str.join(", ", [str(v) for v in bolsa.get_compras_de("APPL")]))
def simple_chk_parcial2():
e1 = Empresa("Ibm", 5000)
e2 = Empresa("Microsoft", 15000)
c1 = Compra(e1, e2, 50)
print(c1)
print(f"{len(c1.dts)=}")
print(f"{c1.dts[0].id=}")
print(f"{c1.dts[1].id=}")
print(f"{c1.dts[2]=}")
with io.StringIO() as out_str:
c1.to_csv(out_str)
print(out_str.getvalue())
if __name__ == "__main__":
op = input("Chk parcial[1], Chk parcial[2], [T]ests (1/2/t): ")
op = op.strip().lower()
print()
if op == "1":
print("Chk parcial 1:")
simple_chk_parcial1()
elif op == "2":
print("Chk parcial 2:")
simple_chk_parcial2()
elif op == "t":
print("Tests:")
unittest.main()
else:
print("No puedes hacer eso.")