Membangun pemecah teka-teki sisi depan atas

15

Teka-teki sisi depan atas adalah teka-teki di mana Anda harus membuat bentuk 3-D dari blok (biasanya kubik) yang diberikan tiga tampilan ortogonal: tampilan atas, tampilan depan, dan tampilan samping.

Misalnya, diberi tampilan atas, depan, dan samping sebagai berikut:

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.

Kubus 2x2x2 (dengan volume 8) akan memuaskan solusi ini, tetapi itu bisa dilakukan dalam volume 4, jika kita memiliki struktur lapisan berikut:

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

Ada juga beberapa pengaturan yang tidak dapat diselesaikan. Ambil, misalnya:

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

Jika tampilan atas mengatakan blok kedua dari kiri, tidak ada cara yang bisa cocok dengan tampilan depan yang mengatakan blok harus ketiga dari kiri. Jadi pengaturan ini tidak mungkin.


Tugas Anda adalah membangun sebuah program yang, mengingat teka-teki sisi-atas 4x4 sewenang-wenang, mencoba menyelesaikannya dalam jumlah paling sedikit kubus, atau menyatakannya tidak dapat dipecahkan.

Program Anda akan mengambil sebagai input serangkaian 48 bit, mewakili tampilan atas, depan, dan samping. Mereka mungkin dalam format apa pun yang Anda inginkan (string 6-byte, string 0 dan 1, angka hex 12-digit, dll.), Tetapi urutan bit harus dipetakan seperti itu:

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

Dengan kata lain, bitnya berada dalam urutan kiri-ke-kanan, atas-ke-bawah, di atas, lalu depan, lalu samping.

Program Anda kemudian akan menampilkan serangkaian 64 bit yang mengindikasikan kubus dalam kisi 4x4x4 yang diisi, atau menunjukkan bahwa kisi tersebut tidak dapat dipecahkan.


Program Anda akan dinilai dengan menjalankan baterai 1.000.000 kasus uji.

Data pengujian akan dihasilkan dengan mengambil hash MD5 dari bilangan bulat "000000" hingga "999999" sebagai string, mengekstraksi 48 bit pertama (12 heksit) dari masing-masing hash ini, dan menggunakannya sebagai input untuk front-front- teka-teki sisi. Sebagai contoh, berikut adalah beberapa input tes dan puzzle yang mereka hasilkan:

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

Dua yang pertama tidak dapat dipecahkan, sedangkan yang terakhir memiliki solusi dengan lapisan berikut, dari depan ke belakang:

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.

Skor program Anda akan ditentukan oleh kriteria berikut, dalam urutan prioritas menurun:

  • Jumlah tertinggi kasus yang diselesaikan.
  • Jumlah blok terendah yang diperlukan untuk menyelesaikan kasus-kasus tersebut.
  • Kode terpendek dalam byte.

Anda harus menyerahkan dan menghitung skor sendiri, yang mengharuskan program Anda dapat menjalankan semua 1.000.000 kasus uji.

Joe Z.
sumber
Saya belajar sambil mencoba membuat kasus uji untuk masalah ini bahwa lebih banyak kasus yang tidak dapat diselesaikan daripada tidak. Saya bertanya-tanya bagaimana ini akan berubah.
Joe Z.
Jika ini masalah optimasi, harus ada batas waktu, jadi orang tidak memaksakannya.
isaacg
Namun batas waktu yang mereka ambil sangat lama untuk mengujinya. Solusi yang membutuhkan waktu selamanya untuk dijalankan tidak akan pernah menghasilkan hasil yang dapat diposting di sini. Begitulah cara semua masalah pengoptimalan yang saya tulis bekerja.
Joe Z.
1
@ Joz. orlp benar. Saya memiliki bug dalam konversi md5-to-puzzle saya. 551 teka-teki yang dapat dipecahkan di 00000-99999 dan 5360 teka-teki yang dapat dipecahkan di 000000-999999.
Jakube

Jawaban:

5

Python: 5360 kasus uji dipecahkan menggunakan 69519 blok, 594 byte

Ini harus menjadi nilai optimal.

Pendekatan:

Pertama saya akan menguji apakah test case-nya benar-benar bisa dipecahkan. Saya melakukan ini dengan menginisialisasi daftar panjang 64 oleh yang dan mengatur semua posisi ke nol, jika ada pixel yang sesuai dari tiga tampilan adalah nol. Lalu saya sederhana melihat puzzle dari ketiga arah, dan melihat apakah pandangannya sama dengan pandangan input. Jika ada yang sama, puzzle dapat dipecahkan (kami sudah menemukan solusi terburuk), jika tidak maka tidak dapat dipecahkan.

Lalu saya melakukan pendekatan cabang-dan-terikat untuk menemukan solusi optimal.

Bercabang: Saya memiliki fungsi rekursif. Kedalaman rekursi memberitahu berapa banyak nilai yang sudah diperbaiki. Dalam setiap panggilan fungsi saya memanggil dirinya dua kali, jika nilai pada indeks saat ini adalah 1 (nilai ini bisa 0 atau 1 dalam solusi optimal), atau sekali, jika nilainya 0 (harus nol dalam solusi optimal).

Bounding: Saya menggunakan dua strategi berbeda di sini.

  1. Saya menghitung tampilan dari 3 sisi dalam setiap panggilan fungsi dan mencari apakah masih cocok dengan nilai input. Jika tidak cocok, saya tidak memanggil fungsi secara rekursif.

  2. Saya menyimpan solusi terbaik dalam memori. Itu sudah ada yang lebih tetap di cabang saat ini daripada di solusi terbaik, saya sudah bisa menutup cabang itu. Selain itu saya dapat memperkirakan batas bawah untuk jumlah blok yang diaktifkan, yang tidak diperbaiki. Dan kondisinya menjadi#number of activated fixed blocks + #lower bound of activated blocks (under the not fixed blocks) < #number of activated blocks in the best solution.

Ini kode Python. Ini mendefinisikan fungsi fyang mengharapkan 3 daftar yang berisi 1s dan 0s dan mengembalikan 0 (tidak dapat dipecahkan) atau daftar 1s dan 0s yang mewakili solusi optimal.

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]

Panjang kode adalah 596 byte / karakter. Dan inilah kerangka tesnya:

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

Anda dapat menemukan versi program yang tidak diklik di sini . Ini juga sedikit lebih cepat.

Hasil:

5360 dari 1000000 teka-teki dapat dipecahkan. Total kita membutuhkan 69519 blok. Jumlah blok bervariasi dari 6 blok hingga 18 blok. Teka-teki tersulit membutuhkan waktu sekitar 1 detik untuk dipecahkan. Ini teka-teki dengan benih "347177", yang terlihat seperti

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

dan memiliki solusi optimal dengan 18 kubus. Berikut ini adalah beberapa dari atas untuk masing-masing lapisan:

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

Total waktu berjalan untuk semua kasus uji adalah sekitar 90 detik. Saya menggunakan PyPy untuk menjalankan program saya. CPython (juru bahasa Python default) sedikit lebih lambat, tetapi juga memecahkan semua teka-teki hanya dalam 7 menit.

Anda dapat menemukan output lengkap di sini : Outputnya cukup jelas. Misalnya output untuk puzzle di atas adalah:

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
sumber
3

5360 kasus diselesaikan dengan 69519 blok; 923 byte

Ini juga optimal. Panggil dengan array yang dan nol. Melempar NullPointerExceptioninput yang tidak valid. Beberapa efisiensi dikorbankan untuk golf itu. Itu masih selesai dalam waktu yang wajar untuk semua input uji 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;}}

Strategi:

Saya sebenarnya cukup sering memainkan puzzle ini ketika saya berumur 10 tahun. Ini menggunakan pendekatan saya.

Langkah 1:

Bentuk kubus dengan blok terbanyak yang sesuai dengan tampilan yang diberikan.

Langkah 2:

Buat daftar potongan yang bisa dilepas. (Setiap bagian dengan yang memiliki bagian lain pada baris dalam, kolom dalam, dan balok dalam.)

Langkah 3:

Uji setiap cara yang mungkin untuk menyimpan atau menghapus setiap bagian yang dapat dilepas. (Melalui brute-force rekursif dengan pemangkasan.)

Langkah 4:

Simpan kubus valid terbaik.

Tidak Disatukan:

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;

    }
}

Program lengkap:

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;

        }
    }

}
TheNumberOne
sumber