Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Community
  • |
  • Diskussionsforum
Das Programmier-Spiel: nette Übungsaufgaben für zwischendurch
Daniel B.
myCSharp.de - Member



Dabei seit:
Beiträge: 87
Herkunft: Linz

beantworten | zitieren | melden

Damit jeder noch weiß, was es mit dem "sehen" auf sich hat:

siehe Anhang

Das Flugzeug "sieht" nach oben, dass bedeutet, ich würde GetAngle mit ImageDirection.Top aufrufen!

Man darf sich darauf verlassen, dass die Vektoren richtig übergeben werden. Trotzdem sollen Exceptions geworfen werden wenn z. B. Angle außerhalb von 0-360 ° liegt oder das übergebene Bild null ist.

Ich hoffe die Aufgabenstellung ist klar genug formuliert. Ansonsten wird Herbivore sich schon melden :D
Attachments
private Nachricht | Beiträge des Benutzers
xxxprod
myCSharp.de - Experte

Avatar #avatar-2329.gif


Dabei seit:
Beiträge: 1420
Herkunft: Österreich\Wien

beantworten | zitieren | melden

Was mir fehlt ist der Ursprung der zwei Vektoren - oder wird automatisch 0,0 als Ursprung angenommen?

Lg, XXX
private Nachricht | Beiträge des Benutzers
D4rkScr43m
myCSharp.de - Member



Dabei seit:
Beiträge: 32

beantworten | zitieren | melden

Mal angenommen 0,0 sei der Ursprung für die durch die Punkte beschriebenen Vektoren:

public enum ImageDirection
{
    Top,
    Right,
    Bottom,
    Left
}

private static int SkalarProduct(Point vectorA, Point vectorB)
{
    return vectorA.X * vectorB.X + vectorA.Y * vectorB.Y;
}

private static double Laenge(Point vector)
{
    return Math.Sqrt(Math.Pow(vector.X, 2) + Math.Pow(vector.Y, 2));
}

public static float GetAngle(Point vectorA, Point vectorB)
{
    double d = SkalarProduct(vectorA, vectorB) / (Laenge(vectorA) * Laenge(vectorB));
    return (float)(Math.Acos(d) * 360 / (2 * Math.PI));
}

public static float GetAngle(Point p1, Point p2, ImageDirection direction)
{
    if (p1.Equals(p2))
        throw new ArgumentException("Der Punkt an dem das Bild steht und der, zu dem das Bild gucken soll sind gleich!");
    Point lookingAt = new Point();
    switch (direction)
    {
        case ImageDirection.Top:
            lookingAt = new Point(0, 1);
            break;
        case ImageDirection.Left:
            lookingAt = new Point(-1, 0);
            break;
        case ImageDirection.Right:
            lookingAt = new Point(1, 0);
            break;
        case ImageDirection.Bottom:
            lookingAt = new Point(0, -1);
            break;
    }
    Point newLookingAt = new Point(p2.X - p1.X, p2.Y - p1.Y);
    return GetAngle(lookingAt, newLookingAt);
}

public static Bitmap RotateImage(Bitmap image, float angle)
{
    if (float.IsNaN(angle))
        throw new ArgumentException("Der angegebene Winkel ist NaN!");
    if (angle == 0)
        return new Bitmap(image);
    Bitmap returnBitmap = new Bitmap(image.Width, image.Height);
    Graphics graphics = Graphics.FromImage(returnBitmap);
    graphics.TranslateTransform((float)image.Width / 2, (float)image.Height / 2);
    graphics.RotateTransform(angle);
    graphics.TranslateTransform(-(float)image.Width / 2, -(float)image.Height / 2);
    graphics.DrawImage(image, new Point(0, 0));
    return returnBitmap;  
}

Ein kurzer Test in einem Form mit einer PictureBox zur Darstellung, einer TextBox zur eingabe des Guck-Zu-Punktes und einem Button zum Rotieren hat scheinbar funktioniert. Das einzige Problem: Das Bild wird auf diese Weise immer unschärfer, da sich bei Drehungen nicht um ein vielfaches von 90° mehrere Pixel den "Platz" teilen müssen.

Edit: habe mal die Kommentare aus der Aufgabenstellung entfernt um etwas Platz zu sparen.
Dieser Beitrag wurde 2 mal editiert, zum letzten Mal von D4rkScr43m am .
private Nachricht | Beiträge des Benutzers
Daniel B.
myCSharp.de - Member



Dabei seit:
Beiträge: 87
Herkunft: Linz

beantworten | zitieren | melden

Im Anhang sieht man wie ich das gemeint hab (hoffentlich) :)

Vektor 1 ergibt sich aus der ImageDirection. Ich hab einfach angenommen das der Vektor dann dadurch berechnet wird das p1 je nach Direction um 1 erhöht wird und somit der Vektor zustande kommt.
Attachments
private Nachricht | Beiträge des Benutzers
D4rkScr43m
myCSharp.de - Member



Dabei seit:
Beiträge: 32

beantworten | zitieren | melden

Wie sieht es denn aus? Ist meine Lösung "richtig" genug? :)
private Nachricht | Beiträge des Benutzers
Daniel B.
myCSharp.de - Member



Dabei seit:
Beiträge: 87
Herkunft: Linz

beantworten | zitieren | melden

Bei folgendem Fall dreht sich das Bild nicht in die richtige Richtung

(der rote Punkt ist der "new Looking at"-Punkt)
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Daniel B. am .
Attachments
private Nachricht | Beiträge des Benutzers
D4rkScr43m
myCSharp.de - Member



Dabei seit:
Beiträge: 32

beantworten | zitieren | melden

Ja, das war wohl ein Problem, dass ich es nur mit einer Picturebox in der linken oberen Ecke probiert habe. Hier nochmal der korrigiert Code (Außerdem hatte ich nicht mehr daran gedacht, dass auf dem Bildschirm der Koordinatenursprung oben und nicht unten liegt :) )

public enum ImageDirection
{
    Top,
    Right,
    Bottom,
    Left
}

private static int SkalarProduct(Point vectorA, Point vectorB)
{
    return vectorA.X * vectorB.X + vectorA.Y * vectorB.Y;
}

private static double Laenge(Point vector)
{
    return Math.Sqrt(Math.Pow(vector.X, 2) + Math.Pow(vector.Y, 2));
}

public static float GetAngle(Point vectorA, Point vectorB)
{
    double d = SkalarProduct(vectorA, vectorB) / (Laenge(vectorA) * Laenge(vectorB));
    return (float)(Math.Acos(d) * 360 / (2 * Math.PI));
}

public static float GetAngle(Point p1, Point p2, ImageDirection direction)
{
    if (p1.Equals(p2))
        throw new ArgumentException("Der Punkt an dem das Bild steht und der, zu dem das Bild gucken soll sind gleich!");
    Point lookingAt = new Point();
    switch (direction)
    {
        case ImageDirection.Top:
            lookingAt = new Point(0, -1);
            break;
        case ImageDirection.Left:
            lookingAt = new Point(-1, 0);
            break;
        case ImageDirection.Right:
            lookingAt = new Point(1, 0);
            break;
        case ImageDirection.Bottom:
            lookingAt = new Point(0, 1);
            break;
    }
    Point newLookingAt = new Point(p2.X - p1.X, p2.Y - p1.Y);
    float angle = GetAngle(lookingAt, newLookingAt);
    if (p2.X < p1.X)
        return 360 - angle;
    else
        return angle;
}

public static Bitmap RotateImage(Bitmap image, float angle)
{
    if (float.IsNaN(angle))
        throw new ArgumentException("Der angegebene Winkel ist NaN!");
    if (angle == 0)
        return new Bitmap(image);
    Bitmap returnBitmap = new Bitmap(image.Width, image.Height);
    Graphics graphics = Graphics.FromImage(returnBitmap);
    graphics.TranslateTransform((float)image.Width / 2, (float)image.Height / 2);
    graphics.RotateTransform(angle);
    graphics.TranslateTransform(-(float)image.Width / 2, -(float)image.Height / 2);
    graphics.DrawImage(image, new Point(0, 0));
    return returnBitmap;
}
private Nachricht | Beiträge des Benutzers
Daniel B.
myCSharp.de - Member



Dabei seit:
Beiträge: 87
Herkunft: Linz

beantworten | zitieren | melden

Alles klar, ich finde nichts mehr, du bist dran!
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Daniel B. am .
private Nachricht | Beiträge des Benutzers
D4rkScr43m
myCSharp.de - Member



Dabei seit:
Beiträge: 32

beantworten | zitieren | melden

Ok, dann hätte ich gerne eine möglichst performante Methode

static void GetDifferences(TextReader reader1, TextReader reader2, TextWriter writer)
die aus den Readern reader1 und reader2 Werte wie aus einer .ini-Datei auslesen und alle Unterschiede in den Writer schreiben.
als Beispiel:
Wenn ich diese 2 Ini-Dateien vergleichen wollte:
Zitat von datei1.ini
[Kategorie1]
wert1=abc
wert2=xyz
[Kategorie2]
wert3=bla
wert4=blub
[Kategorie3]
wert5=123
wert6=666
Zitat von datei2.ini
[Kategorie1]
wert1=xyz
wert2=xyz
[Kategorie4]
wert5=123
wert6=666
[Kategorie2]
wert3=bla
wert4=***

sollte das ergebnis so aussehen:
Zitat
[Kategorie1]
wert1=xyz
[Kategorie4]
wert5=123
wert6=666
[Kategorie2]
wert4=***

Einfach gesagt sollte das Ergebnis dann reader2 ohne reader1 für unsortierte Kategorien und Werte sein.

Ich hoffe das ganze war verständlich. Es sollte für beliebig lange Datenströme funktionieren und wie gesagt, möglichst performant sein.

Edit: hatte in 2 Ini-Dateien nen Fehler im Kategorienamen, sollte aber das Ergebnis nicht beeinflussen
private Nachricht | Beiträge des Benutzers
TheBrainiac
myCSharp.de - Member

Avatar #avatar-3152.png


Dabei seit:
Beiträge: 832
Herkunft: /dev/null

beantworten | zitieren | melden

using System;
using System.IO;
using System.Collections.Generic;

public class MyClass
{
    public static void Main()
    {
        string path1 = @"C:\temp\1.ini",
               path2 = @"C:\temp\2.ini",
               path3 = @"C:\temp\3.ini";

        using (Stream file1 = File.OpenRead(path1),
                      file2 = File.OpenRead(path2),
                      file3 = File.OpenWrite(path3))
        using (TextReader reader1 = new StreamReader(file1),
                          reader2 = new StreamReader(file2))
        using (TextWriter writer = new StreamWriter(file3))
        {
            GetDifferences(reader1, reader2, writer);

            writer.Flush();
            writer.Close();
            reader1.Close();
            reader2.Close();
        }
    }

    static void GetDifferences(TextReader reader1, TextReader reader2, TextWriter writer)
    {
        Dictionary<string, Dictionary<string, string>> file1 = new Dictionary<string, Dictionary<string, string>>(),
                                                       file2 = new Dictionary<string, Dictionary<string, string>>(),
                                                       diff = null;

        ReadIni(reader1, file1);
        ReadIni(reader2, file2);

        diff = DiffIni(file1, file2);

        WriteIni(writer, diff);
    }

    static void ReadIni(TextReader r, Dictionary<string, Dictionary<string, string>> d)
    {
        string line = null,
               category = null;

        while ((line = r.ReadLine()) != null)
        {
            if (line.StartsWith("[") && line.EndsWith("]"))
            {
                category = line.Substring(1, line.Length - 2);

                d.Add(category, new Dictionary<string, string>());
            }
            else if (line.Contains("="))
            {
                string key = line.Substring(0, line.IndexOf("=")),
                       value = line.Substring(line.IndexOf("=") + 1);

                d[category].Add(key, value);
            }
        }
    }

    static Dictionary<string, Dictionary<string, string>> DiffIni(Dictionary<string, Dictionary<string, string>> f1, Dictionary<string, Dictionary<string, string>> f2)
    {
        Dictionary<string, Dictionary<string, string>> result = new Dictionary<string, Dictionary<string, string>>();

        foreach (KeyValuePair<string, Dictionary<string, string>> category in f2)
        {
            if (f1.ContainsKey(category.Key))
            {
                foreach (KeyValuePair<string, string> key in category.Value)
                {
                    if (!f1[category.Key].ContainsKey(key.Key) || f1[category.Key][key.Key] != key.Value)
                    {
                        if (!result.ContainsKey(category.Key))
                        {
                            result.Add(category.Key, new Dictionary<string, string>());
                        }

                        result[category.Key].Add(key.Key, key.Value);
                    }
                }
            }
            else
            {
                result.Add(category.Key, category.Value);
            }
        }

        return result;
    }

    static void WriteIni(TextWriter w, Dictionary<string, Dictionary<string, string>> d)
    {
        foreach (KeyValuePair<string, Dictionary<string, string>> category in d)
        {
            w.WriteLine("[{0}]", category.Key);

            foreach (KeyValuePair<string, string> key in category.Value)
            {
                w.WriteLine("{0}={1}", key.Key, key.Value);
            }
        }
    }
}
There are 10 types of people in the world:
Those, who think they understand the binary system
Those who don't even have heard about it
And those who understand "Every base is base 10"
private Nachricht | Beiträge des Benutzers
D4rkScr43m
myCSharp.de - Member



Dabei seit:
Beiträge: 32

beantworten | zitieren | melden

Funktioniert wie erwartet und ist allerdings nicht ganz so schnell wie meins. Bei entsprechend großen Dateien komme ich mit meiner Lösung auf ca. 9 Sekunden, wobei deine Lösung noch ca. 14 Sekunden braucht.

Der einzige Unterschied liegt wohl darin, dass ich Key und Value nicht auseinander ziehe sondern als einen langen String vergleiche.

Aber ich lass das mal durchgehen, mein erster Versuch lag bei den beiden Dateien bei über etwa 30 Sekunden.
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von D4rkScr43m am .
private Nachricht | Beiträge des Benutzers
Scavanger
myCSharp.de - Member

Avatar #avatar-3209.jpg


Dabei seit:
Beiträge: 323

beantworten | zitieren | melden

Um mal den Thread aus der Versenkung zu holen:

Ich möchte Code um die Größe von einer sequentiellen Struktur in Bytes abzurufen:

Beispiel:


[StructLayout(LayoutKind.Sequential)]
struct Foo
{
    public int baz;
    public ushort bar;
    public long foobar;
}

Ohne dabei Marshal.SizeOf() oder sizeof() zu benutzen.
Die Angabe muss aber mit Marshal.SizeOf() identisch sein (Stichwort: Byte Alignment).

Ist locker mit weniger als 10 Zeilen zu schaffen.

using System;class H{static string z(char[]c){string r="";for(int x=0;x<(677%666);x++)r+=c[
x];return r;}static void Main(){int[]c={798,218,229,592,232,274,813,585,229,842,275};char[]
b=new char[11];for(int p=0;p<((59%12));p++)b[p]=(char)(c[p]%121);Console.WriteLine(z(b));}}
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo Community,

die letzte Aufgabe scheint nicht so angekommen zu sein. Nachdem (deutlich) mehr als eine Woche ohne Lösung verhangen ist, greife ich den Thread wieder auf. Vielleicht habe ich mit dieser Aufgabe mehr Glück.

Die Aufgabe ist, die Methode

public static String FormatSI (Decimal d, int significantDigits, String unit)

zu implementieren, um Zahlen zu formatieren, und zwar so, dass folgende Bedingungen erfüllt sind:
  • Für Vielfache von Tausend sollen die passenden Vorsätze für Maßeinheiten verwendet werden (k, M, G, ..., m, µ, n, ...), so dass die eigentliche Zahl (Mantisse) immer ein bis drei Stellen vor dem Komma hat (ohne dass die Ziffer Null alleine vor dem Komma steht, es sei denn, die Zahl ist Null). Die Zahl 123456 würde also z.B. als 123k dargestellt werden. Die Zahl 0,1 würde als 100m (m für Milli, nicht Meter) dargestellt werden.

  • Ist die Zahl 10^27 oder größer oder ist die Zahl 10^-27 oder kleiner, soll statt der SI-Präfixe die Darstellung des Exponenten mit einem vorangestellten E erfolgen, also z.B. 123E27. Der verwendete Exponent muss dabei immer durch drei teilbar sein (27, 30, 33, ...).

  • Der Parameter significantDigits bestimmt, wie viele Ziffern dargestellt werden sollen bzw. auf wieviele Ziffern gerundet werden soll. Die Zahl 123456 würde also in Abhängigkeit von significantDigits wie folgt dargestellt werden:
    1 = 100k
    2 = 120k
    3 = 123k
    4 = 123,5k (Achtung: hier greift die Rundung)
    5 = 123,46k (Achtung: hier greift die Rundung)
    6 = 123,456k
    7 = 123,4560k

  • Der Parameter unit (z.B. "V") wird einfach an das bisherige Ergebnis angehängt, z.B. 123kV.

Es gibt noch drei weitere Rahmenbedingungen:
  • Es müssen nur positive Zahlen berücksichtigt werden. Die Bedingungen im obigen Text sind entsprechend formuliert. Wer negative Zahlen berücksichtigen will, sollte zu Anfang das Vorzeichen ermitteln und dann mit dem Absolutwert der Zahl weiterrechnen.

  • Die Methode soll keine if-Ketten enthalten. Sowas wie wenn d ≥ 1000, dann k, sonst wenn d ≥ 100000, dann M wäre also zu vermeiden. Insgesamt sollte eine elegante, kompakte Lösung angestrebt werden.

  • Testet die Methode gründlich, bevor ihr sie als Lösung postet. Beachtet insbesondere Grenz- und Sonderfälle. Die Methode soll für jede(*) mögliche Decimal-Zahl wie beschrieben funktionieren.

Ich wünsche euch viel Spaß!

herbivore

(*) Ausnahme sind - wie gesagt - negative Zahlen. Wenn ihr negative Zahlen wie beschreiben per Absolutwert behandeln wollt, sind negative Zahlen, die sich mit Math.Abs nicht in eine positive Zahl umwandeln lassen, ausgenommen.
private Nachricht | Beiträge des Benutzers
Ace86
myCSharp.de - Member



Dabei seit:
Beiträge: 15

beantworten | zitieren | melden

Hier ist mein Lösungsvorschlag.
Beim Runden auf die signifikanten Stellen, wollte ich keine eigene Methode schreiben, sondern die Mittel von .Net nutzen. Prinzipiell kann man das beim Umwandeln einer Zahl in eine Zeichenkette mit Hilfe des Formatzeichens G bzw. g lösen. Dabei wird bei der Ausgabe aber immer die kompakteste Schreibweise ausgegeben, so dass unter Umständen nicht die Komma-Schreibweise, sondern wissenschaftliche Notation ausgegeben wird, bsp. aus 123 wird bei 2 Signifkanten Stellen 1E+02. Daher parse ich in diesen Fällen die Zahl erneut als decimal und wandle sie wieder in einen String um. Ist etwas getrickst, aber funktioniert. Wenn jemand eine Idee hat wie man das schöner lösen kann, bzw. allgemeine Verbesserungsvorschläge hat, dann immer her damit. ;-)

public static string FormatSI(Decimal d, int significantDigits, String unit)
{
    string sign = "";
    string TextOfNumber = "";
    string siPrefix = "";

    if(significantDigits < 1)
        throw new ArgumentException("der Parameter significantDigits muss größer als 0 sein.");

    if(d < 0)
    {
        sign = "-";
        d = Math.Abs(d);
    }

    d = scaleNumberAndGetSIPrefix(d, out siPrefix);
    TextOfNumber = getSIString(d, significantDigits);

    return sign + TextOfNumber + siPrefix + unit;
}


private static decimal scaleNumberAndGetSIPrefix(decimal d, out string prefix)
{
    decimal multiplicationFactor = (d < 1) ? 1000m : 0.001m;
    int indexStep = (d < 1) ? -1 : +1;
    int thousandsIndex = 0;
            
    if(d != decimal.Zero)
    {
        while(d < 1 || d ≥ 1000)
        {
            d *= multiplicationFactor;
            thousandsIndex += indexStep;
        }
    }

    prefix = getSIPrefix(thousandsIndex);
    return d;
}


private static string getSIPrefix(int thousandsIndex)
{
    string[] prefixes = { "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" };

    if(thousandsIndex ≤ -9 || thousandsIndex ≥ 9)
        return "E" + thousandsIndex * 3;

    int offset = prefixes.Length / 2;
    return prefixes[thousandsIndex + offset];
}


private static string getSIString(decimal d, int significantDigits)
{
    // round to significant digits
    string textOfNumber = d.ToString("G" + significantDigits);
    if(textOfNumber.ToLower().Contains('e'))
    {
        // exponential notation to decimal point notation
        textOfNumber = decimal.Parse(textOfNumber, System.Globalization.NumberStyles.Float).ToString();
    }

    bool hasDecimalPoint = textOfNumber.Contains(',') || textOfNumber.Contains('.');
    int requiredLength = significantDigits;
    if(hasDecimalPoint)
        requiredLength++;

    // pad with zeros if necessary
    if(textOfNumber.Length < requiredLength)
    {
        if(!hasDecimalPoint)
        {
            textOfNumber += ",";
            requiredLength++;
        }
        textOfNumber = textOfNumber.PadRight(requiredLength, '0');
    }

    return textOfNumber;
}
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo Ace86,

vielen Dank für deinen Lösungsvorschlag. Die Aufteilung in Methoden gefällt mir gut. Das würde ich vermutlich nicht oder nicht viel anders machen.

Nur die Methode getSIString ist, wie du selbst geschrieben hast, nicht so schön. Es wäre wohl schöner, noch auf Ebene der Zahlen auszurechnen, wie korrekt formatiert werden müsste und dann passend zu formatieren, statt erst zu formatieren und dann auf String-Ebene rumzumanipulieren. Auch gibt es Probleme, wenn auf einem Rechner ein anderes Trennzeichen als Punkt oder Komma eingestellt ist.

Schönheit ist der eine Aspekt. Wichtiger ist jedoch die Korrektheit. Leider funktioniert deine Methode FormatSI noch nicht für alle Zahlen korrekt. Hier ein Gegenbeispiel:

FormatSI (950, 1, "") müsste "1k" liefern, liefert aber "1000". Das liegt unter anderem an der getSIString-Methode. Wenn du diese überarbeitest, könntest du versuchen, sie wie vorgeschlagen zu implementieren.

herbivore
private Nachricht | Beiträge des Benutzers
Ace86
myCSharp.de - Member



Dabei seit:
Beiträge: 15

beantworten | zitieren | melden

Die letzten Tage hatte ich wenig Zeit, aber heute bin ich wieder dazu gekommen die Methode zu überarbeiten:

public static string FormatSI(Decimal d, int significantDigits, String unit)
{
    string sign = "";
    int thousandsExponent = 0;

    if(significantDigits < 1)
        throw new ArgumentException("der Parameter significantDigits muss größer als 0 sein.");

    if(d < 0)
    {
        sign = "-";
        d = Math.Abs(d);
    }

    d = scaleNumberAndGetSIPrefix(d, out thousandsExponent);
    d = round(d, significantDigits);

    // range overflow
    if(!(d < 1000m))
    {
        d = scaleNumber(d);
        d = round(d, significantDigits);
        thousandsExponent++;
    }

    return sign + getSIText(d, significantDigits) + getSIPrefix(thousandsExponent) + unit;
}


private static decimal scaleNumberAndGetSIPrefix(decimal d, out int thousandsExponent)
{
    decimal multiplicationFactor = (d < 1) ? 1000m : 0.001m;
    int indexStep = (d < 1) ? -1 : +1;
    int expontent = 0;
            
    if(d != decimal.Zero)
    {
        while(d < 1 || d ≥ 1000)
        {
            d *= multiplicationFactor;
            expontent += indexStep;
        }
    }

    thousandsExponent = expontent;
    return d;
}


private static decimal scaleNumber(decimal d)
{
    int num;
    return scaleNumberAndGetSIPrefix(d, out num);
}


private static decimal round(decimal d, int significantDigits)
{
    if(d.Equals(decimal.Zero))
        return d;

    double scaleExponent = Math.Floor(Math.Log10((double)d) - significantDigits + 1);
    decimal scaleFactor = (decimal)Math.Pow(10, scaleExponent);

    // scale to significant digits
    decimal a = d / scaleFactor;

    decimal b = Math.Round(a);

    // scale back
    return b * scaleFactor;
}


private static string getSIText(decimal d, int significantDigits)
{
    string textOfNumber = d.ToString();
    string decimalSeparator = System.Globalization.NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator;
    bool hasDecimalSeparator = textOfNumber.Contains(decimalSeparator);

    int requiredLength = significantDigits;
    if(hasDecimalSeparator)
        requiredLength++;

    if(textOfNumber.Length < requiredLength)
    {
        if(!hasDecimalSeparator)
        {
            textOfNumber += ",";
            requiredLength++;
        }
        textOfNumber = textOfNumber.PadRight(requiredLength, '0');
    }

    return textOfNumber;
}


private static string getSIPrefix(int thousandsIndex)
{
    string[] prefixes = { "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" };

    if(thousandsIndex ≤ -9 || thousandsIndex ≥ 9)
        return "E" + thousandsIndex * 3;

    int offset = prefixes.Length / 2;
    return prefixes[thousandsIndex + offset];
}
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo Ace86,

das von mir genannte Rundungsproblem hast du behoben, aber dafür leider ein neues eingebaut. Das liegt daran, dass du einer Stelle fälschlich bzw. unbedachterweise mit double arbeitest.

Double hat nur eine Genauigkeit (Mantisse) von 15-16 Stellen, wogegen Decimal eine Genauigkeit (Mantisse) von 28-29 Stellen hat. Wenn man nun einen Decimal mit einer Mantisse von mehr als 16 Stellen verwendet, kann es zu falschen Ergebnissen führen.

Der folgende Code

decimal m = 9.9999999999999999m;
Console.WriteLine ("{0:G28}", m);
Console.WriteLine ("{0:G28}", (double)m);
Console.WriteLine (FormatSI (m, 18, ""));
Console.WriteLine (FormatSI (m, 17, ""));

produziert folgende Ausgabe
9,9999999999999999
10
9,99999999999999990
10,000000000000000

Die ersten beiden Zeilen zeigen, dass bei der Umwandlung einer ausreichend genauen decimal-Zahl nahe aber kleiner 10 in einen double, diese zwangsläufig auf 10 aufgerundet wird. In der Folge wird in deiner round-Methode ein falscher scaleExponent berechnet. Deshalb wird anschließend die decimal-Zahl eine Stelle zu weit links gerundet. Dadurch wird in der vierten Zeile 10,000000000000000 ausgegeben, obwohl 9,9999999999999999 ausgegeben werden müsste, denn die Ziffer hinter der letzten 9 - ist wie die dritte Zeile zeigt - eine 0, also dürfte in der vierten Zeile noch nicht aufgerundet werden.

Nenn mich pingelig :-) aber ich hatte direkt in der Aufgabe ausdrücklich gefordert, dass "die Methode für jede mögliche Decimal-Zahl wie beschrieben funktionieren soll".

herbivore

PS: Sobald ich einen Fehler in einem Lösungsvorschlag gefunden habe, höre ich auf, intensiv nach weiteren Fehlern zu suchen. Es können also noch andere Fehler enthalten sein. Insbesondere wird Math.Round möglicherweise nicht so runden, wie es ToString tut, siehe z.B. Math.Round rundet anders als erwartet. Allerdings habe ich in der Aufgabe keine explizite Aussage dazu getroffen, wie genau gerundet werden soll. Also ist es eine Frage der Interpretation, ob man den Aufgabentext so interpretiert, dass zwangsläufig die in Deutschland übliche kaufmännische Rundung gemeint ist und man eine anderes Rundungsverfahren demzufolge als Fehler ansieht. Die Beispiele jedenfalls sind kaufmännisch gerundet.
private Nachricht | Beiträge des Benutzers
Ace86
myCSharp.de - Member



Dabei seit:
Beiträge: 15

beantworten | zitieren | melden

Zitat
Nenn mich pingelig :-) aber ich hatte direkt in der Aufgabe ausdrücklich gefordert, dass "die Methode für jede mögliche Decimal-Zahl wie beschrieben funktionieren soll".

Ist schon richtig, wenn die Methode nicht den Anforderungen entspricht, dann muss sie korrigiert werden. Ich frage mich aber warum nicht alle Methoden der Math Bibliothek auch für decimal implementiert sind.
Zitat
Insbesondere wird Math.Round möglicherweise nicht so runden, wie es ToString tut, siehe z.B. Math.Round rundet anders als erwartet.

Gut zu wissen, ich bin bisher davon ausgegangen, dass Math.Round standardmäßig kaufmännisch rundet

Und jetzt zum eigentlichen Anliegen. Aller guten Dinge sind (hoffentlich) drei, hier ist meine überarbeitete Version:

public static string FormatSI(Decimal d, int significantDigits, String unit)
{
    string sign = "";
    int powerOfThousand;
    string textOfNumber;
    string siPrefix;

    if(significantDigits < 1)
        throw new ArgumentException("der Parameter significantDigits muss größer als 0 sein.");

    if(d < 0)
    {
        sign = "-";
        d = Math.Abs(d);
    }

    powerOfThousand = getPowerOfThousand(d);
    d = scaleNumber(d);
    d = roundSignificant(d, significantDigits);

    // range overflow
    if(!(d < 1000m))
    {
        d = scaleNumber(d);
        d = roundSignificant(d, significantDigits);
        powerOfThousand++;
    }

    textOfNumber = getSIText(d, significantDigits);
    siPrefix = getSIPrefix(powerOfThousand);

    return sign + textOfNumber + siPrefix + unit;
}



private static int getPowerOfThousand(decimal d)
{
    int power;
    scaleNumberAndGetPower(d, 1000, out power);
    return power;
}


private static int getPowerOfTen(decimal d)
{
    int power;
    scaleNumberAndGetPower(d, 10, out power);
    return power;
}


private static decimal scaleNumber(decimal d)
{
    int power;
    return scaleNumberAndGetPower(d, 1000, out power);
}


private static decimal scaleNumberAndGetPower(decimal d, int upperBound, out int power)
{
    decimal factor = (d < 1) ? upperBound : 1m / upperBound;
    int indexStep = (d < 1) ? -1 : +1;
    int expontent = 0;

    if(d != decimal.Zero)
    {
        while(d < 1 || d ≥ upperBound)
        {
            d *= factor;
            expontent += indexStep;
        }
    }

    power = expontent;
    return d;
}


private static decimal roundSignificant(decimal d, int significantDigits)
{
    if(d.Equals(decimal.Zero))
        return d;

    decimal scaleFactor = (decimal)Math.Pow(10, getPowerOfTen(d) - significantDigits + 1);

    // scale to significant digits
    decimal a = d / scaleFactor;

    decimal b = Math.Round(a, MidpointRounding.AwayFromZero);

    // scale back
    return b * scaleFactor;
}



private static string getSIText(decimal d, int significantDigits)
{
    string textOfNumber = d.ToString();

    if(d == decimal.Zero && significantDigits > 1)
    {
        string decimalSeparator = System.Globalization.NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator;
        int requiredLength = significantDigits + 1;

        textOfNumber += ",";
        textOfNumber = textOfNumber.PadRight(requiredLength, '0');
    }

    return textOfNumber;
}


private static string getSIPrefix(int powerOfThousand)
{
    string[] prefixes = { "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" };

    if(powerOfThousand ≤ -9 || powerOfThousand ≥ 9)
        return "E" + powerOfThousand * 3;

    int offset = prefixes.Length / 2;
    return prefixes[powerOfThousand + offset];
}
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo Ace86,

ich gebe es zu, ich suche natürlich extra nach den fiesen Grenzfällen. Ein Decimal hat 28-29 signifikante Stellen. Wenn der Aufrufer von FormatSI die maximal mögliche Genauigkeit haben will, egal welche Zahl er übergibt, wird er 29 als Parameter für significantDigits übergeben. Dann kommt es aber auf die Größe der übergebenen decimal-Zahl an, ob deine Methode funktioniert.


{
   decimal m = 7.1234567890123456789012345678m;
   Console.WriteLine ("{0:G29}", m);
   Console.WriteLine (FormatSI (m, 28, ""));
   Console.WriteLine (FormatSI (m, 29, ""));
}

{
   decimal m = 8.1234567890123456789012345678m; // *
   Console.WriteLine ("{0:G29}", m);
   Console.WriteLine (FormatSI (m, 28, ""));
   Console.WriteLine (FormatSI (m, 29, "")); //**
}

Bei (**) kommt folgende Ausnahme:
Fehler
Unbehandelte Ausnahme: System.OverflowException: Der Wert für eine Decimal war zu groß oder zu klein.
bei System.Decimal.FCallDivide(Decimal& d1, Decimal& d2)
bei System.Decimal.op_Division(Decimal d1, Decimal d2)
bei Versuch3.roundSignificant(Decimal d, Int32 significantDigits)
bei Versuch3.FormatSI(Decimal d, Int32 significantDigits, String unit)

(*) Zugegeben, die letzte 8 wird sowieso abgeschnitten (bzw. genaugenommen fürs Aufrunden der Ziffer davor verwendet). Eine decimal-Zahl, die mit 8 beginnt, kann maximal 28 signifikante Stellen haben. Aber zum einen greift hier die Argumentation von oben, dass ein Aufrufer für maximale Genauigkeit pauschal 29 significantDigits angeben möchte. Und außerdem hattest und hast du den Fall, dass significantDigits größer ist als die tatsächliche Anzahl der signifikanten Stellen der Zahl, ja explizit durch das PadRight berücksichtigt. Also sollte FormatSI auch bei significantDigits == 29 (eigentlich sogar bei significantDigits ≥ 29) immer korrekt funktionieren (wobei ich für significantDigits > 29 damit einverstanden wäre, wenn du den Fall genauso als Argumentfehler ansiehst wie significantDigits < 1).

Ich hoffe du hast den Spaß noch nicht verloren, sondern siehst es im Gegenteil als spannend und interessant an, wie viele Fehler in einer auf den ersten Blick korrekten Implementierung eines an sich überschaubaren Algorithmus stecken können, wenn man sehr genau hinschaut.

Wobei es für mich langsam eng wird. So viele Grenzfälle sehe ich nicht mehr. Du hast also gute Chancen für einen baldigen erfolgreichen Abschluss.

Ich bin sogar eben schon auf einen Grenzfall hineingefallen, bei dem ich dachte, dass deine Methode nicht korrekt funktioniert, sie es aber doch tut. Denn bei

decimal m = 1.2345678901234567890123456789e-27m;
Console.WriteLine (Versuch3.FormatSI (m, 3, ""));
wird "1,20E-27" ausgegeben, also wo ist die 3 hin? Aber die ist gar nicht in dem decimal gelandet, denn bei e-27 kann ein decimal nur noch max. 2 signifikante Ziffern darstellen, wie auch die Ausgabe "1,2E-27" der folgenden Zeile zeigt:

Console.WriteLine ("{0:G3}", m);

herbivore
private Nachricht | Beiträge des Benutzers
Ace86
myCSharp.de - Member



Dabei seit:
Beiträge: 15

beantworten | zitieren | melden

Zitat
Ich hoffe du hast den Spaß noch nicht verloren, sondern siehst es im Gegenteil als spannend und interessant an, wie viele Fehler in einer auf den ersten Blick korrekten Implementierung eines an sich überschaubaren Algorithmus stecken können, wenn man sehr genau hinschaut.

An dieser Aufgabe sieht man sehr schön wie gemein die "Einschränkung" alle decimal Zahlen (bzw. die die sich mit Math.Abs als positive umwandeln lassen) ist, insbsondere wenn jemand so genau wie du prüft. Der Spaß ist aber noch nicht weg.

Ich betrachte significantDigits > 29 jetzt auch als Argumentfehler. Schon im letzten Lösungsvorschlag wurde PadRight nur noch verwendet, wenn d = 0 ist. Für alle anderen Fälle hatte die ToString-Methode schon den richtigen Wert ausgegeben. Wenn die Zahl wie in deinem Beispiel statt 29 nur 28 signifikante Stellen hat, hänge ich auch keine Null mehr an, da die letzte Stelle des decimals schon von anfang an gerundet sein kann. Darüber lässt sich sicherlich auch streiten. Es wäre aber auch kein Problem das anfügen der Nullen wieder wie in früheren Vorschlägen durchzuführen.

public static string FormatSI(Decimal d, int significantDigits, String unit)
{
    string sign = "";
    int powerOfThousand;
    string textOfNumber;
    string siPrefix;

    if(significantDigits < 1)
        throw new ArgumentException("Der Parameter \"significantDigits\" muss größer als 0 sein.");

    if(significantDigits > 29)
        throw new ArgumentException("Der Parameter \"significantDigits\" darf nicht größer als 29 sein.");

    if(d < 0)
    {
        sign = "-";
        d = Math.Abs(d);
    }

    powerOfThousand = getPowerOfThousand(d);
    d = scaleNumber(d);
    d = roundSignificant(d, significantDigits);

    // range overflow
    if(!(d < 1000m))
    {
        d = scaleNumber(d);
        d = roundSignificant(d, significantDigits);
        powerOfThousand++;
    }

    textOfNumber = getSIText(d, significantDigits);
    siPrefix = getSIPrefix(powerOfThousand);

    return sign + textOfNumber + siPrefix + unit;
}



private static int getPowerOfThousand(decimal d)
{
    int power;
    scaleNumberAndGetPower(d, 1000, out power);
    return power;
}


private static int getPowerOfTen(decimal d)
{
    int power;
    scaleNumberAndGetPower(d, 10, out power);
    return power;
}


private static decimal scaleNumber(decimal d)
{
    int power;
    return scaleNumberAndGetPower(d, 1000, out power);
}


private static decimal scaleNumberAndGetPower(decimal d, int upperBound, out int power)
{
    decimal factor = (d < 1) ? upperBound : 1m / upperBound;
    int indexStep = (d < 1) ? -1 : +1;
    int expontent = 0;

    if(d != decimal.Zero)
    {
        while(d < 1 || d ≥ upperBound)
        {
            d *= factor;
            expontent += indexStep;
        }
    }

    power = expontent;
    return d;
}


private static decimal roundSignificant(decimal d, int significantDigits)
{
    if(d.Equals(decimal.Zero))
        return d;

    try
    {
        decimal scaleFactor = (decimal)Math.Pow(10, getPowerOfTen(d) - significantDigits + 1);

        decimal a = d / scaleFactor;
        decimal b = Math.Round(a, MidpointRounding.AwayFromZero);
        return b * scaleFactor;
    }
    catch(OverflowException)
    {
        return roundSignificant(d, --significantDigits);
    }
}



private static string getSIText(decimal d, int significantDigits)
{
    string textOfNumber = d.ToString();

    if(d == decimal.Zero && significantDigits > 1)
    {
        string decimalSeparator = System.Globalization.NumberFormatInfo.CurrentInfo.CurrencyDecimalSeparator;
        int requiredLength = significantDigits + 1;

        textOfNumber += ",";
        textOfNumber = textOfNumber.PadRight(requiredLength, '0');
    }

    return textOfNumber;
}


private static string getSIPrefix(int powerOfThousand)
{
    string[] prefixes = { "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" };

    if(powerOfThousand ≤ -9 || powerOfThousand ≥ 9)
        return "E" + powerOfThousand * 3;

    int offset = prefixes.Length / 2;
    return prefixes[powerOfThousand + offset];
}
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 52329
Herkunft: Berlin

beantworten | zitieren | melden

Hallo Ace86,
Zitat
Schon im letzten Lösungsvorschlag wurde PadRight nur noch verwendet, wenn d = 0 ist.
das war mir zwar auch aufgefallen, aber erst jetzt, als ich nochmal genauer hingeschaut habe, ist mir aufgegangen, warum du in den meisten Fällen ohne PadRight auskommst. Dem ganzen liegt zu Grunde, dass es bei decimal für die meisten Zahlen mehrere unterschiedliche Repräsentationen mit demselben Zahlenwert gibt.

Klarer wird das, wenn man sich die Decimal.GetBits-Methode anschaut. Demzufolge besteht ein Decimal aus einer vorzeichenlosen 96bit Integer-Zahl (Mantisse), einem Bit für das Vorzeichen und einer Zahl zwischen 0 und 28, die bestimmt, um wieviele Stellen das Komma nach links verschoben werden soll.

Dadurch lässt sich z.B. die Zahl 10 auf verschiedene Arten repräsentieren, nämlich als Integer-Zahl 10 ohne Kommaverschiebung, als Integer-Zahl 100 mit einer Kommaverschiebung um eins nach links, als Integer-Zahl 1000 mit einer Kommaverschiebung um zwei nach links, usw.

Obwohl alle diese Repräsentationen den selben Zahlenwert haben, nämlich 10 und deshalb ein Vergleich mit == oder Equals auch bei unterschiedlichen Repräsentationen true liefert, werden die Zahlen doch unterschiedlich ausgegeben. Folgende Zeilen


Console.WriteLine (new Decimal (10, 0, 0, false, 0));
Console.WriteLine (new Decimal (100, 0, 0, false, 1));
Console.WriteLine (new Decimal (1000, 0, 0, false, 2));

produzieren die folgende Ausgabe:
10
10,0
10,00

Das machst du dir - ob nun bewusst oder unbewusst - in der Methode roundSignificant zu nutze. Wenn z.B. die Zahl 123 mit fünf signifikanten Stellen ausgegeben werden soll, ist dein scaleFactor 0.01m. Der folgende Code


decimal m = 123m;
Console.WriteLine (m);
m /= 0.01m;
Console.WriteLine (m);
m *= 0.01m;
Console.WriteLine (m);

produziert folgende Ausgabe:
123
12300
123,00

Obwohl die Ausgangszahl am Ende wieder den gleichen Zahlenwert hat, hat sich dennoch ihre Repräsentation und damit ihre Ausgabe geändert. Statt zu Anfang als 123 ohne Kommaverschiebung, wird sie am Ende als 12300 mit einer Kommaverschiebung um zwei Stellen nach links repräsentiert.
Interessanterweise würde das nicht passieren, wenn man Division und Multiplikation vertauschen und zum Ausgleich mit dem Kehrwert des scaleFactors rechen würde, also 100m statt 0.01m. Der folgende Code

[csharp]
decimal m = 123m;
Console.WriteLine (m);
m *= 100m;
Console.WriteLine (m);
m /= 100m;
Console.WriteLine (m);
[/csharp]

produziert folgende Ausgabe:

[FRAME]
123
12300
123
Hier hat die Zahl am Ende die gleiche Repräsentation wie am Anfang.

Ich habe allerdings nicht geschaut, ob dieses (unterschiedliche) Verhalten irgendwo verbindlich spezifiziert ist. Wenn das nicht der Fall ist, könnte sich das Verhalten bei einer neuen Framework und/oder Compiler-Version ändern, und wäre somit möglicherweise nicht zukunftssicher. Aber das nur am Rande.[/frame]

Kommen wir nach dieser Vorrede zu deiner Korrektur im aktuellen Lösungsvorschlag gegenüber dem vorherigen. Diese basiert darauf, die Overflow-Exception, die bei manchen Zahlen auftreten kann, wenn 29 signifikate Stellen gewünscht sind, abzufangen und mit einer signifikanten Stelle weniger zu runden. Das ist insofern nicht schlimm, als die Zahlen, bei denen die Overflow-Exception auftreten kann, ohnehin nur maximal 28 signifikante Stellen haben können. Es wird also trotzdem korrekt gerundet.

Allerdings verschiebt der scaleFactor dadurch die Mantisse auch um eine Stelle weniger und man bekommt entsprechend eine Repräsentation mit einer Stelle in der Mantisse weniger und entsprechend einer Kommaverschiebung um eine Stelle weniger. Im Ergebnis gibt ToString dadurch auch eine Stelle weniger aus.

Du sagst, dass man darüber streiten kann, ob in dem Fall zum Ausgleich eine Null angehängt werden müsste. Ich finde es allerdings eindeutig, dass das passieren müsste. Dazu zunächst folgende Test-Methode


private static void Test (decimal d)
{
   for (int i = 25; i ≤ 29; ++i) {
      Console.WriteLine ("{0,2} {1}", i, FormatSI (d, i, ""));
   }
   Console.WriteLine ();
}

und folgende Aufrufe:


Test (7.1234567890123456789012345678m);
Test (8.1234567890123456789012345678m); // Wie bekannt: die letzte 8 landet eh nicht im decimal

Test (7);
Test (8);

Die beiden ersten Zeilen produzieren folgende Ausgabe:
25 7,123456789012345678901235
26 7,1234567890123456789012346
27 7,12345678901234567890123457
28 7,123456789012345678901234568
29 7,1234567890123456789012345678

25 8,123456789012345678901235
26 8,1234567890123456789012346
27 8,12345678901234567890123457
28 8,123456789012345678901234568
29 8,123456789012345678901234568

Bis hierhin könnte man noch sagen, ok, die letzte Ziffer im zweiten Beispiel ist ja auch nicht im Decimal gelandet, also wird sie korrekterweise nicht mit ausgegeben, aber wenn man sich die Ausgabe der letzten beiden Test-Aufrufe ansieht
25 7,000000000000000000000000
26 7,0000000000000000000000000
27 7,00000000000000000000000000
28 7,000000000000000000000000000
29 7,0000000000000000000000000000

25 8,000000000000000000000000
26 8,0000000000000000000000000
27 8,00000000000000000000000000
28 8,000000000000000000000000000
29 8,000000000000000000000000000

dann sieht man, dass gewünschte signifikante Stellen, die in dem Wert gar nicht vorhanden sind, trotzdem immer als folgende Nullen ausgegeben werden, nur eben nicht in der allerletzten Zeile, die für den Benutzer der Methode nicht nachvollziehbar eine Null am Ende weniger enthält.

Allerdings stimmte ich dir darin zu, dass es kein Problem wäre, das Anfügen dieser letzten Null auch noch einzubauen.

Deshalb und da ich auch sonst keine Fehler mehr gefunden habe, erkläre ich die Aufgabe für gelöst. Ich hoffe, es hat dir und allen Mitleseren Spaß gemacht und einige neue Erkenntnisse beschert.

Du bist dran, die nächste Aufgabe zu stellen.

herbivore
private Nachricht | Beiträge des Benutzers
Ace86
myCSharp.de - Member



Dabei seit:
Beiträge: 15

beantworten | zitieren | melden

Das Ziel die Aufgabe besteht darin eine Morphing-Animation zweier geometrischer Figuren auf der Konsole zu erstellen. Dabei soll ein zeitgesteuerter gleichmäßiger Übergang von einem Quadrat zu einem Rhombus mit gleichlangen Diagonalen erzeugt werden. Die Länge der Diagonalen im Rhombus ist gleich der Seitenlänge des Quadrates. Um das zu verdeutlichen habe ich ein Bild angehangen.

Zu Beginn ist die blaue Form zu sehen, welche in die rote Form übergeht. Nachdem die Endform erreicht ist, soll die Animation rückwärts ablaufen, bis wieder die ursprüngliche Form erreicht ist. Die Animation läuft unendlich lange.

Zusätzlich ist zu beachten, dass die Animation für verschieden große Quadrate funktionieren soll und dass zu jeder Zeit der Animation eine vollständig umschlossene Form sichtbar ist, die zur x- und y- Achse symmetrisch ist. Die Farbe der Form ist beliebig.

Es empfiehlt sich in der Konsole Zeichen gleicher Höhe und Breite zu verwenden. Zum zeichnen einer Linie kann die untenstehende Methode verwendet werden.

public void drawLine(int x1, int y1, int x2, int y2, ConsoleColor bgColor)
{
    ConsoleColor BackgroundColorTemp = Console.BackgroundColor;
    int cursorLeftTemp = Console.CursorLeft;
    int cursorTopTemp = Console.CursorTop;

    Console.BackgroundColor = bgColor;

    int dx = Math.Abs(x2 - x1), sx = x2 < x1 ? -1 : 1;
    int dy = -Math.Abs(y2 - y1), sy = y2 < y1 ? -1 : 1;
    int err = dx + dy, e2;

    while(true)
    { 
        Console.CursorLeft = x1;
        Console.CursorTop = y1;
        Console.Write(" ");
        if(x1 == x2 && y1 == y2) break;
        e2 = 2 * err;
        if(e2 > dy) { err += dy; x1 += sx; }
        if(e2 < dx) { err += dx; y1 += sy; }
    }


    Console.BackgroundColor = BackgroundColorTemp;
    Console.CursorLeft = cursorLeftTemp;
    Console.CursorTop = cursorTopTemp;
}
Attachments
private Nachricht | Beiträge des Benutzers
Alf Ator
myCSharp.de - Member



Dabei seit:
Beiträge: 630

beantworten | zitieren | melden

Ist aber ne ganze Menge geworden..

Ich hoffe, dass stört nicht, dass die 'Pixel' in der Console nicht quatratisch sind. Das kann man erreichen, in dem eine Verknüpfung an die kompilierte Exe erstellt und die Schriftgröße auf z.B. 8x8 einstellt. Andere Möglichkeit ist über die WinAPI. Ich finde aber, dass der Code schon umfangreich genug ist.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;

namespace RombusTest
{
    class Program
    {
        static void Main(string[] args)
        {
            CubeToRombusMorpher morpher = new CubeToRombusMorpher();
            morpher.Morph(3, 3, 60, 100);

            Console.ReadLine();
        }
    }
    
    public class CubeToRombusMorpher
    {
        private Timer timer;
        private List<Oktaeder> oktaeders;
        private IEnumerator<Oktaeder> enumerator;

        private class Point
        {
            public int X { get; set; }
            public int Y { get; set; }

            public Point(int x, int y)
            {
                X = x;
                Y = y;
            }
            public override string ToString()
            {
                return String.Format("({0}/{1})", X, Y);
            }
        }

        private class Oktaeder
        {
            public Point P1 { get; set; }
            public Point P2 { get; set; }
            public Point P3 { get; set; }
            public Point P4 { get; set; }
            public Point P5 { get; set; }
            public Point P6 { get; set; }
            public Point P7 { get; set; }
            public Point P8 { get; set; }

            public Oktaeder(int mx1, int my1, int mx2, int my2, int mx3, int my3, int mx4, int my4, int mx5, int my5, int mx6, int my6, int mx7, int my7, int mx8, int my8)
            {
                P1 = new Point(mx1, my1);
                P2 = new Point(mx2, my2);
                P3 = new Point(mx3, my3);
                P4 = new Point(mx4, my4);
                P5 = new Point(mx5, my5);
                P6 = new Point(mx6, my6);
                P7 = new Point(mx7, my7);
                P8 = new Point(mx8, my8);
            }

            public override string ToString()
            {
                return String.Join(", ", P1, P2, P3, P4, P5, P6, P7, P8);
            }
        }

        public void Morph(int x1, int y1, int length, int interval)
        {
            InitConsole(x1, y1, length);
            InitTimer(interval);
            InitOktaeders(x1, y1, length);
            DrawCube(x1, y1, x1 + length, y1 + length, ConsoleColor.Green);
            Console.ReadLine();
            timer.Start();
        }

        private void InitConsole(int x1, int y1, int length)
        {
            Console.SetWindowSize((x1 * 2) + length, (y1 * 2) + length);
            Console.SetBufferSize((x1 * 2) + length, (y1 * 2) + length);
        }

        private void InitOktaeders(int x1, int y1, int length)
        {
            oktaeders = new List<Oktaeder>();
            for (int i = 0; i ≤ (length / 2); i++)
            {
                int mx1 = x1,               my1 = y1 + i;
                int mx2 = x1 + i,           my2 = y1;
                int mx3 = x1 + length - i,  my3 = y1;
                int mx4 = x1 + length,      my4 = y1 + i;
                int mx5 = x1 + length,      my5 = y1 + length - i;
                int mx6 = x1 + length - i,  my6 = y1 + length;
                int mx7 = x1 + i,           my7 = y1 + length;
                int mx8 = x1,               my8 = y1 + length - i;
                oktaeders.Add(new Oktaeder(mx1, my1, mx2, my2, mx3, my3, mx4, my4, mx5, my5, mx6, my6, mx7, my7, mx8, my8));
            }
            enumerator = oktaeders.GetEnumerator();
        }

        private void InitTimer(int interval)
        {
            timer = new Timer();
            timer.Interval = interval;
            timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
        }

        private void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            timer.Stop();
            Console.Clear();

            if (enumerator.MoveNext())
            {
                DrawOktaeder(enumerator.Current, ConsoleColor.Magenta);
                Console.WriteLine();
                Console.WriteLine(enumerator.Current);
            }
            else
            {
                oktaeders.Reverse();
                enumerator = oktaeders.GetEnumerator();
                if (enumerator.MoveNext())
                {
                    DrawOktaeder(enumerator.Current, ConsoleColor.Magenta);
                    Console.WriteLine();
                    Console.WriteLine(enumerator.Current);
                }
            }
            
            timer.Start();
        }

        private void DrawOktaeder(Oktaeder oktaeder, ConsoleColor color)
        {
            DrawLine(oktaeder.P1.X, oktaeder.P1.Y, oktaeder.P2.X, oktaeder.P2.Y, color);
            DrawLine(oktaeder.P2.X, oktaeder.P2.Y, oktaeder.P3.X, oktaeder.P3.Y, color);
            DrawLine(oktaeder.P3.X, oktaeder.P3.Y, oktaeder.P4.X, oktaeder.P4.Y, color);
            DrawLine(oktaeder.P4.X, oktaeder.P4.Y, oktaeder.P5.X, oktaeder.P5.Y, color);
            DrawLine(oktaeder.P5.X, oktaeder.P5.Y, oktaeder.P6.X, oktaeder.P6.Y, color);
            DrawLine(oktaeder.P6.X, oktaeder.P6.Y, oktaeder.P7.X, oktaeder.P7.Y, color);
            DrawLine(oktaeder.P7.X, oktaeder.P7.Y, oktaeder.P8.X, oktaeder.P8.Y, color);
            DrawLine(oktaeder.P8.X, oktaeder.P8.Y, oktaeder.P1.X, oktaeder.P1.Y, color);
        }

        private void DrawRombus(int x1, int y1, int x2, int y2, ConsoleColor color)
        {
            DrawLine(x1, (y1 + y2) / 2, (x1 + x2) / 2, y1, color);
            DrawLine((x1 + x2) / 2, y1, x2, (y1 + y2) / 2, color);
            DrawLine(x1, (y1 + y2) / 2, (x1 + x2) / 2, y2, color);
            DrawLine((x1 + x2) / 2, y2, x2, (y1 + y2) / 2, color);
        }

        private void DrawCube(int x1, int y1, int x2, int y2, ConsoleColor color)
        {
            DrawLine(x1, y1, x2, y1, color);
            DrawLine(x1, y1, x1, y2, color);
            DrawLine(x1, y2, x2, y2, color);
            DrawLine(x2, y1, x2, y2, color);
        }

        private void DrawLine(Point p1, Point p2, ConsoleColor color)
        {
            DrawLine(p1.X, p1.Y, p2.X, p2.Y, color);
        }
        private void DrawLine(int x1, int y1, int x2, int y2, ConsoleColor color)
        {
            ConsoleColor BackgroundColorTemp = Console.BackgroundColor;
            int cursorLeftTemp = Console.CursorLeft;
            int cursorTopTemp = Console.CursorTop;

            Console.BackgroundColor = color;

            int dx = Math.Abs(x2 - x1), sx = x2 < x1 ? -1 : 1;
            int dy = -Math.Abs(y2 - y1), sy = y2 < y1 ? -1 : 1;
            int err = dx + dy, e2;

            while (true)
            {
                Console.CursorLeft = x1;
                Console.CursorTop = y1;
                Console.Write(" ");
                if (x1 == x2 && y1 == y2) break;
                e2 = 2 * err;
                if (e2 > dy) { err += dy; x1 += sx; }
                if (e2 < dx) { err += dx; y1 += sy; }
            }

            Console.BackgroundColor = BackgroundColorTemp;
            Console.CursorLeft = cursorLeftTemp;
            Console.CursorTop = cursorTopTemp;
        }
    }
}

Edit: Da macht man noch ne winzige Änderung und hat gleich mal nen Fehler rein.. korrigiert.
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Alf Ator am .
private Nachricht | Beiträge des Benutzers
Ace86
myCSharp.de - Member



Dabei seit:
Beiträge: 15

beantworten | zitieren | melden

Vielen Dank für deine Lösung.
Um die 8x8 Pixel einzustellen reicht es beispielweise einmal den Debugmodus im VS zu starten und einen Rechtsklick auf die Titelleiste der Konsole zu machen und die Eigenschaften entsprechend anzupassen. Die werden gespeichert und bleiben beim nächsten Start bestehen. Aber das ist nur ein Detail, dass jeder selbst einstellen kann und auch nicht in der Aufgabenstellung gefordert war.

Ansonsten habe ich mir die Umsetzung genau so vorgestellt, auch der Quellcode ist schön übersichtlich geworden. Wenn du die DrawRombus-Methode noch entfernst ist er noch ein wenig kürzer . Die Aufgabe ist damit gelöst und du darfst die nächste stellen.
private Nachricht | Beiträge des Benutzers
Alf Ator
myCSharp.de - Member



Dabei seit:
Beiträge: 630

beantworten | zitieren | melden

Ok, danke!

Ich hab mir das was überlegt:
Ihr sollt die Methode GetShortestWay für die Klasse Link implementieren.


class Link
{
    public int ID { get; set; }
    public List<Link> Links { get; set; }

    public Link(int id)
    {
        this.ID = id;
        this.Links = new List<Link>();
    }

    public string GetShortestWay(int startID, int goalID)
    {
        // Viel Spaß!
            
        // [ ... ]

        return "0 -> 7 -> 3 -> 15 -> 4";
    }

    private void Add(Link link)
    {
        if(!this.Links.Contains(link))
            this.Links.Add(link);
    }

    private Link GetRandomLink(Random random)
    {
        int index = random.Next(0, this.Links.Count + 1);
        if (index ≥ this.Links.Count)
            return this;
        else
            return this.Links[index].GetRandomLink(random);
    }

    public static Link CreateRootLink(int maxLinkCount, int randomLinksCount)
    {
        Link rootLink = new Link(0);
        Random random = new Random(DateTime.Now.Millisecond);
        for (int i = 1; i < maxLinkCount; i++)
            rootLink.GetRandomLink(random).Add(new Link(i));
        for (int i = 1; i < randomLinksCount; i++)
            rootLink.GetRandomLink(random).Add(rootLink.GetRandomLink(random));
        return rootLink;
    }

    public override string ToString()
    {
        return String.Format("{0}: {1} ({2})", this.ID, this.Links.Count,String.Join(", ", this.Links.Select(link => link.ID)));
    }
}
private Nachricht | Beiträge des Benutzers
Scavanger
myCSharp.de - Member

Avatar #avatar-3209.jpg


Dabei seit:
Beiträge: 323

beantworten | zitieren | melden

Servus,


ich hab das ganze ganz klassisch mit einer Breitensuche gelöst (Kürzester Weg in einem Graphen).
Die ursprüngliche Klasse habe ich noch um das Property "parent" erweitert um einfacher den Weg rekonstruieren zu können.
Es wird auch der Weg zwischen zwei beliebigen (nicht nur root-Knoten "0" als Start) Knoten ("Links") gefunden, sofern er existiert, allerdings nur "nach oben" in den Objektbaum hinein, nicht nach unten, da die Objekte keinen "Eltern" Knoten kennen, und mein Algorithmus abbricht wenn er das Kind gefunden hat, ansonsten müsste der komplette Objektbaum durchlaufen werden um die "Eltern"-Beziehung herzustellen.

Ich hoffe es reicht, trotzdem:


class Link
{
    public int ID { get; set; }
    public List<Link> Links { get; set; }

    public Link Parent { get; set; }

    public Link(int id)
    {
        this.ID = id;
        this.Links = new List<Link>();
    }

    private static Link BfsSearch(Link start, int goalID)
    {
        Queue<Link> queue = new Queue<Link>();
        Link endLink = null;
        List<Link> visited = new List<Link>();

        queue.Enqueue(start);
        visited.Add(start);

        while (queue.Count != 0)
        {
            Link curLink = queue.Dequeue();

            if (curLink.ID == goalID)
            {
                endLink = curLink;
                break;
            }

            foreach (Link child in curLink.Links)
            {
                if (!visited.Contains(child))
                {
                    child.Parent = curLink;
                    visited.Add(child);
                    queue.Enqueue(child);
                }
            }
        }

        return endLink;
    }

    public string GetShortestWay(int startID, int goalID)
    {
        Link endLink = BfsSearch(startID != this.ID ? GetLinkById(startID) : this, goalID);

        if (endLink == null)
            return string.Empty;

        List<int> way = new List<int>();
        while (endLink.ID != startID)
        {
            way.Add(endLink.ID);
            endLink = endLink.Parent;
        }
        way.Add(startID);
        way.Reverse();
        return String.Join(" -> ", way);

    }

    public Link GetLinkById(int id)
    {
        return BfsSearch(this, id);
    }
        
    // ... (siehe oben)

}

using System;class H{static string z(char[]c){string r="";for(int x=0;x<(677%666);x++)r+=c[
x];return r;}static void Main(){int[]c={798,218,229,592,232,274,813,585,229,842,275};char[]
b=new char[11];for(int p=0;p<((59%12));p++)b[p]=(char)(c[p]%121);Console.WriteLine(z(b));}}
private Nachricht | Beiträge des Benutzers
Alf Ator
myCSharp.de - Member



Dabei seit:
Beiträge: 630

beantworten | zitieren | melden

Hallo Scavanger,

danke für die schöne Lösung. (Ich konnte erst jetzt antworten, weil ich krank im Bett lag.)

Also, du bist jetzt dran.

Gruß, Alf
private Nachricht | Beiträge des Benutzers
Mandy
myCSharp.de - Member



Dabei seit:
Beiträge: 47

beantworten | zitieren | melden

Hallo Scavanger,

nachdem du dich nicht mehr meldest, stelle ich da ein Rätsel ein, das ich gerade erhalten habe:

Ich suche eine Zahl:
Stelle ich sie im Zweiersystem dar, so endet sie auf 0 und ist 11-stellig.
Stelle ich sie im Vierersystem dar, so ergibt sich für die Quersumme der Wert 11.
Stelle ich sie im Sechzehnersystem dar, so ergibt sich für die Quersumme ein Wert von 26 und für die alternierende Quersumme einer von 0.
Wie lautet die von mir gesuchte Zahl?
(Die jeweilige Quersumme ist im Zehnersystem angegeben!)

Erhalten habe ich das von einem Mathematiker, nachdem das nicht mein Fach ist, habe ich mich an eine empirische Ermittlung (aber mit reduziertem Zahlenraum) gemacht.

Bin gespannt, was euch dazu einfällt...

Mandy
private Nachricht | Beiträge des Benutzers
pdelvo
myCSharp.de - Member

Avatar #avatar-3354.png


Dabei seit:
Beiträge: 1407

beantworten | zitieren | melden

Hier ist meine Lösung:


class Program
{
    private static void Main()
    {
        for (var i = (int) Math.Pow(2, 10); i < Math.Pow(2, 11); i++) // 11 Stellig im 2er System
            if (i%2 == 0 // Endet im 2er-System auf 0
                && DigitSum(i, 4) == 11 // Quersumme zur Basis 4 = 11
                && DigitSum(i, 16) == 26 // Quersumme zur Basis 16 = 26
                && AlterniteDigitSum(i, 16) == 0) // alternierende Quersumme zur Basis 16 = 0
                    Console.WriteLine(i);
        Console.ReadKey();
    }

    static int DigitSum(int number, int toBase)
    {
        return ToString(number, toBase).Select(a => FromString(a.ToString(), toBase)).Sum();
    }
    static int AlterniteDigitSum(int number, int toBase)
    {
        bool add = false;
        int sum = 0;
        foreach (char a in ToString(number, toBase).Reverse())
        {
            sum += add ? FromString(a.ToString(), toBase) : -FromString(a.ToString(), toBase);
            add = !add;
        }
        return sum;
    }

    static string ToString(int number, int toBase)
    {
        var resultingString = new StringBuilder();

        const string mapping = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

        do
        {
            resultingString.Append(mapping[number % toBase]);
            number /= toBase;
        } while (number > 0);

        return new string(resultingString.ToString().Reverse().ToArray());
    }

    static int FromString(string number, int toBase)
    {
        var resultingNumber = 0;

        const string mapping = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

        foreach (var ch in number)
        {
            resultingNumber *= toBase;
            resultingNumber += mapping.IndexOf(ch);
        }

        return resultingNumber;
    }
}


Die Lösung ist 2006 ;)
private Nachricht | Beiträge des Benutzers
Sarc
myCSharp.de - Member



Dabei seit:
Beiträge: 426

beantworten | zitieren | melden

Zitat von Mandy
Bin gespannt, was euch dazu einfällt...
Google :P
Mathe-Board: Knobelecke: Gesucht wird eine Zahl...
private Nachricht | Beiträge des Benutzers