3

I'm writing a query in Linq which needs to check if one or more items in a list pass predicate A(), and none pass predicate B(). It needs to be a single query to enable Linq to SQL. So for the following lists the results should be as follows where a passes predicate A() and b passes predicate B():

1. [ a, a ] => true
2. [ a ]    => true
3. [ a, b ] => false
4. [ b, b ] => false
5. [ b ]    => false
6. [ ]      => false

I've tried the following, but each have certain situations where they fail:

// Fails in case 6
MyList.All(x => A(x) && !B(x));

// Fails in case 3
MyList.Where(x => !B(x)).Count(x => A(x)) > 0;

// This works, but it's not a single query anymore
MyList.All(x => A(x) && !B(x)) && Mylist.Count() > 0;

I feel like what I need here is something equivalent to all, but which returns false for an empty list. For example this would pass in all cases:

MyList.AllButReturnFalseIfListIsEmpty(x => A(x) && !B(x));

How can I achieve this?

Alfie Woodland
  • 834
  • 11
  • 30

5 Answers5

2

You can do it like this:

var result = MyList
    .Select(x => B(x) ? -1 : (A(x) ? 1 : 0))
    .Aggregate(0, (p, v) => v == -1 || p == -1 ? -1 : p + v) > 0;

The query works as follows:

  • Select produces -1 when B is true; otherwise, it produces 1 if A is true and 0 when A is false
  • Aggregate checks this list of numbers from -1, 0, 1. If it sees -1, the result of the aggregation is -1. Otherwise, the result of the aggregation is sum of positive values.
  • Comparison at the end makes sure there's at least one A and no Bs.
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
1

I would suggest you something similar to my answer to Linq All on empty collection:

var result = MyList.Max(x => B(x) ? (int?)2 : A(X) ? (int?)1 : (int?)0) == 1;

It utilizes how Max<int?> method works

(A) If the sequence is empty, it returns null
(B) If there is at least one element matching B, the result is 2
(C) If there is at least one element not matching B and matching A, the result is 1
(D) Otherwise the result is 0

So we simple check for case (C).

Community
  • 1
  • 1
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
0

Try this:

MyList.All(x => MyList.Any() && A(x) && !B(x));
toadflakz
  • 7,764
  • 1
  • 27
  • 40
  • This is part of a Linq to SQL query within EntityFramework, so I don't have access to the list as a variable. It's all a single expression, e.g. - `dbContext.MyTable.Any(x => x.thing == true);` – Alfie Woodland Sep 06 '16 at 09:19
0

Why don't you just check if there's any item after the evaluation?

MyList.Where(x => A(x) && !B(x)).Any();
Mauro Sampietro
  • 2,739
  • 1
  • 24
  • 50
0

You should not use All() if you want it to return false for empty collection. Take a look at the documentation:

true if every element of the source sequence passes the test in the specified predicate, or if the sequence is empty; otherwise, false.

Instead, I would try something like this:

MyList.Where(x => A(x) && !B(x)).Any();
J. Swietek
  • 405
  • 3
  • 13