verbesserte AggregatFunktion für Linq-Max / Linq-Min

    • VB.NET

      verbesserte AggregatFunktion für Linq-Max / Linq-Min

      Die Linq.Max(selector As Func(T, TProp As IComparable)) - Aggregat-Funktion geht mir auf die Nerven.
      Angenommen eine Punkte-Wolke, und ich brauche den höchsten Punkt:

      VB.NET-Quellcode

      1. Dim rnd = New Random
      2. Dim points = Enumerable.Range(0, 50).Select(Function(i) New Point(rnd.Next(-100, 100), rnd.Next(-100, 100))).ToList
      3. Write("Max(pt.Y):", points.Max(Function(pt) pt.Y))

      Ausgabe: "Max(pt.Y): 92"
      Na toll! Der Y-Wert des höchsten Punktes. X(
      Ich wollte aber den höchsten Punkt selbst haben: {X=47,Y=92}

      Also habichmir was ausgedacht, was ähnlich wie bei Order by nicht den Vergleichswert selbst zurückgibt, sondern den Träger des Vergleichswertes. Logischerweise heißt diese Aggregat-Funktion also MaxBy():
      Spoiler anzeigen

      VB.NET-Quellcode

      1. Public Structure MinMax(Of T, T2 As IComparable)
      2. Public Min, Max As T
      3. Public MinVal, MaxVal As T2
      4. Private _Selector As Func(Of T, T2)
      5. Public ReadOnly HasValue As Boolean
      6. Public Sub New(ByVal first As T, ByVal selector As Func(Of T, T2))
      7. _Selector = selector
      8. Me.Min = first : Me.Max = first
      9. MinVal = _Selector(first) : MaxVal = MinVal
      10. HasValue = True
      11. End Sub
      12. Public Sub Check(ByVal itm As T)
      13. Dim val = _Selector(itm)
      14. If val.CompareTo(MinVal) < 0 Then
      15. Min = itm
      16. MinVal = val
      17. ElseIf val.CompareTo(MaxVal) > 0 Then
      18. Max = itm
      19. MaxVal = val
      20. End If
      21. End Sub
      22. End Structure
      23. <Extension()> _
      24. Public Function Extremum(Of T, T2 As IComparable)( _
      25. ByVal items As IEnumerable(Of T), ByVal selector As Func(Of T, T2)) As MinMax(Of T, T2)
      26. With items.GetEnumerator
      27. If Not .MoveNext Then .Dispose() : Return Nothing
      28. Extremum = New MinMax(Of T, T2)(.Current, selector)
      29. While .MoveNext : Extremum.Check(.Current) : End While
      30. .Dispose()
      31. End With
      32. End Function
      33. <Extension()> _
      34. Public Function MaxBy(Of T, T2 As IComparable)( _
      35. ByVal items As IEnumerable(Of T), ByVal selector As Func(Of T, T2)) As T
      36. Return items.Extremum(selector).Max
      37. End Function
      38. <Extension()> _
      39. Public Function MinBy(Of T, T2 As IComparable)( _
      40. ByVal items As IEnumerable(Of T), ByVal selector As Func(Of T, T2)) As T
      41. Return items.Extremum(selector).Min
      42. End Function
      Jo, diese Extensions lassen sich identisch wie olle Linq.Max() verwenden:

      VB.NET-Quellcode

      1. Module modMain
      2. Public Sub main()
      3. Dim rnd = New Random
      4. Dim points = Enumerable.Range(0, 50).Select(Function(i) New Point(rnd.Next(-100, 100), rnd.Next(-100, 100))).ToList
      5. points.ForEach(Sub(pt) Console.Write("{0} ", pt)) 'alle ausgeben
      6. Write()
      7. 'aufruf als Extension
      8. Write("Max(pt.Y):", points.Max(Function(pt) pt.Y))
      9. Write("MaxBy(pt.Y):", points.MaxBy(Function(pt) pt.Y))
      10. Write("Extremum(pt.Y):", points.Extremum(Function(pt) pt.Y))
      11. 'aufruf in Linq-Syntax
      12. Write("Min(pt.Y):", Aggregate pt In points Into Min(pt.Y))
      13. Write("MinBy(pt.Y):", Aggregate pt In points Into MinBy(pt.Y))
      14. Console.ReadLine()
      15. End Sub
      16. Private Sub Write(ByVal ParamArray msg() As Object)
      17. Console.WriteLine(String.Concat(msg.Select(Function(m) m.ToString & " ")))
      18. End Sub
      19. End Module
      Ausgabe
      {X=82,Y=-64} {X=-84,Y=-82} {X=-92,Y=47} {X=-30,Y=57} {X=-54,Y=46} {X=-96,Y=-63} {X=37,Y=-33} {X=-59,Y=-65} {X=28,Y=-4} {X=-42,Y=7} {X=23,Y=62} {X=80,Y=-8} {X=33,Y=-53} {X=-60,Y=-81} {X=-89,Y=-73} {X=-38,Y=-99} {X=68,Y=-87} {X=4,Y=-12} {X=36,Y=-10} {X=20,Y=7} {X=-11,Y=91} {X=68,Y=-84} {X=-33,Y=80} {X=37,Y=-26} {X=37,Y=-2} {X=-20,Y=-30} {X=96,Y=-17} {X=96,Y=-80} {X=-45,Y=23} {X=-16,Y=60} {X=7,Y=-50} {X=45,Y=-81} {X=-16,Y=-98} {X=-31,Y=23} {X=62,Y=53} {X=-1,Y=-62} {X=76,Y=50} {X=-82,Y=31} {X=93,Y=64} {X=-75,Y=53} {X=10,Y=66} {X=-19,Y=12} {X=47,Y=92} {X=-95,Y=-16} {X=-83,Y=-16} {X=-68,Y=-88} {X=-95,Y=59} {X=-100,Y=-62} {X=-54,Y=49} {X=32,Y=-35}
      Max(pt.Y): 92
      MaxBy(pt.Y): {X=47,Y=92}
      Extremum(pt.Y): MinMax<Point>: {X=-38,Y=-99} - {X=47,Y=92}
      Min(pt.Y): -99
      MinBy(pt.Y): {X=-38,Y=-99}

      Im beiliegenden Sample habich auch noch was mit LinqToDataset verbrochen, nämlich es geht um eine PatientenAkten-Verwaltung, und da will man eine Ansicht des jeweils letzten AusleihVorgangs (History) pro PatientenAkte haben:

      VB.NET-Quellcode

      1. Dim lastFileOuts = From pfile In HospitalDts.PatientFile
      2. Select Aggregate hist In pfile.GetHistoryRows Into MaxBy(hist.OutAt)
      Also selektiert wird von jeder PatientenAkte ihre Ausleih-History mit dem spätesten AusgabeDatum (MaxBy(hist.OutAt))
      Dateien
      • Hospital.zip

        (39,12 kB, 171 mal heruntergeladen, zuletzt: )

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „ErfinderDesRades“ ()