Exercise Overview
This advanced exercise demonstrates how to work with complex data structures in Dart, specifically using Map<String, List<int>> to create a comprehensive student grade management system.
What You’ll Build
A system that manages student grades by subject and provides:
Grade Tracking Store multiple grades for each subject
Average Calculation Calculate the average grade for each subject
Performance Analysis Find the best and worst performing subjects
Complete Reports Generate comprehensive grade reports
Problem Statement
Given a Map containing subjects and their corresponding grades:
Map < String , List < int >> calificacionesPorMateria = {
'Matemáticas' : [ 85 , 90 , 88 ],
'Español' : [ 92 , 88 , 95 ],
'Inglés' : [ 78 , 82 , 80 ],
'Programación' : [ 95 , 98 , 96 ]
};
Create a system that:
Calculate subject averages
Compute the average grade for each subject
Find best performance
Identify the subject with the highest average
Find worst performance
Identify the subject with the lowest average
Generate complete report
Display all subjects with their grades and averages
Solution Architecture
Data Structure
Map < String , List < int >> calificacionesPorMateria = {
'Matemáticas' : [ 85 , 90 , 88 ],
'Español' : [ 92 , 88 , 95 ],
'Inglés' : [ 78 , 82 , 80 ],
'Programación' : [ 95 , 98 , 96 ]
};
We use two Maps: one to store the raw grades (Map<String, List<int>>) and another to store the calculated averages (Map<String, double>).
Function 1: Calculate Subject Averages
void promedioMateria ( Map < String , List < int >> calificacionesPorMateria, Map < String , double > promedios){
// - Calcula el promedio de cada materia
calificacionesPorMateria. forEach ((materia, calificaciones){
int suma = 0 ;
for ( var calificacion in calificaciones) {
suma += calificacion;
}
double promedio = suma / calificaciones.length;
promedios[materia] = promedio;
});
}
Understanding forEach with Maps
The forEach method on a Map provides two parameters:
materia: The key (subject name)
calificaciones: The value (list of grades)
This allows us to iterate through each subject and its grades simultaneously. // For each entry in the map:
'Matemáticas' : [ 85 , 90 , 88 ]
// materia = 'Matemáticas'
// calificaciones = [85, 90, 88]
void obtenerMejorPromedio ( Map < String , double > promedios) {
// - Encuentra la materia con el mejor promedio
double mejorPromedio = 0.0 ;
String mejorMateria = '' ;
promedios. forEach ((materia, promedio){
if (promedio > mejorPromedio) {
mejorPromedio = promedio;
mejorMateria = materia;
}
});
print ( ' \n MATERIA CON MEJOR PROMEDIO: $ mejorMateria ' );
}
This function uses a simple algorithm: start with 0.0 as the best average, then update it whenever we find a higher one.
void obtenerPeorPromedio ( Map < String , double > promedios) {
// - Encuentra la materia con el peor promedio
double peorPromedio = 100.0 ;
String peorMateria = '' ;
promedios. forEach ((materia, promedio){
if (promedio < peorPromedio) {
peorPromedio = promedio;
peorMateria = materia;
}
});
print ( ' \n MATERIA CON PEOR PROMEDIO: $ peorMateria ' );
}
We initialize peorPromedio to 100.0 (the maximum possible grade) so any actual grade will be lower.
Function 4: Display Complete Report
void reporteCompleto ( Map < String , List < int >> calificacionesPorMateria) {
// - Imprime reporte completo
print ( ' \n ==== MATERIAS CON SUS CALIFICACIONES ====' );
calificacionesPorMateria. forEach ((materia, calificaciones){
print ( 'Materia: $ materia \t - \t $ calificaciones ' );
});
print ( '' );
}
Function 5: Display Averages
void imprimirPromedios ( Map < String , double > promedios) {
print ( ' \n ==== MATERIAS CON SUS PROMEDIOS ====' );
promedios. forEach ((materia, promedio){
print ( 'Materia: $ materia \t - ${ promedio . toStringAsFixed ( 2 )} ' );
});
print ( '' );
}
The toStringAsFixed(2) method formats the decimal number to show exactly 2 decimal places, making the output more readable.
Complete Program
/* Sistema de calificaciones por materia
Map<String, List<int>> calificacionesPorMateria = {
'Matemáticas': [85, 90, 88],
'Español': [92, 88, 95],
'Inglés': [78, 82, 80],
'Programación': [95, 98, 96]
};
- Calcula el promedio de cada materia
- Encuentra la materia con el mejor promedio
- Encuentra la materia con el peor promedio
- Imprime reporte completo
*/
void promedioMateria ( Map < String , List < int >> calificacionesPorMateria, Map < String , double > promedios){
// - Calcula el promedio de cada materia
calificacionesPorMateria. forEach ((materia, calificaciones){
int suma = 0 ;
for ( var calificacion in calificaciones) {
suma += calificacion;
}
double promedio = suma / calificaciones.length;
promedios[materia] = promedio;
});
}
void obtenerMejorPromedio ( Map < String , double > promedios) {
// - Encuentra la materia con el mejor promedio
double mejorPromedio = 0.0 ;
String mejorMateria = '' ;
promedios. forEach ((materia, promedio){
if (promedio > mejorPromedio) {
mejorPromedio = promedio;
mejorMateria = materia;
}
});
print ( ' \n MATERIA CON MEJOR PROMEDIO: $ mejorMateria ' );
}
void obtenerPeorPromedio ( Map < String , double > promedios) {
// - Encuentra la materia con el peor promedio
double peorPromedio = 100.0 ;
String peorMateria = '' ;
promedios. forEach ((materia, promedio){
if (promedio < peorPromedio) {
peorPromedio = promedio;
peorMateria = materia;
}
});
print ( ' \n MATERIA CON PEOR PROMEDIO: $ peorMateria ' );
}
void reporteCompleto ( Map < String , List < int >> calificacionesPorMateria) {
// - Imprime reporte completo
print ( ' \n ==== MATERIAS CON SUS CALIFICACIONES ====' );
calificacionesPorMateria. forEach ((materia, calificaciones){
print ( 'Materia: $ materia \t - \t $ calificaciones ' );
});
print ( '' );
}
void imprimirPromedios ( Map < String , double > promedios) {
print ( ' \n ==== MATERIAS CON SUS PROMEDIOS ====' );
promedios. forEach ((materia, promedio){
print ( 'Materia: $ materia \t - ${ promedio . toStringAsFixed ( 2 )} ' );
});
print ( '' );
}
void main () {
Map < String , List < int >> calificacionesPorMateria = {
'Matemáticas' : [ 85 , 90 , 88 ],
'Español' : [ 92 , 88 , 95 ],
'Inglés' : [ 78 , 82 , 80 ],
'Programación' : [ 95 , 98 , 96 ]
};
Map < String , double > promedios = {};
promedioMateria (calificacionesPorMateria, promedios);
imprimirPromedios (promedios);
obtenerMejorPromedio (promedios);
obtenerPeorPromedio (promedios);
reporteCompleto (calificacionesPorMateria);
}
Sample Output
==== MATERIAS CON SUS PROMEDIOS ====
Materia: Matemáticas - 87.67
Materia: Español - 91.67
Materia: Inglés - 80.00
Materia: Programación - 96.33
MATERIA CON MEJOR PROMEDIO: Programación
MATERIA CON PEOR PROMEDIO: Inglés
==== MATERIAS CON SUS CALIFICACIONES ====
Materia: Matemáticas - [85, 90, 88]
Materia: Español - [92, 88, 95]
Materia: Inglés - [78, 82, 80]
Materia: Programación - [95, 98, 96]
Key Concepts
Map Data Structure Maps store key-value pairs for efficient data lookup
Nested Collections Map<String, List<int>> combines Maps and Lists
forEach Method Iterate through Map entries with key-value pairs
Data Aggregation Calculate statistics from collections of data
Advanced Concepts
Why use two separate Maps?
We use two Maps because:
calificacionesPorMateria : Stores the raw data (all individual grades)
promedios : Stores the calculated averages
This separation allows us to:
Keep the original data intact
Access both raw grades and averages when needed
Recalculate averages if grades change
// Original data preserved
print (calificacionesPorMateria[ 'Matemáticas' ]); // [85, 90, 88]
// Calculated average available
print (promedios[ 'Matemáticas' ]); // 87.67
Alternative: Using Map methods
Dart provides functional programming methods that can simplify the code: // Using reduce for sum
double calcularPromedio ( List < int > calificaciones) {
int suma = calificaciones. reduce ((a, b) => a + b);
return suma / calificaciones.length;
}
// Using map to transform values
void promedioMateria ( Map < String , List < int >> calificaciones, Map < String , double > promedios){
calificaciones. forEach ((materia, notas){
promedios[materia] = calcularPromedio (notas);
});
}
In a production system, consider: void promedioMateria ( Map < String , List < int >> calificaciones, Map < String , double > promedios){
calificaciones. forEach ((materia, notas){
// Check for empty lists
if (notas.isEmpty) {
promedios[materia] = 0.0 ;
return ;
}
int suma = 0 ;
for ( var nota in notas) {
suma += nota;
}
promedios[materia] = suma / notas.length;
});
}
Practice Challenges
Challenge 1: Overall Average Add a function to calculate the overall average across all subjects.
Challenge 2: Grade Distribution Create a function that shows how many grades fall into different ranges (90-100, 80-89, etc.).
Challenge 3: Student Class Expand the system to manage multiple students, each with their own subject grades.
Challenge 4: Grade Input Add functionality to let users input new subjects and grades interactively.
Enhanced Version: Multiple Students
Click to see an enhanced version managing multiple students
void main () {
// Map of students, each with their subject grades
Map < String , Map < String , List < int >>> estudiantes = {
'Juan' : {
'Matemáticas' : [ 85 , 90 , 88 ],
'Español' : [ 92 , 88 , 95 ],
},
'María' : {
'Matemáticas' : [ 95 , 92 , 98 ],
'Español' : [ 88 , 90 , 92 ],
}
};
// Calculate average for each student in each subject
estudiantes. forEach ((estudiante, materias){
print ( ' \n === ESTUDIANTE: $ estudiante ===' );
Map < String , double > promedios = {};
promedioMateria (materias, promedios);
imprimirPromedios (promedios);
});
}
Best Practices
Type Safety Always specify types: Map<String, List<int>>
Immutability Consider using final for maps that won’t be reassigned
Null Safety Check for empty collections before operations
Clear Naming Use descriptive names like calificacionesPorMateria