Laden...

Sortieren von Listen anhand mehrerer Kriterien *über mehrere Objekte*

Erstellt von ModelViewPresenter vor 10 Jahren Letzter Beitrag vor 10 Jahren 3.032 Views
ModelViewPresenter Themenstarter:in
67 Beiträge seit 2011
vor 10 Jahren
Sortieren von Listen anhand mehrerer Kriterien *über mehrere Objekte*

Hallo,

nehmen wir an, ich hätte Objekte, die ein Datum, eine Zahl und boolean enthalten. Davon existieren drei in einer Liste:

O1{2013, 2 , false}
O2{2013, 2 , true}
O3{2013, 3 , true}

Was ich möchte ist, nach allen dieser drei Attribute zu sortieren (Datum aufsteigend, Zahl aufsteigend, true vor false). Dabei sollen allerdings, diejenigen Objekte mit dem gleichen Boolean und Zahl zusammen betrachtet werden. Mit einem normalen Comparer könnte ich natürlich zunächst über Datum, dann Zahl, dann Boolean sortieren. Dabei käme Folgendes heraus:

O2{2013, 2 , true}
O1{2013, 2 , false}
O3{2013, 3 , true}

Das möchte ich nicht. Problem ist, dass der Comparer mit seinem Mergesort nur 2 Objekte miteinander vergleicht. Ich möchte, aber, dass, wenn in Objekten mit gleicher Zahl eines der Objekte den false Wert hat, diese ans Ende kommen. Und zwar so:

O3{2013, 3 , true}
O2{2013, 2 , true}
O1{2013, 2 , false}

Meine Frage: Gibt es einfache Möglichkeit soetwas zu implementieren? Vielleicht sogar doch mit einem einfachen Comparer?

Vielen Dank
MVP

16.834 Beiträge seit 2008
vor 10 Jahren

Gewusst wie: Verwenden Sie die IComparable und IComparer-Schnittstellen in Visual C#
Musst halt auf Gleichheit prüfen und wenn dies eintrifft das untergeordnete Kriterium prüfen.
Ich würds aber mit Linq machen, weils einfacher zu warten und mit weniger Code umsetzbar ist.

3.825 Beiträge seit 2006
vor 10 Jahren

Hallo MVP,

(heißt diese Abkürzung nicht auch etwas anderes ?)

Wenn Du die Zeilen aus einer Datenbank holst und in einer Liste anzeigen willst dann bestimme die Reihenfolge im SQL-Statement (order by).

Hinweis : Der SQL-Server kann soweit ich mich erinnere nicht nach bool sortieren. Manche nehmen stattdessen ein Integerfeld mit den Werten 0 und 1.

Wenn der Anwender die Reihenfolge durch Klick auf die Listenspalten selbst bestimmen können soll dann erstelle für die Liste eine IComparer-Schnittstelle, das ist auch nicht schwierig :

Gewusst wie: Sortieren von ListView-Elementen

Grüße Bernd

Workshop : Datenbanken mit ADO.NET
Xamarin Mobile App : Finderwille Einsatz App
Unternehmenssoftware : Quasar-3

ModelViewPresenter Themenstarter:in
67 Beiträge seit 2011
vor 10 Jahren


>

Musst halt auf Gleichheit prüfen und wenn dies eintrifft das untergeordnete Kriterium prüfen.
Ich würds aber mit Linq machen, weils einfacher zu warten und mit weniger Code umsetzbar ist.

Das ist ja genau das, was ich nicht wollte.

Was ich möchte ist, nach allen dieser drei Attribute zu sortieren (Datum aufsteigend, Zahl aufsteigend, true vor false). Dabei sollen allerdings, diejenigen Objekte mit dem gleichen Boolean und Zahl zusammen betrachtet werden. Mit einem normalen Comparer könnte ich natürlich zunächst über Datum, dann Zahl, dann Boolean sortieren. Dabei käme Folgendes heraus:

O2{2013, 2 , true}
O1{2013, 2 , false}
O3{2013, 3 , true}

Das eigentliche Problem ist, dass ich eine Gruppe von Objekten habe und diese mit dem Comparer nicht als solche betrachtet werden kann.

Er sortiert in dem Fall nach Datum. Das untergeordnete Kriterium wäre die Zahl und dann wiederum das untergeordnete Kriterium der Boolean.

PS: Ich hole die Daten nicht aus einer SQL-Datenbank. Es handelt sich um ganz normale Listen (IList<T>).

S
417 Beiträge seit 2008
vor 10 Jahren

Hallo,

der Vorschlag von Abt ist genau das was du brauchst. Z.b.:

class Program
{
	static void Main(string[] args)
	{
		var foos = new List<Foo>();
		foos.Add(new Foo { Year = 2013, IntValue = 2, BoolValue = true });
		foos.Add(new Foo { Year = 2013, IntValue = 3, BoolValue = true });
		foos.Add(new Foo { Year = 2013, IntValue = 2, BoolValue = false });

		foos.Sort(new FooComparer());
	}
}

public class Foo
{
	public int Year;
	public bool BoolValue;
	public int IntValue;

	public override string ToString()
	{
		return string.Format("{0}, {1}, {2}", this.Year, this.IntValue, this.BoolValue);
	}
}

public class FooComparer : IComparer<Foo>
{
	public int Compare(Foo x, Foo y)
	{
		if (x.Year < y.Year)
			return -1;
		else if (x.Year > y.Year)
			return 1;

		if (x.IntValue < y.IntValue)
			return -1;
		else if (x.IntValue > y.IntValue)
			return 1;

		if (x.BoolValue != y.BoolValue)
			return x.BoolValue ? 1 : -1;

		return 0;
	}
}
49.485 Beiträge seit 2005
vor 10 Jahren

Hallo Sarc,

nein, so wird nicht so sortiert, wie ModelViewPresenter es beschrieben hat.

Hallo ModelViewPresenter,

m.E. kommst du nicht drum herum, vorher in einem separaten Schritt deine spezielle Gruppierung zu ermitteln und die Objekte entsprechend zu taggen, damit du im Comparer weißt, dass diese Objekte als größer angesehen werden sollen als nicht solchermaßen gruppierte/getaggte.

herbivore

ModelViewPresenter Themenstarter:in
67 Beiträge seit 2011
vor 10 Jahren

Genau, in diesem einfachen Beispiel funktioniert es tatsächlich. Verwendet man folgende Werte Daten:

		foos.Add(new Foo(2001, 1, true));
		foos.Add(new Foo(2002, 1, true));
		foos.Add(new Foo(2002, 2, true));
		foos.Add(new Foo(2003, 2, true));
		foos.Add(new Foo(2013, 2, false));
		foos.Add(new Foo(2013, 3, true));
		foos.Add(new Foo(2001, 2, true));
		foos.Add(new Foo(2013, 2, true));
		foos.Add(new Foo(2002, 2, true));
		foos.Add(new Foo(2013, 3, true));
		foos.Add(new Foo(2014, 3, true));

sieht die Welt leider anders aus.

m.E. kommst du nicht drum herum, vorher deine spezielle Gruppierung zu ermitteln und die Objekte entsprechend zu taggen, damit du im Comparer weißt, dass diese Objekte als größer angesehen werden sollen als nicht solchermaßen gruppierte/getaggte.

Sowas in der Art habe ich befürchtet. Nun muss man schauen, wie man das am elegantesten umsetzen kann...

L
416 Beiträge seit 2008
vor 10 Jahren

public class Foo
{
    public int Year;
    public bool BoolValue;
    public int IntValue;

    public int Val { get { return Year * 100 + IntValue * 10 + (BoolValue ? 1 : 0); } }

    public override string ToString()
    {
        return string.Format("{0}, {1}, {2}", this.Year, this.IntValue, this.BoolValue);
    }
}

public class FooComparer : IComparer<Foo>
{
    public int Compare(Foo x, Foo y)
    {
        if (x.Val < y.Val)
            return -1;
        else if (x.Val > y.Val)
            return 1;
        return 0;
    }
} 
S
417 Beiträge seit 2008
vor 10 Jahren

Hallo ModelViewPresenter,

ok, jetzt verstehe ich deine gewünschte Sortierung (hoffentlich) 😉
Ohne voriges tagging könntest du auch die zwei Teil-Listen sortieren (zahlen ohne false und zahlen mit false) und später wieder mergen. Ist zwar nicht elegant, aber du ersparst dir das tagging.

static void Main(string[] args)
{
	var foos = new List<Foo>();
	foos.Add(new Foo(2001, 1, true));
	foos.Add(new Foo(2002, 1, true));
	foos.Add(new Foo(2002, 2, true));
	foos.Add(new Foo(2003, 2, true));
	foos.Add(new Foo(2013, 2, false));
	foos.Add(new Foo(2013, 3, true));
	foos.Add(new Foo(2001, 2, true));
	foos.Add(new Foo(2013, 2, true));
	foos.Add(new Foo(2002, 2, true));
	foos.Add(new Foo(2013, 3, true));
	foos.Add(new Foo(2014, 3, true));

	var ints = foos.Where(f => !f.BoolValue).Select(f => f.IntValue).Distinct().ToArray();
	var resultList = foos.Where(f => !ints.Contains(f.IntValue)).OrderBy(f => f, new FooComparer()).ToList();
	resultList.AddRange(foos.Where(f => ints.Contains(f.IntValue)).OrderBy(f => f, new FooComparer()));
}
49.485 Beiträge seit 2005
vor 10 Jahren

Hallo Sarc,

es sollten ja nicht alle Objekte mit false ans Ende (das würde man auch ohne Merge durch einen Comparer erreichen, der zuerst den bool-Wert vergleicht), sondern (sofern wirklich "diese" und nicht "dieses" gemeint ist):

Ich möchte, aber, dass, wenn in Objekten mit gleicher Zahl eines der Objekte den false Wert hat, diese ans Ende kommen.


Hallo Lennart,

auch deine Sortierung berücksichtigt das nicht.

Hallo ModelViewPresenter,

mit Taggen ist nicht unbedingt gemeint, dass die Objekte um eine zusätzliche Property erweitert werden müssen, sondern man kann natürlich auch ein Dictionary aufbauen, um dort zu hinterlegen, zu welchen Zahlenkombinationen es ein Objekt mit dem Wert false gibt.

Hallo zusammen,

so viele falsche Lösungsvorschläge, wie schon gekommen sind, wäre das eine schöne und anscheinend kniffelige Aufgabe für Das Programmier-Spiel: nette Übungsaufgaben für zwischendurch gewesen.

herbivore

ModelViewPresenter Themenstarter:in
67 Beiträge seit 2011
vor 10 Jahren

Um Missverständnisse zu vermeiden, das Ergebnis, das ich haben möchte, sieht so aus:

2001, 1, true
2001, 2, true
2002, 1, true
2002, 2, true
2002, 2, true
2003, 2, true
2013, 3, true
2013, 3, true
2013, 2, true
2013, 2, false
2014, 3, true

Die "2er" in der Kategorie "2013" kommen ans Ende, weil es einen gibt, der einen "falser" hat.

49.485 Beiträge seit 2005
vor 10 Jahren

Hallo ModelViewPresenter,

so hatte ich es allerdings nicht verstanden. Bei "ans Ende" bin ich davon ausgegangen, dass das Ende der Liste gemeint ist. Du meinst mit Ende jedoch den jeweils nächsten Gruppenwechsel bei der Jahreszahl.

Aber wie dem auch sei, ich denke nach den bisherigen Hinweisen, solltest du es jetzt selber lösen können.

herbivore

ModelViewPresenter Themenstarter:in
67 Beiträge seit 2011
vor 10 Jahren

Aber wie dem auch sei, ich denke nach den bisherigen Hinweisen, solltest du es jetzt selber lösen können.

Ja, sollte ich. Meine Lösung sieht wie folgt aus:

Ich lege einen Zwischenspeicher an, der nach Jahreszahl und Int-Wert gruppiert ist. Im Comparer sortiere ich zunächst nach Jahreszahl. Danach wird geschaut, ob es für einen der beiden Foos einen Eintrag im Zwischenspeicher gibt, ist dies so, wird der, für den es zutrifft nach hinten bzw. der für den es nicht zutrifft nach vorn gesetzt. In allen weiteren Fällen wird nach Int-Wert und dann nach Boolean sortiert.

Aus:

2015, 2, true
2001, 1, true
2002, 1, true
2002, 2, true
2003, 2, true
2013, 3, true
2001, 2, true
2015, 1, true
2013, 2, true
2002, 2, true
2013, 3, true
2013, 2, false
2014, 3, false
2013, 7, true
2013, 4, false
2013, 4, true
2013, 4, true
2015, 1, false

wird dadurch:

2001, 1, true
2001, 2, true
2002, 1, true
2002, 2, true
2002, 2, true
2003, 2, true
2013, 3, true
2013, 3, true
2013, 7, true
2013, 2, true
2013, 2, false
2013, 4, true
2013, 4, true
2013, 4, false
2014, 3, false
2015, 2, true
2015, 1, true
2015, 1, false

Danke für die Tipps!