Solved

Linq lambda to get "distinct" items

Posted on 2012-04-10
12
612 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 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
Instantly Create Instructional Tutorials

Contextual Guidance at the moment of need helps your employees adopt to new software or processes instantly. Boost knowledge retention and employee engagement step-by-step with one easy solution.

 
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

Salesforce Made Easy to Use

On-screen guidance at the moment of need enables you & your employees to focus on the core, you can now boost your adoption rates swiftly and simply with one easy tool.

Question has a verified solution.

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

This article is for Object-Oriented Programming (OOP) beginners. An Interface contains declarations of events, indexers, methods and/or properties. Any class which implements the Interface should provide the concrete implementation for each Inter…
The article shows the basic steps of integrating an HTML theme template into an ASP.NET MVC project
Monitoring a network: why having a policy is the best policy? Michael Kulchisky, MCSE, MCSA, MCP, VTSP, VSP, CCSP outlines the enormous benefits of having a policy-based approach when monitoring medium and large networks. Software utilized in this v…
This is my first video review of Microsoft Bookings, I will be doing a part two with a bit more information, but wanted to get this out to you folks.

623 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