Costruisci un risolutore di puzzle dalla parte frontale


15

Un puzzle nella parte anteriore superiore è un puzzle in cui è necessario costruire una forma tridimensionale di blocchi (solitamente cubici) con tre viste ortogonali: una vista dall'alto, una vista frontale e una vista laterale.

Ad esempio, vista dall'alto, anteriore e laterale come segue:

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 2x2x2 (con volume 8) soddisferebbe questa soluzione, ma è realizzabile nel volume 4, se abbiamo la seguente struttura di layer:

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

Ci sono anche alcune disposizioni irrisolvibili. Prendi ad esempio:

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

Se la vista dall'alto dice che il blocco è il secondo da sinistra, non c'è modo che possa corrispondere alla vista frontale che dice che il blocco deve essere il terzo da sinistra. Quindi questa disposizione è impossibile.


Il tuo compito è costruire un programma che, dato un arbitrario puzzle 4x4 nella parte frontale, tenta di risolverlo nel minor numero di cubi o lo dichiara irrisolvibile.

Il tuo programma prenderà come input una serie di 48 bit, che rappresentano le viste superiore, frontale e laterale. Possono essere in qualsiasi formato desiderato (una stringa di 6 byte, una stringa di 0 e 1, un numero esadecimale di 12 cifre, ecc.), Ma l'ordine dei bit deve essere mappato come tale:

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

In altre parole, i bit vanno nell'ordine da sinistra a destra, dall'alto verso il basso, nella vista dall'alto, quindi anteriore, quindi laterale.

Il programma produrrà quindi una serie di 64 bit che indicano i cubi nella griglia 4x4x4 che sono stati compilati o indicano che la griglia è irrisolvibile.


Il tuo programma verrà valutato eseguendo una batteria di 1.000.000 di casi di test.

I dati di test verranno generati prendendo gli hash MD5 degli interi da "000000" a "999999" come stringhe, estraendo i primi 48 bit (12 esadecimali) di ciascuno di questi hash e utilizzandoli come input per la parte superiore-anteriore- puzzle laterale. Ad esempio, ecco alcuni degli input di test e i puzzle che generano:

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

I primi due sono irrisolvibili, mentre l'ultimo ha una soluzione con i seguenti strati, da davanti a dietro:

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.

Il punteggio del tuo programma sarà determinato dai seguenti criteri, in ordine decrescente di priorità:

  • Il maggior numero di casi risolti.
  • Il numero più basso di blocchi richiesti per risolvere questi casi.
  • Il codice più breve in byte.

È necessario inviare e calcolare il punteggio da soli, il che richiede che il programma sia in grado di eseguire tutti i 1.000.000 di casi di test.


Durante il tentativo di generare casi di test per questo problema ho imparato che più casi sono irrisolvibili. Mi chiedo come andrà a finire.
Joe Z.

Se si tratta di un problema di ottimizzazione, dovrebbe esserci un limite di tempo, quindi le persone non lo costringono.
isaacg,

Il limite di tempo è comunque lungo per testarlo. Una soluzione che impiegherà un'eternità a funzionare non produrrà mai un risultato che può essere pubblicato qui. Ecco come funzionano tutti i problemi di ottimizzazione che scrivo.
Joe Z.


1
@JoeZ. orlp ha ragione. Ho avuto un bug nella mia conversione da md5 a puzzle. 551 puzzle risolvibili in 00000-99999 e 5360 puzzle risolvibili in 000000-999999.
Jakube,

Risposte:


5

Python: 5360 casi di test risolti usando 69519 blocchi, 594 byte

Questo dovrebbe essere i valori ottimali.

Approccio:

Per prima cosa testerò se il test case è effettivamente risolvibile. Lo faccio inizializzando un elenco di lunghezza 64 per uno e impostando tutte le posizioni su zero, se lì il pixel corrispondente delle tre viste è zero. Quindi visualizzo semplicemente il puzzle da tutte e 3 le direzioni e guardo se le viste sono uguali alle viste di input. Se ci sono uguali, il puzzle è risolvibile (abbiamo già trovato la soluzione peggiore), altrimenti è irrisolvibile.

Quindi faccio un approccio diretto per trovare la soluzione ottimale.

Branching: ho una funzione ricorsiva. La profondità di ricorsione indica quanti valori sono già stati corretti. In ogni chiamata della funzione mi chiamo due volte, se il valore nell'indice corrente è 1 (questo valore può essere 0 o 1 nella soluzione ottimale) o una volta, se il valore è 0 (deve essere zero nella soluzione ottimale).

Limitazione: uso due diverse strategie qui.

  1. Calcolo le viste dai 3 lati in ciascuna chiamata di funzione e aspetto se corrispondono ancora ai valori di input. Se non corrispondono, non chiamo ricorsivamente la funzione.

  2. Conservo la migliore soluzione in memoria. Se ci sono già più fissi nel ramo corrente che nella soluzione migliore, posso già chiudere quel ramo. Inoltre, posso stimare un limite inferiore per il numero di blocchi attivati, che non sono fissi. E la condizione diventa#number of activated fixed blocks + #lower bound of activated blocks (under the not fixed blocks) < #number of activated blocks in the best solution.

Ecco il codice Python. Definisce una funzionef che prevede 3 elenchi contenenti 1 e 0 e restituisce 0 (non risolvibile) o un elenco di 1 e 0 che rappresentano la soluzione ottimale.

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 lunghezza del codice è 596 byte / caratteri. Ed ecco il framework di test:

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

Puoi trovare una versione non controllata del programma qui . È anche un po 'più veloce.

risultati:

5360 puzzle su 1000000 sono risolvibili. In totale abbiamo bisogno di 69519 blocchi. Il numero di blocchi varia da 6 blocchi a 18 blocchi. Il puzzle più difficile ha impiegato circa 1 secondo per essere risolto. È il puzzle con il seme "347177", che sembra

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

e ha una soluzione ottimale con 18 cubi. Di seguito sono riportati alcuni dall'alto per ciascuno dei livelli:

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

Il tempo di esecuzione totale per tutti i casi di test è stato di circa 90 secondi. Ho usato PyPy per eseguire il mio programma. CPython (l'interprete predefinito di Python) è un po 'più lento, ma risolve anche tutti i puzzle in soli 7 minuti.

Puoi trovare l'output completo qui : L'output è autoesplicativo. Ad esempio, l'output per il puzzle sopra è:

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]

3

5360 casi risolti con 69519 blocchi; 923 byte

Anche questo è ottimale. Chiama con una matrice di uno e zero. Genera a NullPointerExceptionper input non valido. Un po 'di efficienza viene sacrificata per golfarla. Si completa ancora entro un tempo ragionevole per tutti i 1000000 input di test.

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

Strategia:

In realtà giocavo un po 'a questo puzzle quando avevo 10 anni. Questo usa il mio approccio.

Passo 1:

Forma il cubo con il maggior numero di blocchi che si adattano alle viste date.

Passo 2:

Crea un elenco di pezzi rimovibili. (Ogni pezzo con quello ha un altro pezzo sulla riga è in, la colonna è in e il raggio è in.)

Passaggio 3:

Prova ogni possibile modo per conservare o rimuovere ogni pezzo rimovibile. (Tramite forza bruta ricorsiva con potatura.)

Passaggio 4:

Mantieni il miglior cubo valido.

Ungolfed:

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;

    }
}

Programma 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;

        }
    }

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.