【问题标题】:Can I split an IEnumerable into two by a boolean criteria without two queries?我可以在没有两个查询的情况下通过布尔标准将 IEnumerable 一分为二吗?
【发布时间】:2010-12-28 20:39:47
【问题描述】:

我可以使用 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();

【问题讨论】:

  • 该死的,这应该在 MoreLinq 中!
  • 在 C++ 和 Haskell 中,这个函数称为 partition,例如(yooks, zooks) = partition (\c -&gt; butterSide c == Up) creatures

标签: c# .net linq


【解决方案1】:

你可以用这个:

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

【讨论】:

  • 优雅!我总是可以使用 groups.ContainsKey() 或 groups.TryGetValue() 来处理缺少密钥的情况。
  • 如果从未使用过一个键(true 或 false),则会崩溃。例如如果 SomeProp 始终为真,则 groups[false] 将崩溃并出现异常“给定的键不在字典中”
  • GroupBy 使用排序来分组。我不知道坚持扫描迭代两次是否比制作一种更好。
【解决方案2】:

有些人喜欢字典,但由于缺少键时的行为,我更喜欢查找。

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

【讨论】:

  • +1 用于建议查找并注意到使用字典时缺少键的潜在问题。
  • 我认为 IGrouping 在第二个示例中应该是 IEnumerable 吗? IGrouping 无法编译,似乎是 GroupBy 扩展方法的结果。还是需要强制转换为 IGrouping?
【解决方案3】:

为方便起见,复制意大利面扩展方法。

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

【讨论】:

  • 在此处添加注释以说明 c++ 中的等价物是 std::partiton。可能更容易为其他人找到 C# 分区函数。
  • 阅读此答案后,我可能会在我的字典中添加“复制意大利面”这句话。
【解决方案4】:

仅使用 Linq 的现代 C# 示例,没有自定义扩展方法:

(IEnumerable<MyObj> trues, IEnumerable<MyObj> falses) 
    = ints.Aggregate<MyObj,(IEnumerable<MyObj> trues, IEnumerable<MyObj> falses)>(
        (new List<MyObj>(),new List<MyObj>()), 
        (a, i) => i.SomeProp ? (a.trues.Append(i), a.falses) : (a.trues, a.falses.Append(i))
    );

这是否回答了问题,是的;这是否比 foreach 更好或更易读,不。

【讨论】:

    猜你喜欢
    • 2019-12-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-06
    • 2011-02-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多