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
Solved

Linq lambda to get "distinct" items

Posted on 2012-04-10
12
590 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
MIM Survival Guide for Service Desk Managers

Major incidents can send mastered service desk processes into disorder. Systems and tools produce the data needed to resolve these incidents, but your challenge is getting that information to the right people fast. Check out the Survival Guide and begin bringing order to chaos.

 
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

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.

Question has a verified solution.

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

Suggested Solutions

Title # Comments Views Activity
VB.NET 2008 - Windows Protected Your PC 7 41
insert value of checklistbox checked 4 32
defining NULL or 0 10 44
VB.NET - Refactor Class per SOLID principles 2 16
It was really hard time for me to get the understanding of Delegates in C#. I went through many websites and articles but I found them very clumsy. After going through those sites, I noted down the points in a easy way so here I am sharing that unde…
Performance in games development is paramount: every microsecond counts to be able to do everything in less than 33ms (aiming at 16ms). C# foreach statement is one of the worst performance killers, and here I explain why.
With Secure Portal Encryption, the recipient is sent a link to their email address directing them to the email laundry delivery page. From there, the recipient will be required to enter a user name and password to enter the page. Once the recipient …

839 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