Abfrage mit LINQ (Gruppieren, Sortieren und Daten flaggen)

  • C#
  • .NET (FX) 4.5–4.8

Es gibt 12 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

    Abfrage mit LINQ (Gruppieren, Sortieren und Daten flaggen)

    Hallo,

    ich habe eine Klasse mit Fahrzeugen:

    Model

    C#-Quellcode

    1. public class VehicleData
    2. {
    3. public int ID { get; set; }
    4. public bool DuplicateIndicator { get; set; }
    5. public string FIN { get; set; }
    6. public string Customer { get; set; }
    7. public DateTime FirstRegistration { get; set; }
    8. public DateTime LastVisit { get; set; }
    9. public DateTime SafetyInspection { get; set; }
    10. public int LastMileage { get; set; }
    11. public string RegistrationNumber { get; set; }
    12. }

    und eine Liste mit entsprechenden Objekten, die auch entsprechend gefüllt ist

    C#-Quellcode

    1. List<VehicleData> Vehicles = new List<VehicleData>();


    Zur Zeit gehe ich so vor:
    1. Die Liste wird sortiert und zwar nach der Fahrgestellnummer (FIN), dann abseteigend nach dem Datum des letzten Besuchs
    2. In einer Schleife wird die Liste durchlaufen, das erste Auftreten einer FIN bedeutet "dieser Datensatz ist aktuell", alle weiteren Datensätze der selben FIN sind veraltete Daten, also Dubletten
    Nachtrag, die Schleife ist bisher auch nicht Parallel ausgeführt worden. ich bin mir auch nicht so sicher, ob in diesem Fall das Ergebnis bei Paralell-Verarbeitung nicht sogar verfälscht werden könnte.
    Vielleicht kann ja auch dazu jemand Berufener etwas sagen?

    hier der Code dazu

    C#-Quellcode

    1. VehicleData.Vehicles = VehicleData.Vehicles
    2. .OrderBy(x => x.FIN)
    3. .ThenByDescending(x => x.LastVisit)
    4. .ToList();
    5. //Vergleich mit letzter verarbeiteter FIN um die folgende FIN als Dublette erkennen zu können
    6. string Compare = "ZZZZZZZZZZZZZZZZZZZZ"; //am besten eine FIN, die es nicht gibt, Blank ist keine gute Idee, da sowas leider vorkommt....
    7. Parallel.ForEach(VehicleDataList.Vehicles.AsEnumerable(), new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, Item =>
    8. {
    9. if (Item.FIN == Compare)
    10. Item.DuplicateIndicator = true; //Dublette gefunden und markiert
    11. else
    12. Compare = Item.FIN; //letzte FIN für nächsten Vergleich übernehmen
    13. }


    Ich habe leider nicht das Know-How komplexe LINQ-Abfragen zu schreiben, aber gäbe es bessere Möglichkeiten mit Linq?
    Falls mir da jemand die Linq-Query basteln könnte, ich bekomme es leider nicht hin. Jedenfalls keine Abfrage die das tut was ich möchte.

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

    Ich würd das erstmal mit Vehicles.ToLookUp oder Vehicles.GroupBy nach den FINs zusammenfassen. Die Einzelgruppen kannst Du nach Datum sortieren. Meine Frage ist: Was willst Du denn machen, was Du nicht hinbekommst? Das habe ich erfolgreich überlesen …
    Wie sind Fahrzeuge ohne FIN zu behandeln? Ist ja wahrscheinlich nicht nur ein Fahrzeug, sondern x verschiedene. Sollen die überhaupt (nach Duplikaten?) durchforstet werden?
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Jo, seh ich auch so. Auch ich kann nicht erkennen, was eiglich bei rauskommen soll.
    Wichtiger Tipp: Lass das mit Paralell - eine List<T> ist sowas von blitzschnell gruppiert - da machste dich mit paralell nur unnötig verrückt.

    Zu dem Problem, dass man nicht weiss, was bei rauskommen soll: Schreib bitte eine Methode, die das tun ssoll, was du dir vorstellst.
    Muss gar nicht funktionieren - aber die kann man dann angucken und vlt. besser erahnen, was gewünscht ist.
    Ja ist ok, die Anforderung steht doch in der ForEach Schleife, mehr ist ja gar nicht.
    Ja es handelt sich bei jedem Programmlauf zwischen ein paar tausend und ein paar 100.000 Fahrzeuge aus unterschiedlichen Quellen, daher auch die Dubletten.
    Es können auch mal eine halbe Million oder mehr sein, da geht schon Zeit ins Land bei.

    Ist aber auch egal, es gibt noch weitere Gründe die ich gefunden habe, weiterhin die olle foreach-Schleife laufen zu lassen.
    also Danke aber hier kann geschlossen werden, was ich hiermit mache.
    Hi,
    schade, dass du nicht genauer beschreibst, wie deine Prüfungen aussehen, bzw. wie komplex diese ggf. sind. Ich finde solche Performance-Themen sehr interessant und 7 Sekunden für 330000 Datensätze kommen mir verdammt lang vor. Proforma über alle Datensätze iterieren halte ich für keine gute Lösung und würde den Weg von VaporiZeds ausprobieren und mit einer Gruppierung arbeiten. Wenn ich dich richtig verstanden habe, möchtest du nur die doppelten Einträge markieren. Mit einer Linq-Gruppierung bin ich bei ca. 444577 Datensätzen - davon 96875 mehrfach vorkommende - unter 1 Sekunde:

    C#-Quellcode

    1. var groupedVehicles = from vehicle in vehicles
    2. orderby vehicle.LastVisit descending
    3. group vehicle by vehicle.FIN into g
    4. where g.Count() > 1
    5. select g;
    6. int c = 0;
    7. foreach (var g in groupedVehicles)
    8. {
    9. c = 0;
    10. foreach (var vehicle in g)
    11. {
    12. if (c > 0) // nur die alten Einträge als Duplikat markieren
    13. {
    14. vehicle.DuplicateIndicator = true;
    15. }
    16. ++c;
    17. }
    18. }
    Erst einmal recht herzlichen Dank @ISliceUrPanties.
    Ich habe noch nie mit Gruppierungen gearbeitet und zudem benötige immer praktische Beispiele um etwas lernen zu können.
    Also schon mal die Tatsache, dass du erst nach LastVisit die Sortierung vorgibst und dann gruppierst... darauf war ich schon nicht gekommen.
    Klar wollen wir nur die FZ haben, die nicht für sich allein stehen, also mehr als eines. Soweit so klar.
    Mit der Ergebnis laufen wir durch jede Gruppierung, das wusste ich auch nicht, und kommen dann erst an die Datensätze, die einzelnen DS heran.
    Auch vertanden. Der erste Eintrag (Nullbasiert) ist ok, also werden nur die folgenden geflagged, auch verstanden.
    Wieder was sehr hilfreiches gelernt.
    Zu den 7 Sekunden, das war sehr missverständlich von mir, okay. Diese Funktion ist nur ein minimaler kleiner Ausschnitt der ganzen Routine.
    Genaus wie die Daten nur ein minimaler Ausschnitt sind um mein Problem schildeern zu können. Ich habe nicht die Funktion selbst ausgewertet, sondern die Laufzeit der gesamten Routine.
    Da sind noch diverse Prüfungen gegen andere Listen, gegen Kundenmerkmale etc., die alle zur Gesamtroutine gehören. Ich werde noch einmal genau Messen, was die jeweilieg Funktion an Zeit gekostet hat, welche schneller war und welche nicht. Aber gemessen an der Tatsache, dass die Gesamtroutine auch wieder 7 Sekunden benötigt hat (Zeitstempel aus Protokoll) ist der Weg für meine Zwecke fast unerheblich.
    Da gibt es noch viel Optimierungsbedarf, aber ich muss mir auch erst mal ansehen, was welche Funktion in der Routine überhaupt bewirkt, wie sie aufgebaut ist und wo man was optimieren kann.

    Vielen Dank :)
    P.S. Die gestern noch existierende Routine hat bereits ein paar Minuten gefressen, da finde ich 7 Sekunden geht doch schon ;)

    Nachtrag nachträglich gelöscht, weil nicht korrekt.

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Dksksm“ ()

    Dksksm schrieb:

    Nachtrag: Hier habe ich jetzt die reinen Funktionen gemessen, die wir hier diskutiert hatten. Dabei ist sogar noch unberücksichtigt, dass ich vor dem foreach loop noch die Liste sortieren muss und im Nachgang auch wieder zurücksortieren muss über die ID kein Problem, kostet aber Zeit die hier nicht mal berücksichtigt ist.
    Klarer Sieger ist: linq: Time Taken: 284,04 milliseconds
    2. Platz geht an foreach: Time Taken: 1.614,72 milliseconds
    Ich vermute, beim foreach liegts nicht am foreach, sondern an den vor-und nachgeschalteten Sortierungen.
    Eine Sortierung ist ein recht komplizierter und aufwändiger Algorithmus, der den Datenbestand mehrfach durchlaufen muss.
    Wie gesagt: eine foreach ist dagegen simpel.
    Daher gewinnt linq-grouping - wei dafür nicht sortiert werden muss. (wenn ich recht verstehe)
    Es lang an einem anderen Messfehler, nämlich der eingebauten GUI-Ausgabe des Fortschritts.
    Alles bereinigt und noch einmal komplett neu gemessen, hier die Ergebnisse von gerade eben:

    Paralell.ForEach ohne Sortierung: Time Taken: 42,06 milliseconds
    Paralell.ForEach mit Sortierung: Time Taken: 1.702,38 milliseconds
    foreach ohne Sortierung : Time Taken: 33,17 milliseconds
    foreach mit Sortierung : Time Taken: 1.698,92 milliseconds
    Linq-Query (keine Sortierung weil unnötig) : Time Taken: 324,43 milliseconds

    Zum Test waren 329022 Fahrzeuge in den Testdaten vorhanden, es gab keine Dubletten!
    Ich hatte auch Testläufe mit weniger Daten gemacht, die Dubletten enthielten, aber dann sind die Messergebnisse selbst zu klein.

    @ErfinderDesRades hat insofern recht, die nackten foreach loops sind schneller als Linq, aber die notwendigen Zusatzarbeiten (das sortieren) machen den Vorteil zunichte.
    Was mich am meisten wundert ist aber, dass das Paralell.Foreach keinen Vorteil hatte, hierfür ist die Komplexität der Aufgabe wohl zu gering um den Verwaltungsauswand für die Paralellität zu rechtfertigen.
    Man sollte wirklich mehr probieren und messen und sich nicht unbedingt auf eine ungeprüfte Annahme verlassen.

    Danke Leute!

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Dksksm“ ()

    Aha...... Wie ich schon im Post 8 schrieb:
    P.S. Die gestern noch existierende Routine hat bereits ein paar Minuten gefressen, da finde ich 7 Sekunden geht doch schon.

    Vor allem habe ich den Code auf nur noch ~ 20% eingedampft.
    Eine andere Auswertung die vorher knapp 4 Stunden gebraucht hat, benötigt jetzt "nur" noch 40 Minuten, gut, der Rechnerlüfter dreht fast durch dabei, irgend etwas ist ja immer.

    Wenn es hier an dieser Stelle auch nichts wirlkich was an Geschwindigkeit gebracht hat, so finde ich die Linq Query gegenüber dem foreach loop, eingerahmt von den beiden notwendigen Sortierungen, wesentlich besser lesbar und auch kürzer. Ich kann dir nicht zustimmen.
    nee - dass paar Minuten inakzeptabel ist, ist klar.
    Ich bezog das Optimization-Bashing auf den Ansatz, mit Paralell.Foreach da was rauszuholen. Insbesondere ohne vorherige Klärung, obs an der Schleife, am Sortieren, oder noch woanders dran liegt.
    Tatsächlich lags ja an wirklich ganz anderem - iwas mit der Zeitmessung war nicht i.o., wenn ich recht versteh.

    Was du jetzt hast ist auch keine Optimierung in dem Sinne, sondern ist eine echte Verbesserung des Codes - solch ist natürlich immer willkommen.
    Ja - diese Begrifflichkeit ist wohl meist unklar: Optimierung ist nämlich per Definitionem immer eine Verschlechterung des Codes, um einen Vorteil an Geschwindigkeit einzuheimsen.
    Und das ist eben in 99% der Fälle der falsche Weg, und in 99,95% zumindest verfrüht.
    Der richtige Weg ist, den Code zu verbessern. Dann lösen sich Performance-Probleme meist nebenbei in Luft auf.
    Mit Verbessern ist gemeint: logischer, verständlicher, einfacher zu machen - auf Performance guckt man da garnet.

    Eben genau das, wenn du sagst: Linq ist besser verständlich, dann nehm ich das - die paar Millisekunden sinds nicht wert, deswegen die schwerer durchschaubare foreach-Lösung zu nehmen.

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