¡Clasifica algunas manzanas!

11

Problema

Imagine 7 cubos alineados en una fila. Cada cubo puede contener como máximo 2 manzanas. Hay 13 manzanas etiquetadas del 1 al 13. Se distribuyen entre los 7 cubos. Por ejemplo,

{5,4}, {8,10}, {2,9}, {13,3}, {11,7}, {6,0}, {12,1}

Donde 0 representa el espacio vacío. El orden en que aparecen las manzanas dentro de cada cubo no es relevante (por ejemplo, {5,4} es equivalente a {4,5}).

Puede mover cualquier manzana de un cubo a un cubo adyacente, siempre que haya espacio en el cubo de destino para otra manzana. Cada movimiento se describe por el número de la manzana que desea mover (lo cual no es ambiguo porque solo hay un espacio vacío). Por ejemplo, aplicando el movimiento

7

a la disposición anterior resultaría en

{5,4}, {8,10}, {2,9}, {13,3}, {11,0}, {6,7}, {12,1}

Objetivo

Escriba un programa que lea un arreglo de STDIN y lo clasifique en el siguiente arreglo

{1,2}, {3,4}, {5,6}, {7,8}, {9,10}, {11,12}, {13,0}

usando la menor cantidad de movimientos posible. Nuevamente, el orden en que aparecen las manzanas dentro de cada cubo no es relevante. El orden de los cubos es importante. Debería mostrar los movimientos utilizados para ordenar cada disposición separada por comas. Por ejemplo,

13, 7, 6, ...

Su puntaje es igual a la suma del número de movimientos necesarios para resolver los siguientes arreglos:

{8, 2}, {11, 13}, {3, 12}, {6, 10}, {4, 0}, {1, 7}, {9, 5}
{3, 1}, {6, 9}, {7, 8}, {2, 11}, {10, 5}, {13, 4}, {12, 0}
{0, 2}, {4, 13}, {1, 10}, {11, 6}, {7, 12}, {8, 5}, {9, 3}
{6, 9}, {2, 10}, {7, 4}, {1, 8}, {12, 0}, {5, 11}, {3, 13}
{4, 5}, {10, 3}, {6, 9}, {8, 13}, {0, 2}, {1, 7}, {12, 11}
{4, 2}, {10, 5}, {0, 7}, {9, 8}, {3, 13}, {1, 11}, {6, 12}
{9, 3}, {5, 4}, {0, 6}, {1, 7}, {12, 11}, {10, 2}, {8, 13}
{3, 4}, {10, 9}, {8, 12}, {2, 6}, {5, 1}, {11, 13}, {7, 0}
{10, 0}, {12, 2}, {3, 5}, {9, 11}, {1, 13}, {4, 8}, {7, 6}
{6, 1}, {3, 5}, {11, 12}, {2, 10}, {7, 4}, {13, 8}, {0, 9}

Sí, cada uno de estos arreglos tiene una solución.

Reglas

  • Su solución debe ejecutarse en tiempo polinómico en el número de cubos por movimiento. El punto es usar heurísticas inteligentes.
  • Todos los algoritmos deben ser deterministas.
  • En caso de empate, gana el conteo de bytes más corto.
O por
fuente
2
¿Cuál es el punto de denotar el destino cuando solo hay un espacio al que puedes mover una manzana?
John Dvorak
¿Qué sucede si mi solución de fuerza bruta se ejecuta en un tiempo razonable? Solo hay 700 millones de estados, fácilmente enumerables en cuestión de minutos. Definir "cantidad de tiempo razonable".
John Dvorak
@ JanDvorak Per "Cuál es el punto" - buena decisión. Eso no se me había ocurrido. Estoy definiendo razonable aquí para ser menos que la cantidad de tiempo requerida para forzar la solución por la fuerza bruta;)
Orby
¿Su definición de "razonable" significa que primero debemos implementar la solución de fuerza bruta, luego todo lo que sea más rápido cuenta?
John Dvorak
¿Es importante el orden final del cucharón?
AMK

Respuestas:

4

Puntuación: 448

Mi idea es ordenarlos consecutivamente, comenzando con 1. Esto nos da la buena propiedad de que cuando queremos mover el espacio a la cesta anterior / siguiente, sabemos exactamente cuál de las dos manzanas tenemos que mover: el máximo / min uno, respectivamente. Aquí está el desglose de prueba:

#1: 62     #6: 40
#2: 32     #7: 38
#3: 46     #8: 50
#4: 50     #9: 54
#5: 40    #10: 36

Total score: 448 moves

El código se puede jugar mucho más, pero una mejor calidad del código motivará respuestas adicionales.

C ++ (501 bytes)

#include <cstdio>
#define S(a,b) a=a^b,b=a^b,a=a^b;
int n=14,a[14],i,j,c,g,p,q;
int l(int x){for(j=0;j<n;++j)if(a[j]==x)return j;}
int sw(int d){
    p=l(0);q=p+d;
    if(a[q]*d>a[q^1]*d)q^=1;
    printf("%d,", a[q]);
    S(a[q],a[p])
}
int main(){
    for(;j<n;scanf("%d", a+j),j++);
    for(;++i<n;){
        c=l(i)/2;g=(i-1)/2;
        if(c-g){
            while(l(0)/2+1<c)sw(2);
            while(l(0)/2>=c)sw(-2);
            while(l(i)/2>g){sw(2);if(l(i)/2>g){sw(-2);sw(-2);}}
        }
    }
}

Otras mejoras pueden ser cambiar a C e intentar reducir la puntuación comenzando desde los valores grandes hacia abajo (y eventualmente combinando ambas soluciones).

Yasen
fuente
1
Una subcadena de su código ya forma un programa en C. Específicamente, podría funcionar en C simplemente eliminando la primera línea.
fiesta del
@feersum Tienes razón. Al principio, tenía más código específico de C ++, pero después de eso con el cambio a C en mente, parece que me deshice de él.
Yasen
¿Podría especificar que ha cambiado el formato de entrada en su solución para que sea más claro para quienes intentan verificarlo?
Orby
2

C, 426 448

Esto clasifica las manzanas de una en una de 1 a 13, de manera similar al método de yasen , excepto que cada vez que tenga la oportunidad de mover un número mayor hacia arriba o hacia abajo, lo tomará. Lamentablemente, esto solo mejora el rendimiento en el primer problema de prueba, pero es una pequeña mejora. Cometí un error al ejecutar los problemas de prueba. Parece que simplemente he reimplementado el método de yasen.

#1: 62    #6: 40
#2: 32    #7: 38
#3: 46    #8: 50
#4: 50    #9: 54
#5: 40    #10: 36

Se necesita entrada sin llaves ni comas, p. Ej.

8 2 11 13 3 12 6 10 4 0 1 7 9 5

Aquí está el código de golf entrando en 423 bytes contando algunas líneas nuevas innecesarias (probablemente podría jugar más golf, pero espero que este puntaje sea superado):

#define N 7
#define F(x,y) for(y=0;y<N*2;y++)if(A[y]==x)break;
#define S(x,y) x=x^y,y=x^y,x=x^y;
#define C(x,y) ((A[x*2]==y)||(A[x*2+1]==y))
A[N*2],i,j,d,t,b,a,n,s,v,u,w,g;main(){for(;i<N*2;i++)scanf("%d",A+i);g=1;while
(!v){F(0,i);b=i/2;F(g,u);w=u/2;d=b<w?1:-1;n=(b+d)*2;a=(b+d)*2+1;if(A[n]>A[a])
S(n,a);t=d-1?a:n;printf("%d,",A[t]);S(A[i],A[t]);while(C((g-1)/2,g))g++;v=1;for
(j=0;j<N*2;j++)if(!C(j/2,(j+1)%(N*2)))v=0;}}

Y el código sin golf, que también imprime el puntaje:

#include <stdio.h>
#include <stdlib.h>

#define N 7

int apples[N*2];

int find(int apple)
{
    int i;
    for (i = 0; i < N*2; i++) {
        if (apples[i] == apple)
            return i;
    }    
}

void swap(int i, int j)
{
    int temp;
    temp = apples[i];
    apples[i] = apples[j];
    apples[j] = temp;
}

int contains(int bucket, int apple)
{
    if ((apples[bucket * 2] == apple) || (apples[bucket * 2 + 1] == apple))
        return 1;
    return 0;
}

int is_solved()
{
    int i, j;
    for (i = 0; i < N * 2; i++) {
        j = (i + 1) % (N * 2);
        if (!contains(i / 2, j))
            return 0;
    }
    return 1;
}

int main()
{
    int i, j, dir, bucket, max, min, score;
    int target_i, target_bucket, target;

    /* Read the arrangement */
    for (i = 0; i < N*2; i++) {
        scanf("%d ", apples + i);
    }

    target = 1;
    while (1) {

        i = find(0);
        bucket = i / 2;
        target_i = find(target);
        target_bucket = target_i / 2;

        /* Change the direction of the sort if neccesary */
        if (bucket < target_bucket) dir = 1;
        else dir = -1;

        /* Find the biggest and smallest apple in the next bucket */
        if (apples[(bucket + dir) * 2] < apples[(bucket + dir) * 2 + 1]) {
            min = (bucket + dir) * 2;
            max = (bucket + dir) * 2 + 1;
        } else {
            min = (bucket + dir) * 2 + 1;
            max = (bucket + dir) * 2;
        }

        /* If we're going right, move the smallest apple. Otherwise move the
           biggest apple */
        if (dir == 1) {
            printf("%d, ", apples[min]);
            swap(i, min);
            score++;
        } else {
            printf("%d, ", apples[max]);
            swap(i, max);
            score++;
        }

        /* Find the next apple to sort */
        while (contains((target - 1) / 2, target))
            target++;

        /* If we've solved it then quit */
        if (is_solved())
            break;
    }
    printf("\n");
    printf("%d\n", score);
}
O por
fuente
2

Python 3-121

Esto implementa una búsqueda de profundidad primero con profundidad creciente hasta que encuentre una solución. Utiliza un diccionario para almacenar estados visitados para que no los vuelva a visitar a menos que tenga una ventana de mayor profundidad. Al decidir qué estados verificar, utiliza el número de elementos extraviados como heurístico, y solo visita los mejores estados posibles. Tenga en cuenta que, dado que el orden de los elementos dentro de su depósito no importa, siempre mantiene un orden dentro de los depósitos. Esto hace que sea más fácil verificar si un elemento está fuera de lugar.

La entrada es una matriz de entradas, siendo el primer int el número de cubos.

Entonces, por ejemplo, para el n. ° 8 (este tarda mucho tiempo en ejecutarse en mi máquina, los otros terminan en segundos):

c:\python33\python.exe apples.py 7 3 4 10 9 8 12 2 6 5 1 11 13 7 0

Aquí están los resultados en el conjunto de pruebas: # 1: 12, # 2: 12, # 3: 12, # 4: 12, # 5: 11, # 6: 11, # 7: 10, # 8: 14, # 9: 13, # 10: 14

Aquí está el código:

import sys    

BUCKETS = int(sys.argv[1])    

# cleans a state up so it is in order
def compressState(someState):
  for i in range(BUCKETS):
    if(someState[2*i] > someState[2*i + 1]):
      temp = someState[2*i]
      someState[2*i] = someState[2*i + 1]
      someState[2*i + 1] = temp
  return someState    

state = compressState([int(x) for x in sys.argv[2:]])
print('Starting to solve', state)
WINNINGSTATE = [x for x in range(1, BUCKETS*2 - 1)]
WINNINGSTATE.append(0)
WINNINGSTATE.append(BUCKETS*2 - 1)
maxDepth = 1
winningMoves = []
triedStates = {}    

# does a depth-first search
def doSearch(curState, depthLimit):
  if(curState == WINNINGSTATE):
    return True
  if(depthLimit == 0):
    return False
  myMoves = getMoves(curState)
  statesToVisit = []
  for move in myMoves:
    newState = applyMove(curState, move)
    tns = tuple(newState)
    # do not visit a state again unless it is at a higher depth (more chances to win from it)
    if(not ((tns in triedStates) and (triedStates[tns] >= depthLimit))):
      triedStates[tns] = depthLimit
      statesToVisit.append((move, newState[:], stateScore(newState)))
  statesToVisit.sort(key=lambda stateAndScore: stateAndScore[2])
  for stv in statesToVisit:
    if(stv[2] > statesToVisit[0][2]):
      continue
    if(doSearch(stv[1], depthLimit - 1)):
      winningMoves.insert(0, stv[0])
      return True
  return False    

# gets the moves you can make from a given state
def getMoves(someState):
  # the only not-allowed moves involve the bucket with the 0
  allowedMoves = []
  for i in range(BUCKETS):
    if((someState[2*i] != 0) and (someState[2*i + 1] != 0)):
      allowedMoves.append(someState[2*i])
      allowedMoves.append(someState[2*i + 1])
  return allowedMoves    

# applies a move to a given state, returns a fresh copy of the new state
def applyMove(someState, aMove):
  newState = someState[:]
  for i in range(BUCKETS*2):
    if(newState[i] == 0):
      zIndex = i
    if(newState[i] == aMove):
      mIndex = i
  if(mIndex % 2 == 0):
    newState[mIndex] = 0
  else:
    newState[mIndex] = newState[mIndex-1]
    newState[mIndex-1] = 0
  newState[zIndex] = aMove
  if((zIndex % 2 == 0) and (newState[zIndex] > newState[zIndex+1])):
    newState[zIndex] = newState[zIndex+1]
    newState[zIndex+1] = aMove
  return newState    

# a heuristic for how far this state is from being sorted
def stateScore(someState):
  return sum([1 if someState[i] != WINNINGSTATE[i] else 0 for i in range(BUCKETS*2)])    

# go!
while(True):
  triedStates[tuple(state)] = maxDepth
  print('Trying depth', maxDepth)
  if(doSearch(state, maxDepth)):
    print('winning moves are: ', winningMoves)
    break
  maxDepth += 1
RT
fuente
He votado sobre esto porque es útil para ver soluciones óptimas, pero tenga en cuenta que esto no se ejecuta en tiempo polinómico en la cantidad de cubos por movimiento como lo requiere la pregunta. No creo que ningún algoritmo que produzca una solución óptima (en general) pueda ejecutarse en tiempo polinómico.
Orby
Para el primer problema de prueba, su programa genera 10, 8, 1, 12, 6, 7, 11, 3, 5, 13, 4, 9, que no es una solución válida. Creo que puede haber entendido mal la pregunta. Observe que la pregunta establece que "puede mover cualquier manzana de un cubo a un cubo adyacente", es decir, el cubo a la derecha o la izquierda del mismo (no un cubo arbitrario).
Orby
¡Oh, me perdí totalmente la restricción de adyacencia! Después de publicar esto, sospechaba que la restricción del tiempo de ejecución también se había violado. No estaba 100% seguro mientras lo escribía, porque el elemento de programación dinámica de evitar estados repetidos me confundió. Gracias por el voto a pesar de que esto falla por dos razones este es un rompecabezas divertido y veré si puedo encontrar una respuesta mejor y válida.
RT