【问题标题】:Use Linq to statistics data with average使用 Linq 统计平均数据
【发布时间】:2012-04-19 07:12:03
【问题描述】:

我有一个名为 HardwarePerformance 的类,其中包含以下字段:time、cpuUsagePercent、memoryUsagePercent、diskUsage、diskRead、diskWrite、latency。 我已经填充了一个 List 对象来包含非统计数据。 我需要计算 Linq 命令中所有这些字段的平均值。 我不知道如何使用 Linq 获得平均值。下面是我写的代码(当然是不正确的)。

var _hardwarePerf = (from _stats in _statsList 
                    where _time>=DateTime.Parse("2012-04-04 00:00:00")
                        &&_time<=DateTime.Parse("2012-04-04 11:00:00") 
                    select new {
                           Latency = _stats.latency, 
                           CpuUsagePercent = _stats.cpuUsagePercent, 
                           MemoryUsagePercent = _stats.memoryUsagePercent, 
                           DiskUsage = _stats.diskUsage, DiskRead = _stats.diskRead, 
                           DiskWrite = _stats.diskWrite
                    }).Average(Latency => Latency.Latency);

这段代码的语法是正确的。但是我想同时计算所有的平均值,我的代码怎么写?

【问题讨论】:

  • 请格式化您的代码,以便我们下次不需要水平滚动。
  • 顺便说一句,为什么你的局部变量以_ 开头?这是一个非常不寻常的命名约定。
  • _time_stats 有什么关系,因为你没有写过_stats._time

标签: c# linq average


【解决方案1】:

在 LINQ 中没有为此的内置方法。如果您不介意您将多次迭代集合,并且假设匿名对象对您来说没问题,您可以这样做(其中filteredStats 包含您想要平均的数据,可能按时间过滤) :

var averages =
    new
    {
        Latency = filteredStats.Average(x => x.Latency),
        CpuUsagePercent = filteredStats.Average(x => x.CpuUsagePercent),
        …
    };

如果这不是您想要的,您可能需要自己计算平均值。

【讨论】:

  • 我现在就喜欢这种风格的编程,谢谢你的回答。
【解决方案2】:

下面是计算一次迭代中的所有平均值,虽然代码很多。

请注意,我假设 time 是您的 stats 对象上的一个属性,否则您的 select where time 没有多大意义。

var _hardwareAccumulator =
    _statsList
        .Where(s =>
            s.time >= DateTime.Parse("2012-04-04 00:00:00") &&
            s.time <= DateTime.Parse("2012-04-04 11:00:00"))
        .Aggregate(
            new
            {
                count = 0,
                cpuUsagePercent = 0.0,
                memoryUsagePercent = 0.0,
                diskUsage = 0.0,
                diskRead = 0.0
            },
            (acc, s) =>
                new
                {
                    count = acc.count + 1,
                    cpuUsagePercent =
                        acc.cpuUsagePercent + s.cpuUsagePercent,
                    memoryUsagePercent =
                        acc.memoryUsagePercent + s.memoryUsagePercent,
                    diskUsage =
                        acc.diskUsage + s.diskUsage,
                    diskRead =
                        acc.diskRead + s.diskRead
                }
            );
var _hardwarePerf = new
{
    cpuUsagePercent =
        _hardwareAccumulator.cpuUsagePercent / _hardwareAccumulator.count,
    memoryUsagePercent =
        _hardwareAccumulator.memoryUsagePercent / _hardwareAccumulator.count,
    diskUsage =
        _hardwareAccumulator.diskUsage / _hardwareAccumulator.count,
    diskRead =
        _hardwareAccumulator.diskRead / _hardwareAccumulator.count
};

【讨论】:

    【解决方案3】:

    您要么需要在单独的语句中执行此操作,要么可以以这种不太直观的方式执行此操作:

    var averages = statsList.Where(s => s.time >= new DateTime(2012, 4, 4) && s.time <= new DateTime(2012, 4, 04, 11, 0, 0))
                .GroupBy(s => 1)
                .Select(grp => new
                {
                    Latency = grp.Average(s => s.latency),
                    CpuUsagePercent = grp.Average(s => s.cpuUsagePercent),
                    MemoryUsagePercent = grp.Average(s => s.memoryUsagePercent),
                    DiskUsage = grp.Average(s => s.diskUsage),
                    DiskRead = grp.Average(s => s.diskRead),
                    DiskWrite = grp.Average(s => s.diskWrite)
                }).FirstOrDefault();
    

    请注意,.GroupBy(s =&gt; 1) 只是为了能够计算每个属性的平均值。另请注意,我已从您的属性名称中删除了下划线,并将 time 添加到您的课程中。

    另一种方法更容易阅读:

    var theStats = statsList.Where(s => s.time >= new DateTime(2012, 4, 4) && s.time <= new DateTime(2012, 4, 04, 11, 0, 0));
    var Latency = theStats.Average(s => s.latency);
    var CpuUsagePercent = theStats.Average(s => s.cpuUsagePercent);
    // ....
    

    【讨论】:

    • 这与仅过滤time 上的统计数据,然后使用过滤列表中的所有平均值创建一个匿名对象有何不同?分组有什么作用?
    • @Rawling:分组允许在一个查询中选择具有所有平均值的匿名类型。
    • @Rawling:恐怕你还没有理解这个问题的核心问题。您不能按时间过滤,然后只选择全部 平均值 1。因此我添加了伪组。
    • 我知道标准的 Linq Average 只能得到一个平均值。我看不到执行虚拟分组和纯粹选择的实用程序,因此您可以在单个 WTF 诱导 Linq 语句中获得所有平均值。我同意您的第二个解决方案是获得所有平均值的最简单、最容易阅读的方法,并且确实应该是公认的答案,因为 OP 显然不关心多次迭代结果。
    【解决方案4】:

    您可以使用“分组依据”然后您可以使用平均投影来计算。

    var query = from _stats in _statsList 
        group _stats by new {
                   Latency = _stats.latency, 
                   CpuUsagePercent = _stats.cpuUsagePercent, 
                   MemoryUsagePercent = _stats.memoryUsagePercent, 
                   DiskUsage = _stats.diskUsage, 
                   DiskRead = _stats.diskRead, 
                   DiskWrite = _stats.diskWrite
    
        } into myGroup
        select new {
            AverageVal = myGroup.Average(x => x.Value),
            CpuUsagePercent = myGroup.Key.CpuUsagePercent,
            ... // the rest of your projection
    };
    

    LINQ Average

    【讨论】:

    • 你将如何使用它来一次计算所有字段的平均值?
    • 编辑后的代码对我没有任何意义? Value 是什么?而且您按不同值进行分组没有任何意义,它当然不会计算平均值。
    • 感谢您的回答,我会试试这个解决方案。
    猜你喜欢
    • 1970-01-01
    • 2011-02-12
    • 1970-01-01
    • 2020-12-22
    • 2013-03-06
    • 1970-01-01
    • 2021-12-23
    • 2018-11-16
    • 2020-02-16
    相关资源
    最近更新 更多