最前面のパズルソルバーを構築する


15

トップフロントサイドパズルは、3つの直交ビュー(上面図、正面図、側面図)が与えられた(通常は立方体)ブロックの3D形状を構築する必要があるパズルです。

たとえば、次のように上面図、正面図、および側面図を指定します。

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キューブ(ボリューム8)はこのソリューションを満たしますが、次のレイヤー構造があれば、ボリューム4でも実行可能です。

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

解決不可能な取り決めもあります。例:

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

上面図でブロックが左から2番目であると表示されている場合、ブロックが左から3番目でなければならないという正面図と一致する方法はありません。したがって、この配置は不可能です。


あなたの仕事は、任意の4x4のトップフロントサイドパズルが与えられた場合、それを最小数のキューブで解決しようとするか、解決できないと宣言するプログラムを構築することです。

プログラムは、上面、正面、側面のビューを表す一連の48ビットを入力として受け取ります。任意の形式(6バイトの文字列、0と1の文字列、12桁の16進数など)を使用できますが、ビットの順序は次のようにマッピングする必要があります。

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

言い換えると、ビットは左から右、上から下、上、次に正面、側面の順に表示されます。

プログラムは、4x4x4グリッド内の塗りつぶされたキューブを示す一連の64ビットを出力するか、グリッドが解決できないことを示します。


プログラムは、1,000,000のテストケースのバッテリーを実行することで採点されます。

テストデータは、整数 "000000"から "999999"のMD5ハッシュを文字列として取得し、これらの各ハッシュの最初の48ビット(12ヘキシット)を抽出し、それらをトップフロントの入力として使用して生成されます。サイドパズル。例として、テスト入力とそれらが生成するパズルの一部を次に示します。

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

最初の2つは解決できませんが、最後の1つは前から後ろに次のレイヤーのソリューションがあります。

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.

プログラムのスコアは、優先度の高い順に、次の基準によって決定されます。

  • 解決されたケースの最大数。
  • これらのケースを解決するために必要なブロックの最小数。
  • バイト単位の最短コード。

自分でスコアを送信して計算する必要があります。これには、プログラムが1,000,000件すべてのテストケースを実行できる必要があります。


この問題のテストケースを生成しようとしたときに、解決できないケースの数が多いことを知りました。これがどうなるのだろうか。
ジョーZ.

最適化の問題である場合は、制限時間を設けて、人々がそれをブルートフォースしないようにします。
isaacg

ただし、制限時間はテストに時間がかかります。実行に永遠にかかるソリューションは、ここに投稿できる結果を決して生成しません。これが、私が書いたすべての最適化問題の仕組みです。
ジョーZ.


1
@JoeZ。orlpは正しい。md5からパズルへの変換にバグがありました。00000-99999の551の解けるパズルと000000-999999の5360の解けるパズル。
ジャクベ

回答:


5

Python:69519ブロック、594バイトを使用して解決された5360テストケース

これが最適な値です。

アプローチ:

最初に、テストケースが実際に解決可能かどうかをテストします。これを行うには、長さ64のリストを1で初期化し、3つのビューの対応するピクセルがゼロである場合、すべての位置をゼロに設定します。次に、3つの方向すべてからパズルを簡単に表示し、ビューが入力ビューと同じかどうかを確認します。等しい場合、パズルは解決可能です(最悪の解決策をすでに見つけました)、それ以外の場合は解決できません。

次に、最適なソリューションを見つけるための分岐限定アプローチを行います。

分岐:再帰関数があります。再帰の深さは、すでに固定されている値の数を示します。関数の各呼び出しで、現在のインデックスの値が1の場合(最適なソリューションでは0または1になります)、値が0の場合は1回(呼び出しで0である必要があります)最適なソリューション)。

境界:ここでは2つの異なる戦略を使用します。

  1. 各関数呼び出しで3つの側面からビューを計算し、それらがまだ入力値と一致するかどうかを確認します。それらが一致しない場合、関数を再帰的に呼び出しません。

  2. 私は最高のソリューションをメモリに保持しています。現在のブランチには、最適なソリューションよりも多くの修正済みのものが既に存在するため、そのブランチを既に閉じることができます。さらに、アクティブ化されたブロックの数の下限を見積もることができますが、これは修正されていません。そして条件は#number of activated fixed blocks + #lower bound of activated blocks (under the not fixed blocks) < #number of activated blocks in the best solution.

Pythonコードは次のとおりです。これは、f1と0を含む3つのリストを予期し、0(解けない)または最適なソリューションを表す1と0のリストを返す関数を定義します。

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]

コード長は596バイト/文字です。テストフレームワークは次のとおりです。

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

ここで、プログラムの無料版を見つけることができます。また、少し高速です。

結果:

1000000のパズルのうち5360は解ける。合計で69519ブロックが必要です。ブロック数は6ブロックから18ブロックまでさまざまです。最も難しいパズルを解くには約1秒かかりました。それ"347177"は次のようなシードを持つパズルです

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

18個のキューブを使用した最適なソリューションがあります。以下は、各レイヤーの上からの一部です。

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

すべてのテストケースの合計実行時間は約90秒でした。PyPyを使用してプログラムを実行しました。CPython(デフォルトのPythonインタープリター)は少し遅いですが、すべてのパズルをわずか7分で解決します。

完全な出力はここで見つけることができます:出力は自明です。たとえば、上記のパズルの出力は次のとおりです。

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

69519ブロックで5360ケースが解決されました。923バイト

これも最適です。1と0の配列で呼び出します。NullPointerException無効な入力に対してa をスローします。ゴルフではある程度の効率が犠牲になります。すべての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;}}

戦略:

私は10歳のときにこのパズルを実際にかなりプレイしていました。これは私のアプローチを使用しています。

ステップ1:

指定されたビューに適合する最も多くのブロックでキューブを形成します。

ステップ2:

取り外し可能なピースのリストを作成します。(それを持つすべてのピースには、行に別のピースがあり、列にはインがあり、ビームにはインがあります。)

ステップ3:

取り外し可能な各ピースを保持または削除するためのあらゆる可能な方法をテストします。(プルーニングを使用した再帰的ブルートフォース経由)

ステップ4:

最高の有効なキューブを維持してください。

ゴルフをしていない:

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;

    }
}

完全なプログラム:

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;

        }
    }

}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.