[C#] [SourceCode] Römische Zahlen umrechnen

    • C#

    Es gibt 36 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

      Ich wollte es nicht gleich übertreiben, es ging mir eigentlich ja nur um den Code im TryParse und ToString. Und ja, eigentlich hätte man dafür keine Struktur anlegen müssen, allerdings wäre das nicht so schön geworden. Das ToString hätte man einfach als eine "ToRomanNumber"-Extension implementieren können, aber es gibt ja leider keine statischen Extensions, also hätte ich kein "FromRomanNumber" implementieren können. Und dann hätte ich sone blöde statische Helferklasse rumfliegen gehabt, die eher Fehl am Platzt wirkt. Also Microsoft: bitte schenkt uns für C# 6 statische Extensions.

      @ErfinderDesRades:
      Mit der Subtraktionsregel hast du Recht. Das passe ich dann gleich mal oben an.

      Artentus schrieb:

      Also Microsoft: bitte schenkt uns für C# 6 statische Extensions.
      was sind denn statische Extensions?
      Extensions sind doch immer statisch.

      Also wenn mans drauf anlegt, kann man auch für int und string Extensions schreiben.

      Aber ich halte das ganze Thema für zu exotisch, als dass man sich da groß verkünsteln müsste mit allerlein Operatoren, Schnittstellen und Convertern.

      ErfinderDesRades schrieb:

      was sind denn statische Extensions?
      Damit meine ich, dass man z.B. int.FromRomanNumber implementieren kann. Also dass man nicht RomanNumbersHelper.IntFromRomanNumber hat, sondern die Funktion eben für den Typen int definiert wird.
      Ich würde die Abgeschlossenheit des Frameworks an sich befürworten. Zudem denke ich, dass man darauf achten sollte, die vollständige Funktionalität zu gewährleisten, sofern sie gewünscht wird - ist halt projektabhängig. Wenn man quasi Frameworkkomponenten schreibt - wie hier im Forum - sollte man eigentlich auch diese Kompatibilität mit bestehenden Modellierungen gewährleisten, oder?
      Wird mir jetzt zu OT für dieses Unterforum.
      Statische Extensions wären nicht instanzabhängig, sondern eben Erweiterungen der statischen Funktionalitäten.

      Gruß
      ~blaze~

      Artentus schrieb:

      ErfinderDesRades schrieb:

      was sind denn statische Extensions?
      Damit meine ich, dass man z.B. int.FromRomanNumber implementieren kann. Also dass man nicht RomanNumbersHelper.IntFromRomanNumber hat, sondern die Funktion eben für den Typen int definiert wird.

      k.A., was du meinst. Was soll int.FromRomanNumber machen? Eine RomanNumber nach int konvertieren? mach doch einfach RomanNumber.Value public.
      Es ging doch darum, dass man sich den ganzen Typen RomanNumber sparen kann und stattdessen nur zwei Funktionen erstellen soll, die von int nach RomanNumber (als String) und umgekehrt konvertieren. Das Problem ist nur, dass die Richtung RomanNumber-String -> int nur mit einer statischen Funktion bewerkstelligt werden kann (so wie int.Parse), und da funktionieren Extensions nicht.
      Ich würde es so machen, was aber leider (gar)nicht geht:

      VB.NET-Quellcode

      1. int i = int.Parse("VI", NumberStyles.Roman);

      Dazu müsste man das Enum abändern und die Methode halb verändern. Architektonisch wäre es aber schöner. :P
      Von meinem iPhone gesendet
      ich hab mich auch nochmal total dran verkünstelt

      VB.NET-Quellcode

      1. using System;
      2. using System.Text;
      3. using System.Linq;
      4. using System.Diagnostics;
      5. [DebuggerDisplay("{Value}")]
      6. public struct RomanNumber {
      7. private static readonly RomanDigit[] _Digits;
      8. public int Value;
      9. [DebuggerDisplay("{Char}")]
      10. private class RomanDigit {
      11. public char Char;
      12. public int Value;
      13. }
      14. static RomanNumber() {
      15. _Digits = new RomanDigit[9];// array bewust um 2 zu groß gewählt, um im Tostring-Algo auf Array-Grenz-Überprüfung verzichten zu können
      16. _Digits[0] = new RomanDigit() { Char = 'M', Value = 1000 };
      17. _Digits[1] = new RomanDigit() { Char = 'D', Value = 500 };
      18. _Digits[2] = new RomanDigit() { Char = 'C', Value = 100 };
      19. _Digits[3] = new RomanDigit() { Char = 'L', Value = 50 };
      20. _Digits[4] = new RomanDigit() { Char = 'X', Value = 10 };
      21. _Digits[5] = new RomanDigit() { Char = 'V', Value = 5 };
      22. _Digits[6] = new RomanDigit() { Char = 'I', Value = 1 };
      23. _Digits[7] = new RomanDigit() { Value = int.MaxValue }; //Dummi-Value für TryParse
      24. _Digits[8] = new RomanDigit() { Value = 0 }; //Dummi-Value für ToString
      25. }
      26. public override string ToString() {
      27. StringBuilder sb = new StringBuilder();
      28. int rest = this.Value;
      29. var ubound = _Digits.Length - 2;
      30. for(int i = 0; i < ubound; i++) {
      31. var additor = _Digits[i];
      32. var additorCount = Math.DivRem(rest, additor.Value, out rest); //ganzzahl-teilung mit Rest
      33. sb.Append(new string(additor.Char, additorCount)); //den additor.Digit so oft anhängen, wie er in rest hineinpasste.
      34. //subtrakions-regel: falls die Subtraktion des nächst-kleineren 10er-vielfachen doch noch in rest hineinpasst, diesen subtraktor dem additor voranstellen
      35. var subtractor = _Digits[(i | 1) + 1];
      36. if(rest >= additor.Value - subtractor.Value) {
      37. sb.Append(subtractor.Char).Append(additor.Char);
      38. rest -= additor.Value - subtractor.Value;
      39. }
      40. }
      41. return sb.ToString();
      42. }
      43. public static bool TryParse(string s, out RomanNumber result) {
      44. /*parsen gelingt, wenn alle Zeichen gültig sind und abfallend sortiert
      45. Sortier-Ausnahme Subtraktionsregel erlaubt, dass vor einem Zeichen genau das nächstkleinere Zeichen der 10er-Hauptreihe stehen darf - kein anneres, und keine weiteren Subtraktoren.
      46. Die Schleife zieht also 3 Werte durch: den aktuellen - currDigit, den vorherigen - prevDigit und den vorvorherigen - prevPrev */
      47. result = default(RomanNumber);
      48. var res = 0;
      49. var prevDigit = _Digits[7];
      50. var prevPrev = prevDigit;
      51. for(var i = -1; ++i < s.Length; ) {
      52. var indx = Array.FindIndex(_Digits, dg => dg.Char == s[i]);
      53. if(indx < 0) return false; //abbruch wg. unbekanntem Zeichen
      54. var currDigit = _Digits[indx];
      55. res += currDigit.Value;
      56. if(currDigit.Value > prevDigit.Value) { //prevDigit ist kleiner als digit
      57. if(_Digits[(indx | 1) + 1].Char != prevDigit.Char) return false; //prevDigit.Char ist aber der falsche
      58. if(currDigit.Value > prevPrev.Value) return false; //prevPrev zu klein
      59. res -= prevDigit.Value << 1;
      60. }
      61. prevPrev = prevDigit;
      62. prevDigit = currDigit;
      63. }
      64. result = new RomanNumber() { Value = res };
      65. return true;
      66. }
      beachte das DebuggerDisplay-Attribut - echt praktisch zum debuggen!

      noch kl. optimierung:

      VB.NET-Quellcode

      1. //var indx = Array.FindIndex(_Digits, dg => dg.Char == s[i]);
      2. var c = s[i];
      3. var indx = Array.FindIndex(_Digits, dg => dg.Char == c);
      c wird für jeden Digit abgerufen, da ist s[i] eine Performance-Bremse. s.ToCharArray() wäre sicher eine weitere Leistungsverbesserung (ja, kommt mal wieder nicht drauf an, aber ist nur 1 Zeile und kost nix)

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

      mist!! :cursing:

      Dann halt auf blöd:

      C#-Quellcode

      1. public override string ToString() { return Int2String(this.Value); }
      2. private static string Int2String(int rest) {
      3. StringBuilder sb = new StringBuilder();
      4. var ubound = _Digits.Length - 2;
      5. for(int i = 0; i < ubound; i++) {
      6. var additor = _Digits[i];
      7. var additorCount = Math.DivRem(rest, additor.Value, out rest); //ganzzahl-teilung mit Rest
      8. sb.Append(new string(additor.Char, additorCount)); //den additor.Digit so oft anhängen, wie er in rest hineinpasste.
      9. //subtraktions-regel: falls die Subtraktion des nächst-kleineren 10er-vielfachen doch noch in rest hineinpasst, diesen subtraktor dem additor voranstellen
      10. var subtractor = _Digits[(i | 1) + 1];
      11. if(rest >= additor.Value - subtractor.Value) {
      12. sb.Append(subtractor.Char).Append(additor.Char);
      13. rest -= additor.Value - subtractor.Value;
      14. }
      15. }
      16. return sb.ToString();}
      17. public static bool TryParse(string s, out RomanNumber result) {
      18. /*In absteigender Reihe sortierte Ziffern werden aufaddiert. Hat eine Ziffer einen kleineren Vorgänger, so wird dieser subtrahiert (Subtraktions-Regel)
      19. * Die Subtraktion sogar doppelt, denn eine Runde vorher wurde er ja aufaddiert.
      20. Die Schleife schleppt also immer den Vorgänger mit durch: prevDigit. */
      21. result = default(RomanNumber);
      22. var res = 0;
      23. var prevDigit = _Digits[7];
      24. foreach(char c in s) {
      25. var indx = Array.FindIndex(_Digits, dg => dg.Char == c);
      26. if(indx < 0) return false; //abbruch wg. unbekanntem Zeichen
      27. var currDigit = _Digits[indx];
      28. res += currDigit.Value;
      29. if(currDigit.Value > prevDigit.Value) res -= prevDigit.Value << 1;
      30. prevDigit = currDigit;
      31. }
      32. result = new RomanNumber() { Value = res };
      33. return Int2String(res) == s; // Rück-Konversion des Ergebnisses und Abgleich mit der Eingabe überprüft optimal sparsamen Einsatz der Ziffern
      34. }

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