Solved

Linq lambda to get "distinct" items

Posted on 2012-04-10
12
569 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
Comment Utility
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
Comment Utility
0
 
LVL 74

Expert Comment

by:käµfm³d 👽
Comment Utility
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
 
LVL 20

Expert Comment

by:BuggyCoder
Comment Utility
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 74

Expert Comment

by:käµfm³d 👽
Comment Utility
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
Comment Utility
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
How to improve team productivity

Quip adds documents, spreadsheets, and tasklists to your Slack experience
- Elevate ideas to Quip docs
- Share Quip docs in Slack
- Get notified of changes to your docs
- Available on iOS/Android/Desktop/Web
- Online/Offline

 
LVL 35

Author Comment

by:mrichmon
Comment Utility
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 74

Expert Comment

by:käµfm³d 👽
Comment Utility
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
Comment Utility
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 74

Expert Comment

by:käµfm³d 👽
Comment Utility
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
Comment Utility
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
Comment Utility
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

Top 6 Sources for Identifying Threat Actor TTPs

Understanding your enemy is essential. These six sources will help you identify the most popular threat actor tactics, techniques, and procedures (TTPs).

Join & Write a Comment

In my previous two articles we discussed Binary Serialization (http://www.experts-exchange.com/A_4362.html) and XML Serialization (http://www.experts-exchange.com/A_4425.html). In this article we will try to know more about SOAP (Simple Object Acces…
Introduction Hi all and welcome to my first article on Experts Exchange. A while ago, someone asked me if i could do some tutorials on object oriented programming. I decided to do them on C#. Now you may ask me, why's that? Well, one of the re…
This video gives you a great overview about bandwidth monitoring with SNMP and WMI with our network monitoring solution PRTG Network Monitor (https://www.paessler.com/prtg). If you're looking for how to monitor bandwidth using netflow or packet s…
In this tutorial you'll learn about bandwidth monitoring with flows and packet sniffing with our network monitoring solution PRTG Network Monitor (https://www.paessler.com/prtg). If you're interested in additional methods for monitoring bandwidt…

744 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

Need Help in Real-Time?

Connect with top rated Experts

15 Experts available now in Live!

Get 1:1 Help Now