
Kevin Erath
Geschäftsführer
Veröffentlicht am
3. Juli 2021

Ich möchte heute noch ein weiteres Beispiel für das Thema Verzweigung aufführen. Und zwar möchte ich dazu, meine Lösung für die Kata römische Zahlen weiter ausbauen. Die Erweiterung (z.B. Beschreibung auf Codewars.com) besteht darin, syntaktische und semantische Fehler zu finden.
Hier das erweiterte Flow-Design, dass ich dieses Mal mit Diagrams.net (ehemals Draw.io) erstellt habe. Der Hand-gekrizellte Look gefällt mir dabei sehr gut, da er zeigt, dass man das Diagramm auch schnell auf Papier erstellen könnte:
Die Integration Convert
und deren Operationen können quasi unverändert weiterverwendet werden. Die Validierung wird in diesem Falle durch die beiden Operationen CheckSyntax
und CheckSemantic
vorgeschaltet. Alles zusammen wird durch die neue Integration CheckAndConvert
integriert.
Schaut man sich das Diagramm genauer an, stellt man fest, dass es neben Abzweigungen (Splits) auch Zusammenführungen (Joins) gibt. Die eine ist mit den beiden onError
-Datenflüsse der beiden Operationen zur Validierung schnell gefunden. Hier werden die beiden Datenflüsse zu einem kombiniert, da in CheckAndConvert
nur noch interessant ist, ob ein Fehler aufgetreten ist, aber nicht mehr ob der Fehler in der Syntax oder der Semantik liegt. Das Zusammenführen wird durch einen Querbalken symbolisiert. Der andere ist für den unerfahrenen Flow-Design-Modellierer vielleicht schwerer zu sehen. Es handelt sich dabei um die Datenflüsse im validen Fall. Hier wird durch die Kurznotation (aa) | (bb)
eine Abzweigung des Datenflusses angeschrieben. Diese Notation besagt, dass aus der ersten Funktionseinheit der Datenfluss (aa)
herausfließt und in die darauffolgende Funktionseinheit der Datenfluss (bb)
hineinfließt. Die Daten im Datenfluss (bb)
kommen dann in der Regel von anderer Stelle (z.B. von einer zuvor aufgerufenen Funktionseinheit). In unserem konkreten Fall kommen die Daten, nämlich romanNumberUpper
, aus der Operation toUpper
.
Interessant ist sicherlich die Umsetzung der Integration CheckAndConvert
. Diese erfolgt wieder mit Action-Delegaten und Lambda-Ausdrücken:
public static void CheckAndConvert(string romanNumber, Action<string, int> onNumber, Action<string, string> onError)
{
var romanNumberUpper = ToUpper(romanNumber);
CheckSyntax(romanNumberUpper,
() => CheckSemantic(romanNumberUpper,
() => onNumber(romanNumber, Convert(romanNumberUpper)),
x => onError(romanNumber, x)),
x => onError(romanNumber, x));
}
Hier die komplette Umsetzung:
using System;
using System.Collections.Generic;
using System.Linq;
internal static class FromRomanNumerals
{
private static void PrintNumber(string roman, int number) => Console.WriteLine("{0} -> {1}", roman, number);
private static void PrintError(string roman, string message) => Console.WriteLine("{0} -> {1}", roman, message);
public static void CheckAndConvert(string romanNumber, Action<string, int> onNumber, Action<string, string> onError)
{
var romanNumberUpper = ToUpper(romanNumber);
CheckSyntax(romanNumberUpper,
() => CheckSemantic(romanNumberUpper,
() => onNumber(romanNumber, Convert(romanNumberUpper)),
x => onError(romanNumber, x)),
x => onError(romanNumber, x));
}
private static string ToUpper(string romanNumber) => romanNumber.ToUpper();
private static void CheckSyntax(string romanNumber, Action onSyntaxValid, Action<string> onSyntaxInvalid)
{
var invalidChars = romanNumber.Where(x => !"IVXLCDM".Contains(x));
var asString = string.Join(",", invalidChars.Select(x => string.Format("'{0}'", x)));
if (string.IsNullOrEmpty(asString))
onSyntaxValid();
else
onSyntaxInvalid(string.Format("Syntax error. Illegal characters detected: {0}", asString));
}
private static void CheckSemantic(string romanNumber, Action onSemanticValid, Action<string> onSemanticInvalid)
{
var charFollows = new Dictionary<char, List<char>> {
{'I', new List<char> {'V', 'X', 'I'}},
{'V', new List<char> {'I'}},
{'X', new List<char> {'L', 'C', 'X', 'V', 'I'}},
{'L', new List<char> {'X', 'V', 'I'}},
{'C', new List<char> {'D', 'M', 'C', 'L', 'X', 'V', 'I'}},
{'D', new List<char> {'C', 'L', 'X', 'V', 'I'}},
{'M', new List<char> {'M', 'D', 'C', 'L', 'X', 'V', 'I'}}};
for (var i = 0; i < romanNumber.Length - 1; i++)
{
var chr = romanNumber[i];
var nextChr = romanNumber[i + 1];
if (!charFollows[chr].Contains(nextChr))
{
onSemanticInvalid(string.Format("Semantic error. Character {0} follows {1}", chr, nextChr));
return;
}
}
onSemanticValid();
}
public static int Convert(string romanNumber)
{
var romanNumerals = SplitRomanNumerals(romanNumber);
var decimals = ConvertToDecimal(romanNumerals);
var negatedDecimals = NegateWhenLarger(decimals);
return Sum(negatedDecimals);
}
private static char[] SplitRomanNumerals(string romanNumber) => return romanNumber.ToCharArray();
private static int[] ConvertToDecimal(IEnumerable<char> romanNumerals)
{
var mapping = new Dictionary<char, int> {{'I', 1}, {'V', 5}, {'X', 10}, {'L', 50}, {'C', 100}, {'D', 500}, {'M', 1000}};
return romanNumerals.Select(x => mapping[x]).ToArray();
}
private static int[] NegateWhenLarger(int[] decimals)
{
var result = new int[decimals.Length];
for (var i = 0; i < decimals.Length; i++)
{
if (i < decimals.Length - 1 && decimals[i] < decimals[i + 1])
result[i] = -decimals[i];
else
result[i] = decimals[i];
}
return result;
}
private static int Sum(int[] decimals) => return decimals.Sum();
}
Auch wenn die semantische Prüfung noch nicht vollständig umgesetzt ist (Fehlerhafte Beispiele sind IIIII oder IIX) sieht man schön wie man eine Verkettung dank Continuations und Lambda Ausdrücken einfach umsetzen kann.

Hier schreibt
Kevin Erath
Als Mitbegründer und Geschäftsführer von pep.digital verbringe ich zwar nicht mehr jeden Tag ausschließlich damit, coole Lösungen für unsere Kunden zu realisieren. Trotzdem finde ich immer wieder die Zeit, mich auch mal tiefer in die Technik einzutauchen und meine Erkenntnisse hier im Blog zu teilen. Und ehrlich gesagt, das Unternehmen und unsere tollen Mitarbeiter:innen weiterzuentwickeln, macht mir mindestens genauso viel Spaß.
Weitere interessante Artikel
Wir möchten hier nicht nur über Neuigkeiten aus dem Unternehmen berichten, sondern auch das Wissen und die Erfahrung unserer Experten teilen.

Koordinaten – Alternative Datenflüsse
Die Trennung zwischen Integration und Operation ist auch bei Verzweigungen möglich. Dies soll anhand eines einfachen algorithmischen Problems mit abzweigenden Datenflüssen gezeigt werden.

Kevin Erath
Geschäftsführer

1 Jahr pep.digital – ein Beitrag von Alex
Alex ist seit einem Jahr bei pep.digital tätig. Als Software Entwickler sorgt er für stabile Lösungen in unseren Projekten. Er arbeitet in einem Kundenprojekt an der Entwicklung eines großen Lieferantenportals mit. Im Beitrag gibt er weitere Einblicke in sein Leben als Software-Entwickler bei pep.digital.
pep.digital