Подтвердить что ты не робот

Есть ли лучший способ позвонить LINQ Any + NOT All?

Мне нужно проверить, имеет ли последовательность какие-либо элементы, удовлетворяющие некоторому условию, но в то же время НЕ все элементы, удовлетворяющие одному и тому же условию.

Например, для последовательности из 10 элементов я хочу иметь ИСТИННУЮ, если последовательность имеет по крайней мере одну, которая удовлетворяет условию, но не все:

  • 10 элементов, удовлетворяющих, 0 элементов нет, результат FALSE
  • 0 элементов, удовлетворяющих, 10 элементов нет, результат ЛОЖЬ
  • 1 элемент, удовлетворяющий, 9 элементов нет, результат равен TRUE
  • 9 элементов, удовлетворяющих, 1 элемент нет, результат ИСТИНА

Я знаю, что смог:

mySequence.Any (item => item.SomeStatus == SomeConst) && !mySequence.All (item => item.SomeStatus == SomeConst)

Но это не оптимально.

Есть ли лучший способ?

4b9b3361

Ответ 1

Вам понравится.

var anyButNotAll = mySequence
    .Select(item => item.SomeStatus == SomeConst)
    .Distinct()
    .Take(2)
    .Count() == 2;

Take(2) останавливает повторение итераций над любыми другими элементами, чем это необходимо.

Ответ 2

Если ваша проблема повторяется во всех элементах большой коллекции, вы в порядке - Any и All будут как можно скорее закорочены.

Утверждение

mySequence.Any (item => item.SomeStatus == SomeConst)

вернет true, как только будет найден один элемент, который удовлетворяет условию, и

!mySequence.All (item => item.SomeStatus == SomeConst)

вернет true, как только один элемент не будет.

Так как оба условия являются взаимоисключающими, одно из операторов, как гарантируется, будет возвращено после первого элемента, а другое гарантировано будет возвращено, как только будет найден первый элемент.


Как уже отмечалось другими, это решение требует начала повторения в коллекции дважды. Если получение коллекции является дорогостоящим (например, при доступе к базе данных) или итерацией по коллекции, это не дает одинакового результата каждый раз, это не подходящее решение.

Ответ 3

Это, скорее всего, довольно оптимальное решение, если источником является база данных. Этот метод расширения может быть лучше в зависимости от вашего источника (я думаю, я просто выбросил его вместе - возможно, много ошибок, рассмотрим его более псевдокод). Преимущество здесь состоит в том, что он только перечисляет один раз и выполняет короткое замыкание, как только он достаточно читает, чтобы определить результат:

static bool SomeButNotAll<TSource>(this IEnumerable<TSource> source,
                                   Func<TSource, bool> predicate)
{
   using(var iter=source.GetEnumerator())
   {
     if (iter.MoveNext())
     {
       bool initialValue=predicate(iter.Current);
       while (iter.MoveNext())
         if (predicate(iter.Current)!=initialValue)
           return true;
     }
   }     
   return false; /* All */
}

Ответ 4

Вы можете определить свой собственный метод расширения. Эта версия более подробна, но все еще доступна для чтения, и она перечисляет только IEnumerable один раз:

bool AnyButNotAll<ItemT>(this IEnumerable<ItemT> sequence, Func<ItemT, bool> predicate)
{
    bool seenTrue = false;
    bool seenFalse = false;

    foreach (ItemT item in sequence)
    {
        bool predResult = predicate(item);
        if (predResult)
            seenTrue = true;
        if (!predResult)
            seenFalse = true;

        if (seenTrue && seenFalse)
            return true;
    }

    return false;
}

Гораздо короче, но перечисляет IEnumerable дважды:

bool AnyButNotAll<ItemT>(this IEnumerable<ItemT> sequence, Func<ItemT, bool> predicate)
{
    return sequence.Any(predicate) && !sequence.All(predicate);
}

Ответ 5

Вы можете попробовать следующее:

var result = mySequence.Select(item => item.SomeStatus == SomeConst)
                      .Distinct().Count() > 1 ? false : true;

В основном я выбираю true или false для каждого значения, получаю отличное от того, чтобы получить только один из них, а затем подсчитайте их.

Ответ 6

Вы можете поместить свой предикат в переменную, чтобы вам не пришлось дважды повторять предикат:

Func<MyItemType, bool> myPredicate = item => item.SomeStatus == SomeConst;
if (mySequence.Any(myPredicate) && !mySequence.All(myPredicate))
    ...

Ответ 7

Если вы хотите определить это как метод, вы можете принять подход Linq, который использует методы расширения IEnumerable<T> и IQueryable<T>. Это позволяет автоматически выбирать оптимальный подход:

public static bool SomeButNotAll<T>(this IQueryable<T> source, Expression<Func<T, bool>> predicate)
{
  if(source == null)
    throw new ArgumentNullException("source");
  if(predicate == null)
    throw new ArgumentNullException("predicate");
  return source.
    Select(predicate)
    .Distinct()
    .Take(2)
    .Count() == 2;
}
public static bool SomeButNotAll<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
  if(source == null)
    throw new ArgumentNullException("source");
  if(predicate == null)
    throw new ArgumentNullException("predicate");
  using(var en = source.GetEnumerator())
    if(en.MoveNext())
    {
      bool first = predicate(en.Current);
      while(en.MoveNext())
        if(predicate(en.Current) != first)
          return true;
    }
  return false;
}

Если вы используете EntityFramework (или другой поставщик, предоставляющий CountAsync, вы также можете легко предоставить асинхронную версию:

public static async Task<bool> SomeButNotAllAsync<T>(this IQueryable<T> source, Expression<Func<T, bool>> predicate, CancellationToken cancel)
{
  if(source == null)
    throw new ArgumentNullException("source");
  if(predicate == null)
    throw new ArgumentNullException("predicate");
  cancel.ThrowIfCancellationRequested();
  return await source.
    Select(predicate)
    .Distinct()
    .Take(2)
    .CountAsync(cancel)
    .ConfigureAwait(false) == 2;
}
public static Task<bool> SomeButNotAllAsync<T>(this IQueryable<T> source, Expression<Func<T, bool>> predicate)
{
  return source.SomeButNotAllAsync(predicate, CancellationToken.None);
}

Ответ 8

Вы можете использовать метод "Агрегат" для одновременного выполнения обеих задач. Я бы предложил использовать анонимный тип для TAccumulate, содержащий оба счетчика. После агрегации вы можете прочитать оба значения из полученного анонимного типа.

(Я не могу ввести пример, я нахожусь на своем телефоне)

Смотрите документацию здесь: https://msdn.microsoft.com/en-us/library/vstudio/bb549218(v=vs.100).aspx