是否有更好的方法来调用LINQ Any + NOT All?

我需要检查序列是否有任何项目满足某些条件但同时不是所有项目都满足相同的条件。

例如,对于10个项目的序列,如果序列至少有一个满足条件但不是全部,则我想要为TRUE:

  • 满足10项,0项不满意,结果为假
  • 0项目满意,10项不满意,结果为假
  • 1项满意,9项不满意,结果为TRUE
  • 满足9件物品,不包含1件物品,结果为TRUE

我知道我可以这样做:

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

但这不是最佳选择。

有没有更好的办法?

你会喜欢这个。

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

Take(2)停止迭代任何超过它的元素。

如果您的问题是遍历大型集合中的所有元素,那么您可以 – AnyAll将尽快短路。

该声明

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

只要找到满足条件的一个元素,就会返回true,并且

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

只要一个元素没有,它就会返回true。

由于两个条件是互斥的,因此保证其中一个语句在第一个元素之后返回,而另一个语句保证在找到第一个元素后立即返回。


正如其他人所指出的,这个解决方案需要开始两次迭代集合。 如果获取集合很昂贵(例如在数据库访问中)或迭代集合每次都不会产生相同的结果,这不是一个合适的解决方案。

您可以定义自己的扩展方法。 这个版本更详细,但仍然可读,它只枚举IEnumerable一次:

 bool AnyButNotAll(this IEnumerable sequence, Func 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(this IEnumerable sequence, Func predicate) { return sequence.Any(predicate) && !sequence.All(predicate); } 

如果源是数据库,这可能是一个相当理想的解决方案。 这种扩展方法可能会更好,具体取决于你的来源(我想,我只是把它放在一起 – 可能有很多错误,考虑更多伪代码)。 这里的好处是它只列举一次,并且一旦读取到足以确定结果就进行短路:

 static bool SomeButNotAll(this IEnumerable source, Func 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 */ } 

你可以试试这个:

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

基本上我为每个值选择truefalse ,得到distinct,只得到每个值中的一个,然后计算那些。

您可以将谓词放在变量中,这样就不必重复两次谓词:

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

如果你想将它定义为一个方法,你可以采用Linq的方法来定义IEnumerableIQueryable扩展方法。 这允许自动采取最佳方法:

 public static bool SomeButNotAll(this IQueryable source, Expression> 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(this IEnumerable source, Func 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 SomeButNotAllAsync(this IQueryable source, Expression> 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 SomeButNotAllAsync(this IQueryable source, Expression> predicate) { return source.SomeButNotAllAsync(predicate, CancellationToken.None); } 

您可以使用Aggregate方法同时执行这两项操作。 我建议使用TAccumulate的匿名类型,包含两个计数器。 在聚合之后,您可以从生成的匿名类型中读取这两个值。

(我不能打个例子,我在手机上)

请参阅此处的文档: https : //msdn.microsoft.com/en-us/library/vstudio/bb549218(v = vs.100).aspx