2016-07-29 7 views
1

Ich habe eine ziemlich spezialisierte Abfrage, die ich in C# herausfinden möchte.Mittelwertbildung von Elementen nach Array-Indizes in LINQ nach GroupBy()

Ich habe eine Klasse:

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

Dieser Bericht eine Reihe von Sensoren zu einem bestimmten Zeitpunkt darstellt. Was ich in einer ImmutableArray<TimeValues> SomeArray nutze, das stellt eine Reihe von Berichten oft bis auf die Sekunde dar.

Das Problem, das ich versuche zu lösen, ist, wie nach 30-Sekunden-Intervallen zu gruppieren, und die Berichte jedes Sensors einzeln zu berechnen.

So zum Beispiel, wenn ich zwei Berichte haben:

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

und wir gehen davon aus, dass T1 und T2 innerhalb von 30 Sekunden voneinander sind, möchte ich die Operation zu folgenden Ergebnissen führen:

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

ich habe mit etwas begonnen, wie zum Beispiel:

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

Es ist die letzte Zeile, die ich nicht ganz Figur ou t. Ein Punkt, der betont werden muss, ist, dass die Reihenfolge der gemittelten Werte beibehalten werden muss, da sie mit den berichtenden Sensoren übereinstimmen muss. Dies ist das erste Mal, dass ich in LINQ eine Gruppe verwende, und wahrscheinlich eine der komplizierteren.

+0

Mögliches Duplikat von https://stackoverflow.com/questions/24373866/average-int-array-elements-with-a-groupby –

Antwort

2

Wahrscheinlich ist Ihre Frage ein Duplikat von Average int Array elements with a GroupBy. Ich bin jedoch nicht begeistert von der spezifischen Antwort, d. H. Dass es die Gruppenergebnisse mehrere Male wiederholt, einmal für jeden Index im Werte-Array. IMHO ist es besser, die Gruppe einmal zu iterieren und die wiederholten Iterationen über die Werte-Arrays selbst zu setzen. Und die Präsentation Ihrer Frage ist besser als die andere, also gebe ich hier eine Antwort. :)


Zuerst verstehe ich Ihre Gruppierungsfunktion nicht. Wenn Sie Intervalle von 30 Sekunden wünschen, scheint es mir, dass Sie nur einen guten Gruppierungsschlüssel erhalten, wenn Sie die Sekunden durch 30 teilen. Sie scheinen zu viel Mühe zu haben, um dasselbe zu erreichen.

Zweitens hatte ich keine Lust, das Paket mit ImmutableArray<T> zu installieren, und diese Klasse hat nicht wirklich etwas mit der Frage zu tun, so verwendet meine Antwort nur ein einfaches altes Array.

Drittens bin ich nicht überzeugt this answer macht sogar, was Sie wollen.Die ein from Meleagre sieht sehr gut aus, aber ich würde einen anderen Weg nehmen, wie unten gezeigt:

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

Die oben verwendet die LINQ Aggregate() Methode jeden Wert in ihrem jeweiligen Index zu akkumulieren, und berechnet dann den Mittelwert am Ende. Für diese Funktionen werden jeweils zwei verschiedene anonyme Lambda-Methoden verwendet. IMHO, der Code wäre tatsächlich ein bisschen mehr lesbar, wenn Sie diese in tatsächlich benannten Methoden gebrochen. So oder so ist in Ordnung.

Ich bevorzuge diesen Ansatz, weil es Objektzuordnungen minimiert (keine Notwendigkeit, eine Liste zu erstellen und dann in ein Array am Ende konvertieren) und IMHO drückt die Absicht hinter dem Code deutlicher.

Ich vertraue darauf, dass Sie das Array-basierte Beispiel anpassen können, um mit ImmutableArray<T> zu arbeiten. :)

+0

Es gibt eine Menge, die ich an deiner Antwort mag. Ein Problem besteht darin, dass Sie annehmen, wann ein TimeSpan ist, wenn es eine DateTime ist - obwohl ich es in Erwägung ziehen könnte, es für diesen Zweck zu wechseln oder eine Eigenschaft zu erstellen, um es zu interpretieren. Ich werde auf jeden Fall die Zweiteilung verwenden, weil es eine Bedingung vermeidet, für die ich die Alternative nicht in Betracht gezogen habe. –

+0

Ja, tut mir leid, dass ich das vergessen habe. Es spielt keine Rolle, ob Sie 'TimeSpan' oder' DateTime' verwenden, die grundlegende Technik ist die gleiche. Wenn Sie 'DateTime' verwenden möchten, können Sie den Division-Ansatz verwenden, indem Sie die' Ticks'-Eigenschaft verwenden und durch 300.000.000 teilen/multiplizieren (es gibt 10.000.000 Ticks in einer Sekunde) oder einfach bei der Version bleiben, die Sie gerade verwenden. Sie können 'TimeSpan' nur für die Gruppierung/Mittelung verwenden, indem Sie einen 'TimeSpan'-Wert erstellen, indem Sie ein festes' DateTime' von allen 'DateTime'-Werten für die Abfrage subtrahieren und am Ende wieder hinzufügen. –

+0

(Ich gebe zu, der Hauptgrund, warum ich 'TimeSpan 'hier verwendet habe, war die Bequemlichkeit. Da Sie zu Beginn keine leicht zu kopierende [mcve] bereitgestellt haben, war es für mich viel einfacher, einen Beispieldatensatz zu erstellen "TimeSpan" als "DateTime" zu verwenden, hauptsächlich weil es weniger mit dem ersten als dem letzten tippt :)) –

2

Ich denke, man es nicht schreiben kann auf sympatische Weise einzeilige aber man kann es immer noch mit so etwas wie dies funktioniert:

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

Sie sollten auch beachten, dass es unerwünscht ist Feldlänge angeben (Nummer 3) in diesem Selektor-Code wie ich. Sie sollten diese Konstante wahrscheinlich irgendwo statisch deklarieren und sicherstellen, dass Ihre TimeValues-Instanzen bei expliziten Checks im Konstruktor oder Property Setter immer 3 Werte in ihren Value-Arrays haben. Dies wird Ihnen helfen, IndexOutRangeExceptions zu vermeiden.

+0

In jeder der Berichtsdateien ist die Anzahl der Spalten immer gleich. –

+0

Es ist nicht die Art, wie ich es tun würde (siehe meine Antwort), aber das obige ist sicherlich ein vernünftiger Ansatz und IMHO ist dies eine gute, nützliche Antwort. –

+0

Ich habe ziemlich viel über die Gruppierung mit einem einzigen Schlüssel gelernt. Ich habe Peters Antwort und deinen inneren Teil genommen und es in dieses geschrieben: https://dotnetfiddle.net/S30uct –