我有一个像
这样的课程public class Foo
{
public string X;
public string Y;
public int Z;
}
我希望实现的查询是IEnumerable<Foo>
,名为foos
,
“按X分组,然后按Y分组,并选择最大的子组 来自每个超级群体;如果有领带,请选择一个 最大的Z。“
换句话说,一个不那么紧凑的解决方案看起来像
var outer = foos.GroupBy(f => f.X);
foreach(var g1 in outer)
{
var inner = g1.GroupBy(g2 => g2.Y);
int maxCount = inner.Max(g3 => g3.Count());
var winners = inner.Where(g4 => g4.Count() == maxCount));
if(winners.Count() > 1)
{
yield return winners.MaxBy(w => w.Z);
}
else
{
yield return winners.Single();
}
}
和一个效率不高的解决方案就像
from foo in foos
group foo by new { foo.X, foo.Y } into g
order by g.Key.X, g.Count(), g.Max(f => f.Z)
. . . // can't figure the rest out
但理想情况下我喜欢既紧凑又高效。
答案 0 :(得分:1)
你正在重复使用可枚举,导致整个可枚举再次执行,这可能会导致性能显着下降。
你不那么紧凑的代码可以简化为这个。
foreach (var byX in foos.GroupBy(f => f.X))
{
yield return byX.GroupBy(f => f.Y, f => f, (_, byY) => byY.ToList())
.MaxBy(l => l.Count)
.MaxBy(f => f.Z);
}
这是怎么回事,
项目按x分组,因此变量名为byX
,这意味着整个byX
枚举包含类似的X
。
现在,您按Y
对此分组项目进行分组。名为byY
的变量意味着整个byY
枚举包含类似的Y
,它们也有类似X
的
最后,您选择最大的列表,例如winners
(MaxyBy(l => l.Count)
),并从获奖者中选择最高Z
(MaxBy(f => f.Z)
)的项目。
我使用byY.ToList()
的原因是为了防止重复枚举,否则会由Count()
和MaxBy()
引起。
或者,您可以将整个迭代器更改为单个return语句。
return foos.GroupBy(f => f.X, f => f, (_, byX) =>
byX.GroupBy(f => f.Y, f => f,(__, byY) => byY.ToList())
.MaxBy(l => l.Count)
.MaxBy(f => f.Z));
答案 1 :(得分:1)
根据您问题的措辞,我假设您希望结果为IEnumerable<IEnumerable<Foo>>
。元素按X
和Y
分组,因此特定内部序列中的所有元素将具有X
和Y
的相同值。此外,每个内部序列将具有X
的不同(唯一)值。
鉴于以下数据
X Y Z ----- A p 1 A p 2 A q 1 A r 3 B p 1 B q 2
生成的序列序列应由两个序列组成(X = A
和X = B
)
X Y Z ----- A p 1 A p 2 X Y Z ----- B q 2
您可以使用以下LINQ表达式获得此结果:
var result = foos
.GroupBy(
outerFoo => outerFoo.X,
(x, xFoos) => xFoos
.GroupBy(
innerFoo => innerFoo.Y,
(y, yFoos) => yFoos)
.OrderByDescending(yFoos => yFoos.Count())
.ThenByDescending(yFoos => yFoos.Select(foo => foo.Z).Max())
.First());
如果你真的关心性能,你很可能会以一些复杂性为代价来改进它:
当选择具有大多数元素或最高Z值的组时,对每组中的元素执行两次传递。首先使用yFoos.Count()
计算元素,然后使用yFoos.Select(foo => foo.Z).Max()
计算最大Z值。但是,您可以使用Aggregate
一次性完成相同操作。
此外,没有必要对所有组进行排序以找到最大的&#34;组。相反,可以对所有组进行单次传递,以找到最大的&#34;再次使用Aggregate
。
result = foos
.GroupBy(
outerFoo => outerFoo.X,
(x, xFoos) => xFoos
.GroupBy(
innerFoo => innerFoo.Y,
(y, yFoos) => new
{
Foos = yFoos,
Aggregate = yFoos.Aggregate(
(Count: 0, MaxZ: int.MinValue),
(accumulator, foo) =>
(Count: accumulator.Count + 1,
MaxZ: Math.Max(accumulator.MaxZ, foo.Z)))
})
.Aggregate(
new
{
Foos = Enumerable.Empty<Foo>(),
Aggregate = (Count: 0, MaxZ: int.MinValue)
},
(accumulator, grouping) =>
grouping.Aggregate.Count > accumulator.Aggregate.Count
|| grouping.Aggregate.Count == accumulator.Aggregate.Count
&& grouping.Aggregate.MaxZ > accumulator.Aggregate.MaxZ
? grouping : accumulator)
.Foos);
我在ValueTuple
中使用Aggregate
作为累加器,因为我希望它具有良好的性能。但是,如果你真的想知道你应该测量。
答案 2 :(得分:1)
你可以更多地忽略外部分组,剩下的只是一点点推荐MaxBy,类似于两个参数排序。如果你实现了这个,你最终会得到类似的东西:
public IEnumerable<IGrouping<string, Foo>> GetFoo2(IEnumerable<Foo> foos)
{
return foos.GroupBy(f => f.X)
.Select(f => f.GroupBy(g => g.Y)
.MaxBy2(g => g.Count(), g => g.Max(m => m.Z)));
}
当你将所有功能转移到非常普通的功能时,你可以调用这种linq方法是多么值得怀疑。您还可以使用聚合实现功能。有两种选择。种子和没有种子。我喜欢后一种选择:
public IEnumerable<IGrouping<string, Foo>> GetFoo3(IEnumerable<Foo> foos)
{
return foos.GroupBy(f => f.X)
.Select(f => f.GroupBy(g => g.Y)
.Aggregate((a, b) =>
a.Count() > b.Count() ? a :
a.Count() < b.Count() ? b :
a.Max(m => m.Z) >= b.Max(m => m.Z) ? a : b
));
}
如果Count()不是恒定时间,性能会受到影响,这是不能保证的,但在我的测试中它运行良好。带种子的变体会更复杂,但如果做得好可能会更快。
答案 3 :(得分:0)
进一步考虑这一点,我意识到你的orderby
可以大大简化一切,但仍然不确定这是可以理解的。
var ans = foos.GroupBy(f => f.X, (_, gXfs) => gXfs.GroupBy(gXf => gXf.Y).Select(gXgYfs => gXgYfs.ToList())
.OrderByDescending(gXgYfs => gXgYfs.Count).ThenByDescending(gXgYfs => gXgYfs.Max(gXgYf => gXgYf.Z)).First());
虽然可以在LINQ中执行此操作,但如果在使用查询理解语法时将其设置为一个语句,我发现它不再紧凑或易懂:
var ans = from foo in foos
group foo by foo.X into foogX
let foogYs = (from foo in foogX
group foo by foo.Y into rfoogY
select rfoogY)
let maxYCount = foogYs.Max(y => y.Count())
let foogYsmZ = from fooY in foogYs
where fooY.Count() == maxYCount
select new { maxZ = fooY.Max(f => f.Z), fooY = from f in fooY select f }
let maxMaxZ = foogYsmZ.Max(y => y.maxZ)
select (from foogY in foogYsmZ where foogY.maxZ == maxMaxZ select foogY.fooY).First();
如果您愿意使用lambda语法,有些事情会变得更容易和更短,但不一定更容易理解:
var ans = from foogX in foos.GroupBy(f => f.X)
let foogYs = foogX.GroupBy(f => f.Y)
let maxYCount = foogYs.Max(foogY => foogY.Count())
let foogYmCmZs = foogYs.Where(fooY => fooY.Count() == maxYCount).Select(fooY => new { maxZ = fooY.Max(f => f.Z), fooY })
let maxMaxZ = foogYmCmZs.Max(foogYmZ => foogYmZ.maxZ)
select foogYmCmZs.Where(foogYmZ => foogYmZ.maxZ == maxMaxZ).First().fooY.Select(y => y);
使用大量lambda语法,你可以完全理解:
var ans = foos.GroupBy(f => f.X, (_, gXfs) => gXfs.GroupBy(gXf => gXf.Y).Select(gXgYf => new { fCount = gXgYf.Count(), maxZ = gXgYf.Max(f => f.Z), gXgYfs = gXgYf.Select(f => f) }))
.Select(fC_mZ_gXgYfs_s => {
var maxfCount = fC_mZ_gXgYfs_s.Max(fC_mZ_gXgYfs => fC_mZ_gXgYfs.fCount);
var fC_mZ_gXgYfs_mCs = fC_mZ_gXgYfs_s.Where(fC_mZ_gXgYfs => fC_mZ_gXgYfs.fCount == maxfCount).ToList();
var maxMaxZ = fC_mZ_gXgYfs_mCs.Max(fC_mZ_gXgYfs => fC_mZ_gXgYfs.maxZ);
return fC_mZ_gXgYfs_mCs.Where(fC_mZ_gXgYfs => fC_mZ_gXgYfs.maxZ == maxMaxZ).First().gXgYfs;
});
(我修改了第三种可能性以减少重复计算并且更加干燥,但这确实使它更加冗长。)