Colecciones en Java — práctica con un contador de palabras, claves propias y colas de préstamos (y el HashMap que imprimía en un orden que no entendía)
Mi primer contador de palabras funcionó a la primera… o eso creí. Metí el texto, recorrí las palabras con un HashMap, imprimí el resultado y me quedé mirando la pantalla: las palabras salían revueltas. Ni por orden alfabético, ni por orden de aparición, ni por frecuencia. Pensé que había mezclado los datos sin querer y estuve media hora revisando el bucle. El bucle estaba bien. Lo que yo esperaba de un HashMap (un orden) es justo lo que un HashMap no promete.
Esa es la gracia de las colecciones: cada una promete cosas distintas, y la mayoría de los errores vienen de pedirle a una lo que prometía otra. Por eso hoy practicamos con tres programas en los que vas a comparar, romper y arreglar.
Si ya has leído la teoría de colecciones, este artículo es para aplicarla: contar palabras con un Map y sus tres sabores, ver qué pasa cuando tus propios objetos son claves de un HashSet o un HashMap, y montar colas y pilas de préstamos con Deque y PriorityQueue. Son programas de dificultad media. Cada uno trae el código comentado, la salida y las dudas que suelen surgir.
Tabla de Contenidos
Antes de empezar
Conviene tener claro:
- Que
Listadmite repetidos y guarda el orden;Setno admite repetidos;Mapasocia claves con valores. - Que
HashSetyHashMapno garantizan ningún orden;LinkedHash...mantiene el de inserción yTree...ordena. - Que
HashSetyHashMapusanhashCode()para elegir el cubo yequals()para decidir si ya está. - Que
merge(clave, 1, Integer::sum)sirve para contar.
Si alguno te suena dudoso, repasa la teoría antes de seguir.
Los tres programas son independientes. Crea una carpeta para cada uno y compílalos por separado.
Programa 1: contador de palabras con Map
Contar cuántas veces aparece cada palabra de un texto es el «hola mundo» de los Map. Lo haremos con los tres sabores a la vez para ver qué cambia.
La idea
Una palabra es una clave y su número de apariciones, el valor. Por cada palabra del texto: si ya estaba en el mapa, sumamos 1; si no, la añadimos con 1. Eso se puede escribir de dos maneras: con merge (corto) o con getOrDefault (explícito), y comprobaremos que dan lo mismo. Además, lo guardaremos a la vez en un HashMap, un TreeMap y un LinkedHashMap para comparar cómo se imprime cada uno.
El texto es una frase de Hamlet en inglés (sin tildes ni eñes, y de dominio público). Puedes cambiarla por cualquier otra, pero mantén las palabras separadas por espacios y en minúsculas.
Antes de ejecutar, piensa en qué orden saldrá cada mapa. ¿Alfabético? ¿De aparición? ¿Otro? Después lo comprobamos.
El código
Palabras.java
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class Palabras {
public static void main(String[] args) {
String texto = "to be or not to be that is the question whether it is nobler in the mind to suffer";
String[] palabras = texto.split(" ");
Map<String, Integer> hash = new HashMap<>();
Map<String, Integer> arbol = new TreeMap<>();
Map<String, Integer> enlazado = new LinkedHashMap<>();
Map<String, Integer> manual = new HashMap<>();
for (String p : palabras) {
hash.merge(p, 1, Integer::sum);
arbol.merge(p, 1, Integer::sum);
enlazado.merge(p, 1, Integer::sum);
manual.put(p, manual.getOrDefault(p, 0) + 1); // la misma cuenta, sin merge
}
System.out.println("Palabras en total: " + palabras.length);
System.out.println("Palabras distintas: " + hash.size());
System.out.println("HashMap: " + hash);
System.out.println("TreeMap: " + arbol);
System.out.println("LinkedHashMap: " + enlazado);
System.out.println("¿getOrDefault da lo mismo que merge? " + manual.equals(hash));
String masRepetida = null;
int max = 0;
for (Map.Entry<String, Integer> e : arbol.entrySet()) {
if (e.getValue() > max) {
max = e.getValue();
masRepetida = e.getKey();
}
}
System.out.println("La más repetida: " + masRepetida + " (" + max + " veces)");
List<String> unaVez = new ArrayList<>();
for (Map.Entry<String, Integer> e : arbol.entrySet()) {
if (e.getValue() == 1) {
unaVez.add(e.getKey());
}
}
System.out.println("Aparecen una sola vez: " + unaVez);
}
}
Salida
Palabras en total: 19
Palabras distintas: 14
HashMap: {mind=1, or=1, be=2, question=1, in=1, nobler=1, is=2, it=1, the=2, that=1, suffer=1, not=1, whether=1, to=3}
TreeMap: {be=2, in=1, is=2, it=1, mind=1, nobler=1, not=1, or=1, question=1, suffer=1, that=1, the=2, to=3, whether=1}
LinkedHashMap: {to=3, be=2, or=1, not=1, that=1, is=2, the=2, question=1, whether=1, it=1, nobler=1, in=1, mind=1, suffer=1}
¿getOrDefault da lo mismo que merge? true
La más repetida: to (3 veces)
Aparecen una sola vez: [in, it, mind, nobler, not, or, question, suffer, that, whether]
Compruébalo a mano:
- Total y distintas: el texto tiene 19 palabras, pero solo 14 distintas. El mapa tiene 14 entradas porque cada clave aparece una sola vez.
- **
TreeMap:** alfabético (be, in, is, it, mind…). Compara las claves con su orden natural. - **
LinkedHashMap: por orden de primera aparición** (to, be, or, not…). La repetición detono cambia su posición. - **
HashMap: otro orden distinto, que no es ni el alfabético ni el de aparición. Es el que resulta de los códigos hash, y es el mismo cada vez que ejecutas este programa con estas** palabras, pero Java no lo promete. No te apoyes en él. - **
toaparece 3 veces**,be,isythe2, y el resto una sola. - **
manual.equals(hash):** dos mapas son iguales si tienen las mismas parejas, sin importar el orden ni la implementación. Por esogetOrDefaultymergedan el mismo resultado.
Integer::sum es una referencia a método: «la función que suma dos enteros». merge la usa solo si la clave ya existía, para combinar el valor viejo con el 1 nuevo; si no existía, guarda el 1 tal cual.
Preguntas que te puedes hacer
**¿Por qué if (e.getValue() > max) y no >=?** Porque recorremos el TreeMap por orden alfabético: con > nos quedamos con la primera palabra que alcanza el máximo y, si hay empate, gana la alfabéticamente menor. Con >= ganaría la última del empate.
**¿Por qué recorro con entrySet() y no con keySet() y luego get?** Porque entrySet() te da la clave y el valor a la vez, en una sola pasada. Con keySet() tendrías que buscar cada valor con get, que es trabajo repetido.
¿Cuál de los tres uso? HashMap si solo quieres contar y buscar; TreeMap si luego vas a mostrar las palabras ordenadas; LinkedHashMap si quieres respetar el orden en que aparecieron.
¿Y si el texto tiene mayúsculas o signos de puntuación? Entonces The y the serían claves distintas. Lo normal es pasar el texto a minúsculas con toLowerCase() y separar con una expresión regular, por ejemplo split("\\W+"), que corta por cualquier cosa que no sea una letra.
Hacer mapa.put(p, mapa.get(p) + 1) sin comprobar antes si la clave existe. La primera vez get(p) devuelve null, y al sumarle 1 el programa se cae con NullPointerException. Por eso existen getOrDefault y merge.
Pruébalo tú
- Cambia el texto por otro tuyo y comprueba que el
TreeMapsigue saliendo alfabético y elLinkedHashMappor primera aparición. - Añade al programa una última línea que imprima las palabras que aparecen más de una vez, en orden alfabético.
Programa 2: tus objetos como claves
El programa que explica por qué el conjunto del principio de la teoría decía tener dos veces el mismo libro. Aquí lo ponemos con todos los matices.
La idea
Vamos a crear cinco versiones de «libro identificado por su ISBN». Con las cuatro primeras meteremos dos libros con el mismo ISBN en un HashSet:
LibroNada: sinequals()nihashCode().LibroEquals: soloequals().LibroCompleto:equals()yhashCode().LibroRecord: unrecord, que los genera solo.LibroMutable: cumple el contrato, pero su campotitulose puede cambiar después de guardarlo.
Para cada uno, un método probar imprime si son equals, si tienen el mismo hashCode y cuántos elementos acaba teniendo el conjunto. Después usamos LibroCompleto como clave de un HashMap para contar ejemplares, y por último vemos qué pasa cuando la clave cambia después de guardarla.
Antes de ejecutar, apunta qué tamaño crees que dará cada conjunto. La segunda fila (solo equals) es la que más sorprende.
El código
Claves.java
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
public class Claves {
static class LibroNada {
final String isbn;
LibroNada(String isbn) { this.isbn = isbn; }
}
static class LibroEquals {
final String isbn;
LibroEquals(String isbn) { this.isbn = isbn; }
@Override
public boolean equals(Object o) {
return o instanceof LibroEquals otro && isbn.equals(otro.isbn);
}
}
static class LibroCompleto {
final String isbn;
LibroCompleto(String isbn) { this.isbn = isbn; }
@Override
public boolean equals(Object o) {
return o instanceof LibroCompleto otro && isbn.equals(otro.isbn);
}
@Override
public int hashCode() {
return Objects.hash(isbn);
}
@Override
public String toString() {
return "Libro " + isbn;
}
}
record LibroRecord(String isbn) { }
static class LibroMutable {
private String titulo;
LibroMutable(String titulo) { this.titulo = titulo; }
void setTitulo(String titulo) { this.titulo = titulo; }
@Override
public boolean equals(Object o) {
return o instanceof LibroMutable otro && titulo.equals(otro.titulo);
}
@Override
public int hashCode() {
return titulo.hashCode();
}
@Override
public String toString() {
return titulo;
}
}
static void probar(String nombre, Object a, Object b) {
Set<Object> conjunto = new HashSet<>();
conjunto.add(a);
conjunto.add(b);
System.out.println(nombre + ": equals=" + a.equals(b)
+ ", mismo hashCode=" + (a.hashCode() == b.hashCode())
+ ", tamaño del conjunto=" + conjunto.size());
}
public static void main(String[] args) {
System.out.println("== Dos objetos con el mismo ISBN ==");
probar("Sin nada ", new LibroNada("L-101"), new LibroNada("L-101"));
probar("Solo equals ", new LibroEquals("L-101"), new LibroEquals("L-101"));
probar("equals+hash ", new LibroCompleto("L-101"), new LibroCompleto("L-101"));
probar("record ", new LibroRecord("L-101"), new LibroRecord("L-101"));
System.out.println("== Como clave de un HashMap ==");
Map<LibroCompleto, Integer> copias = new HashMap<>();
copias.merge(new LibroCompleto("L-101"), 1, Integer::sum);
copias.merge(new LibroCompleto("L-102"), 1, Integer::sum);
copias.merge(new LibroCompleto("L-101"), 1, Integer::sum);
System.out.println("Copias: " + copias);
System.out.println("== Clave que cambia después de guardarla ==");
LibroMutable libro = new LibroMutable("Dune");
Set<LibroMutable> estante = new HashSet<>();
estante.add(libro);
System.out.println("Antes de cambiar el título: contains=" + estante.contains(libro));
libro.setTitulo("Dune (2.ª edición)");
System.out.println("Después de cambiarlo: contains=" + estante.contains(libro)
+ ", size=" + estante.size());
System.out.println("Pero al recorrerlo sigue ahí: " + estante);
}
}
Salida
== Dos objetos con el mismo ISBN ==
Sin nada : equals=false, mismo hashCode=false, tamaño del conjunto=2
Solo equals : equals=true, mismo hashCode=false, tamaño del conjunto=2
equals+hash : equals=true, mismo hashCode=true, tamaño del conjunto=1
record : equals=true, mismo hashCode=true, tamaño del conjunto=1
== Como clave de un HashMap ==
Copias: {Libro L-101=2, Libro L-102=1}
== Clave que cambia después de guardarla ==
Antes de cambiar el título: contains=true
Después de cambiarlo: contains=false, size=1
Pero al recorrerlo sigue ahí: [Dune (2.ª edición)]
Compruébalo a mano:
- Sin nada: ni
equalsni mismo hash. Dos objetos distintos para Java: tamaño 2. - **Solo
equals:**equalsdatrue, pero el hash sigue siendo distinto. ElHashSetmira primero el hash para elegir el cubo, así que ni se entera de que son iguales: tamaño 2. Es el fallo más traicionero, porque el código parece correcto. - **
equals+hashCode:** el contrato completo. Tamaño 1. - **
record:** lo mismo que el anterior, sin escribir nada. Compara todos los campos (aquí solo hay uno). - **
HashMapcon clavesLibroCompleto:** las dosL-101se reconocen como la misma clave ymergesuma:L-101=2,L-102=1. - Clave que cambia: el libro sigue dentro del conjunto (
size=1, y aparece al recorrerlo), perocontainsdafalse. Es una pérdida silenciosa.
El hashCode de LibroMutable depende de titulo. Al guardarlo, el conjunto lo colocó en el cubo que correspondía al título "Dune". Tras cambiar el título, contains calcula un hash nuevo, mira en otro cubo y no encuentra nada, aunque el objeto esté ahí.
Preguntas que te puedes hacer
**¿Por qué no basta con equals()?** Porque HashSet y HashMap primero usan hashCode() para decidir en qué cubo mirar y solo comparan con equals() los objetos que están en ese cubo. Si dos objetos iguales tienen hashes distintos, nunca se llegan a comparar.
**¿Y si pongo siempre hashCode() devolviendo 1?** Cumple el contrato, pero todos los elementos caerían en el mismo cubo y el HashSet se volvería lento, casi tan lento como recorrer una lista. El hash bueno reparte.
**¿Qué campos uso para equals() y hashCode()? Los que identifican** al objeto. Para un libro, el ISBN; para una persona, quizá el DNI. Y los mismos en los dos métodos. Objects.hash(...) y Objects.equals(...) te ayudan a escribirlos sin preocuparte de los null.
¿Cómo evito lo de la clave que cambia? Usa claves inmutables: campos final y sin setters. Un record, un String o un Integer son buenos candidatos precisamente por eso.
Sobrescribir equals() y olvidar hashCode(). Compila sin avisos, pasa las pruebas con un solo objeto y falla cuando hay dos «iguales» en un HashSet o un HashMap.
Pruébalo tú
- Añade a
LibroCompletoun segundo campotituloque no entre enequals()ni enhashCode(). Comprueba que dos libros con el mismo ISBN y distinto título siguen siendo el mismo. - Cambia
LibroRecordpara que tenga dos campos (isbnytitulo) y comprueba que ahora dos libros con el mismo ISBN y distinto título no son iguales. ¿Por qué?
Programa 3: colas y pilas de préstamos
La biblioteca también tiene colas: de personas que esperan, de libros que se devuelven y de reservas con prioridad. Tres estructuras, tres comportamientos.
La idea
- Una cola (FIFO, first in, first out): el primero que llega es el primero en salir. Es la cola de socios que esperan atención.
- Una pila (LIFO, last in, first out): el último que entra es el primero en salir. Es el carrito de libros devueltos: el que dejaste encima es el que recoges primero.
- Una cola de prioridad: no importa quién llegó antes, sale primero el más urgente. Para saber qué es «más urgente»,
ReservaimplementaComparable(lo viste en la teoría de Comparable).
Tanto la cola como la pila las hacemos con el mismo tipo, ArrayDeque, que es una cola de doble extremo (double ended queue). Usa unos métodos u otros según la quieras como cola o como pila.
push y pop actúan por el principio de la estructura; offer añade por el final y poll saca por el principio. Por eso push+pop se comportan como una pila y offer+poll como una cola.
El código
Cola.java
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.NoSuchElementException;
import java.util.PriorityQueue;
public class Cola {
record Reserva(String socio, int prioridad) implements Comparable<Reserva> {
@Override
public int compareTo(Reserva otra) {
return Integer.compare(prioridad, otra.prioridad); // menor número = más urgente
}
}
public static void main(String[] args) {
// 1. Cola (FIFO): el primero en llegar es el primero en salir
Deque<String> espera = new ArrayDeque<>();
espera.offer("Ana");
espera.offer("Luis");
espera.offer("Marta");
System.out.println("Cola: " + espera);
System.out.println("Quién va primero (peek): " + espera.peek());
System.out.println("Atiendo a (poll): " + espera.poll());
System.out.println("Cola ahora: " + espera);
// 2. Pila (LIFO): el último en llegar es el primero en salir
Deque<String> devueltos = new ArrayDeque<>();
devueltos.push("Dune");
devueltos.push("Emma");
devueltos.push("Hamlet");
System.out.println("Pila: " + devueltos);
System.out.println("Cima (peek): " + devueltos.peek());
System.out.println("Recoloco (pop): " + devueltos.pop());
System.out.println("Pila ahora: " + devueltos);
// 3. Cola de prioridad: sale primero el "menor" según compareTo
PriorityQueue<Reserva> reservas = new PriorityQueue<>();
reservas.add(new Reserva("Pablo", 3));
reservas.add(new Reserva("Lucía", 1));
reservas.add(new Reserva("Irene", 2));
reservas.add(new Reserva("Mario", 5));
System.out.println("Imprimirla no la ordena: " + reservas);
System.out.println("Orden de atención:");
while (!reservas.isEmpty()) {
System.out.println(" " + reservas.poll());
}
// 4. Colas y pilas vacías
Deque<String> vacia = new ArrayDeque<>();
System.out.println("poll() en una cola vacía: " + vacia.poll());
try {
vacia.pop();
} catch (NoSuchElementException e) {
System.out.println("pop() en una pila vacía: " + e.getClass().getSimpleName());
}
}
}
Salida
Cola: [Ana, Luis, Marta] Quién va primero (peek): Ana Atiendo a (poll): Ana Cola ahora: [Luis, Marta] Pila: [Hamlet, Emma, Dune] Cima (peek): Hamlet Recoloco (pop): Hamlet Pila ahora: [Emma, Dune] Imprimirla no la ordena: [Reserva[socio=Lucía, prioridad=1], Reserva[socio=Pablo, prioridad=3], Reserva[socio=Irene, prioridad=2], Reserva[socio=Mario, prioridad=5]] Orden de atención: Reserva[socio=Lucía, prioridad=1] Reserva[socio=Irene, prioridad=2] Reserva[socio=Pablo, prioridad=3] Reserva[socio=Mario, prioridad=5] poll() en una cola vacía: null pop() en una pila vacía: NoSuchElementException
Compruébalo a mano:
- Cola:
Ana, Luis, Martaentran en ese orden;peek()la mira sin sacarla,poll()la saca y la cola queda[Luis, Marta]. - Pila:
Dune,Emma,Hamletentran en ese orden, pero la pila se imprime conHamletdelante (la cima).pop()devuelveHamlet, el último que entró. - Cola de prioridad: al imprimirla, no sale ordenada (
Pablocon prioridad 3 aparece antes queIrenecon 2). Solo se garantiza que el primero sea el menor. Pero al sacar conpoll()sale todo en orden: 1, 2, 3, 5. - Vacías:
poll()en una cola vacía danull;pop()en una pila vacía lanzaNoSuchElementException.
Hay una clase antigua, Stack, que también funciona como pila, pero la recomendación actual es usar ArrayDeque para pilas y colas. Stack arrastra diseños de la primera versión de Java.
Preguntas que te puedes hacer
**¿Por qué peek() y poll() devuelven null y no una excepción?** Porque están pensados para el caso normal «puede que no haya nadie». Existen sus versiones que lanzan excepción (element() y remove()) para cuando una cola vacía sea un error. pop(), en cambio, sí lanza excepción.
¿Qué pasa si dos reservas tienen la misma prioridad? El orden entre ellas no está garantizado. Si el empate importa, añade un segundo criterio a compareTo (por ejemplo, la hora de llegada).
**¿Por qué una PriorityQueue no se imprime ordenada?* Por dentro es un montículo (*heap): una estructura que solo garantiza que el menor está al principio, no que todo esté ordenado. Si quieres recorrerla en orden, tienes que ir sacando con poll().
**¿Puedo recorrer una cola con for-each?** Sí, pero en el orden interno de la estructura (el que imprime). Para ArrayDeque es el esperado; para PriorityQueue, no.
Confundir add/offer con push. Si metes con add y sacas con pop, mezclas el final con el principio y el orden sale al revés de lo que esperabas.
Pruébalo tú
- Cambia
Reservapara que, a igualdad de prioridad, se atienda primero al socio cuyo nombre sea alfabéticamente menor. Pista:compareTocon dos criterios. - Haz que la cola de espera sea de prioridad con una
PriorityQueue<String>y comprueba en qué orden atiende aAna,LuisyMarta. ¿Por qué ese orden?
Errores típicos en esta práctica
- Esperar un orden de un
HashMapoHashSet: no hay ninguno garantizado. UsaLinkedHash...oTree.... - Hacer
mapa.put(p, mapa.get(p) + 1)sin comprobar si existe:NullPointerExceptionla primera vez. UsamergeogetOrDefault. - Sobrescribir solo
equals()(o solohashCode()): elHashSety elHashMapse portan mal sin avisar. - Cambiar un campo que forma parte del hash de un objeto que ya está dentro de un
HashSeto unHashMap: se pierde sin error. - Mezclar los métodos de cola y de pila (
addconpop): el orden sale invertido. - Esperar que una
PriorityQueuese imprima ordenada: solopoll()entrega en orden.
Míralo paso a paso en Python Tutor
Python Tutor ejecuta el código línea a línea y dibuja variables, arrays y referencias. Elige Java. Aquí tienes cómo trabaja por dentro un ArrayDeque: un array circular con un índice de cabeza, donde sacar el primero no obliga a mover a nadie.
public class Tutor {
public static void main(String[] args) {
String[] cola = new String[4];
int cabeza = 0;
int cuantos = 0;
String[] llegan = {"Ana", "Luis", "Marta"};
for (String s : llegan) { // offer() de tres personas
cola[(cabeza + cuantos) % cola.length] = s;
cuantos++;
}
String atendido = cola[cabeza]; // poll(): sale el primero
cola[cabeza] = null;
cabeza = (cabeza + 1) % cola.length;
cuantos--;
cola[(cabeza + cuantos) % cola.length] = "Pablo"; // offer(): ocupa el último hueco
cuantos++;
cola[(cabeza + cuantos) % cola.length] = "Irene"; // offer(): vuelve al principio
cuantos++;
System.out.println(atendido + " ya está atendido");
for (int i = 0; i < cuantos; i++) {
System.out.println(cola[(cabeza + i) % cola.length]);
}
}
}
Salida esperada:
Ana ya está atendido Luis Marta Pablo Irene
Qué observar paso a paso:
- Tras el primer
for,colatieneAna, Luis, Martay un hueco (null) al final.cabezavale 0 ycuantos, 3. - En el «poll»,
atendidorecibe"Ana", su hueco pasa anullycabezaavanza a 1. Nadie se ha movido:LuisyMartasiguen exactamente en las mismas posiciones del array. Pablose guarda en(1 + 2) % 4 = 3, el último hueco del array.Irenese guarda en(1 + 3) % 4 = 0: el índice da la vuelta y reutiliza el hueco que dejóAna. El array queda[Irene, Luis, Marta, Pablo], pero el orden real de la cola, que lee a partir decabeza, esLuis, Marta, Pablo, Irene.
Resumen
Mapasocia claves con valores;merge(clave, 1, Integer::sum)es la forma corta de contar, ygetOrDefaultla explícita.HashMapno promete orden;LinkedHashMaprespeta la inserción yTreeMapordena por clave.HashSetyHashMapexigenequals()yhashCode()coherentes en las claves: el hash elige el cubo yequalsdecide. Con solo uno de los dos, aparecen repetidos.- Las claves deben ser inmutables: si cambias lo que entra en el hash, el elemento «desaparece» de las búsquedas.
ArrayDequesirve como cola (offer/poll) y como pila (push/pop);PriorityQueuesaca siempre el menor segúnComparable(o unComparator).
Checklist: ¿lo has practicado?
- [ ] He compilado y ejecutado los tres programas.
- [ ] He comprobado a mano al menos una salida de cada programa.
- [ ] He visto que
HashMap,TreeMapyLinkedHashMapimprimen en órdenes distintos. - [ ] He reproducido el caso de «solo
equals» y entiendo por qué el conjunto da 2. - [ ] He visto que una clave que cambia deja de encontrarse.
- [ ] He comprobado que una
PriorityQueueimpresa no sale ordenada, peropoll()sí. - [ ] He mirado el programa de Python Tutor y he visto cómo el índice da la vuelta al array.
Y ahora, a por los ejercicios: ejercicios de colecciones, con tres niveles, tres fragmentos para depurar y un chuletario al final. ¡Nos vemos allí!
Siguiente paso: ejerciciosColecciones en Java — ejercicios con autores, catálogos y una fusión de bibliotecas (y la «b» que estaba en el mapa pero no aparecía)¿Te ha servido este artículo? Así puedes apoyar SergioLearns →
