Skip to main content

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:
1

Calculate subject averages

Compute the average grade for each subject
2

Find best performance

Identify the subject with the highest average
3

Find worst performance

Identify the subject with the lowest average
4

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;
  });
}
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]

Function 2: Find Best Performance

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('\nMATERIA 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.

Function 3: Find Worst Performance

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('\nMATERIA 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('\nMATERIA 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('\nMATERIA 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

We use two Maps because:
  1. calificacionesPorMateria: Stores the raw data (all individual grades)
  2. 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
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

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

Build docs developers (and LLMs) love