2017-11-27 2 views
0

Ich schreibe eine Funktion, die eine Reihe von System.Windows.Point übernimmt und ein ValueTuple mit den Grenzwerten X und Y für alle Punkte zurückgibt. Dies soll die Beschriftungen von Diagrammachsen bestimmen.LINQ Min/Max und Minimierung von Iterationen

Ich versuche, die Anzahl der Iterationen der Liste auf nur eine zu minimieren. Nach einiger Zeit des Googelns habe ich einen Ansatz wie unten beschrieben (gelesen: "kopiert"), von dem mir gesagt wird, dass er genau das tun wird. Aber ich bin mir nicht sicher, wie ich das bestätigen soll. Ich frage mich, wenn jemand besser vertraut mit LINQ kann

  1. Bestätigen Sie, dass die folgende Funktion wird in der Tat nur einmal die Liste iterieren, obwohl es 4 verschiedene Werte der Berechnung ist
  2. Wenn ja, mir erklären, wie das ist. Weil es für mich aussieht, als ob der anonyme Typ konstruiert wird, ruft "Min" und "Max" auf der gegebenen Liste zweimal für jeden auf. Warum führt das nicht zu 4 Iterationen?
  3. Vielleicht sogar erklären, wie ich für mich die Anzahl der Iterationen überprüft haben könnte, so dass ich in der Zukunft solche Fragen nicht stellen muss. Ich sehe nicht, wie ich damit umgehen soll.

Mein LINQ-Fu ist noch nicht stark.

Dank

/// <summary> 
    /// X and Y axis boundaries in the form of a System.ValueTuple. 
    /// </summary> 
    public (double MinX, double MaxX, double MinY, double MaxY) 
    GetBounds(List<System.Windows.Point> pts) 
    { 


     // Calculate the bounds with a LINQ statement. Is this one iteration or many? 

     var a = pts.GroupBy(i => 1).Select(
      pp => new 
      { 
       MinY = pp.Min(p => p.Y), 
       MaxY = pp.Max(p => p.Y), 
       MinX = pp.Min(p => p.X), 
       MaxX = pp.Max(p => p.X) 
      }).FirstOrDefault(); 


     return a != null ? (a.MinX, a.MaxX, a.MinY, a.MaxY) : (0, 0, 0, 0); 
    } 
+4

es zu überprüfen, könnten Sie implementieren Ihre eigenen 'IEnumerable ', die an die Konsole schreibt, wenn iteriert. – Blorgbeard

+0

Gibt es einen Grund, warum Sie den Eingabeparameter nicht verwenden? Woher wissen Sie, dass die Sammlung mehrmals durchlaufen wird? –

+0

Vor allem, meine Entschuldigung. Das "ProfilePoints" sollte der Eingabeparameter sein. Ich habe meinen Beitrag in letzter Minute bearbeitet. Es wurde jetzt behoben. Zweitens, ich weiß nicht, ob es mehrmals wiederholt wird. Das ist, was ich versuche zu bestimmen – Joe

Antwort

4

Bestätigen Sie, dass die folgende Funktion wird in der Tat nur die Liste einmal durchlaufen, obwohl es 4 verschiedene Werte der Berechnung ist

Nein - die ursprüngliche Liste effektiv 4 wiederholt werden mal. Sie erstellen eine Gruppierung "null", die die ursprüngliche Sammlung umschließt, sodass Sie die Sammlung auf ein einzelnes Objekt "projizieren" können. Da Sie 4 Linq-Funktionen auf der "Gruppierung" aufrufen, wird die ursprüngliche Liste 4 Mal iteriert. Es ist funktional äquivalent zu:

var a = new 
     { 
      MinY = pts.Min(p => p.Y), 
      MaxY = pts.Max(p => p.Y), 
      MinX = pts.Min(p => p.X), 
      MaxX = pts.Max(p => p.X) 
     }; 

Wenn das ein Problem für Sie ist, der idiomatische Weg, um die Grenzen zu finden wäre, eine foreach Schleife zu verwenden und zu verfolgen, die min und max x und y-Koordinaten manuell. Es wäre eine relativ kurze Funktion, und würde die Anzahl der Iterationen um 75% reduzieren:

int MinX, MaxX, MinY, MaxY; 
MaxX = MaxY = Int.MinValue; 
MinX = MinY = Int.MaxValue; 
foreach(Point p in pts) 
{ 
    MinX = Math.Min(p.X, MinX); 
    MaxX = Math.Max(p.X, MaxX); 
    MinY = Math.Min(p.Y, MinY); 
    MaxY = Math.Max(p.Y, MaxY); 
} 
var a = new 
    { 
     MinY, 
     MaxY, 
     MinX, 
     MaxX 
    }; 

Sie konnte Verwendung Aggregate Schleife die Minuten finden und maxes mit einem Lambda:

var a = pts.Aggregate(
    new { 
     MinX = int.MaxValue, 
     MaxX = int.MinValue, 
     MinY = int.MaxValue, 
     MaxY = int.MinValue 
    }, 
    (acc, p) => new { 
     MinX = Math.Min(p.X, acc.MinX); 
     MaxX = Math.Max(p.X, acc.MaxX); 
     MinY = Math.Min(p.Y, acc.MinY); 
     MaxY = Math.Max(p.Y, acc.MaxY); 
    }); 

Der Aggregator erstellt jedoch für jedes Objekt in der Quellensammlung ein Objekt plus eins für das "ursprüngliche" Objekt. So wird die Liste nur iteriert einmal, aber mehrere temporäre Objekte werden erstellt, wodurch die Menge an Speicher erhöht wird, die GC'ed sein muss.

+0

Jetzt macht das viel mehr Sinn für mich. Es sah für mich genau so aus, wie du es beschreibst - dass ich es viermal wiederholen würde. Zuvor hatte ich die foreach-Schleife wie beschrieben manuell geschrieben, aber auf etwas mehr gehofft ... elegant – Joe

+0

Sie sagen, der Aggregator wird "ein Objekt für jede Iteration erstellen". Wie viele Iterationen gibt es bei Verwendung eines Aggregators? – Joe

+3

@Joe Aggregate iteriert jedes Element einmal. Also eine vollständige Traversierung. – JLRishe

3

Der Ansatz, den Sie dort verwenden, iteriert mindestens fünf Mal über die Eingabewerte (eins, um sie zu "gruppieren", und einmal für jedes min/max) und ist eine sehr seltsame Art und Weise zu gehen, was Sie tun .

Wenn Sie eine Sammlung von Werten zu nehmen und verdichten sie nach unten in einen Wert, ist die go-to Wahl .Aggregate (auch als reduce oder fold in anderen Sprachen bekannt).

In Ihrem Fall können Sie das so machen. Es sollte nur einmal Ihre Sammlung iterieren:

public static (double minX, double maxX, double minY, double maxY) 
GetBounds(List<Point> pts) 
{ 
    return pts.Aggregate(
     (Int32.MaxValue, Int32.MinValue, Integer.MaxValue, Int32.MinValue), 
     (acc, point) => 
     (
      Math.Min(point.X, acc.Item1), 
      Math.Max(point.X, acc.Item2), 
      Math.Min(point.Y, acc.Item3), 
      Math.Max(point.Y, acc.Item4) 
     )); 
} 
+1

* ist eine extrem seltsame Art und Weise * Nun, für L2O könnte es seltsam sein (obwohl ich es nicht extrem nennen würde), aber für L2E und ähnliches ist es ziemlich Standard (und die einzige) Art, mehrere Aggregate in einem Roundtrip zu tun. –