====== 07 - Arrays, colecciones y maps====== Hasta ahora, hemos usado variables simples (números y texto). En Java, existen otro tipo de variables que agrupan datos estructurados: **arrays**, **colecciones** y **maps**. ===== Arrays ===== Los [[https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Arrays.html|Arrays]] permiten almacenar una colección de objetos o datos del mismo tipo. Son muy útiles y su utilización es muy simple: * **Declaración del //array//**: Consiste en definir el tipo de datos que contendrá y el nombre: //tipo[] nombre//. El tipo será un tipo de variable o una clase ya existente, de la cual se quieran almacenar varias unidades. * **Creación del //array//**: La creación de un //array// consiste en decir el tamaño que tendrá el //array//, es decir, el número de elementos que contendrá: //nombre=new tipo[dimension]//, donde dimensión es un número entero positivo que indicará el tamaño del //array//. Una vez creado el //array//, éste **no podrá cambiar de tamaño**. int[] numbers; numbers = new int[10]; También podemos definir un //array// poniendo los corchetes al final del nombre: int numbers[]; Se pueden agrupar las dos instrucciones anteriores en una: int[] numbers = new int[10]; Una vez hecho esto, ya podemos almacenar valores en cada una de las posiciones del //array//, usando corchetes e indicando en su interior la posición en la que queremos leer o escribir, teniendo en cuenta que **la primera posición es la cero y la última el tamaño del //array// menos uno**. En el ejemplo anterior, la primera posición sería la 0 y la última sería la 9. numbers[3] = 10; Para acceder a los datos de un //array//, lo hacemos de forma similar. Simplemente poniendo el nombre del //array// y la posición a la cual se quiere acceder entre corchetes: int number; number = numbers[3]; Los //arrays// disponen de una propiedad pública muy útil: //length//, la cual nos permite saber el tamaño de cualquier //array//. Usando esta propiedad podemos iterar sobre el array accediendo a todos sus elementos: System.out.println(numbers.length); 10 Usando esta propiedad, podemos incializar un //array// (rellenar sus valores) utilizando un bucle //for//, por ejemplo: for (int i = 0; i < numbers.length; i++) { numbers[i] = i + 3; } Otra forma de inicializar un //array// es definiendo sus valores como una lista separada por comas encerrada entre llaves. Ésto lo debemos hacer a la hora de la declaración del //array//, y no hace falta indicarle la longitud, ya que se lo indicamos con el número de elementos que le pasemos: int[] numbers = {1, 3, 312, 15, 69, 7, 8, 9, 0, 1}; Para acceder a los elementos de un //array//, en Java tenemos otra forma más sencilla: el bucle **for-each**. Este bucle es muy parecido al //for//, pero no hace falta calcular el tamaño del //array// con la propiedad //length//, ya lo hace Java por nosotros: for (int elemento : numbers) { System.out.println(elemento); } Cuando lleguemos el tema ([[clase:daw:prog:1eval:programacion_funcional|programación funcional]]) veremos una forma más elegante para recorrer y operar conjuntos de datos (//arrays//, colecciones, //maps//...) ==== Arrays multidimensionales ==== En los ejemplos anteriores hemos utilizado //arrays// de una sola dimensión, pero podemos crear //arrays// de cualquier dimensión. Por ejemplo, podemos crear una matriz (//array// de dos dimensiones) de 5x5 de enteros aumentando las dimensiones del //array//: int[][] m = new int[5][5]; Para acceder a los elementos de un //array// multidimensional habrá que indicar su posición en las dos dimensiones, teniendo en cuenta que los índices de cada una de las dimensiones empieza a numerarse en 0 y que la última posición es el tamaño de la dimensión en cuestión menos 1. m[0][3] = 19; Para inicializar un //array// multidimensional podemos usar el mismo método que con los //arrays// unidimensionales, teniendo en cuenta que necesitamos encadenar bucles //for//, ya que debemos recorrer todas las dimensiones de nuestro //array//: for (int i = 0; i < m.length; i++) { for (int j = 0; j < m[i].length; j++) { m[i][j] = j + 1; } } Igual que con los //arrays// unidimensionales, podemos inicializar un //array// multidimensional a la hora de definirlo: int[][] m = {{3, 5, 6}, {2, 4, 9}, {10, 2, 1}}; Para acceder a los elementos de un //array// multidimensional, lo podemos hacer de forma similar a los //arrays// unidimensionales: for (int[] elemento_fila : m) { for (int elemento_columna : elemento_fila) { System.out.println(elemento_columna); } } El //array// anterior es un ejemplo de **array multidimensional regular**, ya que es un //array// que contiene, a su vez, //arrays// de números del mismo tamaño. Podemos crear **arrays multidimensionales irregulares** si hacemos que en las dimensiones posteriores contengan //arrays// de distinto tamaño entre sí: int[][] irregular = new int[3][]; irregular[0] = new int[5]; irregular[1] = new int[7]; irregular[2] = new int[8]; ==== Paso de arrays como parámetro de una función ==== Fíjate en el siguiente código: class PasoArrayParametro { static void inicilizaArray(int[] array) { for (int i = 0; i < array.length; i++) { array[i] = i + 5; } } public static void main(String[] args) { int[] numbers = new int[3]; for (int i : numbers) { System.out.print(i + " "); } System.out.println(); System.out.println("----"); inicilizaArray(numbers); for (int i : numbers) { System.out.print(i + " "); } System.out.println(""); } } Si lo ejecutamos, vemos la siguiente salida: 0 0 0 ---- 5 6 7 Cuando vimos el paso de parámetros a funciones, dijimos que en Java todos los parámetros se pasan por valor. Entonces, ¿por qué se ha modificado el contenido del //array//? Si el paso de parámetros es por valor, se debería habar pasado una copia del //array// y el //array// original no se debería haber modificado. Cuando pasamos un //array// a un método, en lugar de copiar todos los elementos desde la zona de memoria del programa principal a la zona de memoria del método en cuestión, lo que **se copia únicamente es la referencia o la dirección de memoria donde comienza el array**. Por tanto, es como si el parámetro se pasara por referencia, al modificar los elementos del parámetro en el método, automáticamente quedarán modificados en la zona de memoria del programa principal. Es decir, estaremos modificando los datos originales. El hecho de que los //arrays// se pasen por referencia tiene sentido, puesto que, si el número de elementos de un //array// es muy grande, la acción de realizar la copia de todos los elementos a la zona de memoria del método, tendría un coste espacial y temporal muy alto. Es decir, tendríamos que utilizar memoria y tiempo extra para realizar esta copia. Ésto no solo ocurre con los //arrays//. En realidad, casi cualquier dato estructurado (variables que no sean simples como números y texto) funciona como lo descrito anteriormente cuando lo pasamos como parámetro a una función Los //arrays// almacenan un número de elementos del mismo tipo que definimos cuando lo declaramos. ¿Qué pasa si queremos aumentar el tamaño de un //array// después de definirlo? Para hacerlo, deberíamos crear otro //array// con una dimensión mayor y copiar los elementos del primero en el segundo. Aunque esto funcionaría, tenemos otro tipo de datos estructurados que nos simplifican mucho esa (y otra) tarea: **collections** y **maps**. ===== Colecciones ===== Las colecciones (//collections//) en Java representan un grupo de objetos. Algunas colecciones permiten duplicados y otras no, igual que algunas están ordenadas y otras no. Las colecciones en Java parten de una serie de //interfaces// básicas. Cada //interfaz// define un modelo de colección y las operaciones que se pueden llevar a cabo sobre los datos almacenados. Entenderás lo que es una //interfaz// cuando lleguemos a la POO. Por ahora, lo único que tienes que tener en cuenta es que una //interfaz// define una serie de métodos (operaciones) que tienen todas las clases que deriven de ella (implementen la //interfaz//). La //interfaz// inicial, a través de la cual se han construido el resto de colecciones, es la //interfaz// [[https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Collection.html|Collection]], que define las operaciones comunes a todas las colecciones derivadas. A continuación, se muestran las operaciones más importantes definidas por esta //interfaz//: * **int size()**: retorna el número de elementos de la colección. * **boolean isEmpty()**: retorna verdadero si la colección está vacía. * **boolean contains (Object element)**: retorna verdadero si la colección tiene el elemento pasado como parámetro. * **boolean add(E element)**: añade elementos a la colección. * **boolean remove (Object element)**: elimina elementos de la colección. * **Object[] toArray()**: pasa la colección a un array de objetos tipo Object. * **void clear()**: vacía la colección. En el enlace anterior tienes todas las operaciones (métodos) que tiene definida la //interfaz// //java.util.Collection//. ==== Conjuntos (set) ==== Los conjuntos son un tipo de colección que **NO admite duplicados**, derivados del concepto matemático de conjunto. La interfaz [[https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Set.html|Set]] define cómo deben ser los conjuntos, y extiende la interfaz //Collection// , aunque no añade ninguna operación nueva. Las implementaciones (clases genéricas que implementan la interfaz //Set//) más usadas son las siguientes: * [[https://docs.oracle.com/en/java/javase/17/docs/api//java.base/java/util/HashSet.html|HashSet]]: Conjunto que almacena los objetos usando tablas //hash//, lo cual acelera enormemente el acceso a los objetos almacenado. Inconvenientes: necesitan bastante memoria y no almacenan los objetos de forma ordenada (al contrario, pueden aparecer completamente desordenados). * [[https://docs.oracle.com/javase/7/docs/api/java/util/LinkedHashSet.html|LinkedHashSet]]: Conjunto que almacena objetos combinando tablas //hash//, para un acceso rápido a los datos, y listas enlazadas para conservar el orden. El orden de almacenamiento es el de inserción, por lo que se puede decir que es una estructura ordenada a medias. Inconvenientes: necesitan bastante memoria y es algo más lenta que //HashSet//. * [[https://docs.oracle.com/en/java/javase/17/docs/api//java.base/java/util/TreeSet.html|TreeSet]]: Conjunto que almacena los objetos usando unas estructuras conocidas como árboles rojo-negro. Son más lentas que los dos tipos anteriores. pero tienen una gran ventaja: los datos almacenados se ordenan por valor. Es decir, aunque se inserten los elementos de forma desordenada, internamente se ordenan dependiendo del valor de cada uno. Una tabla //hash// es una estructura de datos que asocia llaves o claves con valores. La operación principal que soporta de manera eficiente es la búsqueda: permite el acceso a los elementos (teléfono y dirección, por ejemplo) almacenados a partir de una clave generada (usando el nombre o número de cuenta, por ejemplo). Funciona transformando la clave con una //función hash// en un //hash//, un número que identifica la posición donde la tabla //hash// localiza el valor deseado. Veamos un ejemplo de uso básico de la estructura //HashSet//. Para crear un conjunto, simplemente creamos el //HashSet// indicando el tipo de objeto que va a almacenar (no olvides hacer la importación de //java.util.HashSet// primero): HashSet conjunto = new HashSet(); Una práctica habitual es definir el tipo de datos //conjunto// como la interfaz genérica (//Set//), así, si queremos cambiar el tipo de //Set// sólo tendríamos que cambiarlo en la definición: Set conjunto = new HashSet<>(); Existen varias formas de inicializar un conjunto. Por ejemplo, podemos ir añadiendo elementos uno a uno con el método //add()//: conjunto.add(2); conjunto.add(10); conjunto.add(3); conjunto.add(23); conjunto.add(99); } Otra forma más cómoda de inicializar un conjunto (válido a partir de Java 9) es utilizar el método //of()//: conjunto = Set.of(2, 10, 3, 23, 99); Si inicializamos un conjunto con el método //of()//, el conjunto que se crea es **inmutable**, es decir, no podemos cambiarlo, con lo que no podremos añadir, eliminar ni modificar elementos. Para recorrer un //HashSet// podemos utilizar, igual que hicimos con los //arrays// un bucle **for-each**: for (Integer number : conjunto) { System.out.println(number); } 2 3 99 23 10 ¿Qué pasa si intentamos añadir un elemento repetido? Vamos a probar: conjunto.add(2); conjunto.add(10); conjunto.add(3); conjunto.add(23); conjunto.add(99); conjunto.add(10); for (Integer number : conjunto) { System.out.println(number); } 2 3 99 23 10 Como ves, el dato repetido no se añade al //HashSet// y el compilador no lanza ningún error. Si te fijas en el método //add//, éste devuelve un //boolean//. Devolverá //true// si el elemento se ha añadido o //false// si no se ha añadido: conjunto.add(2); conjunto.add(10); conjunto.add(3); conjunto.add(23); conjunto.add(99); if(!conjunto.add(10)) { System.out.println("El número ya está en la lista"); } for (Integer number : conjunto) { System.out.println(number); } El número ya está en la lista 2 3 99 23 10 ==== Listas (list) ==== Las listas son elementos de programación un poco más avanzados que los conjuntos. Las principales diferencias con respecto a las colecciones son: * Las listas **SI pueden almacenar duplicados**. Si no queremos duplicados, hay que verificar manualmente que el elemento no esté en la lista antes de su inserción. * **Acceso posicional**: Podemos acceder a un elemento indicando su posición en la lista. * **Búsqueda**. Es posible buscar elementos en la lista y obtener su posición. En los conjuntos, al ser colecciones sin ordenar, solo se podía comprobar si contenía o no un elemento de la lista, retornando //true// o //false//. * **Extracción de sublistas**: Es posible obtener una lista que contenga solo una parte de los elementos de forma muy sencilla. * En Java, para las listas se dispone de una interfaz llamada [[https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/List.html|List]] , y varias implementaciones, entre las que destacan [[https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/LinkedList.html|LinkedList]] y [[https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/ArrayList.html|ArrayList]]. Algunos de los métodos de la //interfaz List//, que obviamente estarán en todas las implementaciones, y que permiten las operaciones anteriores son: * **E get(int index)**: Obtiene un elemento partiendo de su posición (//index//). * **E set(int index, E element)**: Cambia el elemento almacenado en una posición de la lista (//index//), por otro (//element//). * **void add(int index, E element)**: Inserta un elemento (//element//) en la lista en una posición concreta (//index//), desplazando los existentes. Si le pasamos solo el elemento (//element//) la inserción la hará al final de la lista. * **E remove(int index)**: Elimina un elemento indicando su posición (//index//) en la lista. * **boolean addAll(int index, Collection c)**: Inserta una colección pasada por parámetro en una posición de la lista, desplazando el resto de elementos. * **int indexOf(Object o)**: Devuelve la posición (índice) de un elemento en la lista o -1 si el elemento no está en la lista. * **int lastIndexOf(Object o)**: Devuelve la última ocurrencia del objeto en la lista (dado que la lista si puede almacenar duplicados) o -1 si el elemento no está en la lista. * **List subList(int from, int to)**: Genera una sublista (una vista parcial de la lista) con los elementos comprendidos entre la posición inicial (//from//, incluida) y la posición final (//to//, no incluida). Ten en cuenta que los elementos de una lista **empiezan a numerarse por 0**. Es decir, que el primer elemento de la lista es el 0. Ten en cuenta también que //List// es una interfaz genérica, por lo que corresponde con el tipo base usado como parámetro genérico al crear la lista. Las listas se utilizan de forma muy parecida a los conjuntos. Veamos un ejemplo de creación y utilización de un //ArrayList//: List lista = new ArrayList<>(); lista.add(1); // Añade un elemento al final de la lista. lista.add(3); // Añade otro elemento al final de la lista. lista.add(1,2); // Añade en la posición 1 el elemento 2. lista.add(lista.get(1)+lista.get(2)); // Suma los valores contenidos en la posición 1 y 2, y lo agrega al final. lista.remove(0); // Elimina el primer elementos de la lista. for (Integer elemento: lista) System.out.println("Elemento:" + elemento); // Muestra la lista. } Elemento:2 Elemento:3 Elemento:5 Igual que con los conjuntos, podemos usar el método //of()// para crear listas inmutables: lista = List.of(1, 3, 5, 67); Vamos a ver otro ejemplo de como utilizar //indexOf// para encontrar el primer elemento en una lista y sustituirlo: lista.clear(); lista.add(1); lista.add(2); lista.add(3); lista.add(2); lista.set(lista.indexOf(2), 20); for (Integer elemento: lista) { System.out.println("Elemento:" + elemento); // Muestra la lista. } Elemento:1 Elemento:20 Elemento:3 Elemento:2 ¿En qué se diferencia un //LinkedList// de un //ArrayList//? Los //LinkedList// utilizan listas doblemente enlazadas. Los elementos de la lista se encapsulan en los llamados nodos. Los nodos van enlazados unos a otros para no perder el orden y no limitar el tamaño de almacenamiento. Tener un doble enlace significa que en cada nodo se almacena la información de cuál es el siguiente nodo y además, de cuál es el nodo anterior. Si un nodo no tiene nodo siguiente o nodo anterior, se almacena **null** (o nulo) para ambos casos. Los //ArrayList// se implementan utilizando //arrays// que se van redimensionando conforme se necesita más espacio o menos. La redimensión es transparente a nosotros (no nos enteramos cuando se produce), pero eso redunda en una diferencia de rendimiento notable dependiendo del uso. Los //ArrayList// son más rápidos en cuanto a acceso a los elementos (acceder a un elemento según su posición es más rápido en un //array// que en una lista doblemente enlazada, ya que en esta última hay que recorrer la lista). En cambio, eliminar un elemento implica muchas más operaciones en un //array// que en una lista enlazada de cualquier tipo. ¿Y esto que quiere decir? Que si se van a realizar muchas operaciones de eliminación de elementos sobre la lista, conviene usar una lista enlazada (//LinkedList//), pero si no se van a realizar muchas eliminaciones, sino que solamente se van a insertar y consultar elementos por posición, conviene usar una lista basada en //arrays// redimensionados (//ArrayList//). ===== Maps ===== Los //maps// son un tipo de //array// especial (llamado **asociativo**) que permite almacenar pares de valores conocidos como clave y valor. La clave se utiliza para acceder al valor, como una entrada de un diccionario permite acceder a su definición. En Java existe la interfaz [[https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Map.html|Map]], que define los métodos que deben tener los //maps//, y existen tres implementaciones principales de dicha interfaz: * [[https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/HashMap.html|HashMap]]: No existe orden entre las claves y permite un sola clave nula. * [[https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/TreeMap.html|TreeMap]]: Mantiene ordenados los datos según la clave y no permite claves nulas. * [[https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/LinkedHashMap.html|LinkedHashMap]]: Mantiene ordenados los datos según orden de inserción y permite una sola clave nula. Los //maps// utilizan clases genéricas para dar extensibilidad y flexibilidad, y permiten definir un tipo base para la clave, y otro tipo diferente para el valor. Veamos un ejemplo de cómo crear un //HashMap//, que es extensible los otros dos tipos de mapas: Map diccionario = new HashMap<>(); El //map// anterior permite usar cadenas como llaves y almacenar de forma asociada a cada llave un número entero. Veamos los métodos principales de la interfaz //Map// , disponibles en todas las implementaciones. En los ejemplos, //V// es el tipo base usado para el valor y //K// el tipo base usado para la llave: * **V put(K key, V value)**: Asocia el valor (//value//) con la clave (//key//) en el //map//. Si la clave no existe en el //map// crea un nuevo par clave-valor. Si ya existe, reemplazará el valor. * **V get(Object key)**: Obtiene el valor asociado a una clave (//key//) ya almacenada en el mapa. Si no existe la clave, retornará //null//. * **V remove(Object key)**: Elimina la clave (//key//) y el valor (//value//) asociado. Retorna el valor asociado a la clave, por si lo queremos utilizar para algo, o //null//, si la clave no existe. * **boolean containsKey(Object key)**: Devuelve //true// si el //map// tiene almacenada la clave (//key//). En caso contrario devolverá //false//. * **boolean containsValue(Object value)**: Devuelve //true// si el //map// tiene almacenada el valor (//value//). En caso contrario devolverá //false//. * **int size()**: Devuelve el número de pares clave-valor almacenado en el //map//. * **boolean isEmpty()**: Devuelve //true// si el //map// está vacío, //false// en cualquier otro caso. * **void clear()**: Vacía el //map//. * **Set keySet()**: Devuelve el conjunto de claves contenidas en el //map//. Veamos un ejemplo de la utilización de algunos de los métodos anteriores: Map diccionario = new HashMap(); int valor; diccionario.put("edad", 18); //añadimos el par clave = "edad" / valor = 18 diccionario.put("año", 2022); //añadimos el par clave = "año" / valor = 2022 diccionario.put("edad", 34); //Sustituimo el valor de la clave "edad" //Recorremos el HashMap y mostramos las claves y los valores for (String clave : diccionario.keySet()) { valor = diccionario.get(clave); System.out.println(clave + ": " + valor); } edad: 34 año: 2022 Igual que con los conjuntos y las listas, también podemos crear //maps// inmutables con el método //Map.of()//. En este caso, tenemos que ir pasándole pares de clave-valor, según lo establecido cuando creamos el //Map//: diccionario = Map.of("edad", 18, "año", 2022); ===== Ejercicios ===== **Ejercicio 1.a** Crea un //array// de enteros con los siguientes números: {1, 2, 3, 5, 8, 13, 21, 34, 55} Haz que se muestre por pantalla el cuadrado de cada número del //array// utilizando un bucle //for// sencillo. **Ejercicio 1.b** Utiliza un bucle //for-each// para mostrar el cuadrado de los números del ejercicio anterior. **Ejercicio 1.c** Haz que el //array// del ejercicio 1.a se modifique con el cuadrado de cada número y muéstralo por pantalla. **Ejercicio 2.a** Crea un //array// de 5 enteros. Haz que la aplicación pida al usuario cada elemento del array y, al acabar, muéstralo por pantalla. **Ejercicio 2.b** Modifica el ejercicio anterior para que la aplicación para que el usuario pueda elegir el tamaño del //array//. **Ejercicio 2.c** Haz que la inicialización del //array// y mostrar los elementos del ejercicio anterior sean dos funciones separadas. **Ejercicio 3.a** Crea una matriz de 5x8 de //booleans// con los siguientes valores: {true, true, true, true, true}, {true, false, false, false, true}, {true, false, false, false, true}, {true, false, false, false, true}, {true, false, false, false, true}, {true, false, false, false, true}, {true, false, false, false, true}, {true, true, true, true, true} Haz que se muestre por pantalla la matriz según la siguiente regla: * Si el valor es //true//, se mostrará un 0 (cero) * Si el valor es //false// se mostrará un espacio en blanco. Ejemplo de salida: OOOOO O O O O O O O O O O O O OOOOO **Ejercicio 3.b** Crea otra matriz a partir del ejercicio anterior con los siguientes valores: {false, false, false, false, true}, {false, false, false, true, true}, {false, false, true, false, true}, {false, true, false, false, true}, {true, false, false, false, true}, {false, false, false, false, true}, {false, false, false, false, true}, {false, false, false, false, true} Muestra las dos matrices por pantalla según las reglas anteriores. **Ejercicio 3.c** Haz una aplicación que pida un número binario al usuario. El programa mostrará los bits (empezando por el de la derecha) utilizando las matrices anteriores. La aplicación mostrará la frase "No se puede representar el bit" si éste no es un 1 o un 0. **Ejercicio 3.d** Modifica la aplicación anterior para que el usuario introduzca por pantalla un número decimal y la aplicación muestre el número en binario (utilizando los métodos anteriores). **Ejercicio 4.a** Crea un conjunto de números enteros. Pide por pantalla el tamaño del conjunto, y haz que el usuario vaya metiendo números al conjunto hasta alcanzar el tamaño introducido. Muestra el conjunto final por pantalla. **Ejercicio 4.b** Modifica la aplicación anterior para que el tamaño del conjunto sea variable. El usuario introducirá números en el conjunto hasta que escriba un 0 (cero). El 0 no debe formar parte del conjunto final. **Ejercicio 4.c** Haz que la aplicación muestre la frase "El número está repetido y no se añadirá al conjunto" si el usuario introduce un número que ya existe en el conjunto. **Ejercicio 4.d** Separa los números en dos conjuntos: pares e impares. Muestra los dos conjuntos resultantes por pantalla. **Ejercicio 5.a** Haz un programa que contenga una lista de alumnos con los siguientes valores: ("Ana", "Pedro", "Antonio", "Amparo", "Luis", "María") Por cada alumno, el programa pedirá la nota y mostrará la frase "El alumno nombre_alumno está aprobado con nota_alumno" si la nota es mayor o igual a 5. **Ejercicio 5.b** Modifica el programa anterior para que los alumnos aprobados se añadan a otra lista. Muestra la lista de aprobados cuando se hayan introducido todas las notas (no hace falta mostrar la frase anterior por cada alumno aprobado). **Ejercicio 5.c** Modifica el ejercicio anterior para que la aplicación muestre un menú con las opciones "Introducir alumno" y "Salir". Cuando se introduce un alumno, el programa pedirá el nombre del alumno y su nota. Si la nota es mayor o igual a 5, se añadirá a una lista de alumnos aprobados. En caso contrario, se añadirá a la lista de suspendidos. Cuando el usuario elija la opción "Salir", el programa mostrará ambas listas (aprobados y suspendidos). **Ejercicio 6** Crea un //Map// con los nombres de los alumnos y sus notas. El programa deberá pedir la nota de un listados predefinido de alumnos (puedes utilizar los anteriores) y a continuación mostrará el //Map// por pantalla.