Bir üst-ön bulmaca çözücü oluşturun


15

Bir üst-ön-bulmaca, üç dikey görünüm verilen (genellikle kübik) blokların 3-B şeklini inşa etmeniz gereken bir bilmecedir: üstten görünüm, önden görünüm ve yandan görünüm.

Örneğin, üst, ön ve yan görünüm aşağıdaki gibi verilir:

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.

2x2x2'lik bir küp (hacim 8 ile) bu çözümü karşılar, ancak aşağıdaki katman yapısına sahipsek 4. hacimde yapılabilir:

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

Çözülemeyen bazı düzenlemeler de vardır. Örneğin, şunu ele alalım:

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

Üst görünüm bloğun soldan ikinci olduğunu söylüyorsa, bloğun soldan üçüncü olması gerektiğini söyleyen ön görünümle eşleşebilecek bir yol yoktur. Yani bu düzenleme imkansız.


Senin görevin, keyfi bir 4x4 ön-ön bulmaca verilen, en az sayıda küp içinde çözmeye çalışan veya çözülemez ilan eden bir program inşa etmektir.

Programınız, üst, ön ve yan görünümleri temsil eden 48 bitlik bir dizi girdi olarak alacaktır. İstediğiniz formatta olabilirler (6 baytlık bir dize, 0'lı ve 1'li bir dize, 12 basamaklı onaltılık bir sayı vb.), Ancak bitlerin sırası şu şekilde eşleşmelidir:

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

Başka bir deyişle, bitler soldan sağa, yukarıdan aşağıya doğru sırada, üstte, sonra önden, sonra yan görünümde gider.

Programınız daha sonra 4x4x4 ızgarasında doldurulmuş küpleri veya ızgaranın çözülemez olduğunu belirten 64 bitlik bir dizi çıkarır.


Programınız 1.000.000 test vakasıyla çalıştırılarak puanlanacaktır.

Test verileri, "000000" ile "999999" arasındaki tamsayıların MD5 karmalarını dize olarak alarak, bu karmaların her birinin ilk 48 bitini (12 hex) çıkararak ve bunları ön-ön- yan bulmaca. Örnek olarak, bazı test girişleri ve ürettikleri bulmacalar:

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

İlk ikisi çözülemezken, son ikisi önden arkaya aşağıdaki katmanlarla bir çözüme sahiptir:

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.

Programınızın puanı, azalan öncelik sırasına göre aşağıdaki ölçütlere göre belirlenir:

  • En çok çözülen dava sayısı.
  • Bu durumları çözmek için gereken en düşük blok sayısı.
  • Bayt cinsinden en kısa kod.

Skoru kendiniz göndermeli ve hesaplamalısınız, bu da programınızın 1.000.000 test vakasının tümünü geçmesini gerektirir.


Bu sorun için test senaryoları oluşturmaya çalışırken daha fazla vakanın çözülemediğini öğrendim. Bunun nasıl ortaya çıkacağını merak ediyorum.
Joe Z.

Bu bir optimizasyon problemi ise, bir zaman sınırı olmalı, bu yüzden insanlar bunu zorlamıyor.
isaacg

Bununla birlikte, zaman sınırı test için uzun sürer. Sonsuza kadar süren bir çözüm asla burada gönderilebilecek bir sonuç üretmez. Yazdığım tüm optimizasyon problemleri böyle işliyor.
Joe Z.


1
@JoeZ. orlp haklı. Md5-to-puzzle dönüşümümde bir hata vardı. 00000-99999'da 551 çözülebilir bulmaca ve 000000-999999'da 5360 çözülebilir bulmaca.
Mart'ta Jakube

Yanıtlar:


5

Python: 5360 test senaryosu, 69519 blok, 594 bayt kullanılarak çözüldü

Bu en uygun değerler olmalıdır.

Yaklaşmak:

İlk olarak, test senaryosunun gerçek çözülebilir olup olmadığını test edeceğim. Bunu, üç görünümün karşılık gelen pikseli sıfırsa, uzunluk 64'ü bir liste başlatarak ve tüm konumları sıfıra ayarlayarak yaparım. Daha sonra 3 yönden bulmacayı basit bir şekilde görüyorum ve görünümlerin giriş görünümleriyle aynı olup olmadığına bakıyorum. Eşit varsa, bulmaca çözülebilir (zaten en kötü çözümü bulduk), aksi takdirde çözülemez.

Daha sonra en uygun çözümü bulmak için branşman-sınır yaklaşımı yaparım.

Dallanma: Yinelemeli bir fonksiyonum var. Özyineleme derinliği zaten kaç değerin sabit olduğunu belirtir. Fonksiyonun her çağrısında kendisini iki kez çağırırım, eğer mevcut indeksteki değer 1 ise (bu değer optimal çözümde 0 veya 1 olabilir) veya bir kez, değer 0 ise (sıfırda sıfır olmalıdır) en uygun çözüm).

Sınırlayıcı: Burada iki farklı strateji kullanıyorum.

  1. Her bir işlev çağrısındaki 3 taraftan görünümleri hesaplar ve giriş değerleriyle hala eşleşiyorlarsa bakın. Eşleşmezlerse, işlevi özyinelemeli olarak çağırmam.

  2. En iyi çözümü bellekte saklıyorum. Mevcut dalda en iyi çözümden daha fazla sabit olanlar var, o dalı zaten kapatabilirim. Ek olarak, sabit olmayan aktif blokların sayısı için bir alt sınır tahmin edebilirim. Ve durum olur#number of activated fixed blocks + #lower bound of activated blocks (under the not fixed blocks) < #number of activated blocks in the best solution.

İşte Python kodu. f1s ve 0s içeren 3 listeyi bekleyen bir işlev tanımlar ve 0 (çözülemez) veya en uygun çözümü temsil eden 1s ve 0s listesini döndürür.

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]

Kod uzunluğu 596 bayt / karakterdir. Ve işte test çerçevesi:

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

Programın çözülmemiş bir sürümünü burada bulabilirsiniz . Aynı zamanda biraz daha hızlı.

Sonuçlar:

1000000 bulmacanın 5360'ı çözülebilir. Toplamda 69519 bloğa ihtiyacımız var. Blok sayısı 6 bloktan 18 bloğa kadar değişmektedir. En zor bulmacanın çözülmesi yaklaşık 1 saniye sürdü. Bu tohum ile bulmaca "347177", gibi görünüyor

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

ve 18 küp ile en uygun çözümü sunar. Katmanların her biri için aşağıdakiler üstten birkaçıdır:

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

Tüm test senaryoları için toplam çalışma süresi yaklaşık 90 saniyedir. Programımı yürütmek için PyPy kullandım. CPython (varsayılan Python yorumlayıcısı) biraz daha yavaştır, ancak tüm bulmacaları sadece 7 dakikada çözer.

Çıktının tamamını burada bulabilirsiniz : Çıktı kendinden açıklayıcıdır. Yukarıdaki bulmaca için çıktı:

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

535 vaka 69519 blokla çözüldü; 923 bayt

Bu da en uygunudur. Bir ve sıfırlar dizisiyle arayın. NullPointerExceptionGeçersiz giriş için a atar . Golf için bir miktar verimlilik feda edilir. Tüm 1000000 test girişleri için hala makul bir sürede tamamlanır.

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

Strateji:

Aslında bu bulmacayı 10 yaşındayken oynardım. Bu benim yaklaşımımı kullanıyor.

Aşama 1:

Küpü, verilen görünümlere uyan en fazla blokla oluşturun.

Adım 2:

Çıkarılabilir parçaların bir listesini oluşturun. (İçindeki satırda başka bir parça olan sütun, içinde sütun ve ışın içinde.)

Aşama 3:

Çıkarılabilir her parçayı tutmak veya çıkarmak için mümkün olan her yolu test edin. (Budama ile özyinelemeli kaba kuvvet yoluyla.)

4. Adım:

En iyi geçerli küpü saklayın.

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;

    }
}

Tam program:

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;

        }
    }

}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.