전면 퍼즐 해결사 구축


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

상단 뷰에서 블록이 왼쪽에서 두 번째라고 표시되면 블록이 왼쪽에서 세 번째 여야한다는 전면 뷰와 일치하는 방법이 없습니다. 따라서이 배열은 불가능합니다.


당신의 임무는 임의의 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

처음 두 개는 해결할 수 없지만 마지막 두 개는 앞뒤로 다음 레이어가있는 솔루션을 가지고 있습니다.

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 개의 모든 테스트 사례를 실행할 수 있어야 점수를 직접 제출하고 계산해야합니다.


이 문제에 대한 테스트 사례를 생성하는 동안 더 많은 경우를 해결할 수 없다는 것을 배웠습니다. 이것이 어떻게 될지 궁금합니다.
Joe Z.

최적화 문제라면 시간 제한이 있어야하므로 사람들은 그것을 강제하지 않습니다.
isaacg

그러나 시간 제한은 테스트에 시간이 오래 걸립니다. 영원히 실행되는 솔루션은 여기에 게시 할 수있는 결과를 생성하지 않습니다. 그것이 내가 쓰는 모든 최적화 문제가 작동하는 방식입니다.
Joe Z.


1
@JoeZ. orlp가 맞습니다. md5-to-puzzle 변환에 버그가있었습니다. 00000-99999의 551 개의 해결 가능한 퍼즐과 000000-999999의 5360 개의 해결 가능한 퍼즐.
Jakube

답변:


5

Python : 69,519 개의 블록, 594 바이트를 사용하여 해결 된 5360 개의 테스트 사례

이것이 최적의 값이어야합니다.

접근하다:

먼저 테스트 케이스가 실제로 해결 가능한지 테스트합니다. 세 개의 뷰 중 해당 픽셀이 0이면 길이 64의 목록을 1로 초기화하고 모든 위치를 0으로 설정 하여이 작업을 수행합니다. 그런 다음 3 방향에서 퍼즐을 간단하게보고 입력 된 뷰와 동일한 지 확인합니다. 같으면 퍼즐을 풀 수 있습니다 (이미 최악의 해결책을 찾았습니다). 그렇지 않으면 해결할 수 없습니다.

그런 다음 최적의 솔루션을 찾기 위해 분기 및 접근 방식을 사용합니다.

분기 : 재귀 함수가 있습니다. 재귀 깊이는 얼마나 많은 값이 이미 고정되어 있는지 알려줍니다. 함수의 각 호출에서 현재 인덱스의 값이 1 (최적의 솔루션 에서이 값은 0 또는 1 일 수 있음)이거나 값이 0 인 경우 한 번 (자신의 값은 0이어야 함) 최적의 솔루션).

경계 : 나는 두 가지 다른 전략을 사용합니다.

  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.

다음은 파이썬 코드입니다. 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))

ungolfed 버전의 프로그램은 여기 에서 찾을 수 있습니다 . 또한 조금 더 빠릅니다.

결과 :

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으로 구성된 배열로 호출하십시오. A는 예외 NullPointerException유효하지 않은 입력. 골프를하기 위해 약간의 효율성이 희생됩니다. 모든 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.