Pregunta

Recientemente, durante una clase de programación, se nos dio la tarea de escribir un programa en cualquier lenguaje que, dado n, producirá todos los posibles alteraciones de una matriz p de tamaño n tal que p[i] != yo por mi parte:0 <= yo < n.Tuvimos que usar iteradores, por ejemplo, yield.

Ejemplo:n=3, [0, 1, 2] no es un trastorno, pero [2, 0, 1] es así como [1, 2, 0].

Se me ocurrió un pseudocódigo solución que podría funcionar, pero el problema fue que se requiere círculos de poder (que es, n bucles anidados donde n sólo se conoce en tiempo de ejecución).Para hacer esto, he generado n bucles anidados en código Ruby en una cadena, entonces eval-ed la cadena.Mi solución funcionó, sin embargo, mi profesor pensaban que el uso de un par de gotos hubiera sido mejor solución (más fáciles de leer, al menos) que la dinámica de generación de código.

Yo estaba bajo la impresión de que goto siempre fue una mala elección.¿Por qué podría evaluación en tiempo de ejecución de forma dinámica el código generado por el ser una peor elección de goto?El código generado es limpio y sencillo, y parece bastante eficiente para el problema dado.La única entrada de usuario sobre la que la generación de código depende es n, lo que se comprueba para asegurarse de que es un valor entero de antemano.Es yields único alteraciones, como debe ser.

No estoy pidiendo una solución a mi la programación de asignación, sólo quiero saber las razones detrás de la utilización de goto la dinámica de evaluación del código, o viceversa.

Editar: para aclarar, la cesión incluye la escritura de un programa mediante iteradores y otro uso de la recursividad, por lo que el proceso iterativo de la versión no era necesariamente destinado a ser eficiente.

¿Fue útil?

Solución

Esa es una pregunta realmente interesante: no estoy seguro de que haya una respuesta definitiva.

El problema con goto es su uso de forma no estructurada: un goto es un & "; salto aleatorio masivo &"; así que, en general, después del salto, no sabes de dónde vienes, lo que causa todo tipo de problemas, tanto en términos de depuración como de mantenimiento y, en un sentido más formal, al demostrar " corrección " del código Por supuesto, hay idiomas (he estado alrededor por un tiempo) en los que no tiene una opción en el momento en que impone la estructura en el código. La conclusión es que no es que GOTO sea malo, sino que la forma en que se usa goto (y se abusa de ella) es mala y eso hace que sea una construcción peligrosa tener disponible.

Usar la generación de código y luego evaluar el resultado es inteligente :) Sin embargo, " clever " no siempre es algo bueno y sospecho que, en parte, el problema con el uso de eso como solución es que en realidad no está abordando el problema como se esperaba. Eso puede ser & Quot; trampa & Quot; en cierto sentido, al menos en lo que respecta a su profesor, no invalida su solución, pero puede hacerla & "; poco elegante &"; Los problemas de depuración y mantenimiento también surgen con respecto al código.

Una solución recursiva, especialmente porque recuerdo vagamente que me enseñaron (hace unos 25 años) que generalmente se puede desenrollar la recursión en bucles, probablemente sería la más elegante.

¡Definitivamente una pregunta interesante!

Otros consejos

Tanto GOTO como la generación de código son soluciones poco elegantes para este problema de la OMI. Existe un algoritmo recursivo que probablemente sea la respuesta correcta aquí.

Sin ver su código, me inclinaría por el profesor. Si es una elección entre GoTo y el código dinámico, me inclinaría por el primero. GoTo no es siempre una mala elección.

Puede resolver casi todos los problemas sin el uso de GoTos. Específicamente con los bucles, puede usar las declaraciones break y continue para usar implícitamente gotos mientras el código estándar todavía se mantiene.

Los bucles anidados

n suenan como un mal plan, y le sugiero que en su lugar busque funciones recursivas. Cada vez que necesite hacer un n-loops, siempre debe pensar en la recursión.

El código dinámico no es comprobable en tiempo de compilación, lo que significa que cualquier error no se detectará hasta el tiempo de ejecución. Potencialmente haciéndolos más difíciles de encontrar. Para ruby, esto significaría que el IDE o el editor no encontrarán errores de sintaxis, lo que sea que esté utilizando. Esa es una ventaja para elegir goto.

Creo que tendría que ver a ambos para tomar una decisión en este caso. No he visto el código, pero estoy dispuesto a apostar que hay una buena solución que no usa código dinámico, o goto's. goto no siempre es malo, pero si está pensando en usarlo, probablemente no haya tomado las mejores decisiones de diseño hasta este momento y probablemente quiera volver a visitar su solución.

En uno de mis trabajos en la universidad, una vez tuve que hacer algo que era relativamente similar Mi solución fue usar una función recursiva, pasando la matriz, el tamaño de la matriz y el nivel de anidación como argumento. La función se llama a sí misma con el nivel de anidación +1, hasta que el nivel de anidación sea igual al tamaño de la matriz. ¡Sin Goto, sin evaluación de código, solo código limpio!

Ejemplo

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);
        }
    }
}

¡Espero que te ayude!

Nunca he usado goto en mi vida, así que estoy bastante seguro de que siempre hay una forma de evitarlos

La razón principal por la que las personas evitan las declaraciones de goto es que pueden hacer que su programa sea más difícil de entender.

Sin haber visto su código, supongo que es más difícil de entender que el programa equivalente que usa goto ...

Solución GOTO: un goto es conveniente cuando se simula una llamada de función. Aquí hay una solución no recurrente que simplemente simula la solución recursiva utilizando una pila y una etiqueta para ir al punto donde habría tenido lugar la llamada a la función.

Vea el procedimiento recursivo (he publicado esto como una respuesta separada) para comparar.

Opción estricta en Opción explícita en

Módulo Módulo1     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

Finalizar módulo

los goto no están limpios. pero si se requiere un alto rendimiento, debe romper algunas de esas reglas de codificación a veces ...

si la velocidad es realmente un asunto importante, por ejemplo, si desea escribir una biblioteca o código en el que tiene una gran exigencia, puede considerar usar goto. seguro que debes prestar atención para que todo salga bien.

Comentario : Al final, la CPU en ejecución no hace más que saltos simples. Solo que el lenguaje de programación / el compilador los crea. úsalo con precaución y no te metas con él.

Tanto la solución goto como las ideas de generación dinámica de código son malas. Esto se resuelve fácilmente con una solución recursiva como la mencionan otros. Simplemente genera recursivamente todas las permutaciones (solución recursiva estándar) y cuando se completa la generación (es decir, en la hoja de la recursión) simplemente omite las permutaciones que no son alteraciones.

Recursiva solución -- aquí está la solución con los principios de la poda.Mi única pregunta es con respecto a las enumeraciones:quería usted para crear un enumerador que en cada llamada exitosa generaría el siguiente elemento en la lista?Esto probablemente sería más fácil implementado por la creación de una lambda versión de este programa que he usado para hacer esto en lisp todo el tiempo en el escrito de consulta a los generadores que generaría la siguiente respuesta a una pregunta cuando se hace de prólogo-intérprete estilo de consultas.

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

End Module

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top