是否可以通过布尔条件将IEnumerable分为两个,而无需两个查询?


73

我可以使用LINQ和仅一个查询/ LINQ语句将其IEnumerable<T>分成两部分IEnumerable<T>吗?

我想避免重复IEnumerable<T>两次。例如,是否可以合并下面的最后两个语句,使allValues仅被遍历一次?

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

该死的应该在MoreLinq中!
nawfal

Answers:


74

您可以使用此:

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
优雅!我总是可以使用groups.ContainsKey()或groups.TryGetValue()来处理缺少键的情况。
SFun28年

5
如果从不使用一个键(真或假),则会崩溃。例如,如果SomeProp始终为true,则groups [false]将崩溃,并出现异常“给定的密钥在词典中不存在”

4
GroupBy使用排序进行分组。我不知道坚持进行两次扫描迭代是否比制作一种更好。
ejmarino

68

有些人喜欢使用Dictionary,但是由于缺少键时的行为,我更喜欢使用Lookups。

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];

6
+1用于建议“查找”并用于注意在使用“字典”时缺少键的潜在问题。
马克·拜尔斯2010年

我认为第二个示例中的IGrouping应该是IEnumerable?IGrouping无法编译,似乎是GroupBy扩展方法的结果。还是需要强制转换为IGrouping?
SFun28年

13

复制面食扩展方法为您提供方便。

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);

2
在此处添加注释以说c ++中的等效项是std :: partiton。可能会使其他人更容易找到C#分区功能。
Sheph

2
阅读此答案后,我可以在词典中添加短语“ Copy Pasta”。
hbulens
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.