Construisez un résolveur de casse-tête sur le devant


15

Un casse-tête sur le dessus est un casse-tête dans lequel vous devez construire une forme 3D de blocs (généralement cubiques) avec trois vues orthogonales: une vue de dessus, une vue de face et une vue latérale.

Par exemple, étant donné une vue de dessus, de face et de côté comme suit:

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 cube 2x2x2 (avec le volume 8) satisferait cette solution, mais c'est faisable dans le volume 4, si nous avons la structure de couche suivante:

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

Il existe également des arrangements insolubles. Prends pour exemple:

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

Si la vue de dessus indique que le bloc est le deuxième à partir de la gauche, il n'y a aucun moyen qui puisse correspondre à la vue de face qui dit que le bloc doit être le troisième à partir de la gauche. Cet arrangement est donc impossible.


Votre tâche consiste à créer un programme qui, étant donné un casse-tête 4x4 arbitraire, tente de le résoudre dans le moins de cubes ou le déclare insoluble.

Votre programme prendra en entrée une série de 48 bits, représentant les vues de dessus, de face et de côté. Ils peuvent être dans le format de votre choix (une chaîne de 6 octets, une chaîne de 0 et de 1, un nombre hexadécimal à 12 chiffres, etc.), mais l'ordre des bits doit correspondre comme tel:

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 d'autres termes, les bits vont dans un ordre de gauche à droite, de haut en bas, en haut, puis de face, puis de côté.

Votre programme affichera alors soit une série de 64 bits indiquant les cubes de la grille 4x4x4 qui sont remplis, soit indiquer que la grille est insoluble.


Votre programme sera noté en exécutant une batterie de 1 000 000 de cas de test.

Les données de test seront générées en prenant les hachages MD5 des entiers "000000" à "999999" en tant que chaînes, en extrayant les 48 premiers bits (12 hexits) de chacun de ces hachages et en les utilisant comme entrée pour la partie supérieure avant. puzzle latéral. À titre d'exemple, voici quelques entrées de test et les puzzles qu'elles génèrent:

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

Les deux premiers sont insolubles, tandis que le dernier a une solution avec les couches suivantes, d'avant en arrière:

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.

Le score de votre programme sera déterminé par les critères suivants, par ordre décroissant de priorité:

  • Le plus grand nombre de cas résolus.
  • Le plus petit nombre de blocs requis pour résoudre ces cas.
  • Le code le plus court en octets.

Vous devez soumettre et calculer le score par vous-même, ce qui nécessite que votre programme soit capable de parcourir les 1 000 000 cas de test.


J'ai appris en essayant de générer des cas de test pour ce problème que plus de cas sont insolubles qu'improbable. Je me demande comment cela se passera.
Joe Z.

S'il s'agit d'un problème d'optimisation, il devrait y avoir une limite de temps, afin que les gens ne le forcent pas brutalement.
isaacg

Le délai est cependant long pour le tester. Une solution qui prend une éternité à fonctionner ne produira jamais un résultat qui peut être affiché ici. C'est ainsi que fonctionnent tous les problèmes d'optimisation que j'écris.
Joe Z.


1
@JoeZ. orlp a raison. J'ai eu un bug dans ma conversion md5 en puzzle. 551 puzzles résolubles en 00000-99999 et 5360 puzzles résolubles en 000000-999999.
Jakube

Réponses:


5

Python: 5360 cas de test résolus à l'aide de 69519 blocs, 594 octets

Ce devrait être les valeurs optimales.

Approche:

Je vais d'abord tester si le cas de test est réellement résoluble. Je le fais en initialisant une liste de longueur 64 par un et en mettant toutes les positions à zéro, si le pixel correspondant des trois vues est nul. Ensuite, je regarde simplement le puzzle dans les 3 directions et regarde si les vues sont les mêmes que les vues d'entrée. S'il y a égalité, le puzzle est résoluble (nous avons déjà trouvé la pire solution), sinon il est insoluble.

Ensuite, je fais une approche branch-and-bound pour trouver la solution optimale.

Ramification: j'ai une fonction récursive. La profondeur de récursivité indique combien de valeurs sont déjà fixées. Dans chaque appel de la fonction, je m'appelle deux fois, si la valeur à l'index actuel est 1 (cette valeur peut être 0 ou 1 dans la solution optimale), ou une fois, si la valeur est 0 (elle doit être nulle dans le solution optimale).

Limite: j'utilise ici deux stratégies différentes.

  1. Je calcule les vues des 3 côtés dans chaque appel de fonction et regarde si elles correspondent toujours aux valeurs d'entrée. S'ils ne correspondent pas, je n'appelle pas la fonction récursivement.

  2. Je garde en mémoire la meilleure solution. Il y en a déjà plus de fixes dans la branche courante que dans la meilleure solution, je peux déjà fermer cette branche. De plus, je peux estimer une limite inférieure pour le nombre de blocs activés, qui ne sont pas fixes. Et la condition devient#number of activated fixed blocks + #lower bound of activated blocks (under the not fixed blocks) < #number of activated blocks in the best solution.

Voici le code Python. Il définit une fonction fqui attend 3 listes contenant 1 et 0 et renvoie soit 0 (non résoluble), soit une liste de 1 et 0 représentant la solution optimale.

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 longueur du code est de 596 octets / caractères. Et voici le cadre de 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))

Vous pouvez trouver une version non golfée du programme ici . C'est aussi un peu plus rapide.

Résultats:

5360 puzzles sur 1000000 sont résolubles. Au total, nous avons besoin de 69519 blocs. Le nombre de blocs varie de 6 à 18 blocs. Le casse-tête le plus difficile a pris environ 1 seconde à résoudre. C'est le puzzle avec la graine "347177", qui ressemble à

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

et a une solution optimale avec 18 cubes. Voici quelques-unes de haut pour chacune des couches:

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

La durée totale de fonctionnement de tous les cas de test était d'environ 90 secondes. J'ai utilisé PyPy pour exécuter mon programme. CPython (l'interpréteur Python par défaut) est un peu plus lent, mais résout également tous les puzzles en seulement 7 minutes.

Vous pouvez trouver la sortie complète ici : La sortie est explicite. Par exemple, la sortie du puzzle ci-dessus est:

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 cas résolus avec 69519 blocs; 923 octets

C'est également optimal. Appelez avec un tableau de uns et de zéros. Lance un NullPointerExceptionpour une entrée non valide. Une certaine efficacité est sacrifiée au golf. Il se termine toujours dans un délai raisonnable pour toutes les entrées de test 1000000.

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

Stratégie:

En fait, j'avais l'habitude de jouer à ce puzzle un peu quand j'avais 10 ans. Cela utilise mon approche.

Étape 1:

Formez le cube avec le plus de blocs qui correspond aux vues données.

Étape 2:

Créez une liste de pièces amovibles. (Toute pièce avec celle-ci a une autre pièce sur la ligne son entrée, la colonne son entrée et la poutre son entrée.)

Étape 3:

Testez toutes les manières possibles de conserver ou de retirer chaque pièce amovible. (Via la force brute récursive avec l'élagage.)

Étape 4:

Gardez le meilleur cube valide.

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

    }
}

Programme complet:

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;

        }
    }

}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.