Tuesday, August 26, 2008

LINQ SelectMany Usage

Select and SelectMany are two projection operators in LINQ. Select simply goes through a sequence of elements (IEnumerable), and for each input element it exactly projects one output element. On the contrast, SelectMany can projects 0, 1 or multiple elements for each input elements. It works something like this, for each input element, I can do something based on the element context or just fixed logic, then produces some new elements to return.

For instance, each instructor offers many courses in the school, Instructors.SelectMany(i => i.Courses) means for each instructor in the Instructors list, return his/her Courses, so you get all courses for all instructors.

A SelectMany overloading method accepts the second parameter which gives us more power to control the output. Let’s look at a statement:

Instructors.SelectMany(i => i.Courses, (i, c) => new { InstructorName = i.Name, CourseName = c } ).

The first parameter "i => i.Courses" will get all Courses for each instructor, and transform it to the second parameter, so inside "(i, c)", "i" is the original input element just like what we have in "i => i.Courses", and "c" here is each element sent from "i.Courses". What the statement does is go through each instructor’s courses, and return the instructor’s name and course name with anonymous type of object.

Following code example demos a few usages of SelectMany, including how you can join a sequence to another sequence, and do you your customized logic inside the lambda function:
using System;
using System.Linq;
using System.Collections.Generic;

class Program
{
    class Instructor
    {
        public string Name { get; set; }
        public string DeptID { get; set; }
        public string[] Courses { get; set; }
    }

    class Department
    {
        public string ID { get; set; }
        public string Name { get; set; }
    }

    static void Main()
    {
        var departments = new List<Department>()
            {
                new Department() { ID = "EE", Name = "Electrical Engineering" },
                new Department() { ID = "CS", Name = "Computer Science" },
                new Department() { ID = "ME", Name = "Mechanical Engineering" }
            };
        var instructors = new List<Instructor>()
            {
                new Instructor() { Name = "EE1", DeptID = "EE", Courses = new string [] { "EE101", "EE102", "EE103" } },
                new Instructor() { Name = "EE2", DeptID = "EE", Courses = new string [] { "EE201", "EE202", "EE203" } },
                new Instructor() { Name = "CS1", DeptID = "CS", Courses = new string [] { "CS101", "CS102", "CS103" } },
                new Instructor() { Name = "CS2", DeptID = "CS", Courses = new string [] { "CS201", "CS202", "CS203" } },
                new Instructor() { Name = "ME1", DeptID = "ME", Courses = new string [] { "ME101", "ME102", "ME103" } },
                new Instructor() { Name = "ME2", DeptID = "ME", Courses = new string [] { "ME201", "ME202", "ME203" } },
            };
        var CSInstructors = instructors.Where(i => i.DeptID == "CS").Select(i => "CS Instructor: " + i.Name).ToArray();
        // CSInstructors: { "CS Instructor: CS1", "CS Instructor: CS2" }
  
        var CSCourses = instructors.Where(i => i.DeptID == "CS").SelectMany(i => i.Courses).ToArray();
        // CSCourses.Count = 6
        // CSCourses: { "CS101", "CS102", "CS103", "CS201", "CS202", "CS203" }

        var CSInstructorCourses = instructors.Where(i => i.DeptID == "CS").
            SelectMany(i => i.Courses, (i, c) => i.Name + ":" + c).ToArray();
        // CSInstructorCourses.Count = 6
        // CSInstructorCourses: { "CS1:CS101", "CS1:CS102", "CS1:CS103", "CS2:CS201", "CS2:CS202", "CS2:CS203" } 

        var instructorDepartment = instructors.SelectMany(i => departments.Where(d => d.ID == i.DeptID),
            (i, d) => i.Name + ":" + d.Name).ToArray(); 
        // instructorDepartment.Count = 6
        // instructorDepartment: { "EE1:Electrical Engineering", "EE2:Electrical Engineering", "CS1:Computer Science" ... }

        var departmentCourses = instructors.SelectMany(
            (i, index) => i.Courses.Select(c => new { Index = index, CourseName = c}),
            (i, c) => new { Course = c.CourseName, Instructor = i.Name, 
                DeptName= departments.Find(d => d.ID == i.DeptID).Name, Index = c.Index, }).ToArray();
        /* departmentCourses.Count = 18
         * departmentCourses: 
            { Course = "EE101", Instructor = "EE1", DeptName = "Electrical Engineering", Index = 0 }
            { Course = "EE102", Instructor = "EE1", DeptName = "Electrical Engineering", Index = 0 }
            { Course = "EE103", Instructor = "EE1", DeptName = "Electrical Engineering", Index = 0 }
            { Course = "EE201", Instructor = "EE2", DeptName = "Electrical Engineering", Index = 1 }
            { Course = "EE201", Instructor = "EE2", DeptName = "Electrical Engineering", Index = 1 }
            ...
            { Course = "ME202", Instructor = "ME2", DeptName = "Mechanical Engineering", Index = 5 }
            { Course = "ME203", Instructor = "ME2", DeptName = "Mechanical Engineering", Index = 5 }
         */

        var courses101 = instructors.SelectMany(
            i => {
                List<string> filterCourses = new List<string>();
                foreach(var course in i.Courses)
                {
                    if (course.EndsWith("101"))
                        filterCourses.Add(course);
                }
                return filterCourses;
            }, 
            (i, c) => {
                var dept = departments.Find(d => d.ID == i.DeptID);
                var deptName = (dept == null) ? "N/A" : dept.Name;
                return new { Course = c, Instructor = i.Name, DeptName = deptName };
            }).ToArray();
        /* courses101.Count = 3
         * courses101:
            { Course = "EE101", Instructor = "EE1", DeptName = "Electrical Engineering" }
            { Course = "CS101", Instructor = "CS1", DeptName = "Computer Science" }
            { Course = "ME101", Instructor = "ME1", DeptName = "Mechanical Engineering" }
         */

        Console.Read();
    }
}