Построить решатель головоломок с верхней лицевой стороны


15

Головоломка сверху-спереди - это головоломка, в которой вам необходимо построить трехмерную (обычно кубическую) форму блоков, учитывая три ортогональных вида: вид сверху, вид спереди и вид сбоку.

Например, дан вид сверху, спереди и сбоку следующим образом:

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-значное шестнадцатеричное число и т. Д.), Но порядок битов должен отображаться следующим образом:

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

Другими словами, биты идут в порядке слева направо, сверху вниз, сверху, затем спереди, затем сбоку.

Затем ваша программа выведет либо серию из 64 битов, указывающую заполненные кубы в сетке 4x4x4, либо указывает, что сетка неразрешима.


Ваша программа будет оценена путем запуска батареи из 1 000 000 тестовых случаев.

Тестовые данные будут получены путем взятия MD5-хешей целых чисел от «000000» до «999999» в качестве строк, извлечения первых 48 бит (12 шестнадцатеричных чисел) каждого из этих хешей и использования их в качестве входных данных для top-front- боковая загадка. В качестве примера, вот некоторые из тестовых входов и головоломок, которые они генерируют:

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 тестовых случаев.


При попытке создать контрольные примеры для этой проблемы я узнал, что больше случаев неразрешимо, чем нет. Интересно, как это получится.
Джо З.

Если это проблема оптимизации, должно быть ограничение по времени, чтобы люди не переборщили.
Исаак

Тем не менее, для проверки этого времени требуется много времени. Решение, которое работает вечно, никогда не даст результата, который можно опубликовать здесь. Вот как работают все проблемы оптимизации, которые я пишу.
Джо З.


1
@JoeZ. Орлп прав. У меня была ошибка в моем преобразовании md5 в головоломку. 551 разрешимых головоломок в 00000-99999 и 5360 разрешимых головоломок в 000000-999999.
Якуб

Ответы:


5

Python: 5360 тестовых случаев, решенных с использованием 69519 блоков, 594 байта

Это должны быть оптимальные значения.

Подходить:

Сначала я проверим, является ли тестовый пример действительно разрешимым. Я делаю это, инициализируя список длиной 64 единицами и устанавливая все позиции в ноль, если там соответствующий пиксель трех представлений равен нулю. Затем я просто смотрю на головоломку со всех трех направлений и смотрю, совпадают ли виды с входными. Если они равны, головоломка разрешима (мы уже нашли худшее решение), в противном случае она неразрешима.

Затем я делаю ветвистый подход для нахождения оптимального решения.

Ветвление: меня есть рекурсивная функция. Глубина рекурсии говорит о том, сколько значений уже зафиксировано. При каждом вызове функции я вызываю себя дважды, если значение в текущем индексе равно 1 (это значение может быть 0 или 1 в оптимальном решении), или один раз, если значение равно 0 (оно должно быть равно нулю в Оптимальным решением).

Ограничение: здесь я использую две разные стратегии.

  1. Я вычисляю виды с трех сторон в каждом вызове функции и смотрю, соответствуют ли они входным значениям. Если они не совпадают, я не вызываю функцию рекурсивно.

  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. Он определяет функцию, fкоторая ожидает 3 списка, содержащие 1 и 0, и возвращает либо 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))

Вы можете найти версию программы без гольфа здесь . Это также немного быстрее.

Результаты:

5360 из 1000000 головоломок разрешимы. Всего нам нужно 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

5360 дел решено 69519 блоками; 923 байта

Это тоже оптимально. Вызов с массивом единиц и нулей. Выдает 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:

Держите лучший действительный куб.

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;

    }
}

Полная программа:

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;

        }
    }

}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.