2016-07-29 3 views
1

私はC#で把握しようとしている専門的なクエリを持っています。GroupBy()後のLINQの配列インデックスによる要素の平均化

私はクラスを持っている:

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

これは、特定の時間におけるセンサの数の報告を表します。私がImmutableArray<TimeValues> SomeArrayで使用しているのは、しばしば2番目のレポートに続く一連のレポートです。

私が解決しようとしている問題は、30秒間隔でグループ化し、各センサーのレポートを個別に平均化する方法です。

私は2つのレポート持っているのであれば、たとえば、:

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

を、我々はT1、T2は互いに30秒以内であることを前提とし、私は操作がになりたい:

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

私のような何かを開始している:それは私はかなりのouを理解することはできません最後の行である

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, ...)) 

t。ストレスを与えなければならない1つの点は、平均化される値の順序は、報告するセンサと一致しなければならないので、維持されなければならないということである。これはLINQでgroup byを使用して初めてのことですが、おそらくより複雑なものの1つです。

+0

https://stackoverflow.com/questions/24373866/average-int-array-elements-with-a-groupby –

答えて

2

おそらく、ご質問はAverage int Array elements with a GroupByと重複している可能性があります。しかし、私は具体的な答えに驚いていません。つまり、値の配列のインデックスごとに1回、グループ結果を複数回反復します。 IMHOグループを一度反復する方が良いでしょう。値の配列自体を繰り返し繰り返します。あなたの質問のプレゼンテーションは他のプレゼンテーションよりも優れているので、ここで答えを出しています。 :)


まず、私はあなたのグループ化機能を理解していません。 30秒の間隔が必要な場合は、ちょうど秒を30で割ると、良いグループ化キーが得られるはずです。あなたは基本的に同じことを達成するために多くの苦労をしているようです。

第2に、私はImmutableArray<T>とパッケージをインストールする気がなかったし、そのクラスは本当に質問とは関係がないので、私の答えは単純な古い配列を使用しています。

第3に、私は確信していませんthis answerあなたが望むものさえします。1 from 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; 
        })); 

上記は、そのそれぞれのインデックスの各値を蓄積するLINQ Aggregate()方法を使用し、その後終了時に平均値を算出します。これらの関数には、2つの異なるラムダ匿名メソッドがそれぞれ使用されます。実際に名前付きのメソッドに分割した場合、コードは実際にはもっと読みやすくなります。どちらの方法でも問題ありません。

オブジェクトの割り当てを最小限に抑え(リストを作成して最後に配列に変換する必要がないため)、IMHOはインテントをコードの後ろに明確に表現しています。

ImmutableArray<T>で動作するように、配列ベースの例を変更することができます。 :)

+0

私はあなたの答えが好きです。 1つの問題は、TimeSpanがDateTimeのときにいつTimeSpanを使用するかを想定していることです。ただし、この目的のために切り替えたり、それを解釈するプロパティを作成することはできます。私は間違いなく秒の除算を使用します。なぜなら、私が代替を考慮しなかったという条件付きを避けるからです。 –

+0

はい、申し訳ありませんが、私はそれを言及するのを忘れました。 'TimeSpan'と' DateTime'のどちらを使っても問題はありません。基本的なテクニックは同じです。 'DateTime'を使いたい場合は、' Ticks'プロパティを使い、300,000,000(1秒で10,000,000ティックがあります)を除算/掛け算するか、今使っているバージョンに固執するだけです。グループ化/平均化操作のために 'TimeSpan'を使用するには、クエリのすべての' DateTime'値から固定された 'DateTime'を減算して最後に' TimeSpan'値を追加します。 –

+0

(私が認めたのは、ここで 'TimeSpan'を使った主な理由は使い勝手が悪かったからです。あなたが簡単に[mcve]をコピーしてくれなかったので、サンプルデータセット主に、後者よりもタイピングが少ないので、「DateTime」よりも「TimeSpan」を使用しています:) –

2

は、私はあなたが空想一行の方法でそれを書くことができないと思いますが、あなたはまだそれがこのような何かを動作させることができます。

 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; 
      }); 

ます。また、配列の長さを指定することが望ましくないことに注意すべきです私のように、このセレクタコードで(数字3)。おそらく、この定数を静的にどこかに宣言し、TimeValuesインスタンスが常に値の配列に3つの値を持つことをコンストラクタまたはプロパティセッターで明示的に確認してください。これはaviod IndexOutRangeExceptionsに役立ちます。

+0

いずれかのレポートファイルでは、列の数は常に同じです。 –

+0

これは私のやり方ではありませんが(私の答えを見てください)、上記は確かに合理的なアプローチであり、これは良い、有用な答えです。 –

+0

私は1つのキーでグループ化することについてかなり学んだ、私はピーターの答えとあなたの内側の部分を取って、これに入れて終わった:https://dotnetfiddle.net/S30uct –

関連する問題