SelectMany: Obtener elementos de una lista de listas

Tras tanto tiempo sin actualizar el blog, vuelvo con algo sencillito, pero que no he utilizado hasta hace poco: ¿Cómo obtener con LINQ los elementos de una lista de listas, con sintaxis de métodos? Es decir, obtener todos los empleados llamados Juan contenidos en CompaniesDirectory:

static List<Company> CompaniesDirectory;
class Employee
{
    public string Name { get; set; }
}
class Company
{
    public IEnumerable<Employee> Staff { get; set; }
}

Con sintaxis de consulta (Query syntax) sería algo así:

var juanEmployees
    = from c in CompaniesDirectory
        from e in c.Staff
        where e.Name == "Juan"
        select e;

¿Y con sintaxis de método (Method syntax)? Veamos el código:

// Opcion 1: Retorna IEnumerable<IEnumerable<Employee>>
var juanEmployeesFail
    = CompaniesDirectory
      .Select(c => c.Staff.Where(e => e.Name == "Juan"));
// Opcion 2: Retorna IEnumerable<Employee>
var juanEmployeesOk
    = CompaniesDirectory
      .SelectMany(c => c.Staff.Where(e => e.Name == "Juan"));

En el primer caso, lo que obtenemos es un IEnumerable<IEnumerable<Employee>>, que no es exactamente lo que queríamos. Para obtener un IEnumerable<Employee> debemos utilizar el método SelectMany, que nos permite hacer proyecciones sobre listas seleccionando su resultado.

Un ejemplo más chulo es el siguiente, en el que podemos reagrupar una estructura compleja. Definidas estas clases:

static List<Student> MyClass;
class Student
{
    public string Name { get; set; }
    public IEnumerable<Subject> Subjects { get; set; }
}
class Subject
{
    public string Name { get; set; }
    public IEnumerable<Test> Tests { get; set; }
}
class Test
{
    public DateTime Date { get; set; }
    public double Mark { get; set; }
}

Pasaremos de agrupar por Alumno/Asignatura/Nota a Asignatura/Alumno/Nota y después obtendremos las notas medias por asignatura:

// aplanar para reagrupar por asignatura
var groupPerSubject =
    MyClass
        .SelectMany(student => student.Subjects,
                    (st, sj) => new
                                    {
                                        Subject = sj.Name,
                                        StudentTests = sj.Tests
                                    })
        .GroupBy(sbj => sbj.Subject);
// obtener notas medias por asignatura
var avgPerSubject =
    groupPerSubject
        .Select(
            sbjGrp
            => new
                    {
                        Subject = sbjGrp.Key,
                        Average =
                    sbjGrp
                    .SelectMany(sbj => sbj.StudentTests)
                    .Average(t => t.Mark)
                    });

Y esto es todo por hoy. Hasta la próxima!

Esta entrada fue publicada en Dev y etiquetada , . Guarda el enlace permanente.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s