简体   繁体   English

GroupBy()之后按LINQ中的数组索引平均元素

[英]Averaging Elements by Array Indices in LINQ after GroupBy()

I have a rather specialized query I am trying to figure out in C#. 我有一个相当专业的查询,我想用C#找出。

I have a class: 我有一堂课:

class TimeValues 
{
    DateTime When;
    ImmutableArray<float> Values;
}

This represents a report of a number of sensors at a particular time. 这表示特定时间的多个传感器的报告。 Which I use in an ImmutableArray<TimeValues> SomeArray , that represents a series of reports often down to the second. 我在ImmutableArray<TimeValues> SomeArray中使用它表示一系列报告,这些报告通常可以ImmutableArray<TimeValues> SomeArray第二个报告。

The problem I am trying to solve is how to group by 30 second intervals, and average the reports of each sensor individually. 我要解决的问题是如何按30秒间隔进行分组,并分别平均每个传感器的报告。

So for example, if I have two reports: 例如,如果我有两个报告:

      s1   s2   s3
1:20  10   20   30
1:21  30   50   70

and we assume that t1 and t2 are within 30 seconds of each other, I want the operation to result in: 并且假设t1和t2相距30秒以内,我希望该操作产生以下结果:

      s1          s2          s3
1:00  avg(10,30)  avg(20,50)  avg(30,70)

I have started with something such as: 我从以下内容开始:

SomeArray.GroupBy(k => k.When.Second >= 30
       ? k.When.AddSeconds(-k.When.Second + 30)
       : k.When.AddSeconds(-k.When.Second), k => k.Values)
   .Select(group => new TimeValues(group.Key, ...))

It is the last line that I can't quite figure out. 这是我不太清楚的最后一行。 One point that must be stressed is that the order of the values being averaged must be maintained as it has to correspond with the sensors reporting. 必须强调的一点是,必须保持平均值的顺序,因为它必须与传感器报告相对应。 This is my first time using group by in LINQ, and probably one of the more complicated. 这是我第一次在LINQ中使用分组依据,可能是更复杂的一种。

I guess you can't write it in a fancy one-line way but you can still make it work with something like this: 我想您不能以花哨的单行方式编写它,但仍可以使它与以下代码一起工作:

        var aggregateValues = timeValues
            .GroupBy(k => k.When.Second >= 30
                ? k.When.AddSeconds(-k.When.Second + 30)
                : k.When.AddSeconds(-k.When.Second), k => k)
            .Select(group =>
            {
                var tv = new TimeValues() { When = group.Key };
                var values = new List<int>(3);
                for (int index = 0; index < 3; index++)
                {
                    values.Add(group.Average(t => t.Values[index]));
                }
                tv.Values = values.ToImmutableArray();
                return values;
            });

You should also note that it is undesireable to specify array length (number 3) in this selector code like I did. 您还应该注意,像我一样在此选择器代码中指定数组长度(数字3)是不希望的。 You should probably declare this constant somewhere statically and make sure with explicit checks in constructor or property setter that your TimeValues instances always have 3 values in thier Values arrays. 您可能应该在某个地方静态声明此常量,并在构造函数或属性设置器中进行显式检查,以确保您的TimeValues实例在其Values数组中始终具有3个值。 This will help you to aviod IndexOutRangeExceptions . 这将帮助您避免IndexOutRangeExceptions

Arguably, your question is a duplicate of Average int Array elements with a GroupBy . 可以说,您的问题是带有GroupByAverage int Array元素的重复。 However, I'm not thrilled by the specific answer, ie that it iterates the group results multiple times, once for each index in the values array. 但是,我对特定的答案并不感到兴奋,即对值数组中的每个索引一次遍历组结果多次。 IMHO it's better to iterate the group once, putting the repeated iterations over the values arrays themselves. 恕我直言,最好将组迭代一次,将重复的迭代放在values数组本身上。 And the presentation of your question is better than the other one, so I'm putting an answer here. 而且您提出的问题比其他问题要好,所以我在这里回答。 :) :)


First, I don't understand your grouping function. 首先,我不了解您的分组功能。 If you want intervals of 30 seconds, it seems to me that just dividing the seconds by 30 should give you a good grouping key. 如果您希望间隔为30秒,在我看来,仅将秒除以30即可为您提供良好的分组键。 You seem to be going to a lot of trouble to accomplish basically the same. 要完成基本相同的工作,您似乎会遇到很多麻烦。

Second, I didn't feel like installing the package with ImmutableArray<T> and that class doesn't really have anything to do with the question so my answer just uses a plain old array. 其次,我不希望使用ImmutableArray<T>安装该软件包,并且该类与问题实际上没有任何关系,因此我的答案仅使用普通的旧数组。

Third, I'm not convinced this answer even does what you want. 第三,即使您想要什么,我也不相信这个答案 The one from Meleagre looks pretty good, but I would take a different approach, shown below: 来自Meleagre的一个看起来不错,但是我将采用另一种方法,如下所示:

var result = from g in (from d in data
                 group d by (int)(d.When.TotalSeconds / 30))
             let c = g.Count()
             select new TimeValues(TimeSpan.FromSeconds(g.Key * 30),
                g.Aggregate(new float[g.First().Values.Length],
                    (a, tv) =>
                    {
                        for (int i = 0; i < a.Length; i++)
                        {
                            a[i] += tv.Values[i];
                        }

                        return a;
                    },
                    a =>
                    {
                        for (int i = 0; i < a.Length; i++)
                        {
                            a[i] /= c;
                        }

                        return a;
                    }));

The above uses the LINQ Aggregate() method to accumulate each value in its respective index, and then computes the average at the end. 上面的方法使用LINQ Aggregate()方法在其各自的索引中累加每个值,然后最后计算平均值。 Two different lambda anonymous methods are used for these functions, respectively. 这些函数分别使用两种不同的lambda匿名方法。 IMHO, the code would actually be a bit more readable if you broke those out into actual named methods. 恕我直言,如果将代码分解为实际的命名方法,则代码实际上将更具可读性。 Either way is fine. 两种方法都可以。

I prefer this approach because it minimizes object allocations (no need to build a list and then convert to an array at the end) and IMHO expresses the intent behind the code more clearly. 我更喜欢这种方法,因为它最大程度地减少了对象分配(无需构建列表,然后在最后将其转换为数组),并且IMHO更清楚地表达了代码背后的意图

I trust you can adapt the array-based example to work with ImmutableArray<T> . 我相信您可以修改基于数组的示例以与ImmutableArray<T> :) :)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM