static async Task Main(string[] args)
        {
            Console.OutputEncoding = Encoding.Unicode;


            // Laden der Standorte - entweder kann die csv-Datei mit den msg-Standorten geladen werden, oder man sucht nach eigenen Orten
            Standort[] standorte = null;
            while (standorte == null)
            {
                Console.Clear();
                MyConsole.WriteLine("Hallo :),");
                MyConsole.Write("dies ist die Einsendung von *red*Leo Decking** zur Coding-Challenge 2020 von *|white**black*get in *green*{*black*IT*green*}** und *|white**dRed*.*dGray*msg**.");
                MyConsole.WriteLine("\nMach diese Konsole am besten in den *red*Vollbildmodus** und stell die *red*Schriftgröße** etwas kleiner.\n");

                MyConsole.WriteLine("Wie willst du die Standorte, für die der beste Rundweg gefunden werden soll, laden?");
                MyConsole.WriteLine("*green*1** - csv-Datei mit *|white**dRed*.*dGray*msg**-Standorten");
                MyConsole.WriteLine("*green*2** - Suche nach eigenen Adressen/Städten");
                if (oldStandorte != null)
                {
                    MyConsole.WriteLine("*green*3** - Suche nach eigenen Adressen/Städten - *yellow* vorherige Standorte übernehmen**");
                }
                MyConsole.Write(oldStandorte == null ? "(*green*1** oder *green*2**): > " : "(*green*1**, *green*2** oder *green*3**): > ");

                string input = Console.ReadLine().Trim();
                if (input == "1")
                {
                    Console.Clear();
                    MyConsole.WriteLine("*green*1** - csv-Datei mit *|white**dRed*.*dGray*msg**-Standorten laden\n");

                    MyConsole.WriteLine("Bitte füge den Inhalt der .csv-Datei *dGray*(in einem Texteditor öffnen und kopieren)** hier ein:");
                    MyConsole.Write("Die Überschriften müssen übereinstimmen: *dMagenta*Nummer,msg Standort,Straße,Hausnummer,PLZ,Ort,Breitengrad,Längengrad\n*cyan*");

                    while (Console.KeyAvailable)
                    {
                        Console.ReadKey();
                    }

                    List <Standort> standorteList = new List <Standort>();
                    // Zeilen mit falschen Eingaben werden solange übersprungen bis die richtigen Spaltenüberschriften eingefügt werden
                    while (Console.ReadLine() != "Nummer,msg Standort,Straße,Hausnummer,PLZ,Ort,Breitengrad,Längengrad")
                    {
                        Console.CursorTop--;
                    }

                    string line;
                    while (!string.IsNullOrEmpty(line = Console.ReadLine()))
                    {
                        standorteList.Add(new Standort(line));
                    }
                    standorte = standorteList.ToArray();
                }
                else if (input == "2" || (oldStandorte != null && input == "3"))
                {
                    List <Standort> standorteList = new List <Standort>();
                    if (input == "3")
                    {
                        standorteList.AddRange(oldStandorte);
                    }
                    while (standorte == null)
                    {
                        Console.Clear();
                        MyConsole.WriteLine(input == "2" ? "*green*2** - Suche nach eigenen Adressen/Städten" : "*green*3** - Suche nach eigenen Adressen/Städten - *yellow* vorherige Standorte übernehmen**");

                        MyConsole.WriteLine("\n\nDie Standorten werden mit Hilfe der *blue*Openrouteservice API** (*cyan*https://openrouteservice.org/**) gesucht.");
                        MyConsole.WriteLine("Es wird eine Internetverbindung benötigt.");
                        MyConsole.WriteLine("Weitere Informationen zu den Begrifflichkeiten findest du hier: *blue*https://github.com/pelias/documentation/blob/master/search.md#filter-by-data-type**");

                        if (standorteList.Count > 0)
                        {
                            MyConsole.WriteLine("\n**Es wurden bisher folgende *green*" + standorteList.Count + "** Orte geladen:\n");
                            for (int i = 0; i < standorteList.Count; i++)
                            {
                                Standort s = standorteList[i];
                                MyConsole.WriteLine("*green*" + (new string(' ', (standorteList.Count + " ").Length - ((i + 1) + "").Length) + (i + 1)) + "**: " + (i % 2 == 0 ? "*magenta*" : "*dYellow*") + s.name + "*dGray*: " + s.text + " *dRed*" + s.lon + " " + s.lat);
                            }
                            ;
                        }
                        MyConsole.WriteLine("**");

                        MyConsole.Write("Suchbegriff: (*red*a** zum Abbrechen, *dYellow*e** zum Entfernen, *green*w** für weiter) > ");
                        string consoleInput = Console.ReadLine().Trim().ToLower();
                        Console.CursorTop--;
                        MyConsole.Write(new string(' ', Console.WindowWidth) + "\r");
                        Console.CursorTop--;
                        if (consoleInput == "a") // Abbrechen
                        {
                            bool abbrechen;
                            while (true)
                            {
                                MyConsole.Write("Willst du die Suche wirklich abbrechen / von vorne beginnen? (*red*ja** oder *yellow*nein**): > ");
                                string read = Console.ReadLine().Trim().ToLower();
                                if (read == "ja")
                                {
                                    abbrechen = true;
                                    break;
                                }
                                else if (read == "nein")
                                {
                                    abbrechen = false;
                                    break;
                                }
                                else
                                {
                                    Console.CursorTop--;
                                    MyConsole.Write(new string(' ', Console.WindowWidth) + "\r");
                                    Console.CursorTop--;
                                }
                            }
                            if (abbrechen)
                            {
                                break;
                            }
                        }
                        else if (consoleInput == "e") // Entfernen eines Standorts
                        {
                            if (standorteList.Count == 0)
                            {
                                MyConsole.Write("Es wurden noch *red*keine** Standorte hinzugefügt, welche entfernt werden können. *dGray*[**OK*dGray*]**");
                                Console.ReadLine();
                            }
                            else
                            {
                                while (true)
                                {
                                    MyConsole.Write("Welchen Standort willst du entfernen? (*red*0** zum Abbrechen) (*red*0**-*green*" + standorteList.Count + "**)> ");
                                    int index;
                                    if (int.TryParse(Console.ReadLine().Trim(), out index) && index >= 0 && index <= standorteList.Count)
                                    {
                                        if (index > 0)
                                        {
                                            standorteList.RemoveAt(index - 1);
                                        }
                                        break;
                                    }
                                    else
                                    {
                                        Console.CursorTop--;
                                        MyConsole.Write(new string(' ', Console.WindowWidth) + "\r");
                                        Console.CursorTop--;
                                    }
                                }
                            }
                        }
                        else if (consoleInput == "w") // Weiter zur Routen-Berechnung
                        {
                            if (standorteList.Count < 3)
                            {
                                MyConsole.Write("Es werden mindestens *red*3** Standorte benötigt. Füge weitere hinzu. *dGray*[**OK*dGray*]**");
                                Console.ReadLine();
                            }
                            else
                            {
                                while (true)
                                {
                                    MyConsole.Write("Willst du mit *green*" + standorteList.Count + "** Standorten fortfahren? (*green*ja** oder *red*nein**): > ");
                                    string read = Console.ReadLine().Trim().ToLower();
                                    if (read == "ja")
                                    {
                                        standorte = standorteList.ToArray();
                                        break;
                                    }
                                    else if (read == "nein")
                                    {
                                        break;
                                    }
                                    else
                                    {
                                        Console.CursorTop--;
                                        MyConsole.Write(new string(' ', Console.WindowWidth) + "\r");
                                        Console.CursorTop--;
                                    }
                                }
                            }
                        }
                        else // Sucheingabe
                        {
                            Standort[] newStandorte = await OpenRouteService.SearchLocations(consoleInput);

                            if (newStandorte.Length == 0)
                            {
                                MyConsole.Write("Es wurden leider *red*keine** passenden Orte gefunden, versuche es erneut mit einem anderen Suchbegriff. *dGray*[**OK*dGray*]**");
                                Console.ReadLine();
                            }
                            else if (newStandorte.Length == 1)
                            {
                                MyConsole.WriteLine("Willst du folgenden Ort hinzufügen:");
                                Standort s = newStandorte[0];
                                MyConsole.WriteLine("*blue*" + s.name + "*dGray*: " + s.text + " *dRed*" + s.lon + " " + s.lat);
                                MyConsole.WriteLine("**");
                                while (true)
                                {
                                    MyConsole.Write("(*green*ja** oder *red*nein**): > ");
                                    string read = Console.ReadLine().Trim().ToLower();
                                    if (read == "ja")
                                    {
                                        standorteList.Add(s);
                                        break;
                                    }
                                    else if (read == "nein")
                                    {
                                        break;
                                    }
                                    else
                                    {
                                        Console.CursorTop--;
                                        MyConsole.Write(new string(' ', Console.WindowWidth) + "\r");
                                        Console.CursorTop--;
                                    }
                                }
                            }
                            else
                            {
                                MyConsole.WriteLine("Es wurden folgende *yellow*" + newStandorte.Length + "** Orte gefunden, welchen willst du hinzufügen?\n");
                                MyConsole.WriteLine("*yellow*" + new string(' ', (newStandorte.Length + " ").Length - 1) + "0**: *red*Keinen** dieser Orte hinzufügen");
                                for (int i = 0; i < newStandorte.Length; i++)
                                {
                                    Standort s = newStandorte[i];
                                    MyConsole.WriteLine("*yellow*" + new string(' ', (newStandorte.Length + " ").Length - ((i + 1) + "").Length) + (i + 1) + "**: " + (i % 2 == 0 ? "*blue*" : "*dGreen*") + s.name + "*dGray*: " + s.text + " *dRed*" + s.lon + " " + s.lat);
                                }
                                ;
                                MyConsole.WriteLine("**");
                                while (true)
                                {
                                    MyConsole.Write("Eingabe: > ");
                                    int index;
                                    if (int.TryParse(Console.ReadLine().Trim(), out index) && index >= 0 && index <= newStandorte.Length)
                                    {
                                        if (index > 0)
                                        {
                                            standorteList.Add(newStandorte[index - 1]);
                                        }
                                        break;
                                    }
                                    else
                                    {
                                        Console.CursorTop--;
                                        MyConsole.Write(new string(' ', Console.WindowWidth) + "\r");
                                        Console.CursorTop--;
                                    }
                                }
                            }
                        }
                    }
                }
            }


            // Auswahl des Fortbewegungsprofils
            MyConsole.WriteLine("**Für welche Fortbewegungsprofil soll der Weg berechnet werden?:\n");
            for (int i = 0; i < Profile.Profiles.Length; i++)
            {
                MyConsole.WriteLine("*yellow*" + (i + 1) + ": " + Profile.Profiles[i].Color + Profile.Profiles[i].Name);
            }
            ;
            MyConsole.WriteLine();
            Profile profile = new Profile();

            while (true)
            {
                MyConsole.Write("**(*yellow*1**-*yellow*" + (Profile.Profiles.Length) + "**): > ");
                int input;
                if (int.TryParse(Console.ReadLine(), out input) && input >= 1 && input <= Profile.Profiles.Length)
                {
                    profile = Profile.Profiles[input - 1];
                    break;
                }
                Console.CursorTop--;
                MyConsole.Write("\r" + new string(' ', Console.WindowWidth) + "\r");
                Console.CursorTop--;
            }

            Console.Clear();

            // Anzeige der geladenen Standorte
            MyConsole.WriteLine("**Es wurden folgende *green*" + standorte.Length + "** Standorte geladen:\n");
            for (int i = 0; i < standorte.Length; i++)
            {
                Standort s = standorte[i];
                MyConsole.WriteLine((i % 2 == 0 ? "*magenta*" : "*dYellow*") + s.name + "*dGray*: " + s.text + " *dRed*" + s.lon + " " + s.lat);
            }
            ;


            if (profile.Id == "linear")
            {
                MyConsole.WriteLine("\n**Die Entfernungen zwischen den einzelnen Standorten für *dGray*'" + profile.Color + profile.Name + "*dGray*'** werden jetzt berechnet. [OK]");
            }
            else
            {
                MyConsole.WriteLine("\n**Die Entfernungen zwischen den einzelnen Standorten für *dGray*'" + profile.Color + profile.Name + "*dGray*'** werden jetzt mit Hilfe der *blue*Openrouteservice API** geladen.");
                MyConsole.WriteLine("Da die Wege möglichst effizient sein sollen, wird nicht der *red*kürzeste**, sondern der *green*schnellste** Weg genommen.");
                MyConsole.Write("Hierfür wird eine Internetverbindung benötigt. [OK]");
            }
            while (Console.KeyAvailable)
            {
                Console.ReadKey();
            }
            Console.ReadLine();
            // Die Entfernungen zwischen den einzelnen Standorten werden als Matrix mit der von https://openrouteservice.org/ bereitgestellten API geladen
            // Es wird für den jeweils schnellsten Weg die Entfernung und die Zeit angegeben
            int[][][] matrixes = profile.Id == "linear" ? OpenRouteService.GetLinearDistances(standorte) : await OpenRouteService.GetDistances(standorte, profile);

            int[][] distanceMatrix = matrixes[0];
            int[][] durationMatrix = matrixes[1];


            // Anzeige der Entfernungen
            Console.Clear();
            MyConsole.WriteLine("Die Entfernungen für *dGray*'" + profile.Color + profile.Name + "*dGray*'** wurden geladen.");
            MyConsole.WriteLine("Es wird jeweils von den angebgebenen Koordinaten, bzw. der nächsten Straße ausgegangen.");
            MyConsole.WriteLine("Alle Werte sind in Stunden:Minuten angegeben.");
            MyConsole.WriteLine("Die Zeilen geben den jeweiligen Start- und die Spalten den Zielstandort an:");
            MyConsole.WriteLine("\tVon *dRed*" + standorte[1].name + "** nach *dRed*" + standorte[2].name + "** dauert es zum Beispiel *red*" + DurationToString(durationMatrix[1][2]) + "**.\n");

            MyConsole.WriteLine("  ↓von↓  nach→ " + string.Join("*dGray*|**", standorte.Select(s => s.name.Length > 9 ? s.name.Substring(0, 9) : (new string(' ', 9 - s.name.Length) + s.name))));

            for (int i = 0; i < durationMatrix.Length; i++)
            {
                Standort s     = standorte[i];
                string   sName = s.name.Length > 14 ? s.name.Substring(0, 14) : (new string(' ', 14 - s.name.Length) + s.name);
                MyConsole.WriteLine(sName + ": " + string.Join(" *dGray*|** ", durationMatrix[i].Select(x => new string(' ', 6 - DurationToString(x).Length) + DurationToString(x) + " ")));
            }
            MyConsole.WriteLine();
            MyConsole.WriteLine("\nEs wird nun die optimale Reihenfolge zum Anfahren der Standorte berechnet.");

            //Berechnung des effizientesten Weges
            Path path = Algorithm.GetShortestPath(durationMatrix, distanceMatrix);

            // Arbeitsspeicher bereinigen
            GC.Collect();
            Console.CursorTop--;
            MyConsole.Write(new string(' ', Console.WindowWidth) + "\r");

            // Anzeigen des schnellsten Weges
            MyConsole.WriteLine("Der schnellste Weg wurde in *red*" + Math.Round(path.ComputationTime, 2) + "ms** gefunden:\n");

            for (int i = 0; i < standorte.Length; i++)
            {
                MyConsole.Write("*green*" + standorte[path.Places[i]].name + "** → *blue*" + DurationToString(path.Durations[i]) + " (" + Math.Round((double)path.Distances[i] / 1000, 1) + "km) **→ ");
            }
            MyConsole.WriteLine("*green*" + standorte[0].name + "**");

            MyConsole.WriteLine("\nFortbewegungsprofil: *blue*" + profile.Name);
            MyConsole.WriteLine("**Gesamtdauer: *blue*" + DurationToString(path.Duration));
            MyConsole.WriteLine("**Gesamtdistanz: *blue*" + Math.Round((double)path.Distance / 1000, 2) + "km**");

            MyConsole.WriteLine("\nÖffne diesen Link in deinem Browser, um dir den Streckenverlauf genauer anzusehen:");
            MyConsole.WriteLine("*blue*https://maps.openrouteservice.org/*cyan*directions?a=" + string.Join(',', path.Places.Select(i => standorte[i].lat.ToString(CultureInfo.InvariantCulture) + "," + standorte[i].lon.ToString(CultureInfo.InvariantCulture))) + "&b=" + profile.Code + "&c=0&k1=de-DE&k2=km");
            if (profile.Id == "linear")
            {
                MyConsole.WriteLine("*red*Achtung**: Da die openrouteserve-Karte leider keine Luftlinien-Routen unterstützt, wird der Weg stattdessen für *cyan*Fußgänger** angezeigt, die dort angegebenen Routen-Informationen *red*weichen** also etwas *red*ab**.");
            }


            MyConsole.Write("**\nDieses Programm wurde im Juli 2020 von *red*Leo Decking** als Beitrag zur Coding-Challenge 2020 von *|white**black*get in *green*{*black*IT*green*}** und *|white**dRed*.*dGray*msg** programmiert.\n");

            // Programm neustarten
            MyConsole.WriteLine("Drücke *dGray*[*green*Enter*dGray*]**, um von vorne zu beginnen.");
            MyConsole.WriteLine("Mit *dGray*[*red*Escape*dGray*]** kannst du das Programm beenden.");

            while (Console.KeyAvailable)
            {
                Console.ReadKey();
            }
            while (true)
            {
                ConsoleKeyInfo k = Console.ReadKey();
                if (k.Key == ConsoleKey.Enter)
                {
                    oldStandorte = standorte;
                    await Main(args);

                    return;
                }
                else if (k.Key == ConsoleKey.Escape)
                {
                    return;
                }
                else
                {
                    MyConsole.Write("\r \r");
                }
            }
        }
Esempio n. 2
0
        // Berechnung des kürzesten Weges, der alle Punkte beinhaltet, weitere Erklärung in README.txt
        // Zur Berechnung wird nur die durationMatrix genutzt, da der Weg möglichst Zeit-effizient sein soll.
        // Wenn in den Kommentaren von "Entfernung" gesprochen wird, ist damit die zeitliche Entfernung gemeint.
        // int[i][j] -> Entfernung von Punkt i zu Punkt j
        public static Path GetShortestPath(int[][] durationMatrix, int[][] distanceMatrix)
        {
            // Um die Laufzeit zu vergleichen, wird die Zeit gemessen
            DateTime startTime = DateTime.Now;

            // Da es um einen Rundweg geht, ist es egal, bei welchem Punkt gestartet wird - um es übersichtlich zu halten, wird Punkt 0 als Startpunkt festgelegt.

            // In diesem Array werden die möglichen Punkt-Kombinationen gespeichert                          876543210
            // int[i][l][cIndex] -> jedes 1-er Bit des Integers zeigt, dass der Punkt besucht wurde ->  z.B. 000100101 -> Der Startpunkt und die Punkte 2 und 5 wurden besucht
            // i -> Anzahl der besuchten Punkte
            // l -> Der Index des zuletzt besuchten Punkts, von dem aus es weiter geht
            // cIndex -> Der Index der Kombination (Sobald der Weg für eine neue Kombination berechnet wurde, wird diese hinten angefügt)
            int[][][] allCombinations = new int[durationMatrix.Length + 1][][];

            // In diesem Array werden alle bisher errechneten kürzesten Entfernungen gespeichert
            // int[l][c]
            // l -> Die Nummer des letzten Punktes
            // c -> Die Punkt-Kombination (mit i 1-er Bits, eins davon ist Nr. l), z.B. 000100101
            // z.B. memo[2][00110101] = 23 -> Der kürzeste Weg, der am Startpunkt startet und über die Punkte 4 und 5 zum Punkt 2 führt, ist 23 lang
            int[][] memo = new int[durationMatrix.Length][];


            // Durchgehen der Kombinationen mit 2 Punkten: Alle starten beim Startpunkt und führen jeweils zu einem anderen
            allCombinations[2] = new int[durationMatrix.Length][];
            for (int n = 1; n < durationMatrix.Length; n++)                             // n -> Der Punkt, zu dem es vom Startpunkt aus geht
            {
                allCombinations[2][n]    = new int[1];                                  // Es gibt nur eine Kombination mit 2 Punkten, die am Startpunkt startet und an Punkt n endet
                allCombinations[2][n][0] = 1 << n | 1;                                  // Die Bits 0 (Der Startpunkt) und n werden gesetzt
                memo[n]             = new int[(int)Math.Pow(2, durationMatrix.Length)]; // Initialisierung von memo: Für jeden Endpunkt n gibt es jeweils 2^(Anzahl Punkte) Kombinations-Möglichkeiten
                memo[n][1 << n | 1] = durationMatrix[0][n];                             // Die Länge der Kombination vom Startpunkt zu Punkt n wird gespeichert
            }

            // Durchgehen der Kombinationen mit i besuchten Punkten
            for (int i = 3; i <= durationMatrix.Length; i++) // i -> Anzahl der besuchten Orte
            {
                allCombinations[i] = new int[durationMatrix.Length][];
                int[][] combinations     = allCombinations[i];
                int[][] lastCombinations = allCombinations[i - 1];

                int binCoeff = GetBinCoeff(durationMatrix.Length - 2, i - 2); // Bei jedem Punkt, an dem der Weg enden kann, stehen 2 Punkte, die besucht worden sind fest, der Startpunkt und der Endpunkt. Von den restlichen (Anzahl der Punkte) - 2 Punkte wurden i - 2 Punkte besucht. Es gibt ((Anzahl der Punkte) - 2) über (i - 2) Möglichkeiten.

                // Es wird jeweils ein neuer Endpunkt n genommen und an die bestehenden Kombinationen mit (i - 1) besuchten Punkten angehangen
                for (int n = 1; n < durationMatrix.Length; n++) // n -> Der Punkt, der als nächstes besucht werden soll
                {
                    combinations[n] = new int[binCoeff];        // Die Kombinationen mit i besuchten Punkten, die am Startpunkt starten und an Punkt n enden
                    int[] cmb = combinations[n];

                    int cIndex = 0; // Der Index, unter der die nächste neue Kombination gespeichert wird

                    // Alle bisherigen Kombinationen mit (i - 1) besuchten Punkten werden durchgegangen
                    for (int l = 1; l < durationMatrix.Length; l++)// l -> Der letzte Punkt, von dem aus es weiter zu Punkt n geht
                    {
                        if (l == n)
                        {
                            continue;                                                                // Der Punkt darf natürlich nicht mit dem Punkt übereinstimmen, bei dem es weitergehen soll
                        }
                        for (int cOldIndex = 0; cOldIndex < lastCombinations[1].Length; cOldIndex++) // cOldIndex -> Index der Kombination mit (i - 1) besuchten Punkten, die am Startpunkt startet und am Punkt l endet
                        {
                            int last        = lastCombinations[l][cOldIndex];                        // Die Kombination, an der der neue Punkt n angehangen wird
                            int combination = last | 1 << n;                                         // Bei der neuen Kombination wird zusätzlich zu den zuvor besuchten Punkten auch Punkt n gesetzt

                            if (last == combination)
                            {
                                continue;                                        // Wenn das Bit von Punkt n auch vorher schon gesetzt war, der Punkt also schon besucht wurde, wird übersprungen
                            }
                            int duration = memo[l][last] + durationMatrix[l][n]; // Die neue Entfernung entspricht der bisherigen Entfernung + der Entfernung von Punk l zu Punkt n

                            if (memo[n][combination] == 0)
                            {
                                memo[n][combination] = duration;      // Wenn für die Kombination noch keine Entfernung berechnet wurde, wird sie gespeichert
                                cmb[cIndex++]        = combination;   // Die Punkt-Kombination wird gespeichert, damit man beim Durchgang mit größerem i anschließend einfach über cOldIndex darauf zugreifen kann
                            }
                            else if (duration < memo[n][combination]) // Wenn für die KOmbination schon eine Entfernung gespeichert ist, die aber größer ist, wird die neue, kleinere, Entfernung gespeichert
                            {
                                memo[n][combination] = duration;
                            }
                        }
                    }
                }
            }
            // In dem memo-Array befinden sich nun die kürzesten Entfernungen für alle 2^(Anzahl der Punkte) Punkt-Kombinationen. Sie starten jeweils beim Startpunkt und enden an Punkt n
            // Nun muss noch die Reihenfolge der Punkte ermittelt werden, da die ja vorher nicht gespeichert wurde. Zusätzlich muss noch die Entfernung von Punkt n zum Startpunkt addiert werden, da es ja ein Rundweg sein soll

            int[] path        = new int[durationMatrix.Length + 1];    // Hier wird die schnellste Reihenfolge der Punkte gespeichert
            int[] durations   = new int[durationMatrix.Length];        // Hier die zeitlichen Entfernungen zwischen den einzelnen Punkten des besten Weges
            int[] distances   = new int[durationMatrix.Length];        // Hier die "normalen" Entfernungen zwischen den einzelnen Punkten des besten Weges
            int   minDistance = 0;                                     // Hier werden die einzelnen "normalen" Entfernungen zwischen den Punkten des besten Weges zusammen addiert
            // Die Wegberechnung fängt hinten an und verfolgt den Weg sozusagen zurück, es wird bei jedem Schritt geguckt, wo eine Entfernung mit einem Punkt weniger so viel niedriger ist, wie die Verbindung in distanceMatrix zum entsprechenden Punkt hin lang ist.
            int currentCombination = (1 << durationMatrix.Length) - 1; // Hier wird gespeichert, wo man sich gerade in der Wegberechnung befindet, zuerst (man geht ja von hinten aus) sind alle Bits gesetzt

            path[0] = 0;                                               // Der Startpunkt ist Punkt 0
            path[durationMatrix.Length] = 0;                           // Der Endpunkt auch

            // Berechnung der kürzesten Weg-Länge, es werden aus dem memo-Array die Werte genommen, bei denen alle Bits gesetzt sind, und der Weg zum Startpunkt hinzugefügt, da es ein Rundweg sein soll
            int minDuration = int.MaxValue;                 // Hier wird die Länge des im Moment kürzesten, berechneten Weges gespeichert

            for (int l = 1; l < durationMatrix.Length; l++) // l -> Der Knoten, der zuletzt besucht wurde
            {
                // Die Entfernung entspricht der in memo gespeicherten kürzesten Entfernung, mit der alle Punkte (i = Anzahl der Punkte) besucht werden und die an Punkt l endet, addiert mit der Entfernung von Punkt l zum Startpunkt
                int duration = memo[l][allCombinations[durationMatrix.Length][l][0]] + durationMatrix[l][0];
                if (duration < minDuration) // Dies wird nur ausgeführt, wenn bisher noch keine kürzere Entfernung gefunden wurde
                {
                    // Die Eigenschaften des aktuell gefundenen Weges werden gespeichert
                    minDuration = duration;
                    path[durationMatrix.Length - 1]      = l;                    // Der letzte besuchte Punkt ist Punkt l
                    durations[durationMatrix.Length - 1] = durationMatrix[l][0]; // Der letzte zeitliche Abstand ist der von Punkt l zum Startpunkt
                    distances[durationMatrix.Length - 1] = distanceMatrix[l][0]; // Der letzte "normale" Abstand ist der von Punkt l zum Startpunkt
                    minDistance = distanceMatrix[l][0];                          // Hier werden die einzelnen "normalen" Abstände addiert, sodass man am Ende auch die gesamt-räumliche Entfernung hat (bisher wurde ja nur die zeitliche Berechnet)
                }
            }



            int d = minDuration - durationMatrix[path[durationMatrix.Length - 1]][0]; // Der Weg, der noch übrig ist; Die Gesamtentfernung abzüglich der Entfernung vom letzten Punkt zum Startpunkt. Hiervon werden immer die berechneten "Wegstücke" abgezogen

            currentCombination ^= 1 << path[durationMatrix.Length - 1];               // Bei der aktuelle Kombination wird das Bit vom letzten Punkt entfernt

            for (int i = durationMatrix.Length - 2; i >= 0; i--)                      // Von hinten ausgehend wird der Weg zurück verfolgt, immmer mit einem Punkt weniger besucht
            {
                for (int l = 1; l < durationMatrix.Length; l++)                       // Der Punkt, der vielleicht, zuletzt besucht wurde
                {
                    if ((currentCombination & 1 << l) == 0)
                    {
                        continue;                                       // Wenn der Punkt l, nicht mehr in der aktuellen Kombination vorhanden ist, kann übersprungen werden
                    }
                    int duration = memo[l][currentCombination];         // Die gespeicherte Entfernung, die mit der aktuellen Kombination an Punkt l endet

                    if (duration + durationMatrix[l][path[i + 1]] == d) // Wenn gespeicherte Entfernung zu Punkt l addiert mit der Entfernung von Punkt l zu dem nächstem, schon herausgefundenen Punkt des Weges übereinstimmt, mit der Entfernung d, die noch übrig ist, ist Punkt l der nächste Punkt des Weges
                    {
                        // Die Eigenschaften des Pfades werden aktualisiert, ausgehend davon, dass Punkt l der nächste (da ja rückwärts vorgegangen wird eigentlich der nächst letzte) Punkt ist
                        d                   = duration;
                        path[i]             = l;
                        durations[i]        = durationMatrix[l][path[i + 1]];
                        distances[i]        = distanceMatrix[l][path[i + 1]];
                        minDistance        += distances[i];
                        currentCombination ^= 1 << l; // Das Bit von Punkt l wird entfernt
                        break;
                    }
                }
            }
            durations[0] = durationMatrix[0][path[1]]; // Die zeitliche Entfernung vom Startpunkt zum ersten Punkt des Weges wird addiert
            distances[0] = distanceMatrix[0][path[1]]; // Die "normale Entfernung vom Startpunkt zum ersten Punkt des Weges wird addiert
            minDistance += distances[0];               // Auch die räumliche Entfernung wird aktualisiert
            //Nun wird noch überpüft, ob das übrig gebliebene Wegstück, von der Entfernung her passend ist:
            if (durationMatrix[0][path[1]] != d)
            {
                MyConsole.WriteLine("Es gab leider einen Fehler beim Überprüfen der Route, ist eine Landverbindung zwischen allen Standorten möglich? Versuche es nochmal. " + durationMatrix[0][path[1]] + " != " + d + ")");
            }

            // Der Endzeitpunkt wird gespeichert, um die Berechnungsdauer zu ermitteln
            DateTime endTime = DateTime.Now;

            return(new Path {
                Duration = minDuration, Distance = minDistance, Durations = durations, Distances = distances, Places = path, ComputationTime = (endTime - startTime).TotalMilliseconds
            });
        }