1

Let's say I have a few people in a collection "Employees":

Name  | Position | Age
------+----------+----
Tom   | Manager  | 35
Hank  | Driver   | 38
Harry | Driver   | 45
...   | ...      | ...
Mark  | Driver   | 30
----------------------

and I want to get all Drivers if there is at least one among them who's older that 40 years old. Could you help me to complete my LINQ?

UPD. I'd like to do this task using a single LINQ and perfomance has no matter. So decision

var allDrivers = Employees.Where(n => n.Position == "Driver").ToList();
return allDrivers.Any(n => n.Age > 40)
? allDrivers
: new List<Employee>();

is good, but I can't mark it as an answer.

In this particular case I need to get Hank, Harry and Mark. Because all they're drivers and Harry is 45 (>40). But if Harry was 39 I would get nothing as a result because in this case all drivers were under 40.

Tomcat
  • 35
  • 5
  • 1
    This might be relevant: https://stackoverflow.com/questions/879391/linq-any-vs-exists-whats-the-difference?rq=1 – jpw Aug 06 '18 at 11:42
  • 1
    "Drivers" is a different table? if a employee is a driver, just adding `&& Age > 40` can resolve this – Ricardo Pontual Aug 06 '18 at 11:42
  • I mean I could write something like this var drv = Employees .Where(n => n.Position == "Driver") .OrderBy(n => n.Age) if (drv.Exists(n => n.Age > 40) == true) { Print(x); } but I was wondering if it was possible to do it easier, using one linq sequence. Using >40 condition as Tim showed is not an option, because in this case I will not get drivers who's under 40. And I need them all if there is at least one who's above 40. – Tomcat Aug 06 '18 at 11:56
  • Well if you don't care about performance or cleanness then my `Aggregate` answer is a single-query solution. – V0ldek Aug 06 '18 at 13:17

2 Answers2

3

So if I were to parse "I want to get all Drivers if there is at least one among them who's older that 40 years old" literally, it'd be

var allDrivers = Employees.Where(n => n.Position == "Driver").ToList();
return allDrivers.Any(n => n.Age > 40)
    ? allDrivers
    : new List<Employee>();

Or something similar.

For one-query functional craziness:

(int maxAge, List<Employee> result) = Employees
    .Aggregate(
        (age: 0, list: new List<Employee>()), 
        (al, n) => n.Position == "Driver" 
            ? (Math.Max(al.age, n.Age), al.list.Concat(new [] {n}).ToList() 
            : al));

return maxAge > 40 ? result : new List<Employee>();

This is just PoC that you CAN. But remember - when asking yourself if you could, don't forget to ask yourself if you should :)

V0ldek
  • 9,623
  • 1
  • 26
  • 57
  • The OrderBy isn't even mentioned in the spec (only in the code example). – Peter B Aug 06 '18 at 11:50
  • That's why "or something similar". Wether it returns `null` or an empty list depends on what the author wants. Also, why move `OrderBy` to the second line? It won't be actually executed until the `ToList` call, or am I wrong? – V0ldek Aug 06 '18 at 11:52
  • Looks nice. But is it posible to do it using one linq sentence? – Tomcat Aug 06 '18 at 12:00
  • You are evaluating your `allDrivers`sequence twice. I theory, you could get wrong results if somebody else is changing your datasource at the same time. – nvoigt Aug 06 '18 at 12:08
  • You could do a crazy functional-style query using `Aggregate` that would select drivers into a result list and update max age at the same time. But if you're concerned about performance it's not gonna be any faster in practice. – V0ldek Aug 06 '18 at 12:09
  • The literal requirement doesn't say anything about the ordering of the drivers, so I would leave it out. Personally I have a gut feeling that the OP doesn't want all drivers, maybe he'd better rephrase his requirement – Harald Coppoolse Aug 06 '18 at 12:10
  • 1
    I edited to avoid multiple enumerations and removed OrderBy as many people are saying to leave it out. – V0ldek Aug 06 '18 at 12:12
  • And now I added the one-query monstrosity, I take no liability for damages. – V0ldek Aug 06 '18 at 12:26
1

If you really want it inside one statement (this feels very artificial to me) then you can do this:

IEnumerable<Employee> result = 
     employees.GroupBy(n => n.Position)
              .Where(g => g.Key == "Driver" && g.Max(x => x.Age) > 40)
              .FirstOrDefault();

Don't forget to do

return result ?? Enumerable.Empty<Employee>();

if you want to return an empty result instead of null when nothing is found.

nvoigt
  • 75,013
  • 26
  • 93
  • 142
  • Wait, the result of the query is not an `Enumerable`, you're calling `FirstOrDefault` which returns a single element. – V0ldek Aug 06 '18 at 12:13
  • 2
    @V0ldek Yes, but the single Element is a *group*, which in turn is an `IEnumerable` of all it's members. – nvoigt Aug 06 '18 at 12:14
  • 1
    It might actually be slower because it does also calculate the max age of all other groups though... – nvoigt Aug 06 '18 at 12:37