Mir war gerade ziemlich langweilig, und deswegen hab ich mir einfach mal den Spaß erlaubt nen Algo zum Umrechnen von und nach römischen Zahlen zu implementieren.
Ich werd jetzt nichts mehr speziell dazu sagen, da ich den Quelltext ziemlich ausführlich kommentiert hab, so dass alle meine Gedankengänge ersichtlich werden sollten.
Es handelt sich im Groben einfach um eine Struktur, die römische zahlen speichert. Intern wird ein Integer verwendet und die eigentliche Funktionalität liegt nur in der TryParse- und der ToString-Funktion, den Rest hab ich auch nicht kommentiert (sind eh bloß ein paar Operatoren).
Vielleicht kann ja jemand irgend was damit anfangen oder wenigstens was daraus lernen.
Spoiler anzeigen
Ich werd jetzt nichts mehr speziell dazu sagen, da ich den Quelltext ziemlich ausführlich kommentiert hab, so dass alle meine Gedankengänge ersichtlich werden sollten.
Es handelt sich im Groben einfach um eine Struktur, die römische zahlen speichert. Intern wird ein Integer verwendet und die eigentliche Funktionalität liegt nur in der TryParse- und der ToString-Funktion, den Rest hab ich auch nicht kommentiert (sind eh bloß ein paar Operatoren).
Vielleicht kann ja jemand irgend was damit anfangen oder wenigstens was daraus lernen.
C#-Quellcode
- public struct RomanNumber
- {
- static readonly RomanDigit[] Digits;
- int value;
- private struct RomanDigit
- {
- public char Digit;
- public int Value;
- }
- static RomanNumber()
- {
- Digits = new RomanDigit[7];
- Digits[0] = new RomanDigit() { Digit = 'M', Value = 1000 };
- Digits[1] = new RomanDigit() { Digit = 'D', Value = 500 };
- Digits[2] = new RomanDigit() { Digit = 'C', Value = 100 };
- Digits[3] = new RomanDigit() { Digit = 'L', Value = 50 };
- Digits[4] = new RomanDigit() { Digit = 'X', Value = 10 };
- Digits[5] = new RomanDigit() { Digit = 'V', Value = 5 };
- Digits[6] = new RomanDigit() { Digit = 'I', Value = 1 };
- }
- public static bool TryParse(string s, out RomanNumber result)
- {
- result = default(RomanNumber);
- s = s.ToUpperInvariant();
- // --------------------------------------------- Regeln ---------------------------------------------
- //
- // 1. Es dürfen nur bekannte Zeichen (M, D, C, L, X, V, I) im String vorkommen.
- //
- // 2. Die Zeichen sind in ihrer Wertigkeit von links nach rechts absteigend sortiert.
- // -> Zeichen mit niedrigerer Wertigkeit dürfen nicht weiter links als Zeichen
- // mit höherer Wertigkeit stehen.
- //
- // 3. Einzige Ausnahme für 2. ist, wenn das Zeichen zum Subtrahieren verwendet wird (z.B. IV).
- //
- // 4. Es dürfen nur Zeichen, deren Wertigkeit zu Basis 10 liegt, zum Subtrahieren verwendet werden.
- // Ebenso darf ausschließlich die Ziffer mit der nächst kleineren Wertigkeit subtrahiert werden.
- //
- // 5. Es muss immer die geringst mögliche Anzahl Zeichen verwendet werden.
- // -> Zeichen mit niedrigerer Wertigkeit dürfen nur so oft verwendet werden,
- // bis man den selben Zahlenwertdurch ein Zeichen mit höherer Wertigkeit erreichen würde.
- //
- // 6. Ergänzung zu 5.
- // -> Die Subtraktionsregel ist hier mit zu beachten, also IV ist IIII vorzuziehen.
- //
- // 7. Es muss immer zuerst von der kleinstmöglichen Ziffer subtrahiert werden, also XIV
- // wird IXV vorgezogen.
- //
- // --------------------------------------------------------------------------------------------------
- int value = 0;
- int currentDigitIndex = -1;
- int currentDigitCount = 0;
- int lowestDigitIndex = -1;
- //Hier nehmen wir eine while-Schleife statt einer for-Schleife, da wir später unterschiedlich inkrementieren müssen.
- int index = 0;
- while (index < s.Length)
- {
- //Wir brauchen sowohl die Ziffer des aktuellen alsauch des nächsten Zeichens.
- int digitIndex = Array.FindIndex(Digits, x => x.Digit == s[index]);
- int nextDigitIndex = index < s.Length - 1 ? Array.FindIndex(Digits, x => x.Digit == s[index + 1]) : -1;
- if (digitIndex == -1)
- //Der Eingabestring ist ungültig, siehe 1.
- return false;
- //Wir müssen nun zwei Fälle unterscheiden, Subtraktion und Addition.
- if ((nextDigitIndex > -1) && (digitIndex > nextDigitIndex))
- {
- //Hier liegt Subtraktion vor, da die nächste Ziffer eine größere Wertigkeit als die aktuelle besitzt.
- if ((digitIndex % 2 == 1) || (nextDigitIndex < digitIndex - 2))
- //Der Eingabestring ist ungültig, siehe 4.
- return false;
- if (nextDigitIndex < lowestDigitIndex)
- //Der Eingabestring ist ungültig, siehe 2.
- //Wichtig ist hier, dass wir die nächste Ziffer zum Vergleichen verwenden, da die aktuelle
- //wegen 3. aus der Regel herausfällt.
- return false;
- //Wir müssen auch dafür sorgen, dass die aktuelle Ziffer als die mit der niedrigsten Wertigkeit
- //festgelegt wird, um weiterhin auf 2. prüfen zu können. Wir zählen allerdings gleich noch 1 dazu, da
- //eine solche Konstellation nur genau einmal pro Ziffer erlaubt ist, da ansonsten 5. nicht erfüllt ist.
- lowestDigitIndex = nextDigitIndex + 1;
- //Um 5. einzuhalten müssen wir auch noch das übernächste Zeichen prüfen, da sonst Konstrukte wie IVI möglich wären.
- int behindNextIndex = index < s.Length - 2 ? Array.FindIndex(Digits, x => x.Digit == s[index + 2]) : -1;
- if ((behindNextIndex > -1) && (behindNextIndex == digitIndex))
- return false; //Hier haben wir einen Verstoß gegen 5.
- //Die übernächste Ziffer brauchen wir auch, um auf 7. zu prüfen.
- if ((behindNextIndex > -1) && ((behindNextIndex | 1) + 1 == digitIndex))
- return false; //Hier haben wir einen Verstoß gegen 7.
- //Schließlich zählen wir die Wertigkeit der nächsten Ziffer abzüglich der Wertigkeit der aktuellen Ziffer hinzu.
- value += Digits[nextDigitIndex].Value - Digits[digitIndex].Value;
- index += 2; //Und wir gehen gleich zwei Zeichen weiter im String, da wir gerade zwei Zeichen bearbeitet haben.
- }
- else
- {
- //Hier haben wir die Addition, bzw. keine speziellen Umstände.
- if (digitIndex < lowestDigitIndex)
- //Hier ist der Eingabestring nach 2. wieder ungültig.
- return false;
- //Diesmal lassen wir das + 1 weg, da nach einer Addition nicht unbedingt die Ziffernwertigkeit sinken muss,
- //anders als eben bei der Subtraktion.
- lowestDigitIndex = digitIndex;
- //Jetzt müssen wir noch 5. berücksichtigen.
- if (currentDigitIndex == digitIndex)
- {
- //Wir brauchen einen Counter, damit wir berechnen können, ob wir schon zu viele gleiche Ziffern hinterheinander hatten.
- currentDigitCount++;
- //Hier müssen wir jetzt wegen 6. zwei Fälle unterscheiden.
- //Wenn die aktuelle Ziffer eine Hilfsbasis ist, dann brauchen wir nichts speziell zu berücksichteigen,
- //andernfalls jedoch müssen wir auf die Subtraktionsregel achten.
- if (digitIndex % 2 == 1)
- {
- //Wenn die Wertigkeit aller Ziffern größer ist, als die der nächst höheren Ziffer, liegt ein Verstoß gegen 5. vor.
- if (digitIndex > 0 && Digits[digitIndex].Value * currentDigitCount >= Digits[digitIndex - 1].Value)
- return false;
- }
- else
- {
- //Hier müssen wir jezt auch noch einmal den Wert der aktuellen Ziffer abziehen, wegen der Subtraktionsregel.
- if (digitIndex > 0 && Digits[digitIndex].Value * currentDigitCount >= Digits[digitIndex - 1].Value - Digits[digitIndex].Value)
- return false;
- }
- }
- else
- {
- //Hier sind wir aus dem kritischen Bereich raus, da wir mindestens eine Wertigkeitsstufe runtergegangen sind.
- //Wir können also einfach alles wieder zurück auf Start stellen.
- currentDigitIndex = digitIndex;
- currentDigitCount = 1;
- }
- //Beim Addieren zählen wir jetzt einfach die Wertigkeit der aktuellen Ziffer dazu.
- value += Digits[digitIndex].Value;
- index++; //Hier inkrementieren wir auch nur um eins, denn das nächste Zeichen haben wir noch nicht bearbeitet.
- }
- }
- result = value;
- return true;
- }
- public static RomanNumber Parse(string s)
- {
- RomanNumber result;
- if (!TryParse(s, out result))
- throw new FormatException();
- return result;
- }
- public override string ToString()
- {
- //In der umgekehrten Richtung haben wirs wesentlich einfacher, da wir selbstverständlich
- //keinerlei mögliche Fehler in der Eingabe überprüfen müssen, wir generierens einfach richtig. ;)
- StringBuilder sb = new StringBuilder();
- int value = this.value; //Wir müssen uns den Zahlenwert zwischenspeichern, da wir da dran rumpfuschen werden.
- //Das Prinzip ist eigentlich recht einfach. Wir gehen alle vorhandenen Ziffern durch, wobei wir
- //die mit der größten Wertigkeit zuerst durchlaufen.
- for (int i = 0; i < Digits.Length; i++)
- {
- //Jetzt hängen wir diese Ziffer so lange an die Ausgabe an, bis sie nicht mehr in den Restbetrag reinpasst.
- int count = Math.DivRem(value, Digits[i].Value, out value);
- sb.Append(Digits[i].Digit, count);
- //Wäre da nicht die Subtraktionsregel, wären wir jetzt schon fertig und könnten mit der nächsten Ziffer weitermachen,
- //aber diese Regel gibts nunmal.
- if (i < Digits.Length - 1)
- {
- //Wir bestimmen zuerst die Subtraktionsziffer...
- int subtractionIndex = (i | 1) + 1;
- //...und prüfen dann, ob der Wert mit Subtraktion noch reinpasst.
- if (value >= Digits[i].Value - Digits[subtractionIndex].Value)
- {
- sb.Append(Digits[subtractionIndex].Digit);
- sb.Append(Digits[i].Digit);
- value -= Digits[i].Value - Digits[subtractionIndex].Value;
- }
- }
- }
- return sb.ToString();
- }
- public static implicit operator RomanNumber(int value)
- {
- if (value < 0)
- throw new OverflowException("Negative Werte sind nicht zulässig");
- return new RomanNumber() {value = value};
- }
- public static implicit operator int(RomanNumber value)
- {
- return value.value;
- }
- public static RomanNumber operator +(RomanNumber left, RomanNumber right)
- {
- return left.value + right.value;
- }
- public static RomanNumber operator -(RomanNumber left, RomanNumber right)
- {
- return left.value - right.value;
- }
- public static RomanNumber operator *(RomanNumber left, RomanNumber right)
- {
- return left.value * right.value;
- }
- public static RomanNumber operator /(RomanNumber left, RomanNumber right)
- {
- return left.value / right.value;
- }
- public static bool operator ==(RomanNumber left, RomanNumber right)
- {
- return left.value == right.value;
- }
- public static bool operator !=(RomanNumber left, RomanNumber right)
- {
- return left.value != right.value;
- }
- public static bool operator <(RomanNumber left, RomanNumber right)
- {
- return left.value < right.value;
- }
- public static bool operator >(RomanNumber left, RomanNumber right)
- {
- return left.value > right.value;
- }
- public static bool operator <=(RomanNumber left, RomanNumber right)
- {
- return left.value <= right.value;
- }
- public static bool operator >=(RomanNumber left, RomanNumber right)
- {
- return left.value >= right.value;
- }
- }
Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Artentus“ ()