Anzahl unterschiedlicher Werte in einer Spalte innerhalb einer Gruppierung / LinQ

  • C#
  • .NET (FX) 4.0

Es gibt 11 Antworten in diesem Thema. Der letzte Beitrag () ist von stepper71.

    Anzahl unterschiedlicher Werte in einer Spalte innerhalb einer Gruppierung / LinQ

    Hallo,

    ich habe eine Liste "holzstücke" die wie folgt aufgebaut ist:

    C#-Quellcode

    1. List<Variable2010> holzstücke = new List<Variable2010>();
    2. holzstücke.Add(new Variable2010 { BaumartKey = 1, Stammnummer = 5 });
    3. holzstücke.Add(new Variable2010 { BaumartKey = 1, Stammnummer = 5 });
    4. holzstücke.Add(new Variable2010 { BaumartKey = 1, Stammnummer = 6 });
    5. holzstücke.Add(new Variable2010 { BaumartKey = 2, Stammnummer = 7 });
    6. holzstücke.Add(new Variable2010 { BaumartKey = 2, Stammnummer = 7 });
    7. holzstücke.Add(new Variable2010 { BaumartKey = 2, Stammnummer = 8 });
    8. holzstücke.Add(new Variable2010 { BaumartKey = 2, Stammnummer = 8 });


    Ziel ist es diese Liste nach "BaumartKey" zu gruppieren und dann innerhalb jeder Gruppe die unterschiedlichen Stammnummern zu zählen, womit ich die Stammanzahl je Gruppe hätte.
    Als Ergebnis müsste also herauskommen:
    Baumartkey 1 => 2 Stämme
    Baumartkey 2 => 2 Stämme

    Ich hatte gehofft mit einer LinQ Abfrage das Ergebnis zu bekommen.
    Mit folgendem code gruppiere ich nach der Baumart:

    C#-Quellcode

    1. var query = from item in holzstücke.AsEnumerable()
    2. group item by new
    3. {
    4. Baumart = item.BaumartKey
    5. } into grp
    6. select new
    7. {
    8. grp.Key.Baumart,
    9. baumzahl = ???
    10. }


    Innerhalb der Gruppierung möchte ich nun die Anzahl der unterschiedlichen Werte der Stammnummer ermitteln.
    Wie gehe ich dazu vor?


    Wäre für jede Hilfe dankbar.

    Gruß
    stepper

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

    Die interessantesten Fragen sind immer am schwierigsten...

    Hier in Erweiterungsmethodensyntax:

    C#-Quellcode

    1. var result = holzstücke.GroupBy((e) => e.BaumartKey, (e, lst) => new {Baumart = e, DistinctCount = lst.Select((f) => f.Stammnummer).Distinct().Count()});


    Und hier nochmal in Abfragesyntax:

    C#-Quellcode

    1. var result = from h in holzstücke group h.Stammnummer by h.BaumartKey into g
    2. select new { Baumart = g.Key, DistinctCount = g.Distinct().Count()};


    Edit: Hier nochmal in VB, als Referenz:

    VB.NET-Quellcode

    1. Dim result = From h In holzstücke Group e = h.Stammnummer By k = h.BaumartKey Into g = Group Select Baumart = k, DistinctCount = Aggregate e In g Distinct Into Count()
    2. Dim result = holzstücke.GroupBy(Function(e) e.BaumartKey, Function(e, lst) New With {.Baumart = e, .DistinctCount = lst.Select(Function(f) f.Stammnummer).Distinct().Count()})


    Für beide Sprachen gilt: Zwischen Group und By kann man die spätere Gruppe vorfiltern, d.h. deren Inhalt auf Teile der gruppierten Objekte beschränken. Ohne den Filter landen immer die ganzen Objekte in den Gruppen.

    In VB sollte der Key nach By einen Alias bekommen, um im Select darauf zugreifen zu können. C# bietet dagegen g.Key an.
    Gruß
    hal2000

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „hal2000“ ()

    Wow, passt, sitzt, wackelt und hat Luft.
    Funktioniert wunderbar, vielen Dank dafür!!!

    Eine kleine Bonusfrage noch.
    Wenn ich diese Form habe:

    C#-Quellcode

    1. holzstücke.Add(new Variable2010 { BaumartKey = 1, Stammnummer = 5, VolumenPreistyp = 0.53});
    2. holzstücke.Add(new Variable2010 { BaumartKey = 1, Stammnummer = 5, VolumenPreistyp = 0.39});
    3. holzstücke.Add(new Variable2010 { BaumartKey = 1, Stammnummer = 6, VolumenPreistyp = 0.52 });
    4. holzstücke.Add(new Variable2010 { BaumartKey = 2, Stammnummer = 7, VolumenPreistyp = 0.256 });
    5. holzstücke.Add(new Variable2010 { BaumartKey = 2, Stammnummer = 7, VolumenPreistyp = 0.22 });
    6. holzstücke.Add(new Variable2010 { BaumartKey = 2, Stammnummer = 8, VolumenPreistyp = 0.478 });
    7. holzstücke.Add(new Variable2010 { BaumartKey = 2, Stammnummer = 8, VolumenPreistyp = 0.2467 });



    und hier wie gehabt eine Gruppierung über die Baumart mache, eine Summe über die unterschiedlichen Stammnummern aber zusätzlich eine Summe des "VolumenPreistyp" je Baumart haben möchte.
    Ginge das noch in einer Abfrage?

    Ich mache das jetzt in zwei getrennten Abfragen und es funktioniert.
    Ich verstehe nicht ganz, was du meinst. Du meinst vermutlich die Anzahl der unterschiedlichen Stammnummern, nicht die Summe. VolumenPreistyp kannst du nur für die gesamte Gruppe summieren, weil sonst nicht klar ist, welcher Wert bei gleicher Stammnummer summiert und welcher aussortiert werden soll (Bsp. Key = 1, nehme ich 0.53 oder 0.39?). Sinnvoller wäre für Key = 1 das Aggregat 0.53 + 0.39 + 0.52.

    Die Summe macht die Abfrage vorne einfacher, weil man statt zu filtern einfach das gesamte Objekt mitnimmt. Hinten wirds dagegen komplizierter, weil die einzelnen Gruppen nicht direkt nach Distinct().Count() aggregiert werden können. Stattdessen müssen Unterabfragen zunächst die jeweils zu aggregierende Eigenschaft der Objekte jeder Gruppe auswählen.

    C#-Quellcode

    1. var result = from h in holzstücke
    2. group h by h.BaumartKey into g
    3. select new {
    4. Baumart = g.Key,
    5. DistinctCount = (from e in g select e.Stammnummer).Distinct().Count(),
    6. PreistypSum = (from e in g select e.VolumenPreistyp).Sum()
    7. };
    Gruß
    hal2000

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

    @stepper71 Wenn Du Deine Zahlen in einer Excel-Tabelle ablegst, kannst Du Sortierebenen hinzufügen. In einer solchen Darstellung kannst Du Dein Problem zumindest sehr elegant beschreiben.
    Ohne jetzt LINQ zu verwenden würde ich sagen, Du machst Dir einen eigenen Sorter und sortierst die Werte zuerst nach dem 1., dann nach dem 2. und ggf. nach dem 3. Kriterium und wertest die resultierenden Daten entsprechend aus.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    @hal2000, du hast genau richtig vermutet, genau so soll es funktionieren!!!
    Es soll nach dem Baumartkey gruppiert werden. Innerhalb jeder Gruppe soll dann die Anzahl (sorry für die unsaubere Ausdrucksweise im vorigen Post...) der unterschiedlichen Stammnummern gezählt werden.
    Zusätzlich soll in der Gruppe eine Summe über alle Werte von VolumenPreistyp gebildet werden.

    Deine Lösung geht in die Richtung meines ursprünglichen Lösungsansatzes.
    Ich habe bisher zum summieren innerhalb einer Gruppe folgenden code benutzt:
    VolumenPreis = g.Sum(i => i.VolumenPreistyp),

    Darauf aufbauend hatte ich dann für die Zählung der Stammnummern etwas in dieser Art versucht:
    Baumzahl = g.Distinct().Count(i => i.Stammnummer)

    was aber nicht funktioniert (Konvertierung von Typ in Bool nicht möglich)
    Mir fehlte also der etwas "komplizierte" Teil hinten, wie du es genannt hast.

    Ist der Unterschied in der Schreibweise von: VolumenPreis = g.Sum(i => i.VolumenPreistyp), zu VolumenPreis = (from e in g select e.VolumenPreistyp).Sum() ein rein semantischer oder gibt es auch einen konkreten inhaltlichen Unterschied?
    Vom Ergebnis scheint es das Gleiche zurückzugeben.

    @RodFromGermany, da man selber genau weiss um was es geht und vieles Inhaltlich selbstverständlich ist, ist manchmal die Darstellung für Außenstehende nicht immer nachvollziehbar. Ich werde in Zukunft versuchen die Problemstellungen mit mehr Beispieldaten (wie du vorgeschlagen hast) darzustellen.

    Abschließend vielen Dank nochmal für die schnelle und konstruktive Hilfe!!!!

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

    Syntax = Struktur, wie etwas formuliert ist
    Semantik = (inhaltliche) Bedeutung des syntaktisch formulierten etwas.

    Es gibt keinen semantischen Unterschied, sondern nur den syntaktischen. Bei VolumenPreis = g.Sum(i => i.VolumenPreistyp), gemeint ist hier die Verwendung eines Selektors (hier der Lambda-Ausdruck) innerhalb der Aggregatfunktion (hier Sum), musst du jedoch genau wissen, welchen Typ von Selektor die Funktion erwartet - das steht in der Dokumentation. Natürlich könnte man vermuten, dass der Sum-Aggregator als Selektor einen Funktor (wählt ein konkretes Element aus) erwartet - es könnte aber genauso gut ein Prädikat (Boolean-Ausdruck) sein.

    Genau aus diesem Grund ist deine Formulierung von Baumzahl = g.Distinct().Count(i => i.Stammnummer) gescheitert: Count erwartet ein Prädikat. Da du aber keins hast (es sollen schließlich alle unterschiedlichen Elemente gezählt werden und nicht nur bestimmte), muss der Item-Selektor vor dem Count stehen. Richtig wäre daher:
    Baumzahl = g.Select(i => i.Stammnummer).Distinct().Count()

    Um all diesen Problemen aus dem Weg zu gehen, nehme man die semantisch äquivaluente Ausdruckssyntax from ... in ... select. In VB lässt sich die gesamte Abfrage in Ausdruckssyntax beschreiben, was ich für besser lesbar halte. Außerdem erschließt sich beim Lesen des Ausdrucks bereits auf der natürlichen Sprachebene, ob ein Prädikat oder ein Funktor als Selektor erforderlich ist.

    Anmerkung: Bitte lass den Excel-Kram. Wenn jemand eine Frage als Excel-Datei präsentieren will, neige ich eher dazu, die Frage zu ignorieren. Der Grund ist, dass dadurch nur eine zusätzliche Abstraktionsschicht eingefügt wird, die wieder ihre Eigenheiten hat und erstmal interpretiert werden muss. Außerdem liegen die Daten dann in einem völlig anderen Kontext vor, was nur Verwirrung stiftet. Dein gepostetes Beispiel war schon nicht schlecht, weil ich die Daten per copy/paste in ein Testprogramm übernehmen konnte. Nur die Klassendefinition von Variable2010 wäre noch schön gewesen.
    Gruß
    hal2000

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „hal2000“ ()

    Syntax.... Semantik.... bei einem Smartphone hätte ich jetzt das Autovervollständigen vorgeschoben....

    Danke für deine ausführliche Erläuterung, gefühlt würde ich auch sagen das ich es verstanden habe.
    Ich stimme deiner Einschätzung der besseren Lesbarkeit von der Form from ... in ... select absolut zu.

    Deine Erläuterungen machen (zumindest mir) mal wieder klar, dass man als Gelegenheitsprogrammierer hier mit einem System arbeitet, dessen Komplexität und Umfang man nicht annähernd überblickt.
    Daher kommen dann die eher "empirisch" hergeleiteten Lösungsversuche.
    Hals Erklärung von Syntax und Semantik vereinfacht:
    Syntax sind die Regeln der Sprache, Semantik ist das inhaltliche, das, was gesagt wird.
    Deine Frage

    stepper71 schrieb:

    Ist der Unterschied in der Schreibweise von: VolumenPreis = g.Sum(i => i.VolumenPreistyp), zu VolumenPreis = (from e in g select e.VolumenPreistyp).Sum() ein rein semantischer oder gibt es auch einen konkreten inhaltlichen Unterschied?
    verwendet den Begriff "semantisch" also falsch, du meinst: "syntaktisch".

    Also Antwort in deim Wortlaut:
    der Unterschied in der Schreibweise von: VolumenPreis = g.Sum(i => i.VolumenPreistyp), zu VolumenPreis = (from e in g select e.VolumenPreistyp).Sum() ist ein rein syntaktischer (2 verschiedene, gültige Regeln werden befolgt), es gibt keinen konkreten inhaltlichen (semantischen) Unterschied.

    Diese Unterscheidungen sind aber doch nicht weiter kompliziert, und haben nichtmal was mit komplexem Programmier-System zu tun - dasselbe meint auch der Volksmund, wenn er sagt: "Viele Wege führen nach Rom".
    Die Wege sind Syntax, und Rom ist der Inhalt/das Ziel/die Semantik.

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

    Ähhh, danke für den Hinweis aber die Sache mit der Syntax und der Semantik ist nun wirklich nicht das Problem....

    Mein abschließender Kommentar bezog sich auf die detaillierte Erklärung warum mein Lösungsversuch gescheitert ist.

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