C# | for-schleife als Rekursiv?

2 Antworten

Ich würde das stückweise lösen, also erstmal nur eine Ziehung in eine Rekursion umwandeln. Im zweiten Schritt kann dann das mehrfache Ziehen erfolgen.

Da ich keine Möglichkeit zum Testen habe, kann ich nur den Ansatz skizzieren.

void Ziehung(int iAnz, zahlen[]) 
{
  if(zahlen.length()<iAnz) // Anker: Wenn iAnz erreicht, soll er nicht tiefer in ie Rekursion einsteigen.
  {
     int zufallszahl = generator.Next(1, von + 1);
     if(!zahlen.contains(zufallszahl);
       zahlen.add(zufallszahl);
     Ziehung(iAnz, zahlen);
  }
}

Die einzelnen Befehlsausprägungen müsstest du noch umsetzen und eventuell mit der Referenzierung des letzten Elements etwas jonglieren.

"zahlen.add" kannst du zum Beispiel in eine separate Funktion auslagern, wenn es das so noch nicht fertig gibt.

Woher ich das weiß:Studium / Ausbildung
hufsenpai 
Fragesteller
 28.01.2019, 09:44

Danke :)

Werde ich gleich ausprobieren ^^

1
Suboptimierer  28.01.2019, 09:48
@hufsenpai

Wie gesagt 1:1 wird das nicht funktionieren. Ich hoffe, dir mit dem Ansatz trotzdem geholfen zu haben.

1

Ganz ehrlich: Lass es bleiben

Rekursion mag zwar cool klingen, tatsächlich ist es aber so, dass man nach Möglichkeit versucht, sie zu vermeiden. Der Grund ist, dass Du damit sehr viele aufeinander folgende Methoden-Aufrufe hast, die in jeder Hinsicht deine Arbeit und die im Hintergrund des Programms verkomplizieren.

Bei sehr vielen Durchläufen, kann das auf die Performance schlagen, theoretisch könnte das sogar zu einer StackOverflowException führen (hatte ich schon, ist ziemlich nervig). Aber auch wenn alles gut geht, hast Du Freude, wenn Du einen Fehler suchen willst und im CallStack zig tausende Aufrufe von ein und der selben Methode hast; oder wenn Du debuggen willst und keinen klaren Ablauf hast. Da sind deine Schleifen sehr viel einfacher :)

Daneben ist die Lesbarkeit aber der wichtigste Punkt. Wenn Du rein intuitiv for-Schleifen geschrieben hast und jetzt nach einer rekursiven Version suchst, dann zeigt das schon ganz eindeutig, dass deine jetzige Variante lesbarer und besser wartbar ist, als die Rekursion je sein könnte ;)

Wenn ich mir die Lösung von Suboptimierer anschaue, sehe ich meine Behauptung bestätigt. Der Code mag zwar auf den ersten Blick kürzer sein, aber wenn ein Fremder den Code liest, hätte er damit mehr Probleme, als mit deinen Schleifen.

Ich persönlich finde die for-Schleifen auch sehr gut lesbar. Ich würde die Variablen zwar auf englisch benennen, aber ansonsten sieht der Code recht simpel und leicht verständlich aus - genau so, wie es sein sollte.

Allerdings erschließt sich mir der Sinn der ersten Schleifen nicht :D

Woher ich das weiß:Berufserfahrung
hufsenpai 
Fragesteller
 28.01.2019, 10:36
Allerdings erschließt sich mir der Sinn der ersten Schleifen nicht :D

Das ist ein Programm was Zahlen generiert.

Die erste Schleife fragt nur ab wie oft man Generieren möchte.

Der Output von dem Programm sieht folgendermaßen aus:

Ziehungen = 5

Wie viel = 3

Von = 4

Output:

2 1 4

3 2 1

1 3 2

4 2 3

3 1 2

0
Palladin007  28.01.2019, 10:54
@hufsenpai

Dann hast Du da aber einen massiven Fehler drin :P

Denn mit jeder neuen Ziehung überschreibst Du die Ergebnisse der vorherigen Ziehung - zumindest wenn Du nichts vom Code entfernt hast.

0
hufsenpai 
Fragesteller
 28.01.2019, 11:00
@Palladin007

Wieso`?

Ich bekomme normal den Output wie es sein sollte mit dem Code aus der Frage.

Nur soll ich eben aufgrund meines Lehrers den Code Rekursiv schreiben.

0
Palladin007  28.01.2019, 11:34
@hufsenpai

Der Code, den ich sehe, überschreibt mit jeder Ziehung (erste for-Schleife) das Ergebnis der einzelnen tatsächlichen Ziehungen (zweite for-Schleife).

Du nutzt nirgendwo den i-Index, nur den j-Index. Bei jeder Ziehung nutzt Du im Array die selben j-Indices wie bei den vorherigen Ziehungen, Du überschreibst das Ergebnis im Array.

Es fehlt der Part, der nach jeder Ziehung das Ergebnis anzeigt - oder irgendwo weg archiviert um dann alle Ziehungen auf einen Schlag anzuzeigen. Ob das nun bewusst von dir entfernt wurde, oder tatsächlich fehlt, kann ich natürlich nicht sagen.

0
hufsenpai 
Fragesteller
 28.01.2019, 13:21
@Palladin007
for (int i = 0; i < ziehungen; i++)
                    {
                                                  
                        for (int j = 0; j < wieviel; j++)
                        {
                            /* Neue Zahlen werden hier Zufällig generiert */
                            
                            int neuezahlen = generator.Next(1, von + 1);

                            /* Überprüfen ob eine Zahl bereits vorhanden ist -> Wenn ja wird eine neue gezogen */
                            while (zahlen.Contains((neuezahlen)))
                            {
                                neuezahlen = generator.Next(1, von + 1);
                            }

                            zahlen[j] = neuezahlen;
                            
                        }

                        Console.WriteLine((string.Join(" ", zahlen) + " | -> " + zaehlerEigenesSpiel));

Das ist der ganze Code

0
hufsenpai 
Fragesteller
 28.01.2019, 13:49
@Palladin007

Ja *facepalm* :D

Ich Idiot hab vergessen den oben mit rein zuschreiben...

0
Palladin007  28.01.2019, 14:24
@hufsenpai

Tipp:

In Visual Studio den Code markieren, Shift gedrückt halten und so lange Tab drücken, bis der Code ganz links ausgerichtet ist. Dann brauchst Du hier nichts neu schreiben, sondern kannst einfach rüber kopieren, ohne dass Du die hässlichen Einrückungen hast.

0
hufsenpai 
Fragesteller
 28.01.2019, 10:40

Nur ist das ziemlich blöd..

Weil unser Lehrer in der Berufsschule meinte wir sollen diesen Code in Rekursiv nachschreiben :/

0
Palladin007  28.01.2019, 11:27
@hufsenpai

Berufsschul-Lehrer ... Meiner Erfahrung nach sind deren Aufgaben meistens Quatsch :D Ein sehr, sehr viel besseres Beispiel wäre z.B. ein kleiner Such-Algorithmus um in Dateien zu suchen, in so einem Fall macht Rekursion nämlich Sinn.

Aber jetzt kannst Du ihm ja erklären, warum das Bullshit ist ;)

Ich kann nicht erklären, wie ich es lösen würde, ohne es dabei selber zu lösen. Daher kann ich es auch gleich ganz machen:

static void V1_ZieheZahlen(int anzahlZiehungen, int anzahlZahlen)
{
    for (int i = 0; i < anzahlZiehungen; i++)
        for (int j = 0; j < anzahlZahlen; j++)
            ZieheZahl(i, j);
}

static void V2_ZieheZahlen(int anzahlZiehungen, int anzahlZahlen)
{
    V2_ZieheZahlen(0, anzahlZahlen, anzahlZahlen);
}
static void V2_ZieheZahlen(int ziehungNr, int anzahlZiehungen, int anzahlZahlen)
{
    if (ziehungNr >= anzahlZiehungen)
        return;

    V2_ZieheZahl(ziehungNr, 0, anzahlZahlen);

    V2_ZieheZahlen(ziehungNr + 1, anzahlZiehungen, anzahlZahlen);
}
static void V2_ZieheZahl(int ziehungNr, int zahlNr, int anzahlZahlen)
{
    if (zahlNr >= anzahlZahlen)
        return;

    ZieheZahl(ziehungNr, zahlNr);

    V2_ZieheZahl(ziehungNr, zahlNr + 1, anzahlZahlen);
}

static void V3_ZieheZahlen(int ziehungNr, int anzahlZiehungen, int zahlNr, int anzahlZahlen)
{
    if (ziehungNr >= anzahlZiehungen)
        return;

    if (zahlNr < anzahlZahlen)
    {
        ZieheZahl(ziehungNr, zahlNr);

        V3_ZieheZahlen(ziehungNr, anzahlZiehungen, zahlNr + 1, anzahlZahlen);
    }
    else
        V3_ZieheZahlen(ziehungNr + 1, anzahlZiehungen, 0, anzahlZahlen);
}

static void ZieheZahl(int ziehungNr, int zahlNr)
{
    // Für mich zum testen:
    Console.WriteLine($"{ziehungNr}-{zahlNr}");

    #region Dein Code
    ///* Neue Zahlen werden hier zufällig generiert */
    //int neuezahlen = generator.Next(1, von + 1);

    ///* Überprüfen ob eine Zahl bereits vorhanden ist -> Wenn ja wird eine neue gezogen */
    //while (zahlen.Contains((neuezahlen)))
    //{
    //    neuezahlen = generator.Next(1, von + 1);
    //}

    //zahlen[j] = neuezahlen;
    #endregion
}
static void Main(string[] args)
{
    var ziehungen = 3;
    var wieviel = 5;

    V1_ZieheZahlen(ziehungen, wieviel);
    Console.WriteLine("=====");
    V2_ZieheZahlen(0, ziehungen, wieviel);
    Console.WriteLine("=====");
    V3_ZieheZahlen(0, ziehungen, 0, wieviel);
    Console.ReadKey();
}

Ich hab das Ziehen in eine eigene "ZieheZahl"-Methode ausgelagert, damit es einheitlich bleibt.

V1 - Mit for-Schleifen

V2 - Zwei Rekursionen, je eine pro Schleife

V3 - Eine Rekursion, die beide Schleifen abdeckt

Du siehst, V1 ohne Rekursion ist die mit Abstand einfachste Lösung.

Ich lege dir nahe, dir den Code im Detail anzuschauen und auch zu debuggen. Du wirst fest stellen, V1 ist besonders beim Debuggen sehr viel einfacher bzw. übersichtlicher.

0
hufsenpai 
Fragesteller
 28.01.2019, 11:33
@Palladin007

Woow !! Vielen vielen Dank :)

Ich werde mir das nun erstmal durchlesen alles :D

0
Palladin007  28.01.2019, 11:37
@hufsenpai

Ach ja: Ich würde kein Array nutzen.

Das lernt man zwar als erstes in der Schule, ist aber für die meisten Fälle einfach zu unflexibel. .NET hat dafür die List<T>. Die verwaltet intern selber ein Array, kümmert sich aber darum, dass Du Items hinzufügen oder entfernen kannst, ohne dich in irgendeiner Form um die Größe des Arrays kümmern zu müssen.

0
NatanInfoPhilo  28.01.2019, 11:53

Berufserfahrung? So..So..

Ich schreibe den folgenden Text unter einer Annahme und zwar der dass C genauso wie Haskell (ich arbeite nicht mit C) auch über laziness verfügt - wie Haskell.

Du hast Recht. Rekursion ist einfach langsamer als eine induktive Lösung.

Aber ;D hier kommt ein kleiner Witz unseres wunderschönen Kompliers:

Der Kompiler rechnet Rekursionen um zu einer induktiven Lösung :P.
Damit ist das Endprodukt gleich schnell.

(zumindest in Haskell)

Abgesehen davon hilft Rekursion beim Verständnis von Zusammenhängen und ermöglicht es sehr kurze Lösungen zu schreiben.

0
Palladin007  28.01.2019, 14:22
@NatanInfoPhilo

Ja, Berufserfarung, das steht da nicht nur zum Spaß

Und ja, ich hab auch gelesen, dass manche Compiler das tun. Der C#-Compiler tut das aber nicht, zumindest hat er bei meinem Minimal-Test die Rekursion nicht aufgelöst.

Abgesehen davon hilft Rekursion beim Verständnis von Zusammenhängen und ermöglicht es sehr kurze Lösungen zu schreiben.

Nur, wenn die Rekursion zu den Zusammenhängen passt. Daher habe ich geschrieben: Wenn man bewusst nach einer rekursiven Lösung suchen muss, obwohl man intuitiv eine iterative Lösung geschrieben hat, dann ist die rekursive Lösung sehr wahrscheinlich nicht besser für das Verständnis.

Ein Gegenbeispiel wäre ein Such-Algorithmus in z.B. einer Ordner-Struktur, das schreit förmlich nach einer rekursiven Lösung. Hier finde ich die rekursive Lösung besser lesbar, die anderen genannten Nachteile bleiben natürlich trotzdem.

1