我可以通过布尔条件将IEnumerable拆分为两个而不需要两个查询吗?

时间:2010-12-28 20:39:48

标签: .net linq

我可以使用LINQ将IEnumerable<T>拆分成两个IEnumerable<T>,只使用一个查询/ LINQ语句吗?

我想避免两次迭代IEnumerable<T>。例如,是否可以组合下面的最后两个语句,以便只遍历一次allValues?

IEnumerable<MyObj> allValues = ...
List<MyObj> trues = allValues.Where( val => val.SomeProp ).ToList();
List<MyObj> falses = allValues.Where( val => !val.SomeProp ).ToList();

3 个答案:

答案 0 :(得分:62)

您可以使用:

var groups = allValues.GroupBy(val => val.SomeProp);

强制立即评估,例如:

var groups = allValues.GroupBy(val => val.SomeProp)
                      .ToDictionary(g => g.Key, g => g.ToList());
List<MyObj> trues = groups[true];
List<MyObj> falses = groups[false];

答案 1 :(得分:59)

有些人喜欢字典,但由于缺少密钥时的行为,我更喜欢Lookup。

IEnumerable<MyObj> allValues = ...
ILookup<bool, MyObj> theLookup = allValues.ToLookup(val => val.SomeProp);

  //does not throw when there are not any true elements.
List<MyObj> trues = theLookup[true].ToList();
  //does not throw when there are not any false elements.
List<MyObj> falses = theLookup[false].ToList();

不幸的是,这种方法枚举了两次 - 一次创建查找,然后一次创建列表。

如果你真的不需要列表,你可以将它归结为一次迭代:

IEnumerable<MyObj> trues = theLookup[true];
IEnumerable<MyObj> falses = theLookup[false];

答案 2 :(得分:7)

为方便起见,复制面食延长方法。

public static void Fork<T>(
    this IEnumerable<T> source,
    Func<T, bool> pred,
    out IEnumerable<T> matches,
    out IEnumerable<T> nonMatches)
{
    var groupedByMatching = source.ToLookup(pred);
    matches = groupedByMatching[true];
    nonMatches = groupedByMatching[false];
}

或者在C#7.0中使用元组

public static (IEnumerable<T> matches, IEnumerable<T> nonMatches) Fork<T>(
    this IEnumerable<T> source,
    Func<T, bool> pred)
{
    var groupedByMatching = source.ToLookup(pred);
    return (groupedByMatching[true], groupedByMatching[false]);
}

// Ex.
var numbers = new [] { 1, 2, 3, 4, 5, 6, 7, 8 };
var (numbersLessThanEqualFour, numbersMoreThanFour) = numbers.Fork(x => x <= 4);