AjayK
AjayK

Reputation: 443

Merging two Lists and a dictionary to create a single dictionary

I have two Generic lists having objects of class :

class Subject
 {
   public string Code { get; set; }
   public string Name { get; set; }
 }

And another class as below :

class Student
{
    public string RollNo { get; set; }
    public string Name { get; set; 
}

And a List of type having Roll No and Subject codes as KeyValuePair for student as below :

List<KeyValuePair<string,string>>  RoleList=
           new  List<KeyValuePair<string,string>> ();

Student st1=new Student();          
Subject sb1=new Subject();
Subject sb2=new Subject();
Subject sb3=new Subject();

RoleList.Add(new KeyValuePair<string,string>(st1.RollNo,sb1.Code));
RoleList.Add(new KeyValuePair<string,string>(st1.RollNo,sb2.Code));
RoleList.Add(new KeyValuePair<string,string>(st1.RollNo,sb3.Code));

What I need is another Dictionary of type

Dictionary<string, List<Subject>> StSbList =
    new Dictionary<string, List<Subject>>();

where StSbList should have a list of subjects for given student using dictionary RoleList having all the subjects for a given Roll No. using LINQ in C# 4.0 , something like

 StSbList(st1.RollNo, {sb1,sb2,sb3});

I tried and was able merge to similar collections but struck with different type of collections. Any suggestions on any other better approach.

As rightly pointed by Douglas and Martin, both the solutions are working fine but on my test data ToLookup works faster so used the same.

Solution 1

Dictionary<string, List<Subject>> stSbList =
RoleList.GroupBy(kvp => kvp.Key)
        .ToDictionary(
            grouping => grouping.Key,
            grouping => grouping.Select(kvp => (from sub in listSub
                                                where sub.Code.Equals(kvp.Value)
                                                select sub).First()).ToList(),
            EqualityComparer<string>.Default);

Solution 2

var tSbList = RoleList.ToLookup(kvp => kvp.Key, kvp => 
             (from sub in listSub
              where sub.Code.Equals(kvp.Value)
              select sub).First());

Upvotes: 1

Views: 3771

Answers (3)

Martin Liversage
Martin Liversage

Reputation: 106836

To get you desired dicionary you can use the ToLookup extension method.

You want to keep the Subject objects but in your question you seem to throw them away and only store their code in the dictionary. I have taken the liberty to slightly modify how you create RoleList:

var subject1 = new Subject { "Code 1", "Subject 1" };
var subject2 = new Subject { "Code 2", "Subject 2" };
var subject3 = new Subject { "Code 3", "Subject 3" };
var RoleList = new  List<KeyValuePair<string, Subject>() {
  "Roll 1", subject1,
  "Roll 1", subject2,
  "Roll 1", subject3,
  "Roll 2", subject2,
  "Roll 2", subject3
}

StSbList = RoleList.ToLookup(kvp => kvp.Key, kvp => kvp.Value);

Assuming that RoleList has the following data:

Key    | Value
-------+---------
Roll 1 | subject1
Roll 1 | subject2
Roll 1 | subject3
Roll 2 | subject2
Roll 2 | subject3

The StSbList will contain the following data:

Key    | Value
-------+-----------------------------
Roll 1 | subject1, subject2, subject3
Roll 2 | subject2, subject3

Upvotes: 2

To achieve the desired dictionary it is necessary to create an associative storage for subjects. Please take a look at the code.

Student st1 = new Student
    {
        Name = "John",
        RollNo = "Roll 1"
    };

Subject sb1 = new Subject
    {
        Code = "Subject 1"
    };
Subject sb2 = new Subject
    {
        Code = "Subject 2"
    };
Subject sb3 = new Subject
    {
        Code = "Subject 3"
    };

// Create the associative dictionary for subjects.
var subjectsByCode = new [] { sb1, sb2, sb3 }.ToDictionary(subject => subject.Code, subject => subject);

// Many-to-many relationship: Student (RollNo) <=> Subject (Code).
List<KeyValuePair<string,string>> roleList = new List<KeyValuePair<string,string>>
    {
        new KeyValuePair<string, string>(st1.RollNo, sb1.Code),
        new KeyValuePair<string, string>(st1.RollNo, sb2.Code),
        new KeyValuePair<string, string>(st1.RollNo, sb3.Code)
    };

// Create Student RollNo => List of Subjects dictionary.
Dictionary<string, List<Subject>> studentRollNoToSubjects = roleList
    .GroupBy(pair => pair.Key) // Group by RollNo
    .ToDictionary(group => group.Key, // Key of the dictionary is RollNo.
        group => group.Select(pair => subjectsByCode[pair.Value]).ToList()); // List of subjects is created using subjects-by-code dictionary.

Upvotes: 2

Douglas
Douglas

Reputation: 54887

Your question isn’t really clear. If you’re asking for an example of how to populate your dictionary, here’s one:

Dictionary<string, List<Subject>> stSbList = new Dictionary<string, List<Subject>>();
stSbList.Add(st1.RollNo, new List<Subject> { sb1, sb2, sb3 });
stSbList.Add(st2.RollNo, new List<Subject> { sb1, sb3 });
stSbList.Add(st3.RollNo, new List<Subject> { sb1, sb2 });

More succinctly using nested collection initializers:

Dictionary<string, List<Subject>> stSbList = new Dictionary<string, List<Subject>>
{
    { st1.RollNo, new List<Subject> { sb1, sb2, sb3 } },
    { st2.RollNo, new List<Subject> { sb1, sb3 } },
    { st3.RollNo, new List<Subject> { sb1, sb2 } },
};

Update: As others have pointed out, your definition of RoleList as a dictionary is probably erroneous, since it will not allow you to define multiple subjects for the same student. What you need is a data structure that supports the representation of a many-to-many relation; for example, a List<T> of Tuple<string,string>, where Item1 of each tuple contains the Student.RollNo, whilst Item2 contains the Subject.Code:

var roleList = new List<Tuple<string, string>>();
roleList.Add(Tuple.Create(st1.RollNo, sb1.Code));
roleList.Add(Tuple.Create(st1.RollNo, sb2.Code));
roleList.Add(Tuple.Create(st1.RollNo, sb3.Code));
roleList.Add(Tuple.Create(st2.RollNo, sb1.Code));
roleList.Add(Tuple.Create(st2.RollNo, sb3.Code));
roleList.Add(Tuple.Create(st3.RollNo, sb1.Code));
roleList.Add(Tuple.Create(st3.RollNo, sb2.Code));

To convert this into a dictionary, you could use first use the GroupBy operator, followed by ToDictionary:

var subjects = new[] { sb1, sb2, sb3 }.ToDictionary(sb => sb.Code);

Dictionary<string, List<Subject>> stSbList =
    roleList.GroupBy(tuple => tuple.Item1)
            .ToDictionary(
                grouping => grouping.Key,
                grouping => grouping.Select(tuple => subjects[tuple.Item2]).ToList(),
                EqualityComparer<string>.Default);

Update2: For completeness, this is how to construct the dictionary from your definition of RoleList (although this probably isn’t what you want):

var roleList = new Dictionary<string, string>();
roleList.Add(st1.RollNo, sb1.Code);
roleList.Add(st2.RollNo, sb2.Code);
roleList.Add(st3.RollNo, sb2.Code);

var subjects = new[] { sb1, sb2, sb3 }.ToDictionary(sb => sb.Code);

Dictionary<string, List<Subject>> stSbList = roleList.ToDictionary(
    kvp => kvp.Key,
    kvp => new List<Subject> { subjects[kvp.Value] });

Update3: Adapted to work with the latest version of your code:

var subjects = new[] { sb1, sb2, sb3 }.ToDictionary(sb => sb.Code);

Dictionary<string, List<Subject>> stSbList =
    RoleList.GroupBy(kvp => kvp.Key)
            .ToDictionary(
                grouping => grouping.Key,
                grouping => grouping.Select(kvp => subjects[kvp.Value]).ToList());

Upvotes: 3

Related Questions