如何懒惰地获得空组
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
行中的一行,甚至两行,allTrue
和 allFalse
将不会包含正确的组。如果我们删除两者,我们甚至会在尝试调用 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
运算符生成的可枚举是惰性的,因此每次使用时都会对其求值。评估此运算符的成本相对较高,因此最好避免多次评估它。
我想按 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
行中的一行,甚至两行,allTrue
和 allFalse
将不会包含正确的组。如果我们删除两者,我们甚至会在尝试调用 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
运算符生成的可枚举是惰性的,因此每次使用时都会对其求值。评估此运算符的成本相对较高,因此最好避免多次评估它。