Construye un solucionador de acertijos en el frente

15

Un rompecabezas de la parte superior frontal es un rompecabezas en el que se requiere que construyas una forma tridimensional de bloques (generalmente cúbicos) con tres vistas ortogonales: una vista superior, una vista frontal y una vista lateral.

Por ejemplo, dada una vista superior, frontal y lateral de la siguiente manera:

Top:        Front:      Side:
. . . .     . . . .     . . . .
. x x .     . x x .     . x x .
. x x .     . x x .     . x x .
. . . .     . . . .     . . . .

In this problem, the side view is taken from the right.

Un cubo de 2x2x2 (con volumen 8) satisfaría esta solución, pero es factible en el volumen 4, si tenemos la siguiente estructura de capas:

. . . .     . . . .
. x . .     . . x .
. . x .     . x . .
. . . .     . . . .

También hay algunos arreglos sin solución. Tomar como ejemplo:

Top:        Front:      Side:
. . . .     . . . .     . . . .
. . . .     . . x .     . . . .
. x . .     . . . .     . x . .
. . . .     . . . .     . . . .

Si la vista superior dice que el bloque es el segundo desde la izquierda, no hay forma de que coincida con la vista frontal que dice que el bloque debe ser el tercero desde la izquierda. Entonces este arreglo es imposible.


Su tarea es construir un programa que, dado un rompecabezas arbitrario de 4x4 en la parte superior frontal, intente resolverlo en la menor cantidad de cubos, o lo declare insoluble.

Su programa tomará como entrada una serie de 48 bits, que representan las vistas superior, frontal y lateral. Pueden estar en el formato que desee (una cadena de 6 bytes, una cadena de 0 y 1, un número hexadecimal de 12 dígitos, etc.), pero el orden de los bits debe mapearse como tal:

Top: 0x00   Front: 0x10 Side: 0x20
0 1 2 3     0 1 2 3     0 1 2 3
4 5 6 7     4 5 6 7     4 5 6 7
8 9 a b     8 9 a b     8 9 a b
c d e f     c d e f     c d e f

En otras palabras, los bits van en un orden de izquierda a derecha, de arriba a abajo, en la parte superior, luego frontal y luego lateral.

Luego, su programa generará una serie de 64 bits que indica los cubos en la cuadrícula de 4x4x4 que están rellenados, o indica que la cuadrícula no tiene solución.


Su programa se puntuará ejecutando una batería de 1,000,000 de casos de prueba.

Los datos de prueba se generarán tomando los hashes MD5 de los enteros "000000" a "999999" como cadenas, extrayendo los primeros 48 bits (12 hexits) de cada uno de estos hashes y usándolos como entrada para el frente superior rompecabezas lateral. A modo de ejemplo, estas son algunas de las entradas de prueba y los acertijos que generan:

Puzzle seed: 000000   hash: 670b14728ad9
Top:        Front:      Side:
. x x .     . . . x     x . . .
x x x x     . x . x     x . x .
. . . .     . x x x     x x . x
x . x x     . . x .     x . . x

Puzzle seed: 000001   hash: 04fc711301f3
Top:        Front:      Side:
. . . .     . x x x     . . . .
. x . .     . . . x     . . . x
x x x x     . . . x     x x x x
x x . .     . . x x     . . x x

Puzzle seed: 000157   hash: fe88e8f9b499
Top:        Front:      Side:
x x x x     x x x .     x . x x
x x x .     x . . .     . x . .
x . . .     x x x x     x . . x
x . . .     x . . x     x . . x

Los dos primeros no tienen solución, mientras que el último tiene una solución con las siguientes capas, de adelante hacia atrás:

x . . .   . . . .   x x x .   x x x .
. . . .   x . . .   . . . .   . . . .
x . . .   . . . .   . . . .   x x x x
x . . .   . . . .   . . . .   x . . x

There are a total of 16 blocks here, but it can probably be done in less.

La puntuación de su programa estará determinada por los siguientes criterios, en orden descendente de prioridad:

  • El mayor número de casos resueltos.
  • El menor número de bloques necesarios para resolver esos casos.
  • El código más corto en bytes.

Debe enviar y calcular el puntaje usted mismo, lo que requiere que su programa pueda ejecutar todos los 1,000,000 de casos de prueba.

Joe Z.
fuente
Mientras intentaba generar casos de prueba para este problema, aprendí que más casos no tienen solución. Me pregunto cómo resultará esto.
Joe Z.
Si se trata de un problema de optimización, debe haber un límite de tiempo, para que la gente no lo fuerce.
isaacg
El límite de tiempo es el tiempo que tarden en probarlo. Una solución que tarda una eternidad en ejecutarse nunca producirá un resultado que pueda publicarse aquí. Así es como funcionan todos los problemas de optimización que escribo.
Joe Z.
1
@JoeZ. orlp tiene razón. Tuve un error en mi conversión de md5 a rompecabezas. 551 rompecabezas solucionables en 00000-99999 y 5360 rompecabezas solucionables en 000000-999999.
Jakube

Respuestas:

5

Python: 5360 casos de prueba resueltos usando 69519 bloques, 594 bytes

Estos deberían ser los valores óptimos.

Acercarse:

Primero probaré si el caso de prueba tiene solución real. Hago esto inicializando una lista de longitud 64 por unidades y estableciendo todas las posiciones en cero, si el píxel correspondiente de las tres vistas es cero. Luego, veo el rompecabezas desde las 3 direcciones y miro si las vistas son las mismas que las vistas de entrada. Si son iguales, el rompecabezas es solucionable (ya encontramos la peor solución), de lo contrario no se puede resolver.

Luego hago un enfoque de bifurcación para encontrar la solución óptima.

Derivación: tengo una función recursiva. La profundidad de recursión indica cuántos valores ya están fijos. En cada llamada de la función me llamo dos veces, si el valor en el índice actual es 1 (este valor puede ser 0 o 1 en la solución óptima), o una vez, si el valor es 0 (tiene que ser cero en el solucion optima).

Límite: uso dos estrategias diferentes aquí.

  1. Calculo las vistas desde los 3 lados en cada llamada de función y miro si aún coinciden con los valores de entrada. Si no coinciden, no llamo a la función de forma recursiva.

  2. Mantengo la mejor solución en la memoria. Si ya hay más fijos en la rama actual que en la mejor solución, ya puedo cerrar esa rama. Además, puedo estimar un límite inferior para el número de bloques activados, que no son fijos. Y la condición se vuelve#number of activated fixed blocks + #lower bound of activated blocks (under the not fixed blocks) < #number of activated blocks in the best solution.

Aquí está el código de Python. Define una función fque espera 3 listas que contienen 1s y 0s y devuelve 0 (no solucionable) o una lista de 1s y 0s que representa la solución óptima.

S=sum;r=range
def f(t,f,s):
 for i in r(4):s[4*i:4*i+4]=s[4*i:4*i+4][::-1]
 c=[min(t[i%16],f[(i//16)*4+i%4],s[i//4])for i in r(64)]
 m=lambda:([int(S(c[i::16])>0)for i in r(16)],[int(S(c[i+j:i+j+16:4])>0)for i in r(0,64,16)for j in r(4)],[int(S(c[i+j:i+j+4])>0)for i in r(0,64,16)for j in r(0,16,4)])==(t,f,s)
 Z=[65,0];p=[]
 def g(k):
  if k>63and S(c)<Z[0]:Z[:]=[S(c),c[:]]
  if k>63or S(c[:k])+p[k]>=Z[0]:return
  if c[k]:c[k]=0;m()and g(k+1);c[k]=1
  m()and g(k+1)
 for i in r(64):h,R=(i//16)*4+4,(i//4)%4;p+=[max(S(f[h:]),S(s[h:]))+max((R<1)*S(f[h+i%4-3:h]),S(s[h+R-3:h]))]
 g(0);return Z[1]

La longitud del código es de 596 bytes / caracteres. Y aquí está el marco de prueba:

from hashlib import md5
from time import time

N = 1000000
start=time();count=blocks=0
for n in range(N):
 bits = list(map(int,"{:048b}".format(int(md5("{:06}".format(n).encode("utf-8")).hexdigest()[:12], 16))))
 result = f(bits[:16], bits[16:32], bits[32:])
 if result:
  count += 1
  blocks += sum(result)
  print("Seed: {:06}, blocks: {}, cube: {}".format(n, sum(result), result))
print()
print("{} out of {} puzzles are solvable using {} blocks.".format(count, N, blocks))
print("Total time: {:.2f} seconds".format(time()-start))

Puedes encontrar una versión no adaptada del programa aquí . También es un poco más rápido.

Resultados:

5360 de 1000000 rompecabezas son solucionables. En total necesitamos 69519 bloques. El número de bloques varía de 6 a 18 bloques. El rompecabezas más difícil tomó alrededor de 1 segundo para resolver. Es el rompecabezas con la semilla "347177", que parece

Top:      Front:    Side:
x x . .   x x x x   x . x .
x x x x   x x x x   x x x x
x x x x   x x x x   x x x x
x x . x   x x x x   x . x x

y tiene una solución óptima con 18 cubos. Los siguientes son algunos de arriba para cada una de las capas:

Top 1:    Top 2:    Top 3:    Top 4:
. . . .   . x . .   . x . .   x . . .
. . x x   . . x .   x . . .   . x x .
. . . .   . . . x   x x x .   . . . .
x x . .   x . . .   . . . x   . . . x

El tiempo total de ejecución para todos los casos de prueba fue de aproximadamente 90 segundos. Usé PyPy para ejecutar mi programa. CPython (el intérprete predeterminado de Python) es un poco más lento, pero también resuelve todos los acertijos en solo 7 minutos.

Puede encontrar el resultado completo aquí : el resultado se explica por sí mismo. Por ejemplo, el resultado del rompecabezas anterior es:

Seed: 347177, blocks: 18, cube: [0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1]
Jakube
fuente
3

5360 casos resueltos con 69519 bloques; 923 bytes

Esto también es óptimo. Llame con una matriz de unos y ceros. Lanza un NullPointerExceptionpara entrada no válida. Se sacrifica algo de eficiencia para jugar golf. Todavía se completa dentro de un tiempo razonable para todas las 1000000 entradas de prueba.

import java.util.*;int[]a(int[]a){a b=new a(a);b=b.b(64);return b.d();}class a{List<int[]>a=new ArrayList();List b=new ArrayList();int c;a(int[]d){int e=0,f,g,h,i[]=new int[48];for(;e<64;e++){f=e/16;g=(e%16)/4;h=e%4;if(d[f+12-4*h]+d[16+f+g*4]+d[32+h+g*4]>2){i[f+12-4*h]=i[16+f+g*4]=i[32+h+g*4]=1;a.add(new int[]{f,g,h});c++;}}if(!Arrays.equals(d,i))throw null;b=f();}a(){}a b(int d){if(c-b.size()>d|b.size()<1)return this;a e=c();e.a.remove(b.get(0));e.b.retainAll(e.f());e.c--;e=e.b(d);d=Math.min(e.c,d);a f=c();f=f.b(d);return e.c>f.c?f:e;}a c(){a c=new a();c.a=new ArrayList(a);c.b=new ArrayList(b);c.b.remove(0);c.c=this.c;return c;}int[]d(){int[]d=new int[64];for(int[]e:a)d[e[2]*16+e[1]*4+e[0]]=1;return d;}List f(){List d=new ArrayList();int e,f,g;for(int[]h:a){e=0;f=0;g=0;for(int[]p:a)if(p!=h){e|=h[0]==p[0]&h[1]==p[1]?1:0;f|=h[0]==p[0]&h[2]==p[2]?1:0;g|=h[1]==p[1]&h[2]==p[2]?1:0;}if(e+f+g>2)d.add(h);}return d;}}

Estrategia:

En realidad, solía jugar a este rompecabezas bastante cuando tenía 10 años. Esto utiliza mi enfoque.

Paso 1:

Forme el cubo con la mayor cantidad de bloques que se ajuste a las vistas dadas.

Paso 2:

Crea una lista de piezas extraíbles. (Cualquier pieza con eso tiene otra pieza en la fila que está adentro, la columna está adentro y la viga está adentro).

Paso 3:

Pruebe todas las formas posibles de mantener o quitar cada pieza removible. (Vía fuerza bruta recursiva con poda).

Etapa 4:

Mantenga el mejor cubo válido.

Sin golf:

int[] main(int[] bits) {
    Cube cube = new Cube(bits);
    cube = cube.optimize(64);
    return cube.bits();
}

class Cube {

    List<int[]> points = new ArrayList();
    List removablePoints = new ArrayList();
    int size;

    Cube(int[] bits) {
        int i = 0,x,y,z,placed[] = new int[48];
        for (; i < 64; i++) {
            x = i / 16;
            y = (i % 16) / 4;
            z = i % 4;
            if (bits[x + 12 - 4 * z] + bits[16 + x + y * 4] + bits[32 + z + y * 4] > 2) {
                placed[x + 12 - 4 * z] = placed[16 + x + y * 4] = placed[32 + z + y * 4] = 1;
                points.add(new int[]{x, y, z});
                size++;
            }
        }

        if (!Arrays.equals(bits, placed))
            throw null;

        removablePoints = computeRemovablePoints();
    }

    Cube() {
    }

    Cube optimize(int smallestFound) {
        if (size - removablePoints.size() > smallestFound | removablePoints.size() < 1)
            return this;

        Cube cube1 = duplicate();
        cube1.points.remove(removablePoints.get(0));

        cube1.removablePoints.retainAll(cube1.computeRemovablePoints());
        cube1.size--;

        cube1 = cube1.optimize(smallestFound);
        smallestFound = Math.min(cube1.size, smallestFound);

        Cube cube2 = duplicate();

        cube2 = cube2.optimize(smallestFound);

        return cube1.size > cube2.size ? cube2 : cube1;

    }

    Cube duplicate() {
        Cube c = new Cube();
        c.points = new ArrayList(points);
        c.removablePoints = new ArrayList(removablePoints);
        c.removablePoints.remove(0);
        c.size = size;
        return c;
    }

    int[] bits() {
        int[] bits = new int[64];
        for (int[] point : points)
            bits[point[2] * 16 + point[1] * 4 + point[0]] = 1;
        return bits;
    }

    List computeRemovablePoints(){
        List removablePoints = new ArrayList();
        int removableFront, removableTop, removableSide;
        for (int[] point : points) {
            removableFront = 0;
            removableTop = 0;
            removableSide = 0;
            for (int[] p : points)
                if (p != point) {
                    removableFront |= point[0] == p[0] & point[1] == p[1] ? 1 : 0;
                    removableTop |= point[0] == p[0] & point[2] == p[2] ? 1 : 0;
                    removableSide |= point[1] == p[1] & point[2] == p[2] ? 1 : 0;
                }
            if (removableFront + removableTop + removableSide > 2)
                removablePoints.add(point);
        }
        return removablePoints;
    }

    public String toString() {

        String result = "";
        int bits[] = bits(),x,y,z;

        for (z = 0; z < 4; z++) {
            for (y = 0; y < 4; y++) {
                for (x = 0; x < 4; x++) {
                    result += bits[x + 4 * y + 16 * z] > 0 ? 'x' : '.';
                }
                result += System.lineSeparator();
            }
            result += System.lineSeparator();
        }

        return result;

    }
}

Programa completo:

import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Example cube:
 *
 * origin
 * |   ........
 * |  .      ..
 * | . top  . .
 * v.      .  .
 * ........   .  <- side
 * .      .  .
 * . front. .
 * .      ..
 * ........
 *
 *     / z
 *    /
 *  /
 * .-----> x
 * |
 * |
 * |
 * V y
 */

public class PPCG48247 {

    public static void main(String[] args) throws Exception{
        MessageDigest digest = MessageDigest.getInstance("MD5");
        int totalSolved = 0;
        int totalCubes = 0;

        for (int i = 0; i < 1000000; i++){
            byte[] input = String.format("%06d", i).getBytes();

            byte[] hash = digest.digest(input);
            int[] bits = new int[48];

            for (int j = 0, k = 0; j < 6; j++, k += 8){
                byte b = hash[j];
                bits[k] = (b >> 7) & 1;
                bits[k + 1] = (b >> 6) & 1;
                bits[k + 2] = (b >> 5) & 1;
                bits[k + 3] = (b >> 4) & 1;
                bits[k + 4] = (b >> 3) & 1;
                bits[k + 5] = (b >> 2) & 1;
                bits[k + 6] = (b >> 1) & 1;
                bits[k + 7] = b & 1;
            }

            try {
                int[] solution = new PPCG48247().a(bits);
                totalSolved++;
                for (int b : solution){
                    totalCubes += b;
                }
            } catch (NullPointerException ignored){}

        }
        System.out.println(totalSolved);
        System.out.println(totalCubes);
    }

    int[] main(int[] bits) {
        Cube cube = new Cube(bits);
        cube = cube.optimize(64);
        return cube.bits();
    }

    class Cube {

        List<int[]> points = new ArrayList();
        List removablePoints = new ArrayList();
        int size;

        Cube(int[] bits) {
            int i = 0,x,y,z,placed[] = new int[48];
            for (; i < 64; i++) {
                x = i / 16;
                y = (i % 16) / 4;
                z = i % 4;
                if (bits[x + 12 - 4 * z] + bits[16 + x + y * 4] + bits[32 + z + y * 4] > 2) {
                    placed[x + 12 - 4 * z] = placed[16 + x + y * 4] = placed[32 + z + y * 4] = 1;
                    points.add(new int[]{x, y, z});
                    size++;
                }
            }

            if (!Arrays.equals(bits, placed))
                throw null;

            removablePoints = computeRemovablePoints();
        }

        Cube() {
        }

        Cube optimize(int smallestFound) {
            if (size - removablePoints.size() > smallestFound | removablePoints.size() < 1)
                return this;

            Cube cube1 = duplicate();
            cube1.points.remove(removablePoints.get(0));

            cube1.removablePoints.retainAll(cube1.computeRemovablePoints());
            cube1.size--;

            cube1 = cube1.optimize(smallestFound);
            smallestFound = Math.min(cube1.size, smallestFound);

            Cube cube2 = duplicate();

            cube2 = cube2.optimize(smallestFound);

            return cube1.size > cube2.size ? cube2 : cube1;

        }

        Cube duplicate() {
            Cube c = new Cube();
            c.points = new ArrayList(points);
            c.removablePoints = new ArrayList(removablePoints);
            c.removablePoints.remove(0);
            c.size = size;
            return c;
        }

        int[] bits() {
            int[] bits = new int[64];
            for (int[] point : points)
                bits[point[2] * 16 + point[1] * 4 + point[0]] = 1;
            return bits;
        }

        List computeRemovablePoints(){
            List removablePoints = new ArrayList();
            int removableFront, removableTop, removableSide;
            for (int[] point : points) {
                removableFront = 0;
                removableTop = 0;
                removableSide = 0;
                for (int[] p : points)
                    if (p != point) {
                        removableFront |= point[0] == p[0] & point[1] == p[1] ? 1 : 0;
                        removableTop |= point[0] == p[0] & point[2] == p[2] ? 1 : 0;
                        removableSide |= point[1] == p[1] & point[2] == p[2] ? 1 : 0;
                    }
                if (removableFront + removableTop + removableSide > 2)
                    removablePoints.add(point);
            }
            return removablePoints;
        }

        public String toString() {

            String result = "";
            int bits[] = bits(),x,y,z;

            for (z = 0; z < 4; z++) {
                for (y = 0; y < 4; y++) {
                    for (x = 0; x < 4; x++) {
                        result += bits[x + 4 * y + 16 * z] > 0 ? 'x' : '.';
                    }
                    result += System.lineSeparator();
                }
                result += System.lineSeparator();
            }

            return result;

        }
    }

}
El numero uno
fuente