Informatiker Board (http://www.informatikerboard.de/board/index.php)
- Themengebiete (http://www.informatikerboard.de/board/board.php?boardid=1)
-- Informatik in der Schule (http://www.informatikerboard.de/board/board.php?boardid=21)
--- C# Sortieren von Werten (http://www.informatikerboard.de/board/thread.php?threadid=1850)


Geschrieben von InformaTiger am 25.04.2014 um 17:27:

  C# Sortieren von Werten

Hallo,
ich programmiere gerade einen Selfmonitor der mir anhand von Werten die in einer Datenbank gespeichert sind einen bzw. mehrere Graphen zeichnen soll. Die Schwierigkeit besteht für mich darin die Werte so einzusetzen damit ich mir möglichst Ressourcen sparend die Graphen zeichnen kann. Mein bisheriger Ansatz ist so:

code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
            Database_Request request = new Database_Request();
            string[] arr = request.ReadFromTable("SELECT tbl_values.Datum, tbl_values.Wert, tbl_categories.Name, tbl_categories.Farbe FROM tbl_values INNER JOIN tbl_categories ON tbl_categories.ID = tbl_values.FK_Kategorie;");
            List<DateTime> datum = new List<DateTime>();
            List<int> bewertung = new List<int>();
            List<string> kategorie = new List<string>();
            List<string> farbe = new List<string>();

            for (int i = 0; i < arr.Length; i++)
            {
                switch (i % 4)
                {
                    case 0:
                        {
                            // Datum
                            break;
                        }
                    case 1:
                        {
                            // Bewertung
                            break;
                        }
                    case 2:
                        {
                            // Kategorie
                            break;
                        }
                    case 3:
                        {
                            // Farbe
                            break;
                        }
                }
            }


Ich bin der Meinung das müsste einfacher gehen um sich die vielen Listen zu sparen. Leider weiß ich nicht wie genau aber ich könnte mir vorstellen es hätte was mit mehrdimensonalen Arrays zu tun.

verwirrt

Mfg
InformaTiger



Geschrieben von eulerscheZahl am 25.04.2014 um 19:00:

 

erstelle doch eine Klasse, in der du datum, bewertung, kategorie und farbe speicherst:
code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
class Foo{
    DateTime datum;
    int bewertung;
    string kategorie, farbe;

    public Foo(DateTime datum, int bewertung, string kategorie, string farbe){
        this.datum = datum;
        this.bewertung = bewertung;
        this.kategorie = kategorie;
        this.farbe = farbe;
    }
}

List<Foo> liste = new List<Foo>();
for(int i = 0; i < arr.Length / 4; i++)
{
    liste.Add(new Foo(new DateTime(arr[4*i]), int.Parse(arr[4*i+1]), arr[4*i+2], arr[4*i+3]));
}



Geschrieben von InformaTiger am 25.04.2014 um 19:08:

 

Danke!

Auf diese Idee bin ich logischerweise wieder mal nicht gekommen.

Inzwischen bin ich auf ein weiteres Problem gestoßen. Wenn ich den Graph mittels DrawLine zeichne:

code:
1:
2:
3:
4:
            for (int i = 0; i < datum.Count - 1; i++)
            {
                g.DrawLine(new Pen(new SolidBrush(ColorTranslator.FromHtml('#' + farbe[i].ToLower())), 2.0f), (datum[i].Day + 0.5f) * del, bewertung[i] * del, (datum[i + 1].Day + 0.5f) * del, bewertung[i + 1] * del);
            }


Sieht das Ergebnis wie im Anhang aus, also muss ich es zusätzlich noch irgendwie schaffen auszulesen wann eine neue Kategorie von der Schleife bearbeitet wird.

Mfg
InformaTiger



Geschrieben von eulerscheZahl am 25.04.2014 um 20:12:

 

so vielleicht:
code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
string letzteKategorie = kategorie[0];
int x = 0;
for(int i = 1; i < datum.Count; i++)
{
    if (letzteKategorie != kategorie[i])
    {
        letzteKategorie = kategorie[i];
        x = 0; //wieder links anfangen
    }
    else
    {
        drawline(...) //in Abhängigkeit von x, bewertung[i-1], bewertung[i]
        x++;
    }
}



Geschrieben von InformaTiger am 25.04.2014 um 20:43:

 

Das Problem hierbei besteht eben darin dass die Werte nicht sortiert nach Kategorie gespeichert sind, weil der Benutzer ja in beliebiger Reihenfolge die Werte in die Tabellen schreiben kann. Kann man die Liste aus Objekten die wir vorhin angelegt haben nach Kategorie sortieren?

Mfg
InformaTiger



Geschrieben von eulerscheZahl am 25.04.2014 um 20:51:

 

Bin gerade mit Linux unterwegs, daher ohne Syntaxprüfung:
code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
public class Foo : IComparable
{
 //...
    public int CompareTo(object obj)
    {
        Foo comp = (Foo)obj;
        return this.kategorie.CompareTo(comp.kategorie);
    }
}

dann einfach über liste.Sort();


hier noch ein ausführlicheres Beispiel: msdn.microsoft.com



Geschrieben von InformaTiger am 25.04.2014 um 21:44:

 

Ich habe soeben diese Methode getestet, mein Ergebnis war alles andere als erfreulich: ein totales durcheinander aus Strichen. Allerdings habe ich das Problem durch einen ORDER BY beim SQL-Command gelöst. Den Code habe ich jetzt folgendermaßen erweitert:

code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
            Database_Request request = new Database_Request();
            string[] arr = request.ReadFromTable("SELECT tbl_values.Datum, tbl_values.Wert, tbl_categories.Name, tbl_categories.Farbe FROM tbl_values INNER JOIN tbl_categories ON tbl_categories.ID = tbl_values.FK_Kategorie WHERE MONTH(Datum) = " + DateTime.Now.Month + " ORDER BY tbl_categories.Name;");
            List<DBEntry> entries = new List<DBEntry>();
            List<string> kategorien = new List<string>();

            for (int i = 0; i < arr.Length / 4; i++)
            {
                entries.Add(new DBEntry(DateTime.Parse(arr[4 * i]), int.Parse(arr[4 * i + 1]), arr[4 * i + 2], arr[4 * i + 3]));
                kategorien.Add(arr[4 * i + 2]);
            }

            kategorien = kategorien.Distinct().ToList();

            g.Restore(gs);

            for (int i = 0; i < kategorien.Count; i++)
            {
                int count = entries.Count(c => c.kategorie == kategorien[i]);

                for (int j = 0; j < count - 1; j++)
                {
                    g.DrawLine(new Pen(new SolidBrush(ColorTranslator.FromHtml('#' + entries[j].farbe.ToLower())), 2.0f), (entries[j].datum.Day + 0.5f) * del, entries[j].bewertung * del, (entries[j + 1].datum.Day + 0.5f) * del, entries[j + 1].bewertung * del);
                }
            }


Funktionieren laut Haltepunkte müsste es, aber grafisch sehe ich jetzt nur mehr den 1 Graphen.

Edit: habe meinen Fehler gefunden, ich habe den ersten Graphen 2 mal gezeichnet. ich muss jetzt da ich die Anzahl jeder Kategorie habe nur herausfinden wie ich zurücksetzen kann sodass nach jeder Kategorie abgebrochen und bei dem neuen Datum wieder begonnen wird.

Mfg
InformaTiger



Geschrieben von eulerscheZahl am 25.04.2014 um 22:38:

 

Hatte dein edit nicht gesehen, falls es noch aktuell ist:
Mein Code macht das, was in Anhang zu sehen ist, ich dachte, das wolltest du?

code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
Random r = new Random(0);
string[] arr = new string[1000];
List<Foo> liste = new List<Foo>();
for (int i = 0; i < arr.Length / 4; i++)
{
    liste.Add(new Foo(DateTime.Now, r.Next(100), ((char)('a' + r.Next(5))).ToString(), "rot"));
}
liste.Sort();
Bitmap bmp = new Bitmap(500, 500);
Graphics g = Graphics.FromImage(bmp);
g.Clear(Color.White);
string letzteKategorie = liste[0].kategorie;
Color cl = new Color();
int x = 0;
for (int i = 1; i < liste.Count; i++)
{
    if (letzteKategorie != liste[i].kategorie)
    {
        letzteKategorie = liste[i].kategorie;
        x = 0; //wieder links anfangen
        cl = Color.FromArgb(r.Next(1 << 8), r.Next(1 << 8), r.Next(1 << 8));
    }
    else
    {
        g.DrawLine(new Pen(new SolidBrush(cl), 2.0f), 5*x, 5*liste[i - 1].bewertung, 5*(x + 1), 5*liste[i].bewertung); //in Abhängigkeit von x, bewertung[i-1], bewertung[i]
        x++;
    }
}
pictureBox1.Image = bmp;



Geschrieben von InformaTiger am 26.04.2014 um 00:13:

 

So... dank der Hilfe meines Bruders ist es mir jetzt endlich gelungen. Der Code dazu sieht so aus:

code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
            Database_Request request = new Database_Request();
            string[] arr = request.ReadFromTable("SELECT tbl_values.Datum, tbl_values.Wert, tbl_categories.Name, tbl_categories.Farbe FROM tbl_values INNER JOIN tbl_categories ON tbl_categories.ID = tbl_values.FK_Kategorie WHERE MONTH(Datum) = " + DateTime.Now.Month + " ORDER BY tbl_categories.Name, tbl_values.Datum;");
            List<DBEntry> entries = new List<DBEntry>();
            List<string> kategorien = new List<string>();

            for (int i = 0; i < arr.Length / 4; i++)
            {
                entries.Add(new DBEntry(DateTime.Parse(arr[4 * i]), int.Parse(arr[4 * i + 1]), arr[4 * i + 2], arr[4 * i + 3]));
                kategorien.Add(arr[4 * i + 2]);
            }

            kategorien = kategorien.Distinct().ToList();

            g.Restore(gs);

            int count = 0;

            for (int i = 0; i < kategorien.Count; i++)
            {
                int old;
                old = count;
                count += entries.Count(c => c.kategorie == kategorien[i]);

                if (count - old == 1)
                {
                    g.DrawLine(new Pen(new SolidBrush(ColorTranslator.FromHtml('#' + entries[old].farbe.ToLower())), 2.0f), (entries[old].datum.Day + 0.5f) * del, entries[old].bewertung * del, (entries[old].datum.Day + 1) * del, entries[old].bewertung * del);
                }
                else
                {
                    for (int j = old; j < count - 1; j++)
                    {
                        g.DrawLine(new Pen(new SolidBrush(ColorTranslator.FromHtml('#' + entries[j].farbe.ToLower())), 2.0f), (entries[j].datum.Day + 0.5f) * del, entries[j].bewertung * del, (entries[j + 1].datum.Day + 0.5f) * del, entries[j + 1].bewertung * del);
                    }
                             
                }
                
            }


Die Variable old speichert jeweils den letzten Stand und sorgt dafür dass Einträge die alleine stehen und noch keinen Referenzpunkt haben auch angezeigt werden. Wichtig war auch bei der SQL Anweisung eine sekundäre Sortierung hinzuzufügen: ORDER BY tbl_categories.Name, tbl_values.Datum;

PS: wenn du noch was verbessern wüsstest, bitte einfach nur machen Augenzwinkern

Danke für deine Hilfe!

Mfg
InformaTiger



Geschrieben von eulerscheZahl am 26.04.2014 um 07:14:

 

Wenn du schon so fragst, das gefällt mir nicht:
code:
1:
2:
kategorien.Add(arr[4 * i + 2]);
kategorien = kategorien.Distinct().ToList();

Ich weiß nicht, wie viele Einträge du bei deiner SQL-Abfrage erhältst, aber die musst du alle zwischenspeichern, um dann den Großteil wieder zu löschen.

Ich würde das hier bevorzugen:
code:
1:
2:
if (kategorien.Contains(arr[4*i + 2])
    kategorien.Add(arr[4*i + 2]);


Je nach Anzahl der Kategorien könnte es Sinn machen, die Liste sortiert zu halten, das beschleunigt das Suchen.
Das ginge so:
code:
1:
2:
3:
4:
5:
6:
        int index = dinosaurs.BinarySearch("Coelophysis");
        if (index < 0)
        {
            dinosaurs.Insert(~index, "Coelophysis");
        }

(von Microsoft)



Geschrieben von InformaTiger am 26.04.2014 um 16:41:

 

Also wenn ich das richtig verstanden habe, was du zu bemängeln hast dann ist das Prinzip dasselbe wie das hier:

code:
1:
2:
3:
4:
5:
int index = kategorien.IndexOf(arr[4 * i + 2]);
if (index < 0)
{
    kategorien.Add(arr[4 * i + 2]);
}


Nur, dass du mit einer anderen Methode den index bekommst als ich in dem Beispiel. Sortiert ist meine Liste ja ohnehin schon, da ich sie mit dem ORDER BY Kommando sortiert aus der Datenbank erhalte. Wobei ich sagen muss dass der zweite Code von dir noch effizienter ist.

code:
1:
2:
3:
4:
if (!kategorien.Contains(arr[4 * i + 2]))
{
    kategorien.Add(arr[4 * i + 2]);
}


Mfg
InformaTiger



Geschrieben von Karlito am 26.04.2014 um 23:43:

 

Hallo,

wenn es Kategorien nicht doppelt geben kann, warum verwendet ihr dann nicht ein Set? Sets entsprechen mathematischen Mengen und sind so gebaut, dass sie effizient ermöglichen Elemente nur einmalig aufzunehmen (In Mengen kann es ja auch keine Doppelten geben). Bei einem HashSet hat man idealisiert konstante Laufzeit, d.h. jeder Einfügevorgang (mit Prüfung auf doppelte) und jeder Suchvorgang (contains()) dauert idealisiert eine konstante Zeit, unabhängig von der Anzahl der Elemente (in der Realität klappt das nicht ganz). Problem ist, dass ein HashSet nicht sortiert ist.
Für sortierte Mengen gibt es noch das SortedSet. Ich denke das hat dann logarithmische Laufzeit (auch sehr gut). Jedenfalls spart man sich die Prüfung auf doppelte und das selektive Einfügen.

Gruß,

Karlito



Geschrieben von eulerscheZahl am 27.04.2014 um 08:44:

 

Guter Einwand, Karlito.

Noch ein Vorschlag, wie es auch mit einer einfachen List effizient geht: da bereits nach kategorie sortiert ist, reicht es, den letzten Eintrag zu prüfen:
code:
1:
2:
if (kategorien[kategorien.Count - 1] != arr[4*i + 2])
    kategorien.Add(arr[4 * i + 2]);



Geschrieben von Karlito am 27.04.2014 um 13:27:

 

Hallo euler, hallo InformaTiger,

auch wenn man es vielleicht so biegen kann, dass eine Liste effizient wird, so würde ich mich darauf verlassen, das die Implementierung des Sets effiizienter ist. Schließlich ist sie für genau diesen Zweck gebaut. Ansonsten würde ich eine Liste, welche optimiert ist selbst implementieren.
Mich würde an der Stelle mal ein Vergleich Interessieren: 1 Mio eindeutige Einfügevorgänge für eine Liste und das Selbe für ein Set. Ich würde darauf tippen, dass das Set gewinnt. Spätestens beim Speicherverbrauch.

Sorry, wenn die Diskussion hier zu weit geht.

Gruß,

Karlito



Geschrieben von InformaTiger am 02.05.2014 um 16:20:

 

Hallo,
entschuldigt bitte, dass ich mich nicht mehr gemeldet habe. Ich hatte in letzter Zeit nur etwas Stress unglücklich . Dann sollte ich also ein Set verwenden und die Geschichte wäre von der Preformance her, gegessen?

Mfg
InformaTiger


Forensoftware: Burning Board, entwickelt von WoltLab GmbH