0

I am currently learning LINQ in C# and was wondering if there was a better way to return an object using the Max() function in a LINQ statement.

Here is my User class:

public class User
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public double MonthlyWage { get; set; }
    }

and this is my table population class:

public class UsersTable
    {
        public IList<User> Populate()
        {
            IList<User> Users = new List<User>()
            {
                new User{ID = 1, Name = "Bob", MonthlyWage = 1200.00},
                new User{ID = 2, Name = "Lee", MonthlyWage = 2200.00},
                new User{ID = 3, Name = "Dan", MonthlyWage = 3200.00},
                new User{ID = 4, Name = "Liam", MonthlyWage = 4200.00},
                new User{ID = 5, Name = "Danny", MonthlyWage = 4213.00},
                new User{ID = 6, Name = "Jonathan", MonthlyWage = 1222.00},
                new User{ID = 7, Name = "Martin", MonthlyWage = 1233.00},
                new User{ID = 8, Name = "Dec", MonthlyWage = 9999.99}
            };
            return Users;
        }
    }

Here is the Main method:

class Program
    {
        static void Main(string[] args)
        {
            UsersTable UserTable = new UsersTable();
            IList<User> Users = UserTable.Populate();

            double max = Users.Max(x => x.MonthlyWage);
            var maxMonthlyWage = Users
                .Where(m => m.MonthlyWage == max)
                .Select(x => x);

            foreach (var item in maxMonthlyWage)
            {
                Console.WriteLine("{0}: {1} {2} MAX", item.ID, item.Name, item.MonthlyWage);
            }

            Console.ReadLine();
    }

Is there a way I can return the User where the monthly wage is the maximum without creating the double max beforehand? Is this the best way to carry out this type of query?

Win
  • 2,593
  • 3
  • 25
  • 35
  • 1
    possible duplicate of [How can I get LINQ to return the object which has the max value for a given property?](http://stackoverflow.com/questions/3188693/how-can-i-get-linq-to-return-the-object-which-has-the-max-value-for-a-given-prop) – Jon Skeet Sep 12 '13 at 14:09
  • 1
    You don't need the `.Select(x => x)` - It is completely redundant. The `Where` method will give you what you need. You should only use select if you need to change what gets returned like this: `.Select(x => new SomethingElse { Something = x })`. – Dan Sep 12 '13 at 14:21
  • From your original code, I understood that you were expecting more than one Max. Value occurence (that's why I didn't rely on First). If this is the case, Tommy Grovnes' approach does not deliver what you want as far as will always output just one occurence. My original code was too inefficient, as rightly point out by MarcinJuraszek. I have corrected it and now is much better. In any case, its efficiency is worse than the one in your original code (which might even be improved further by not relying on LINQ). – varocarbas Sep 12 '13 at 14:50
  • possible duplicate of [How to use LINQ to select object with minimum or maximum property value](http://stackoverflow.com/questions/914109/how-to-use-linq-to-select-object-with-minimum-or-maximum-property-value) – nawfal Jul 19 '14 at 18:42

2 Answers2

4

One liner

  var item = Users.OrderByDescending(x => x.MonthlyWage).FirstOrDefault();

  if(item != null)
    Console.WriteLine("{0}: {1} {2} MAX", item.ID, item.Name, item.MonthlyWage);

  Console.ReadLine();

If we want all top earners:

var wageGroups = from u in Users
                group u by u.MonthlyWage into ug
                orderby ug.Key descending
                select new { MonthlyWage = ug.Key, Users = ug.ToList() };

var topEarners = wageGroups.First().Users;

foreach (var item in topEarners)
{
    Console.WriteLine("{0}: {1} {2} MAX", item.ID, item.Name, item.MonthlyWage);
}

Console.ReadLine();
Tommy Grovnes
  • 4,126
  • 2
  • 25
  • 40
  • The OP seems to be expecting more than one occurrence. – varocarbas Sep 12 '13 at 14:21
  • OK. But I didn't mean that. I meant that First() (or FirstOrDefault()) returns just one value; but the original code from the OP seemed to indicate that he is expecting more than one occurrence (foreach (var item in maxMonthlyWage)) -> more than one case having the max. value. That's why I didn't use First; but I have highlighted this point to the OP and he doesn't seem to care about that, so I guess that my assumption was wrong and he is actually expecting just one value. – varocarbas Sep 12 '13 at 14:56
  • I misunderstood the question initially, just thought I'd leave both examples there for reference. Nice catch – Tommy Grovnes Sep 12 '13 at 15:00
1

You can put everything together:

var maxMonthlyWage = Users
                    .OrderByDescending(x => x.MonthlyWage)
                    .TakeWhile(x => x.MonthlyWage == Users.Max(y => y.MonthlyWage))
                    .ToList();

NOTE: I have just answered the OP's concern of removing the intermediate variable (also deleted some redundant bits). In any case I don't want to be misunderstood: the proposed approach is not better than the OP's one from an efficiency point of view.

NOTE2: as highlighted by MarcinJuraszek, this query performs the analyses twice. He proposes an external library to avoid that (moreLINQ). The oher option might be relying on First (as proposed by Tommy Grovnes), although this would deliver just one result (unlikely what the OP seems to be looking for).

NOTE3: as rightly highlighted by MarcinJuraszek, the original OP's code iterates just once to calculate the max value. The new version of my answer (better than the initial one) still iterates more than once and thus is less efficient than the original version. Nonetheless, the OP requested the removal of the intermediate variable and this is the reason for this answer.

varocarbas
  • 12,354
  • 4
  • 26
  • 37
  • Inefficient because will require 2 passes over the collection. – MarcinJuraszek Sep 12 '13 at 14:17
  • @MarcinJuraszek how can it be improved? – varocarbas Sep 12 '13 at 14:18
  • use `MaxBy` from `moreLINQ`library, or write custom extension method to handle that if you don't like to include whole `moreLINQ` into your code. – MarcinJuraszek Sep 12 '13 at 14:19
  • 1
    @MarcinJuraszek I honestly don't like to rely on LINQ too much (and less on an external library). But the OP was asking how to remove the intermediate variable and this is what I did. I will update my answer with these ideas. – varocarbas Sep 12 '13 at 14:21
  • 1
    I'a afraid your solution is actually `O(n^2)`. Calculating `max` before `Where` makes it just an `int`, but passing it as a part of predicate will cause `Max()` method call for every collection element. – MarcinJuraszek Sep 12 '13 at 14:24
  • @MarcinJuraszek you are right. I will think about an alternative a bit and, in case of not finding something on the same lines delivering, at least, the same performance, I will delete it. – varocarbas Sep 12 '13 at 14:29
  • @MarcinJuraszek I have come up with something. This is still not extremely efficient but is better than the previous version. (Be honest, please) do you think that I should maintain this answer or delete it? – varocarbas Sep 12 '13 at 14:44
  • I'm not competent enough to give you any advice about that. You find your answer useful to question author or another person that comes here? Leave it as it is. You don't? Remove it. – MarcinJuraszek Sep 12 '13 at 16:38
  • @MarcinJuraszek You are too modest. I do think that you are in a position to advise on this matter. My opinion? the OP should continue relying on his original code (or look for a way to optimise it further). I have been quite clear on the "this-is-not-more-efficient" front. Also the other answer (much more efficient, no doubt on that) does not address the multiple-occurrence problem. I do consider all this information useful, if not to be applied blindly, to get a grasp of what better-not-doing. I guess that will let it to the community: if it gets below 0 pts, I would delete it. Thanks. – varocarbas Sep 12 '13 at 16:55