UNIVERSIDAD NACIONAL AUTÓNOMA DE NICARAGUA UNAN - LEÓN Componente: Proyecto Integrador I Demostración de estructuras dinámicas en un sistema simulado Pilas, colas y listas Nombre del Proyecto: Sistema de Agenda Personal Autores: Br. Gilder Talavera Triguero Br. Mario Trujillo Ugarte Docente: Ing. William Martínez Orozco Carrera: Ingeniería en Sistemas ndice Introducción ................................ ................................ ................................ ................................ .... 1 IV. Estructuras de datos dinámicas ................................ ................................ ................................ 1 1. Pilas: push, pop, mostrar ................................ ................................ ................................ ...... 1 2. Colas: mostrar ................................ ................................ ................................ ...................... 1 3. Listas: inserción, eliminación y recorrido ................................ ................................ ............. 2 4. Aplicación práctica en un sistema real ................................ ................................ ................. 2 5. Lenguaje de programación estructurado ................................ ................................ ............. 2 Código fuente ................................ ................................ ................................ ................................ .. 3 Evidencia de ejecución ................................ ................................ ................................ .................... 7 Conclusión ................................ ................................ ................................ ................................ ....... 9 1 Introducción En esta cuarta entrega ampliamos nuestro Sistema de Agenda Personal con estructuras de datos dinámicas: una pila, una cola y una lista. Estas estructuras pueden crecer o reducirse mientras el programa se ejecuta, sin tener que definir su tamaño de antemano . Las aplicamos a dos situaciones reales de la agenda: recuperar un contacto que se eliminó por error y organizar en orden las llamadas pendientes. IV. Estructuras de datos dinámicas 1. Pilas: push, pop, mostrar Una pila es una estructura donde el último elemento que entra es el primero que sale. Se conoce como LIFO, por sus siglas en inglés. Funciona como una pila de platos: solo se puede poner o quitar por arriba, y a esa posición le llamamos tope. Push: agrega un elemento en el tope. En el código lo hace la función push con pila.append(elemento). Pop: quita y devuelve el elemento del tope. La función pop usa pila.pop() y devuelve None si la pila está vacía, para no provocar un error. Mostrar: mostrar_pila recorre la pila de arriba hacia abajo y marca el tope con la palabra TOPE. Nuestra pila se llama pila_eliminados y guarda los contactos que se van eliminando. 2. Colas: mostrar Una cola es una estructura donde el primer elemento que entra es el primero que sale. Se conoce como FIFO y se parece a la fila de un banco: se atiende primero a quien llegó primero. Para implementarla usamos deque, de la librería collections de Python. La función encolar agrega un elemento al final, desencolar saca el que está al frente con popleft() y mostrar_cola lista los elementos numerados en el orden en que llegaron. Nuestra cola se llama cola_llamadas y guarda los contactos a los que hay que llama r. 2 3. Listas: inserción, eliminación y recorrido La lista es la propia agenda, donde cada elemento es un contacto con su nombre, teléfono, fecha y nota. Inserción: insertar_contacto usa lista.insert(posicion, contacto). Si se marca la casilla Insertar al inicio de la lista, el contacto entra en la posición 0; si no, entra al final. Eliminación: eliminar_contacto usa lista.pop(indice), que quita el contacto de esa posición y lo devuelve para poder guardarlo después en la pila. Recorrido: recorrer_agenda pasa por cada elemento con un ciclo for y arma una línea numerada por contacto. El botón Recorrer lista muestra ese resultado junto con el total de contactos. 4. Aplicación práctica en un sistema real En la agenda, las tres estructuras resuelven situaciones que pasan en la vida real. Cuando se elimina un contacto no se pierde: se guarda en la pila, y el botón Deshacer eliminación aplica un pop y lo devuelve a la lista. Se recupera primero el último que se eliminó, igual que el botón deshacer de un editor de texto. Para las llamadas pendientes, se selecciona un contacto y se presiona Agregar a la cola. Cuando se presiona Atender siguiente, el sistema saca al primero de la cola y avisa a quién llamar, de modo que nadie se salta el turno. 5. Lenguaje de programación estructurado Programamos el sistema en Python. El código está organizado con las tres estructuras básicas de la programación estructurada: secuencia, selección (if) y repetición (for y while). Además, está dividido en funciones y procedimientos, y cada uno se encarga d e una sola tarea. Para la ventana usamos Tkinter, para validar las fechas usamos datetime y para la cola usamos deque. 3 Código fuente import tkinter as tk from tkinter import ttk, messagebox from datetime import datetime from collections import deque agenda = [] # LISTA (matriz): cada fila es un contacto [nombre, telefono, fecha, nota] pila_eliminados = [] # PILA (LIFO): el último contacto eliminado es el primero en recuperarse cola_llamadas = deque() # COLA (FIFO): la primera llamada pendiente es la primera en atenderse def formatear_telefono(telefono): return telefono.strip().replace(" - ", "").replace(" ", "") def validar_contacto(nombre, telefono, fecha): if not nombre.strip(): raise ValueError("El nombre es obligatorio.") if not telefono.isdigit() or len(telefono) != 8: raise ValueError("El teléfono debe tener 8 dígitos numéricos.") try: datetime.strptime(fecha, "%d/%m/%Y") except ValueError: raise ValueError("La fecha no es válida. Use el formato dd/mm/aaaa (ej. 10/09/2026).") # ---------------- LISTA: inserción, eliminación, recorrido ---------------- def insertar_contacto(lista, posicion, contacto): lista.insert(posicion, contacto) def editar_contacto(lista, indice, contacto): lista[indice] = contacto def eliminar_contacto(lista, indice): return lista.pop(indice) def recorrer_agenda(lista): lineas = [] for i, fila in enumerate(lista, start=1): lineas.append(f"{i}. {fila[0]} | {fila[1]} | {fila[2]} | {fila[3]}") return lineas # ---------------- PILA: push, pop, mostrar ---------------- def push(pila, elemento): pila.append(elemento) def pop(pila): return pila.pop() if pila else None def mostrar_pila(pila): 4 return [f"{'TOPE - > ' if i == 0 else ''}{f[0]} ({f[1]})" for i, f in enumerate(reversed(pila))] # ---------------- COLA: encolar, desencolar, mostrar ---------------- def encolar(cola, elemento): cola.append(elemento) def desencolar(cola): return cola.popleft() if cola else None def mostrar_cola(cola): return [f"{i}. {f[0]} ({f[1]})" for i, f in enumerate(cola, start=1)] class AgendaApp: def __init__(self, ventana): self.ventana = ventana self.ventana.title("Sistema de Agenda Personal") self.ventana.geometry("760x680") self.ventana.resizable(False, False) self.indice_seleccionado = None self.crear_widgets() def crear_widgets(self): marco_form = tk.LabelFrame(self.ventana, text="Datos del contacto", padx=10, pady=6) marco_form.pack(fill="x", padx=10, pady=6) etiquetas = ["Nombre:", "Teléfono (8 dígitos):", "Fecha (dd/mm/aaaa):", "Nota:"] self.entradas = [] for i, texto in enumerate(etiquetas): tk.Label(marco_form, text=texto).grid(row=i, column=0, sticky="w", pady=2) entrada = tk.Entry(marco_form, width=40) entrada.grid(row=i, column=1, pady=2) self.entradas.append(entrada) self.var_inicio = tk.BooleanVar() tk.Checkbutton(marco_form, text="Insertar al inicio de la lista", variable=self.var_inicio).grid(row=4, column=1, sticky="w") marco_botones = tk.Frame(self.ventana) marco_botones.pack(fill="x", padx=10) for texto, comando in [("Agregar", self.accion_agregar), ("Modificar", self.accion_modificar), ("Eliminar", self.accion_eliminar), ("Limpiar", self.limpiar_campos), ("Recorrer lista", self.accion_recorrer)]: tk.Button(marco_botones, text=texto, width=13, command=comando).pack(side="left", padx=4) marco_tabla = tk.LabelFrame(self.ventana, text="Lista de contactos", padx=10, pady=6) marco_tabla.pack(fill="both", expand=True, padx=10, pady=6) columnas = ("nombre", "telefono", "fecha", "nota") self.tabla = ttk.Treeview(marco_tabla, columns=columnas, show="headings", height=7) for col, ancho in zip(columnas, (170, 120, 110, 280)): self.tabla.heading(col, text=col.capitalize()) self.tabla.column(col, width=ancho) self.tabla.pack(fill="both", expand=True) self.tabla.bind("<<TreeviewSelect>>", self.seleccionar_fila) marco_estructuras = tk.Frame(self.ventana) marco_estructuras.pack(fill="x", padx=10, pady=6) marco_pila = tk.LabelFrame(marco_estructuras, text="Pila: contactos eliminados", padx=8, pady=6) marco_pila.pack(side="left", fill="both", expand=True, padx=(0, 5)) 5 self.lista_pila = tk.Listbox(marco_pila, height=6) self.lista_pila.pack(fill="x") tk.Button(marco_pila, text="Deshacer eliminación (pop)", command=self.accion_deshacer).pack(pady=4) marco_cola = tk.LabelFrame(marco_estructuras, text="Cola: llamadas pendientes", padx=8, pady=6) marco_cola.pack(side="left", fill="both", expand=True, padx=(5, 0)) self.lista_cola = tk.Listbox(marco_cola, height=6) self.lista_cola.pack(fill="x") marco_botones_cola = tk.Frame(marco_cola) marco_botones_cola.pack(pady=4) tk.Button(marco_botones_cola, text="Agregar a la cola", command=self.accion_encolar).pack(side="left", padx=3) tk.Button(marco_botones_cola, text="Atender siguiente", command=self.accion_atender).pack(side="left", padx=3) def leer_campos(self): nombre, telefono, fecha, nota = [e.get().strip() for e in self.entradas] return nombre, formatear_telefono(telefono), fecha, nota def accion_agregar(self): try: nombre, telefono, fecha, nota = self.leer_campos() validar_contacto(nombre, telefono, fecha) posicion = 0 if self.var_inicio.get() else len(agenda) insertar_contacto(agenda, posicion, [nombre, telefono, fecha, nota]) except ValueError as error: messagebox.showerror("Dato inválido", f"{error} \ nVerifique los datos ingresados.") return self.actualizar_todo() self.limpiar_campos() messagebox.showinfo("Éxito", f"Contacto '{nombre}' agregado correctamente.") def accion_modificar(self): if self.indice_seleccionado is None: messagebox.showwarning("Sin selección", "Seleccione un contacto de la tabla para modificar.") return try: nombre, telefono, fecha, nota = self.leer_campos() validar_contacto(nombre, telefono, fecha) editar_contacto(agenda, self.indice_seleccionado, [nombre, telefono, fecha, nota]) except ValueError as error: messagebox.showerror("Dato inválido", f"{error} \ nVerifique los datos ingresados.") return self.actualizar_todo() self.limpiar_campos() messagebox.showinfo("Éxito", "Contacto modificado correctamente.") def accion_eliminar(self): if self.indice_seleccionado is None: messagebox.showwarning("Sin selección", "Seleccione un contacto de la tabla para eliminar.") return if messagebox.askyesno("Confirmar", "¿Desea eliminar este contacto?"): eliminado = eliminar_contacto(agenda, self.indice_seleccionado) push(pila_eliminados, eliminado) self.actualizar_todo() self.limpiar_campos() def accion_deshacer(self): contacto = pop(pila_eliminados) if contacto is None: messagebox.showwarning("Pila vacía", "No hay contactos eliminados para recuperar.") return insertar_contacto(agenda, len(agenda), contacto) self.actualizar_todo() 6 messagebox.showinfo("Recuperado", f"Contacto '{contacto[0]}' recuperado de la pila.") def accion_encolar(self): if self.indice_seleccionado is None: messagebox.showwarning("Sin selección", "Seleccione un contacto de la tabla para agregarlo a la cola.") return encolar(cola_llamadas, list(agenda[self.indice_seleccionado])) self.actualizar_todo() def accion_atender(self): contacto = desencolar(cola_llamadas) if contacto is None: messagebox.showwarning("Cola vacía", "No hay llamadas pendientes.") return self.actualizar_todo() messagebox.showinfo("Llamada", f"Llamar a {contacto[0]} al {contacto[1]}.") def accion_recorrer(self): lineas = recorrer_agenda(agenda) if not lineas: messagebox.showwarning("Sin datos", "La lista no tiene contactos.") return texto = "RECORRIDO DE LA LISTA \ n" + " - " * 60 + " \ n" + " \ n".join(lineas) + f" \ n{' - ' * 60} \ nTotal de contactos: {len(lineas)}" ventana_recorrido = tk.Toplevel(self.ventana) ventana_recorrido.title("Recorrido de la lista") caja = tk.Text(ventana_recorrido, width=80, height=16, font=("Consolas", 10)) caja.insert("1.0", texto) caja.config(state="disabled") caja.pack(padx=10, pady=10) def seleccionar_fila(self, evento): seleccion = self.tabla.selection() if not seleccion: return self.indice_seleccionado = self.tabla.index(seleccion[0]) self.limpiar_campos(mantener_seleccion=True) for entrada, valor in zip(self.entradas, agenda[self.indice_seleccionado]): entrada.insert(0, valor) def actualizar_todo(self): for item in self.tabla.get_children(): self.tabla.delete(item) for fila in agenda: self.tabla.insert("", "end", values=fila) self.lista_pila.delete(0, tk.END) for linea in mostrar_pila(pila_eliminados): self.lista_pila.insert(tk.END, linea) self.lista_cola.delete(0, tk.END) for linea in mostrar_cola(cola_llamadas): self.lista_cola.insert(tk.END, linea) def limpiar_campos(self, mantener_seleccion=False): for entrada in self.entradas: entrada.delete(0, tk.END) if not mantener_seleccion: self.indice_seleccionado = None if __name__ == "__main__": ventana = tk.Tk() app = AgendaApp(ventana) ventana.mainloop() 7 8 Evidencia de ejecución Estas son las capturas del programa funcionando en Visual Studio Code. Figura 1. Ventana principal con la lista de contactos y los paneles de la pila y la cola. Figura 2. Recorrido de la lista con los contactos registrados y el total. 9 Figura 3. Pila de contactos eliminados: el último en eliminarse (Gordo) queda en el tope. Figura 4. Cola de llamadas pendientes con un contacto en espera. 10 Figura 5. Atención del siguiente de la cola: el sistema avisa a quién llamar. Conclusión Con esta entrega aprendimos a trabajar con estructuras de datos dinámicas y a notar la diferencia entre ellas: la pila saca primero lo último que entró, la cola saca primero lo que llegó antes y la lista permite insertar y eliminar en cualquier posición. A plicarlas a nuestra agenda nos ayudó a entender para qué sirven, porque ahora podemos recuperar un contacto eliminado y atender las llamadas pendientes en orden. El sistema queda más completo y con una base clara para las siguientes entregas.