Verificar una solución de la Torre de Hanoi


29

Si no sabe qué es la Torre de Hanoi , lo explicaré brevemente: hay tres barras y algunos discos, cada uno de los cuales tiene un tamaño diferente. Al principio, todos los discos están en la primera torre, en orden: el más grande está en la parte inferior, el más pequeño en la parte superior. El objetivo es llevar todos los discos a la tercera barra. ¿Suena fácil? Aquí está el truco: no puede colocar un disco encima de un disco que sea más pequeño que el otro disco; solo puedes sostener un disco en tu mano a la vez para moverlos a otra barra y solo puedes colocar el disco en barras, no en la mesa, bastardo astuto.

Solución de ejemplo ASCII:

  A      B      C
  |      |      |      
 _|_     |      |      
__|__    |      |


  A      B      C
  |      |      |      
  |      |      |      
__|__   _|_     |


  A      B      C
  |      |      |      
  |      |      |      
  |     _|_   __|__


  A      B      C
  |      |      |      
  |      |     _|_     
  |      |    __|__      

Reto

Hay tres barras llamadas A, B y C. (También puede llamarlas 1,2 y 3 respectivamente si eso ayuda) Al principio, todos los n discos están en la barra A (1).

Su desafío es verificar una solución para la torre de hanoi. Deberá asegurarse de que:

  1. Al final, todos los n discos están en la barra C (3).
  2. Para cualquier disco dado en cualquier estado dado no hay un disco más pequeño debajo de él.
  3. No hay errores obvios como intentar sacar discos de una barra vacía o mover discos a barras no existentes.

(la solución no tiene que ser óptima).

Entrada

Su programa recibirá dos entradas:

  1. El número de discos n (un entero)
  2. Los movimientos que se toman, que consistirán en un conjunto de tuplas de: (torre para tomar el disco más alto actualmente), (torre para llevar este disco) donde cada tupla se refiere a un movimiento. Puedes elegir cómo se representan. Por ejemplo, algo así como las siguientes formas de representar la solución para n = 2 que he dibujado en ascii arriba. (Usaré el primero en los casos de prueba, porque es fácil para la vista):

    "A-> B; A-> C; B-> C"

    [("A", "B"), ("A", "C"), ("B", "C")]

    [(1,2), (1,3), (2,3)]

    "ABACBC"

    [1,2,1,3,2,3]

Salida

  • Verdad, si las condiciones que se pueden encontrar bajo "desafío" se mantienen.

  • Falsy, si no lo hacen.

Casos de prueba:

Cierto:

n=1, "A->C"

n=1, "A->B ; B->C"

n=2, "A->B ; A->C ; B->C"

n=2, "A->C ; C->B ; A->C ; B->C"

n=2, "A->C ; A->B ; C->B ; B->A ; B->C ; A->C"

n=3, "A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; A->C"

n=4, "A->B ; A->C ; B->C ; A->B ; C->A ; C->B ; A->B ; A->C ; B->C ; B->A ; C->A ; B->C ; A->B ; A->C ; B->C"

Falso:

3 ° sugerido por @MartinEnder, 7 ° por @Joffan

n=1, "A->B"

n=1, "C->A"

n=2, "A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; A->C"

n=2, "A->B ; A->C ; C->B"

n=2, "A->C ; A->B ; C->B ; B->A"

n=2, "A->C ; A->C"

n=3, "A->B ; A->D; A->C ; D->C ; A->C"

n=3, "A->C ; A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; A->C"

n=3, "A->C ; A->B ; C->B ; A->B ; B->C ; B->A ; B->C ; A->C"

n=3, "A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; C->B"

n=4, "A->B ; A->C ; B->C ; A->B ; C->A ; C->B ; A->B ; A->C ; B->C ; B->A ; C->A ; B->C ; A->B ; A->C"

n=4, "A->B ; A->B ; A->B ; A->C ; B->C ; B->C ; B->C"

Este es el código de golf , la solución más corta gana. Se aplican reglas estándar y lagunas. No incluye pilas.


¿Es también bien si la segunda entrada puede ser representada mediante su método, pero utilizando números en lugar de letras (es decir A=1, B=2, C=3, etc.)?
R. Kap

1
¿Puedo poner a cero las entradas?
Rohan Jhunjhunwala

1
¿Está bien si se produce un error cuando se extrae un disco de una barra vacía o inexistente?
R. Kap

1
¿Podemos suponer que no habrá movimientos sin movimiento A->A?
Martin Ender

2
@Kobi, tienes que comprobarlo, por moving discs to nonexistant rods.supuesto que sí, es unD
edc65

Respuestas:


7

Retina , 84 80 bytes

-5 bytes gracias a Martin Ender

~
 ~$'
$
ABC
{`^(.)(.*)( ~+)\1
$3$2$1
}`^(\W+)(\w)(.*)(?<=\1~+|\w)\2
$3$1$2
^AB 

Pruébalo en línea! (más 5 bytes para pruebas línea por línea)

El código simula un juego completo.

  • La entrada se da como ACABCBACBABCAC~~~.
    ~~~significa tres discos.
  • Las primeras cuatro líneas convierten la entrada al formato del juego: ACABCBACBABCAC ~~~ ~~ ~ABC.
    Al principio, la barra A tiene los 3 discos, y las barras B y C están vacías.
  • A continuación tenemos un ciclo de dos pasos:
    • Tome la primera letra de la línea, que indica la siguiente barra fuente. Encuentre esta varilla y tome el último disco. Retire la letra y mueva el disco hasta el extremo (recójalo).
      En el ejemplo, tras el primer paso, el texto se verá así: ~CABCBACBABCAC ~~~ ~~ABC.
    • En la segunda etapa encontramos la varilla objetivo y movemos el disco allí. Validamos la barra está vacía, o tiene un disco grande en la parte superior: ABCBACBABCAC ~~~ ~~AB ~C.
  • Finalmente confirmamos que las barras A y B están vacías, esto significa que todos los discos están en C (hay un espacio adicional en la última línea).

Wow, eso es impresionante
Rohan Jhunjhunwala

17

Retina , 167 165 157 150 123 bytes

Esto parece totalmente un desafío que debería resolverse con una única expresión regular ... (a pesar de que el encabezado dice "Retina", esto es solo una expresión regular .NET de vainilla, que coincide con entradas válidas).

^(?=\D*((?=(?<3>1+))1)+)((?=A(?<1-3>.+)|B(?<1-4>.+)|C(?<1-5>.+)).(?=A.*(?!\3)(\1)|B.*(?!\4)(\1)|C.*(?!\5)(\1)).)+(?!\3|\4)1

El formato de entrada es la lista de instrucciones del formulario AB, seguido nen unario usando el dígito 1. No hay separadores La salida es 1válida y 0no válida.

Pruébalo en línea! (Los primeros dos caracteres habilitan un conjunto de pruebas separado por salto de línea).

Solución alternativa, mismo recuento de bytes:

^(?=\D*((?=(?<3>1+))1)+)((?=A(?<1-3>.+)|B(?<1-4>.+)|C(?<1-5>.+)).(?=A.*(?!\3)(\1)|B.*(?!\4)(\1)|C.*(?!\5)(\1)).)+(?<-5>1)+$

Esto posiblemente puede ser acortada mediante el uso 1, 11y 111en lugar de A, By Cpero voy a tener que mirar en eso más adelante. También podría ser más corto dividir el programa en varias etapas, pero ¿dónde está el desafío en eso? ;)

Explicación

Esta solución hace un uso intensivo de los grupos de equilibrio de .NET. Para obtener una explicación completa, vea mi publicación sobre Desbordamiento de pila , pero lo esencial es que los grupos de captura en .NET son pilas, donde cada nueva captura empuja otra subcadena y donde también es posible volver a aparecer desde dicha pila. Esto le permite contar varias cantidades en una cadena. En este caso, nos permite implementar las tres barras directamente como tres grupos de captura diferentes donde cada disco está representado por una captura.

Para mover los discos entre las barras, utilizamos una peculiaridad extraña de la (?<A-B>...)sintaxis. Normalmente, esto muestra una captura de la pila By empuja en la pila Ala cadena entre esa captura emergente y el comienzo de este grupo. Tan (?<A>a).(?<B-A>c)emparejado contra abcdejaría Avacío y Bcon b(a diferencia de c). Sin embargo, debido a los retrospectivos de longitud variable de .NET, es posible que se capturen (?<A>...)y se (?<B-A>...)superpongan. Por alguna razón, si ese es el caso, se empuja la intersección de los dos grupos B. He detallado este comportamiento en la "sección avanzada" sobre grupos de equilibrio en esta respuesta .

A la expresión regular. Varillas A, By Ccorresponden a grupos 3, 4y 5en la expresión regular. Comencemos por inicializar la barra A:

^                 # Ensure that we start at the beginning of the input.
(?=               # Lookahead so that we don't actually move the cursor.
  \D*             # Skip all the instructions by matching non-digit characters.
  (               # For each 1 at the end of the input...
    (?=(?<3>1+))  # ...push the remainder of the string (including that 1)
                  # onto stack 3.
  1)+
)

Entonces, por ejemplo, si la entrada termina con 111, entonces el grupo 3 / barra Aahora tendrá la lista de capturas [111, 11, 1](la parte superior está a la derecha).

El siguiente bit del código tiene la siguiente estructura:

(
  (?=A...|B...|C...).
  (?=A...|B...|C...).
)+

Cada iteración de este ciclo procesa una instrucción. La primera alternancia extrae un disco de la barra dada (en un grupo temporal), la segunda alternancia pone ese disco en la otra barra dada. Veremos en un momento cómo funciona esto y cómo nos aseguramos de que el movimiento sea válido.

Primero, sacando un disco de la barra de origen:

(?=
  A(?<1-3>.+)
|
  B(?<1-4>.+)
|
  C(?<1-5>.+)
)

Esto usa el extraño comportamiento de intersección grupal que describí anteriormente. Tenga en cuenta ese grupo 3, 4y 5siempre tendrá subcadenas de 1s al final de la cadena cuya longitud corresponde al tamaño del disco. Ahora usamos (?<1-N>.+)para Nsacar el disco superior de la pila y empujar la intersección de esta subcadena con la coincidencia .+en la pila 1. Como .+siempre cubre necesariamente la captura completa N, sabemos que esto simplemente mueve la captura.

A continuación, colocamos este disco de la pila 1en la pila correspondiente a la segunda barra:

(?=
  A.*(?!\3)(\1)
|
  B.*(?!\4)(\1)
|
  C.*(?!\5)(\1)
)

Tenga en cuenta que no tenemos que limpiar la pila 1, simplemente podemos dejar el disco allí, ya que colocaremos uno nuevo encima antes de volver a usar la pila. Eso significa que podemos evitar la (?<A-B>...)sintaxis y simplemente copiar la cadena con (\1). Para garantizar que el movimiento sea válido, usamos el lookahead negativo (?!\N). Esto garantiza que, desde la posición en la que queremos hacer coincidir el disco actual, es imposible hacer coincidir el disco que ya está en la pila N. Esto solo puede suceder si a) \Nnunca coincidirá porque la pila está completamente vacía o b) the disc on top of stackN is larger than the one we're trying to match with\ 1`.

Finalmente, todo lo que queda es asegurarse de que a) coincidamos con todas las instrucciones yb) varillas Ay que Bestén vacías, de modo que todos los discos se hayan movido C.

(?!\3|\4)1

Simplemente verificamos que ni \3ni \4pueden coincidir (que es el caso si ambos están vacíos, porque cualquier disco real podría coincidir) y que luego nos puede coincidir con una 1forma que no hemos omitido cualquier instrucción.


14

Java "solo" 311 272 263 261 260 259 256 bytes

Salvado 39 incontables bytes debido a @Frozn notando una característica de depuración mayores, así como algunos trucos de golf inteligente.

Versión de golf

int i(int n,int[]m){int j=0,k=0,i=n;Stack<Integer>t,s[]=new Stack[3];for(;j<3;)s[j++]=new Stack();for(;i-->0;)s[0].push(i);for(;k<m.length;k+=2)if((t=s[m[k+1]]).size()>0&&s[m[k]].peek()>t.peek())return 0;else t.push(s[m[k]].pop());return s[2].size()<n?0:1;}

sin dudas con explicaciones y bonitas pilas impresas en cada paso

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package codegolf;

/**
 *
 * @author rohan
 */
import java.util.Arrays;
import java.util.Stack;
public class CodeGolf {
    //golfed version
    int i(int n,int[]m){int j=0,k=0,i=n;Stack<Integer>[] s=new Stack[3];for(;j<3;j++)s[j]=new Stack();for(;i-->0;)s[0].push(i);for(;k<m.length;System.out.println(Arrays.toString(s)),k+=2)if(!s[m[k+1]].isEmpty()&&s[m[k]].peek()>s[m[k+1]].peek())return 0;else s[m[k+1]].push(s[m[k]].pop());return s[2].size()==n?1:0;}
    /** Ungolfed
        * 0 as falsy 1 as truthy
        * @param n the number of disks
        * @param m represents the zero indexed stacks in the form of [from,to,from,to]
        * @return 0 or 1 if the puzzle got solved, bad moves result in an exception
        */
    int h(int n, int[] m) {
        //declarations
        int j = 0, k = 0, i = n;
        //create the poles
        Stack<Integer>[] s = new Stack[3];
        for (; j < 3; j++) {
            s[j] = new Stack();
        }
        //set up the first tower using the "downto operator
        for (; i-- > 0;) {
            s[0].push(i);
        }
    //go through and perform all the moves
        for (; k < m.length; System.out.println(Arrays.toString(s)), k += 2) {
            if (!s[m[k + 1]].isEmpty() && s[m[k]].peek() > s[m[k + 1]].peek()) {
                return 0;//bad move
            } else {
                s[m[k + 1]].push(s[m[k]].pop());
            }
        }
        return s[2].size() == n ? 1 : 0;// check if all the disks are done
    }
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
    //test case
        System.out.println( new CodeGolf().h(3,new int[]{0,2,0,1,2,1,0,2,1,0,1,2,0,2})==1?"Good!":"Bad!");
    }

}

La versión sin golf tiene una función en la que imprimirá el aspecto de las pilas en cada paso, así que ...

[[2, 1], [], [0]]
[[2], [1], [0]]
[[2], [1, 0], []]
[[], [1, 0], [2]]
[[0], [1], [2]]
[[0], [], [2, 1]]
[[], [], [2, 1, 0]]
Good!

¿Qué System.out.println(Arrays.toString(s))hacer?
Frozn

Imprimirá bastante las pilas. Al igual que [[2,1,0], [] []]
Rohan Jhunjhunwala

Whoops @Frozn que ahora era una función de depuración que eliminaba
Rohan Jhunjhunwala

Lo sé, solo me pregunto por qué está allí :) También puedes reemplazarlo &&por &.
Frozn

@Frozn No puedo reemplazar eso tristemente porque confiaba en el comportamiento de cortocircuito para evitar intentar echar un vistazo a una pila vacía. Gracias por la reducción de 39 bytes
Rohan Jhunjhunwala

9

Python 2, 186 167 158 135 127 115 110 102 bytes

n,m=input()
x=[range(n),[],[]]
for a,b in m:p=x[a].pop();e=x[b];e and 1/(p>e[-1]);e+=p,
if x[0]+x[1]:_

Toma entrada en STDIN en el siguiente formato:

(1,[(0,1),(1,2)])

Es decir, una tupla de Python del número de discos y una lista de tuplas de Python (from_rod,to_rod). Como en Python, los paréntesis son opcionales. Las varillas están indexadas a cero.

Por ejemplo, este caso de prueba:

n=2; "A->B ; A->C ; B->C"

se daría como:

(2,[(0,1),(0,2),(1,2)])

Si la solución es válida, no genera nada y sale con un código de salida de 0. Si no es válida, lanza una excepción y sale con un código de salida de 1. Lanza un IndexErrorsi se mueve a una barra no existente o intenta sacar un disco de un varilla que no tiene discos, a ZeroDivisionErrorsi se coloca un disco encima de un disco más pequeño, o NameErrorsi quedan discos en la primera o segunda varillas al final.

¡Ahorró 13 bytes gracias a @KarlKastor!

¡Guardado 8 bytes gracias a @xnor!


1
La verificación de que cada pila está ordenada parece demasiado complicada. ¿No puedes comprobar que el disco movido es más grande que el disco superior de la pila sobre la que se movió?
xnor

@xnor Gracias, eso debería funcionar. Agregándolo ahora.
Cobre

5

Python 2.7, 173 158 138 130 127 123 bytes:

r=range;a,b=input();U=[r(a,0,-1),[],[]]
for K,J in b:U[J]+=[U[K].pop()]if U[J]<[1]or U[K]<U[J]else Y
print U[-1]==r(a,0,-1)

Toma entrada a través del stdin en el formato (<Number of Discs>,<Moves>)donde<Moves> se proporciona como una matriz que contiene tuplas correspondientes a cada movimiento, cada una de las cuales contiene un par de enteros separados por comas. Por ejemplo, el caso de prueba:

n=3, "A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; A->C" 

dado en la publicación se daría como:

(3,[(0,2),(0,1),(2,1),(0,2),(1,0),(1,2),(0,2)]) 

a mi programa Emite una IndexErrorsi no se cumple la tercera condición, a NameErrorsi no se cumple la segunda condición y Falsesi no se cumple la primera condición. De lo contrario salidas True.


dos cosas: la variable Yno está definida en el código (creo que debe haber J) y U[J]+=[Y,[U[K].pop()]][U[J]<[1]or U[K]<U[J]]es más corto por 3 personajes que elstmt1 if cond else stmt2
jermenkoo

@jermenkoo Bueno, yo uso esa Yvariable de esa manera para elevar NameErrorcuando no se cumple la segunda condición. Si tuviera que cambiar Ya J, entonces NameErrorno se plantearía. Por esta razón, tampoco puedo hacerlo U[J]+=[Y,[U[K].pop()]][U[J]<[1]or U[K]<U[J]]porque esto aumentaría NameError todo el tiempo , no solo cuando no se cumple la segunda condición.
R. Kap

Muy bien, gracias por tu explicación!
jermenkoo

5

VBA, 234 217 213 196 bytes

Function H(N,S)
ReDim A(N)
While P<Len(S)
P=P+2:F=1*Mid(S,P-1,1):T=1*Mid(S,P,1)
E=E+(T>2):L=L+T-F
For i=1 To N
If A(i)=F Then A(i)=T:Exit For
E=E+(A(i)=T)+(i=N)
Next
Wend
H=L+9*E=2*N
End Function

El formato de entrada para movimientos es una cadena con un número par de dígitos (012). La invocación está en la hoja de cálculo, = H ([número de discos], [mover cadena])

El conjunto A mantiene la posición de la barra de los diversos discos. Un movimiento es simplemente actualizar la primera aparición del número de barra "De" en el número de barra "A". Si primero encuentra un disco de varilla "A", o no tiene un disco de varilla "De", es un movimiento no válido. El "valor de barra" total de A se mantiene en L, que debe terminar en 2N. Los errores se acumulan como un recuento negativo en E.

Al igual que otras soluciones, "mover" un disco de una torre a la misma torre no está prohibido. Podría prohibirlo por otros 6 bytes.

Resultados

Resultado de la función en la primera columna (el último caso n = 3 es mi adición usando una barra adicional).

TRUE    1   02
TRUE    1   0112
TRUE    2   010212
TRUE    2   02210212
TRUE    2   020121101202
TRUE    3   02012102101202
TRUE    4   010212012021010212102012010212

FALSE   1   01
FALSE   1   20
FALSE   2   02012102101202
FALSE   2   010221
FALSE   2   02012110
FALSE   2   0202
FALSE   3   0202012102101202
FALSE   3   0201210112101202
FALSE   3   02012102101221
FALSE   3   0103023212
FALSE   4   0102120120210102121020120102
FALSE   4   01010102121212

2

php, 141 bytes

<?php $a=$argv;for($t=[$f=range($a[++$i],1),[],[]];($r=array_pop($t[$a[++$i]]))&&$r<(end($t[$a[++$i]])?:$r+1);)$t[$a[$i]][]=$r;echo$t[2]==$f;

La secuencia de comandos de la línea de comando toma la entrada como la altura y luego una serie de índices de matriz (0 indexados), por ejemplo, 1 0 2 o 2 0 1 0 2 1 2 para los casos de prueba más cortos de 1 o 2 alturas.
Echos 1 en casos verdaderos y nada en casos falsos.
Da 2 avisos y 1 advertencia, por lo que debe ejecutarse en un entorno que los silencie.


1

JavaScript (ES6), 108

n=>s=>!s.some(([x,y])=>s[y][s[y].push(v=s[x].pop())-2]<v|!v,s=[[...Array(s=n)].map(_=>s--),[],[]])&s[2][n-1]

Formato de entrada: función con 2 argumentos

  • arg 1, numérico, número de anillos
  • arg 2, matriz de cadenas, cada cadena 2 caracteres '0', '1', '2'

Salida: devuelve 1 si está bien, 0 si no es válido, excepción si la varilla no existe

Menos golf y explicado

n=>a=>(
  // rods status, rod 0 full with an array n..1, rod 1 & 2 empty arrays
  s = [ [...Array(t=n)].map(_=>t--), [], [] ],
  // for each step in solution, evaluate function and stop if returns true
  err = a.some( ([x,y]) => {
    v = s[x].pop(); // pull disc from source rod
    // exception is s[x] is not defined
    if (!v) return 1; // error source rod is empty
    l = s[y].push(v); // push disc on dest rod, get number of discs in l
    // exception is s[y] is not defined
    if(s[y][l-2] < v) return 1; // error if undelying disc is smaller
  }),
  err ? 0 // return 0 if invalid move
  : s[2][n-1]; // il all moves valid, ok if the rod 2 has all the discs
)

Nota de prueba : la primera fila de la función Prueba es necesaria para convertir el formato de entrada dado en la pregunta a la entrada esperada por mi función

F=
n=>s=>!s.some(([x,y])=>s[y][s[y].push(v=s[x].pop())-2]<v|!v,s=[[...Array(s=n)].map(_=>s--),[],[]])&s[2][n-1]

Out=x=>O.textContent+=x+'\n'

Test=s=>s.split`\n`.map(r=>[+(r=r.match(/\d+|.->./g)).shift(),r.map(x=>(parseInt(x[0],36)-10)+''+(parseInt(x[3],36)-10))])
.forEach(([n,s],i)=>{
  var r
  try {
    r = F(+n)(s);
  } 
  catch (e) {
    r = 'Error invalid rod';
  }
  Out(++i+' n:'+n+' '+s+' -> '+r)
})

Out('OK')
Test(`n=1, "A->C"
n=1, "A->B ; B->C"
n=2, "A->B ; A->C ; B->C"
n=2, "A->C ; C->B ; A->C ; B->C"
n=2, "A->C ; A->B ; C->B ; B->A ; B->C ; A->C"
n=3, "A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; A->C"
n=4, "A->B ; A->C ; B->C ; A->B ; C->A ; C->B ; A->B ; A->C ; B->C ; B->A ; C->A ; B->C ; A->B ; A->C ; B->C"`)

Out('\nFail')
Test( `n=1, "A->B"
n=1, "C->A"
n=2, "A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; A->C"
n=2, "A->B ; A->C ; C->B"
n=2, "A->C ; A->B ; C->B ; B->A"
n=2, "A->C ; A->C"
n=3, "A->B ; A->D; A->C ; D->C ; A->C"
n=3, "A->C ; A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; A->C"
n=3, "A->C ; A->B ; C->B ; A->B ; B->C ; B->A ; B->C ; A->C"
n=3, "A->C ; A->B ; C->B ; A->C ; B->A ; B->C ; C->B"
n=4, "A->B ; A->C ; B->C ; A->B ; C->A ; C->B ; A->B ; A->C ; B->C ; B->A ; C->A ; B->C ; A->B ; A->C"
n=4, "A->B ; A->B ; A->B ; A->C ; B->C ; B->C ; B->C"`)
<pre id=O></pre>

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.