如何懒惰地获得空组

how to get empty groups, lazily

我想按 boolean 值对对象进行分组,我需要始终得到两组(一组用于 true,一组用于 false),无论是否有其中的任何元素。

通常使用 GroupBy 的方法不起作用,因为它只会生成非空组。举个例子此代码:

var list = new List<(string, bool)>();
list.Add(("hello", true));
list.Add(("world", false));
var grouping = list.GroupBy(i => i.Item2);
var allTrue = grouping.Last();
var allFalse = grouping.First();

这仅在每个 boolean 值至少有一个元素时有效。如果我们删除 Add 行中的一行,甚至两行,allTrueallFalse 将不会包含正确的组。如果我们删除两者,我们甚至会在尝试调用 Last()(“序列不包含任何元素”)时遇到运行时异常。

注意:我想偷懒做这个。 (不是:创建两个空集合,遍历输入,填充集合。)

当没有像这样的匹配对象时,您可以确保得到空集合:

var list = new List<(string, bool)>();
list.Add(("hello", true));
list.Add(("world", false));
var allTrue = list.Where(x => x.Item2);
var allFalse = list.Where(x => !x.Item2);

.NET 平台不包含 built-in 生成空 IGrouping 的方法。没有可公开访问的 class 实现此接口,因此我们必须手动创建一个:

class EmptyGrouping<TKey, TElement> : IGrouping<TKey, TElement>
{
    public TKey Key { get; }

    public EmptyGrouping(TKey key) => Key = key;

    public IEnumerator<TElement> GetEnumerator()
        => Enumerable.Empty<TElement>().GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator()
        => GetEnumerator();
}

为了检查是否所有必需的分组都可用,我们需要一种方法来根据 Key 对它们进行比较。下面是 IGrouping 的简单 IEqualityComparer 实现:

public class GroupingComparerByKey<TKey, TElement>
    : IEqualityComparer<IGrouping<TKey, TElement>>
{
    public bool Equals(IGrouping<TKey, TElement> x, IGrouping<TKey, TElement> y)
        => EqualityComparer<TKey>.Default.Equals(x.Key, y.Key);

    public int GetHashCode(IGrouping<TKey, TElement> obj)
        => obj.Key.GetHashCode();
}

有了这个基础设施,我们现在可以创建一个惰性 LINQ 运算符,将缺失的分组附加到枚举。让我们称之为 EnsureContains:

public static IEnumerable<IGrouping<TKey, TElement>> EnsureContains<TKey, TElement>(
    this IEnumerable<IGrouping<TKey, TElement>> source, params TKey[] keys)
{
    return source
        .Union(keys.Select(key => new EmptyGrouping<TKey, TElement>(key)),
            new GroupingComparerByKey<TKey, TElement>());
}

用法示例:

var groups = list
    .GroupBy(i => i.Item2)
    .EnsureContains(true, false);

注意: GroupBy 运算符生成的可枚举是惰性的,因此每次使用时都会对其求值。评估此运算符的成本相对较高,因此最好避免多次评估它。