Linq lambda to get "distinct" items

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
LVL 35
mrichmonAsked:
Who is Participating?
 
BuggyCoderCommented:
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
 
nishant joshiTechnology Development ConsultantCommented:
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
 
binaryevoCommented:
0
Cloud Class® Course: Python 3 Fundamentals

This course will teach participants about installing and configuring Python, syntax, importing, statements, types, strings, booleans, files, lists, tuples, comprehensions, functions, and classes.

 
käµfm³d 👽Commented:
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
 
BuggyCoderCommented:
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
 
käµfm³d 👽Commented:
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
 
mrichmonAuthor Commented:
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
 
mrichmonAuthor Commented:
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
 
käµfm³d 👽Commented:
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
 
mrichmonAuthor Commented:
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
 
käµfm³d 👽Commented:
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
 
mrichmonAuthor Commented:
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
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.