Solved

Linq lambda to get "distinct" items

Posted on 2012-04-10
12
581 Views
Last Modified: 2012-04-11
I have a list of courses a student took.
List<Course> courses;

The course object includes among other things, the course number, the date the course was taken, and the location the course was taught.

A student may take a course multiple times on different dates, at different locations.

I need to filter the list to a "distinct" list of courses the student has taken.  Distinct is based on the course number.  If a course exists in the list more than once, only one instance should be returned in the final list, with precedence being given to if the course was taught at location A - return that (even if taken at a later date), else return based on the earliest date the course was taken .

Something like:

return courses.Where(course => ???);

Example:

CourseName: Intro to Cooking
CourseNumber: COOK 101
DateTaken: 5/1/2007
Location: B

CourseName: Intro to Cooking
CourseNumber: COOK 101
DateTaken: 6/1/2007
Location: A

CourseName: Continued Cooking
CourseNumber: COOK 102
DateTaken: 6/30/2007
Location: B

CourseName: Advanced Cooking
CourseNumber: COOK 103
DateTaken: 6/30/2008
Location: B

CourseName: Advanced Cooking
CourseNumber: COOK 103
DateTaken: 6/30/2009
Location: C

In the above the final list should include:

CourseName: Intro to Cooking
CourseNumber: COOK 101
DateTaken: 6/1/2007
Location: A

CourseName: Continued Cooking
CourseNumber: COOK 102
DateTaken: 6/30/2007
Location: B

CourseName: Advanced Cooking
CourseNumber: COOK 103
DateTaken: 6/30/2008
Location: B
0
Comment
Question by:mrichmon
  • 4
  • 4
  • 2
  • +2
12 Comments
 
LVL 14

Expert Comment

by:nishant joshi
ID: 37829471
check code for distinct cource list
var courselist=courses.Where(course =>course.CourseNumber).select();
var discourselist=courselist.Distinct();

Open in new window


have a great day...
0
 
LVL 14

Expert Comment

by:binaryevo
ID: 37829518
0
 
LVL 75

Expert Comment

by:käµfm³d 👽
ID: 37829629
A Lambda version:

var query = courses.OrderBy(x => x.CourseNumber)
                   .OrderBy(x => x.Location)
                   .GroupBy(x => x.CourseName)
                   .Select(x => x);

Open in new window


A Linq version:

var query = from c in courses
            orderby c.Location, c.DateTaken
            group c by c.CourseName into g
            select g;

Open in new window

0
Master Your Team's Linux and Cloud Stack!

The average business loses $13.5M per year to ineffective training (per 1,000 employees). Keep ahead of the competition and combine in-person quality with online cost and flexibility by training with Linux Academy.

 
LVL 20

Expert Comment

by:BuggyCoder
ID: 37829662
phew!!!, Here goes your linq code:-

Here is My Course Type:-

    class Course
    {
        public string CourseName { get; set; }
        public string CourseNumber { get; set; }

        public string Location { get; set; }
        public DateTime CourseDate { get; set; }

        public bool IsAtPrefferedLocation()
        {
            return Location == "A";
        }

        public bool IsPreviousDateCourse(Course course)
        {
            return this.CourseDate < course.CourseDate;
        }
    }

Open in new window


IList<Course> lstCourses = new List<Course>()
                                           {
                                               new Course()
                                                   {
                                                       CourseDate = DateTime.Today.AddDays(-1),
                                                       CourseName = "Cooking",
                                                       CourseNumber = "Cook 101",
                                                       Location = "A"
                                                   },
                                               new Course()
                                                   {
                                                       CourseDate = DateTime.Today.AddDays(-2),
                                                       CourseName = "Advanced Cooking",
                                                       CourseNumber = "Cook 102",
                                                       Location = "B"
                                                   },
                                               new Course()
                                                   {
                                                       CourseDate = DateTime.Today.AddDays(-2),
                                                       CourseName = "Cooking",
                                                       CourseNumber = "Cook 101",
                                                       Location = "C"
                                                   },
                                               new Course()
                                                   {
                                                       CourseDate = DateTime.Today,
                                                       CourseName = "continued Cooking",
                                                       CourseNumber = "Cook 103",
                                                       Location = "B"
                                                   },
                                               new Course()
                                                   {
                                                       CourseDate = DateTime.Today.AddDays(-3),
                                                       CourseName = "Advanced Cooking",
                                                       CourseNumber = "Cook 102",
                                                       Location = "C"
                                                   }
                                           };

            var orderedCourses = lstCourses
                .OrderBy(c => c.CourseNumber)
                .ThenBy(c => c.Location)
                .ThenBy(c => c.CourseDate)
                .Select(c => c);

            var dicCourses = new Dictionary<string, Course>();

            foreach(var item in orderedCourses)
            {
                if(!dicCourses.ContainsKey(item.CourseNumber))
                {
                    dicCourses.Add(item.CourseNumber, item);
                }
                else
                {
                    var existingCourse = dicCourses[item.CourseNumber];
                    if(item.IsAtPrefferedLocation())
                    {
                        dicCourses[item.CourseNumber] = item;
                    }
                    else if(item.IsPreviousDateCourse(existingCourse))
                    {
                        dicCourses[item.CourseNumber] = item;
                    }
                }
            }

Open in new window

0
 
LVL 75

Expert Comment

by:käµfm³d 👽
ID: 37829738
Ah yes, I always forget about ThenBy. The "Linq" version of my suggestion should be fine, but the "Lambda" version should (similar to BuggyCoder's offering) use a ThenBy instead of the 2nd OrderBy.
0
 
LVL 35

Author Comment

by:mrichmon
ID: 37830161
DIstinct() doesn't work - because even if I specify a comparer, how do I specify which of the non-distinct values to return.

Not sure how
var query = courses.OrderBy(x => x.CourseNumber)
                   .OrderBy(x => x.Location)
                   .GroupBy(x => x.CourseName)
                   .Select(x => x);

Would work since a) I can't rely on the course name to be the same, and it doesn't take date into account.  Also I don't see that it is just returning the first element.  But I think the group by might be on the right track.

Maybe something like this:
var query = courses.OrderBy(x => x.CourseNumber)
                   .ThenBy(x => x.Location)
                  .ThenBy(x => x.DateTaken)
                   .GroupBy(x => x.CourseNumber)
                   .Select(x => x.First());

I think I'll try that out.

I don't really want to process the courses into a dictionary after the fact.  If I did that I could just have that do all the processing in a giant loop with less effort than shown above to parse into a dictionary.    I was trying to see if I could simplify that.

But thanks for all the ideas... I'll post results....
0
 
LVL 35

Author Comment

by:mrichmon
ID: 37830287
One further wrinkle....

If a course can be repeated for credit, I need all of that course, not just the first instance.  I tried this, but get the error "Type of conditional expression cannot be determined because there is no implicit conversion between 'System.Collections.Generic.IEnumerable<string>' and 'string'"

IEnumerable<string> courseIds = courses.OrderBy(x => x.CourseNumber)
                   .ThenBy(x => x.Location)
                  .ThenBy(x => x.DateTaken)
                   .GroupBy(x => x.CourseNumber)
                   .Select(courseGroup => courseGroup.First().CanRepeatForCredit ? courseGroup.Select(course => course.Id) : courseGroup.First().Id);

Is there some other way to have the first part of the conditional statement select every course, else just the first course....
0
 
LVL 75

Expert Comment

by:käµfm³d 👽
ID: 37830532
Not sure how ... Would work since a) I can't rely on the course name to be the same, and it doesn't take date into account.
Yeah, I botched that. The fields you mentioned should be those listed in that lambda (the same as what I put in the Linq version). I'm not quite sure how I managed that, but I agree it is incorrect. Corrected to match the Linq version:

var query = courses.OrderBy(x => x.Location)
                   .ThenBy(x => x.DateTaken)
                   .GroupBy(x => x.CourseName)
                   .Select(x => x);

Open in new window


Also I don't see that it is just returning the first element.
Yes, I forgot to post that bit as well. I had actually broken that out in later code:

foreach (var item in query)
{
    var first = item.First();

    Console.WriteLine("CourseName: {0}", first.CourseName);
    Console.WriteLine("CourseNumber: {0}", first.CourseNumber);
    Console.WriteLine("DateTaken: {0}", first.DateTaken);
    Console.WriteLine("Location: {0}", first.Location);
    Console.WriteLine();
}

Open in new window


I believe it can be incorporated, though.

As for the "wrinkle," let's try this update:

var query = courses.OrderBy(x => x.CourseNumber)
                   .ThenBy(x => x.DateTaken)
                   .GroupBy(x => x.CourseNumber)
                   .Select(x => x.Any(y => y.CanRepeatForCredit) ?
                                x.AsEnumerable() :
                                x.Where(y => string.Equals(y.Location, "A")) ?? x.Take(1));

Open in new window


With the above, you should get an Enumerable of Enumerables. The outer Enumerable should correspond to each course number; the inner Enumerable should correspond to one or more classes, depending on whether or not the CanRepeatForCredit. You will have to iterate over the inner Enumerable regardless of how many items are in that collection.
0
 
LVL 35

Author Comment

by:mrichmon
ID: 37830904
I have the enumerable of enumerables, but need a single list of ids.  That is the part I can't figure out...  

Thanks for the additional help
0
 
LVL 75

Expert Comment

by:käµfm³d 👽
ID: 37832524
If I'm understanding the new requirement, then I believe you'll want a SelectMany:

var query = courses.OrderBy(x => x.CourseNumber)
                   .ThenBy(x => x.DateTaken)
                   .GroupBy(x => x.CourseNumber)
                   .Select(x => x.Any(y => y.CanRepeatForCredit) ?
                                x.AsEnumerable() :
                                x.Where(y => string.Equals(y.Location, "A")) ?? x.Take(1))
                   .SelectMany(x => x.Select(y => y.Id));

Open in new window

0
 
LVL 20

Accepted Solution

by:
BuggyCoder earned 500 total points
ID: 37832669
Here is what i could do:-

var lst =
                    lstCourses
                        .OrderBy(c => c.CourseNumber)
                        .ThenBy(c => c.Location)
                        .ThenBy(c => c.CourseDate)
                        .GroupBy(c => c.CourseNumber)
                        .Select(g => g.Any(c => c.CanRepeatForCredit)
                                         ? g.AsEnumerable()
                                         : g.Any(c => c.IsAtPrefferedLocation())
                                               ? g.Take(1)
                                               : g.OrderBy(c => c.CourseDate).Take(1))
                        .SelectMany(g => g.Select(c => c))
                        .ToList();

Open in new window

0
 
LVL 35

Author Comment

by:mrichmon
ID: 37835429
Thanks for the help.  It worked.  Here is the final I used:

courses.OrderBy(course => course.CourseNumber)
                              .ThenBy(course => course.Location == A ? "0" : course.Location)
                              .GroupBy(course => course.CourseNumber)
                              .SelectMany(courseGroup => courseGroup.First().CanRepeatForCredit ? courseGroup.Select(course => course.CourseNumber) : courseGroup.Take(1).Select(course => course.CourseNumber));
0

Featured Post

3 Use Cases for Connected Systems

Our Dev teams are like yours. They’re continually cranking out code for new features/bugs fixes, testing, deploying, testing some more, responding to production monitoring events and more. It’s complex. So, we thought you’d like to see what’s working for us.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

A long time ago (May 2011), I have written an article showing you how to create a DLL using Visual Studio 2005 to be hosted in SQL Server 2005. That was valid at that time and it is still valid if you are still using these versions. You can still re…
Entity Framework is a powerful tool to help you interact with the DataBase but still doesn't help much when we have a Stored Procedure that returns more than one resultset. The solution takes some of out-of-the-box thinking; read on!
This Micro Tutorial will teach you how to censor certain areas of your screen. The example in this video will show a little boy's face being blurred. This will be demonstrated using Adobe Premiere Pro CS6.
This Micro Tutorial will give you a basic overview how to record your screen with Microsoft Expression Encoder. This program is still free and open for the public to download. This will be demonstrated using Microsoft Expression Encoder 4.

770 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question