Question

Récemment, pour une classe de programmation, nous avons reçu la tâche d'écrire un programme dans n'importe quel langage qui, étant donné n , produira tous les dérangements possibles pour un tableau p de taille n telle que p [i]! = i pour tout i: 0 < = i < n. Nous devions utiliser des itérateurs, par exemple yield.

Exemple: n = 3, [0, 1, 2] n’est pas un dérangement, mais [2, 0, 1] est aussi bien que [1, 2, 0].

J'ai proposé une solution de pseudocode qui fonctionnerait, mais le problème était qu'elle nécessitait des boucles d'alimentation (c'est-à-dire, n boucles imbriquées où n est uniquement connu à runtime). Pour ce faire, j'ai généré n des boucles imbriquées en code Ruby dans une chaîne, puis eval - ed la chaîne. Ma solution a bien fonctionné, mais mon professeur a estimé qu’utiliser quelques goto s aurait été une meilleure solution (plus facile à lire, au moins) que la génération de code dynamique.

J'avais l'impression que <=> était toujours un mauvais choix. Pourquoi l'évaluation à l'exécution du code généré de manière dynamique peut-elle être un choix pire que <=>? Le code généré est propre et simple, et semble assez efficace pour le problème donné. La seule entrée utilisateur dont dépend la génération de code est n , qui est vérifiée pour s'assurer qu'il s'agit bien d'une valeur entière au préalable. Comme il se doit, ce sont <=> les seuls désordres uniques.

Je ne demande pas de solution à mon travail de programmation, je veux simplement connaître les raisons de l'utilisation de <=> sur l'évaluation de code dynamique, ou inversement.

Modifier: , précisons que l'écriture d'un programme utilisant des itérateurs et d'un autre utilisant la récursivité implique que la version itérative ne soit pas forcément efficace.

Était-ce utile?

La solution

C'est une question très intéressante - je ne suis pas sûr qu'il y ait une réponse définitive.

Le problème avec goto est son utilisation non structurée - un goto est un & "grand saut aléatoire"! Ainsi dans le cas général, après le saut, vous ne savez pas d'où vous venez, ce qui cause toutes sortes de problèmes à la fois en termes de débogage et de maintenabilité et - dans un sens plus formel avec prouver & "; exactitude " du code. Bien sûr, il y a des langues (cela fait un moment que je suis) où vous n'avez pas d'option pour imposer une structure au code. L’essentiel, c’est que ce n’est pas parce que GOTO est mauvais, c’est la manière dont le goto est utilisé (et maltraité) qui est mauvais et qui en fait une construction dangereuse à avoir.

Utiliser la génération de code puis évaluer le résultat est malin :) Cependant, & "malin &"; Ce n’est pas toujours une bonne chose et j’imagine que le problème de l’utilisation de cette solution est que le problème n’est pas réglé comme prévu. Cela peut être & "; Tricherie &"; dans un sens, du moins en ce qui concerne votre professeur, n’invalide pas votre solution mais peut la rendre & "; inélégante &"; Des problèmes de débogage et de maintenance se posent également pour le code.

Une solution récursive - en particulier si je me souviens vaguement d’avoir appris (il ya environ 25 ans) que l’on peut généralement décompresser en boucle - serait probablement la plus élégante.

Certainement une question intéressante!

Autres conseils

GOTO et la génération de code sont des solutions inélégantes à ce problème, OMI. Il existe un algorithme récursif qui est probablement la réponse right ici.

Sans voir votre code, j'aurais tendance à me ranger du côté du prof. Si c'est un choix entre GoTo et le code dynamique, je me tournerais vers l'ancien. GoTo n'est pas toujours un mauvais choix.

Vous pouvez résoudre presque tous les problèmes sans utiliser GoTos. Spécifiquement, avec les boucles, vous pouvez utiliser les instructions break et continue pour utiliser implicitement gotos tant que le code standard est toujours maintenu.

Les boucles imbriquées

n sonnent comme un mauvais plan et je vous suggère d’examiner plutôt les fonctions récursives. Chaque fois que vous devez faire un n-boucles, vous devriez toujours penser à la récursivité.

Le code dynamique n'est pas vérifiable à la compilation, ce qui signifie que toute erreur restera non détectée jusqu'au moment de l'exécution. Les rendant potentiellement plus difficiles à trouver. Pour ruby, cela signifierait que les erreurs de syntaxe ne seraient pas trouvées par l'EDI ou l'éditeur, peu importe ce que vous utiliseriez. C’est un avantage pour choisir goto.

Je pense que je devrais voir les deux pour prendre une décision dans ce cas. Je n'ai pas encore vu le code, mais je suis prêt à parier qu'il existe une bonne solution qui n'utilise pas de code dynamique ni de goto. goto n’est pas toujours mauvais, mais si vous envisagez de l’utiliser, vous n’avez probablement pas pris les meilleures décisions de conception jusqu’à présent et vous souhaitez probablement revoir votre solution.

Lors d’un de mes travaux au collège, j’ai déjà eu à faire quelque chose de relativement similaire Ma solution consistait à utiliser une fonction récursive, en passant le tableau, la taille du tableau et le niveau d'imbrication en argument. La fonction s'appelle alors elle-même avec le niveau d'imbrication +1, jusqu'à ce que le niveau d'imbrication soit égal à la taille du tableau. No Goto, pas d'évaluation du code, seulement du code propre!

Exemple

function computeDerangement(yourArray, loopLevel, arraySize)
{
    //We check to see if the loop level is the same as the array size
    //if true, then we have executed exactly n loop
    if (loopLevel == arraySize) {
         display(yourArray); //Display being some kind of function that show the array,
                             //you get the idea
    } else {
        while(something) {
            //Here you put the logic that you execute at one level of the loop

            //Then you call yourself with one more level of nesting
            computeDerangement(yourArray, loopLevel + 1, arraySize);
        }
    }
}

Espérons que cette aide!

Je n'ai jamais utilisé de goto dans ma vie, alors je suis sûr qu'il y a toujours un moyen de les éviter

La principale raison pour laquelle les gens évitent les déclarations goto est qu’ils peuvent compliquer la compréhension de votre programme.

Sans avoir vu votre code, j’imagine qu’il est plus difficile à comprendre que le programme équivalent utilisant goto ...

Solution GOTO - une goto est pratique pour simuler un appel de fonction. Voici une solution non récurrente qui simule simplement la solution récursive en utilisant une pile et une étiquette goto pour revenir au point où l'appel de fonction se serait produit.

Voir la procédure récursive (j'ai posté ceci en tant que réponse séparée) pour la comparaison.

Option Strict On Option Explicit On

Module Module1     Dim x As Stack

Private Sub printGeneratedList(ByVal generatedList As List(Of Integer))
    For Each el In generatedList
        Console.Write(el & " ")
    Next
    Console.WriteLine()
End Sub
Private Sub generateAux(ByVal i As Integer, ByVal n As Integer, _
                        ByVal generatedList As List(Of Integer))
    Dim stackI As Stack(Of Integer) = New Stack(Of Integer)
    Dim stackJ As Stack(Of Integer) = New Stack(Of Integer)


    Dim j As Integer

StartLabel:

    j = 0

    If i >= n Then
        printGeneratedList(generatedList)
        If stackI.Count = 0 Then
            Return
        Else
            GoTo ReturnLabel
        End If
    End If

     While j < n
        If Not j = i Then
            If Not generatedList.Contains(j) Then
                generatedList.Add(j)
                stackI.Push(i)
                stackJ.Push(j)
                i = i + 1
                GoTo StartLabel

ReturnLabel:

                i = stackI.Pop()
                j = stackJ.Pop()
                generatedList.Remove(j)
            End If
        End If
        j = j + 1
    End While
    If stackI.Count = 0 Then
        Return
    Else
        GoTo ReturnLabel
    End If
End Sub

Private Sub generate(ByVal n As Integer)
    Console.WriteLine("Generating for n = " & n.ToString())
    Dim l As List(Of Integer) = New List(Of Integer)
    If n < 0 Then
        Throw New Exception("n must be >= 0")
    End If
    generateAux(0, n, l)
End Sub

Sub Main()
    generate(0)
    Console.ReadLine()
    generate(1)
    Console.ReadLine()
    generate(2)
    Console.ReadLine()
    generate(3)
    Console.ReadLine()
    generate(4)
    Console.ReadLine()
End Sub

Module de fin

Les

gotos ne sont pas propres. mais si des performances élevées sont requises, vous devez parfois enfreindre certaines de ces règles de codage ...

Si la vitesse est vraiment une question importante. Par exemple, si vous voulez écrire une bibliothèque ou un code sur lequel vous avez une grande exigence, vous pouvez envisager d'utiliser goto. bien sûr, il faut faire attention à ce que tout se passe bien.

Commentaire : À la fin, la CPU en cours d'exécution ne fait rien d'autre que de simples sauts. Juste que le langage de programmation / le compilateur les crée. utiliser avec prudence et ne plaisante pas.

La solution goto et les idées de génération de code dynamique sont mauvaises. Ceci est facilement résolu avec une solution récursive comme mentionné par d'autres. Vous générez simplement de manière récursive toutes les permutations (solution récursive standard) et lorsque la génération est terminée (c'est-à-dire à la fin de la récursion), vous ignorez simplement les permutations renvoyées qui ne sont pas des dérangements.

Solution récursive - voici une solution avec une taille précoce. Ma seule question concerne les énumérations: voulait-il que vous créiez un énumérateur qui générerait à chaque appel le prochain élément de la liste? Cela serait probablement plus facile à mettre en œuvre en créant une version lambda de ce programme - je le faisais toujours lentement quand j'écrivais des générateurs de requêtes qui généreraient la réponse suivante à une question lors de l'exécution de requêtes de style interprète prologue.

Option Strict On Option Explicit On

Module Module1

Private Sub printGeneratedList(ByVal generatedList As List(Of Integer))
    For Each el In generatedList
        Console.Write(el & " ")
    Next
    Console.WriteLine()
End Sub

Private Sub generateAux(ByVal i As Integer, ByVal n As Integer, _
                    ByVal generatedList As List(Of Integer))
    If i >= n Then
        printGeneratedList(generatedList)
        Return
    End If
    For j As Integer = 0 To n - 1
        If Not j = i Then
            If Not generatedList.Contains(j) Then
                generatedList.Add(j)
                generateAux(i + 1, n, generatedList)
                generatedList.Remove(j)
            End If
        End If
    Next
End Sub

Private Sub generate(ByVal n As Integer)
    Console.WriteLine("Generating for n = " & n.ToString())
    Dim l As List(Of Integer) = New List(Of Integer)
    If n < 0 Then
        Throw New Exception("n must be >= 0")
    End If
    generateAux(0, n, l)
End Sub

Sub Main()
     generate(0)

    Console.ReadLine()
    generate(1)

    Console.ReadLine()
    generate(2)

    Console.ReadLine()
    generate(3)

    Console.ReadLine()
    generate(4)

    Console.ReadLine()

End Sub

Module de fin

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top