【发布时间】:2019-10-05 21:24:43
【问题描述】:
我碰巧做到了这一点,并想询问有关 O(n)、订单稳定性和所涉及的枚举器实例的底层保证。
我了解每个聚合的枚举数拐点数(例如计数和持续时间)取决于子级到父级的实际分布,但至少每个记录仅在每个聚合中枚举一次,这不是真的?
在这个例子中,我们有 3 个聚合和 3 个层次级别要聚合,因此 O(9n) ~ O(n)。
LINQ GroupBy 问题:
- GroupBy 是否记录为既具有线性复杂性又具有稳定的顺序,还是 Microsoft .NET 的 impl 是如何做到的?我没有特别怀疑它可能是非线性的,只是出于好奇而询问。
- 似乎实例化的枚举数应该始终与层次结构中看到的父节点数成线性关系,对吗?
- 在同一聚合统计信息和级别期间,没有多次枚举叶或非叶,对吗?因为每个节点只是某个“结果”中一个父节点的子节点。
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace NestedGroupBy
{
[TestClass]
public class UnitTest1
{
struct Record
{
// cat->subcat->event is the composite 3-tier key
private readonly string category;
private readonly string subcategory;
private readonly string ev;
private readonly double duration;
public Record(string category, string subcategory, string ev, double duration)
{
this.category = category;
this.subcategory = subcategory;
this.ev = ev;
this.duration = duration;
}
public string Category { get { return category; } }
public string Subcategory { get { return subcategory; } }
public string Event { get { return ev; } }
public double Duration { get { return duration; } }
}
static readonly IList<Record> Rec1 = new System.Collections.ObjectModel.ReadOnlyCollection<Record>
(new[] {
new Record ("Network", "SecurityCheck", "ReadAcl", 0.0145),
new Record ("Network", "Security", "ReadSocket", 0.0145),
new Record ("Network", "SecurityCheck", "ReadAcl", 0.0145),
new Record ("File", "ReadMetadata", "ReadDirectory", 0.0145),
new Record ("File", "ReadMetadata", "ReadDirectory", 0.0145),
new Record ("File", "ReadMetadata", "ReadDirectory", 0.0145),
new Record ("File", "ReadMetadata", "ReadSize", 0.0145),
new Record ("File", "ReadMetadata", "ReadSize", 0.0145),
new Record ("File", "ReadMetadata", "ReadSize", 0.0145),
new Record ("Registry", "ReadKey", "ReadKeyAcl", 0.0145),
new Record ("Registry", "ReadKey", "ReadKeyAcl", 0.0145),
new Record ("Registry", "ReadKey", "ReadKeyAcl", 0.0145),
new Record ("Registry", "ReadKey", "ReadKeyAcl", 0.0145),
new Record ("Registry", "ReadKey", "CacheKeyAcl", 0.0145),
new Record ("Registry", "ReadKey", "CacheKeyAcl", 0.0145),
new Record ("Registry", "ReadKey", "CheckKeyAcl", 0.0145),
new Record ("Registry", "ReadKey", "CheckKeyAcl", 0.0145),
new Record ("Registry", "ReadKey", "CheckKeyAcl", 0.0145),
new Record ("Registry", "WriteKey", "CheckPermissions", 0.0145),
new Record ("Registry", "WriteKey", "CheckOwner", 0.0145),
new Record ("Registry", "WriteKey", "InheritPermissions", 0.0145),
new Record ("Registry", "WriteKey", "ValidateKey", 0.0145),
new Record ("Registry", "WriteKey", "RecacheKey", 0.0145),
new Record ("File", "WriteData", "FlushData", 0.0145),
new Record ("File", "WriteData", "WriteBuffer", 0.0145),
new Record ("File", "WriteData", "WritePermissions", 0.0145),
new Record ("File", "ReadData", "CheckDataBuffer", 0.0145),
new Record ("File", "ReadData", "ReadBuffer", 0.0145),
new Record ("File", "ReadData", "ReadPermissions", 0.0145),
new Record ("Network", "SecurityCheck", "ReadAcl", 0.0145),
new Record ("Network", "SecurityCheck", "ReadAcl", 0.0145),
new Record ("Network", "SecurityCheck", "ReadAcl", 0.0145),
new Record ("Network", "SecurityCheck", "ReadAcl", 0.0145),
new Record ("Network", "SecurityCheck", "ReadAcl", 0.0145),
new Record ("Network", "Security", "ReadSocket", 0.0145),
new Record ("Network", "SecurityCheck", "ReadAcl", 0.0145),
new Record ("Network", "SecurityCheck", "ReadAcl", 0.0145),
new Record ("Network", "Security", "ReadSocket", 0.0145),
new Record ("Network", "SecurityCheck", "ReadAcl", 0.0145),
});
[TestMethod]
public void TestMethod1()
{
// Perform one big sort to order all child rungs properly
var s = Rec1.OrderBy(
r => r,
Comparer<Record>.Create(
(x, y) =>
{
int c = x.Category.CompareTo(y.Category);
if (c != 0) return c;
c = x.Subcategory.CompareTo(y.Subcategory);
if (c != 0) return c;
return x.Event.CompareTo(y.Event);
}));
// This query enumerates bottom-up (in the key hierarchy-sense),
// then proceedes to each higher summary (parent) level and retains
// the "result" collection of its children determined by the preceding GroupBy.
//
// This is so each level can later step down into its own children for looping.
// And the leaf durations, immediate child counts and leaf event counts are already calculated as well.
//
// I think this is O(n), since each record does not get repeatedly scanned for different levels of the same accumulation stat.
// But under-the-hood there may be much grainy processing like enumerator instantiation, depending on child count density.
var q = s
.GroupBy(
r => new { Category = r.Category, Subcategory = r.Subcategory, Event = r.Event },
(key, result) => {
int c = result.Count();
return new
{
LowKey = key,
// at this lowest summary level only,
// the hierarchical (immediate child) count is the same as the event (leaf) count
LowChildCount = c,
LowEventCount = c,
LowDuration = result.Sum(x => x.Duration),
LowChildren = result
};
})
.GroupBy(
r => new { Category = r.LowKey.Category, Subcategory = r.LowKey.Subcategory },
(key, result) => new {
MidKey = key,
MidChildCount = result.Count(),
MidEventCount = result.Sum(x => x.LowEventCount),
MidDuration = result.Sum(x => x.LowDuration),
MidChildren = result
})
.GroupBy(
r => new { Category = r.MidKey.Category },
(key, result) => new {
HighKey = key,
HighChildCount = result.Count(),
HighEventCount = result.Sum(x => x.MidEventCount),
HighDuration = result.Sum(x => x.MidDuration),
HighChildren = result
});
foreach (var high in q)
{
Console.WriteLine($"{high.HighKey.Category} child#={high.HighChildCount} event#={high.HighEventCount} duration={high.HighDuration}");
foreach (var mid in high.HighChildren)
{
Console.WriteLine($" {mid.MidKey.Subcategory} child#={mid.MidChildCount} event#={high.HighEventCount} duration={mid.MidDuration}");
foreach (var low in mid.MidChildren)
{
Console.WriteLine($" {low.LowKey.Event} child#={low.LowChildCount} event#={high.HighEventCount} duration={low.LowDuration}");
foreach (var leaf in low.LowChildren)
{
Console.WriteLine($" >> {leaf.Category}/{leaf.Subcategory}/{leaf.Event} duration={leaf.Duration}");
}
}
}
}
}
}
}
【问题讨论】:
-
查看GroupBy的源代码,这将回答您的所有问题
-
谢谢,这一切都说得通。