Найти все решения этой головоломки в кратчайшие сроки

16

история

Моя компания рассылает еженедельную рассылку всем сотрудникам компании. В эти новостные рассылки включена загадка, а также крик тому, кто в компании первым отправил электронное письмо / предоставил решение загадки на прошлой неделе. Большинство из этих загадок довольно тривиальны и, честно говоря, довольно скучны для технической компании, но один, несколько месяцев назад, привлек мое внимание.

Оригинальная загадка:

Учитывая форму ниже:

Изображение головоломки

У вас есть натуральные числа от 1 до 16. Подберите их все так, чтобы все смежные ряды и смежные столбцы суммировались до 29.

Например, одним из таких решений этой головоломки (которое было «каноническим» решением, которое я отправил в информационный бюллетень) было следующее:

Решенная головоломка

Однако в процессе ее решения я нашел довольно интересную информацию:

  • Есть значительно больше решений, чем только это; На самом деле существует 9368 решений.
  • Если развернуть набор правил, чтобы требовать, чтобы только строки и столбцы были равны друг другу, а не обязательно 29, вы получите 33 608 решений:
    • 4440 решений на сумму 27.
    • 7400 решений на сумму 28.
    • 9 368 решений на сумму 29.
    • 6 096 Решений на сумму 30.
    • 5,104 решения на сумму 31.
    • 1200 решений на сумму 32.

Поэтому я и мои коллеги (хотя в основном только мой менеджер, так как он был единственным человеком, кроме меня, обладающим навыками программирования «Общего назначения») поставили перед собой задачу, которая длилась большую часть месяца - у нас была другая, реальная работа - сопутствующие обязательства, которые мы должны были выполнить - попытаться написать программу, которая найдет каждое решение максимально быстрым способом.

Исходная статистика

Самая первая программа, которую я написал для решения проблемы, просто проверяла случайные решения снова и снова и останавливалась, когда находила решение. Если вы провели математический анализ этой проблемы, вы, вероятно, уже знаете, что это не должно сработать; но как-то мне повезло, и программе понадобилось всего минуту, чтобы найти единственное решение (то, которое я выложил выше). Повторные запуски программы часто занимали целых 10 или 20 минут, поэтому очевидно, что это не было строгим решением проблемы.

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

Используя этот алгоритм, я получил первый «правильный» успех: программа могла сгенерировать и выплюнуть все 33 608 решений примерно за 5 минут.

У моего менеджера был другой подход: зная, основываясь на моей работе, что единственно возможные решения имели суммы 27, 28, 29, 30, 31 или 32, он написал многопоточное решение, которое проверяло возможные суммы только для этих конкретных значений. Ему удалось запустить свою программу всего за 2 минуты. Так что я повторил еще раз; Я хэшировал все возможные суммы в 3/4 цифры (в начале программы; она учитывается в общем времени выполнения) и использовал «частичную сумму» строки, чтобы найти оставшееся значение на основе ранее заполненной строки, а не протестировал все оставшиеся значения и сократил время до 72 секунд. Затем, используя некоторую многопоточную логику, я сократил ее до 40 секунд. Мой менеджер забрал программу домой, выполнил некоторые оптимизации работы программы и довел ее до 12 секунд. Я переупорядочил оценку строк и столбцов,

Самые быстрые наши программы получили за месяц 0,15 секунды для моего менеджера и 0,33 секунды для меня. Я закончил тем, что утверждал, что моя программа была быстрее, так как программа моего менеджера, хотя и находила все решения, не печатала их в текстовый файл. Если он добавил эту логику в свой код, это часто занимало более 0,4-0,5 секунд.

С тех пор мы позволили существовать нашему внутриличностному вызову, но, конечно, остается вопрос: можно ли сделать эту программу быстрее?

Это вызов, который я собираюсь поставить перед вами, ребята.

Ваш вызов

Параметры, с которыми мы работали, ослабили правило «сумма 29», чтобы вместо них быть «равными суммам всех строк / столбцов», и я собираюсь установить это правило и для вас, ребята. Задача, следовательно, заключается в следующем: написать программу, которая находит (и печатает!) Все решения этой загадки в кратчайшие сроки. Я собираюсь установить предел для представленных решений: если программа занимает более 10 секунд на относительно приличном компьютере (<8 лет), вероятно, она слишком медленная для подсчета.

Также у меня есть несколько бонусов за головоломку:

  • Можете ли вы обобщить решение так, чтобы оно работало для любого набора из 16 чисел, а не только int[1,16]? Оценка времени будет оцениваться на основе исходного набора номеров подсказок, но проходить через этот кодовый путь. (-10%)
  • Можете ли вы написать код таким образом, чтобы он изящно обрабатывал и решал дублирующиеся числа? Это не так просто, как может показаться! Решения, которые «визуально идентичны», должны быть уникальными в наборе результатов. (-5%)
  • Можете ли вы обрабатывать отрицательные числа? (-5%)

Вы также можете попытаться сгенерировать решение, которое обрабатывает числа с плавающей запятой, но, конечно, не удивляйтесь, если это не удастся полностью. Если вы найдете надежное решение, это может стоить большого бонуса!

По сути, «Вращения» считаются уникальными решениями. Таким образом, решение, представляющее собой просто вращение другого решения, считается его собственным решением.

Средами разработки, на которых я работаю, являются Java и C ++. Я могу принимать ответы на других языках, но вам может понадобиться указать ссылку, где я могу получить простую в настройке среду выполнения для вашего кода.

Xirema
источник
3
Святые коты, хороший первый вопрос! ... за исключением бонусов, которые мы как-то обескураживаем (в основном по вопросам код-гольфа , поэтому с ними все должно быть в порядке)
кошка
4
@cat Я думаю, что здесь бонусы имеют смысл, потому что когда я решал эти проблемы в своем собственном коде, они имели тенденцию вызывать значительное замедление работы кода. Поэтому я думаю, что бонус для того, чтобы оправдать включение.
Xirema
2
Кстати, есть ли работа у вас дома? Похоже, у вас спокойный босс и много времени на руках :-)
Level River St
1
С двойными номерами можно ли печатать дубликаты решений, в которых происходит обмен двумя одинаковыми номерами? это имело бы большое значение для того, как интерпретируется этот бонус. Пожалуйста, уточните, но я хотел бы отменить этот бонус в целом.
Уровень Река St
1
Кроме того, считается ли вращение на 180 градусов одним и тем же решением или разными?
Уровень Река St

Ответы:

7

C - около 0,5 сек

Эта очень наивная программа дает все решения за полсекунды на моем 4-летнем ноутбуке. Нет многопоточности, нет хеширования.

Windows 10, Visual Studio 2010, процессорное ядро ​​I7 64 бит

Попробуйте онлайн на ideone

#include <stdio.h>
#include <time.h>

int inuse[16];
int results[16+15+14];

FILE *fout;

int check(int number)
{
    if (number > 0 && number < 17 && !inuse[number-1])
    {
        return inuse[number-1]=1;
    }
    return 0;
}

void free(int number)
{
    inuse[number-1]=0;
}

void out(int t, int* p)
{
    int i;
    fprintf(fout, "\n%d",t);
    for(i=0; i< 16; i++) fprintf(fout, " %d",*p++);
}

void scan() 
{
    int p[16];
    int t,i;
    for (p[0]=0; p[0]++<16;) if (check(p[0]))
    {
        for (p[1]=0; p[1]++<16;) if (check(p[1]))
        {
            for (p[2]=0; p[2]++<16;) if (check(p[2]))
            {
                t = p[0]+p[1]+p[2]; // top horiz: 0,1,2
                for (p[7]=0; p[7]++<16;) if (check(p[7]))
                {
                    if (check(p[11] = t-p[7]-p[2])) // right vert: 2,7,11
                    {
                        for(p[9]=0; p[9]++<16;) if (check(p[9]))
                        {
                            for (p[10]=0; p[10]++<16;) if (check(p[10]))
                            {
                                if (check(p[12] = t-p[9]-p[10]-p[11])) // right horiz: 9,10,11,12
                                {
                                    for(p[6]=0; p[6]++<16;) if (check(p[6]))
                                    {
                                        if (check(p[15] = t-p[0]-p[6]-p[9])) // middle vert: 0,6,9,15
                                        {
                                            for(p[13]=0; p[13]++<16;) if (check(p[13]))
                                            {
                                                if (check(p[14] = t-p[13]-p[15])) // bottom horiz:  13,14,15
                                                {
                                                    for(p[4]=0; p[4]++<16;) if (check(p[4]))
                                                    {
                                                        if (check(p[8] = t-p[4]-p[13])) // left vert: 4,8,13
                                                        {
                                                            for(p[3]=0; p[3]++<16;) if (check(p[3]))
                                                            {
                                                                if (check(p[5] = t-p[3]-p[4]-p[6])) // left horiz: 3,4,5,6
                                                                {
                                                                    ++results[t];
                                                                    out(t,p);
                                                                    free(p[5]);
                                                                }
                                                                free(p[3]);
                                                            }
                                                            free(p[8]);
                                                        }
                                                        free(p[4]);
                                                    }
                                                    free(p[14]);
                                                }
                                                free(p[13]);
                                            }
                                            free(p[15]);
                                        }
                                        free(p[6]);
                                    }
                                    free(p[12]);
                                }
                                free(p[10]);
                            }
                            free(p[9]);
                        }
                        free(p[11]);
                    }
                    free(p[7]);
                }    
                free(p[2]);
            } 
            free(p[1]);
        }
        free(p[0]);
    }
    for(i=0;i<15+16+14;i++)
    {
        if(results[i]) printf("%d %d\n", i, results[i]);
    }
}

void main()
{
    clock_t begin, end;
    double time_spent;
    begin = clock();

    fout = fopen("c:\\temp\\puzzle29.txt", "w");
    scan();
    fclose(fout);

    end = clock();
    time_spent = (double)(end - begin) / CLOCKS_PER_SEC;
    printf("Time %g sec\n", time_spent);
}
edc65
источник
Святой сладкий Злой Иисус Вампир, гнездящийся для петель. Могу поспорить, что это сделало ваш компилятор действительно счастливым. XD
Xirema
@ edc65, к вашему сведению, вы можете заменить int inuse[16];просто, int inuse;а затем использовать побитовые операторы для управления им. Это не похоже , чтобы увеличить скорость , что много, но это помогает мало.
Эндрю Эпштейн
@AndrewEpstein это могло бы даже стать медленнее - битовое смещение против индексации
edc65
@ edc65, я взял на себя смелость использовать dumbbench, чтобы проверить твою оригинальную версию против версии с бит-смещением. Вот результаты: Индексирование: 0,2253 +/- 5,7590e-05 Битшифтинг: 0,2093 +/- 6,6595e-05 Итак, на моей машине примерно 16 мс ускорения. Команда, которую я использовал, была:dumbbench --precision=.01 -vvv --initial=500 ./solve
Эндрю Эпштейн
3

C ++ - 300 миллисекунд

По запросу я добавил свой код для решения этой головоломки. На моем компьютере он срабатывает в среднем за 0,310 секунды (310 миллисекунд), но в зависимости от дисперсии может работать так же быстро, как 287 миллисекунд. Я очень редко вижу, что он поднимается выше 350 миллисекунд, обычно только если моя система перегружена другой задачей.

Эти времена основаны на самоотчете, используемом в программе, но я также проверил с использованием внешнего таймера и получил аналогичные результаты. Накладные расходы в программе, кажется, добавляют около 10 миллисекунд.

Кроме того, мой код не совсем правильно обрабатывает дубликаты. Он может решить их использование, но не исключает «визуально идентичные» решения из набора решений.

#include<iostream>
#include<vector>
#include<random>
#include<functional>
#include<unordered_set>
#include<unordered_map>
#include<array>
#include<thread>
#include<chrono>
#include<fstream>
#include<iomanip>
#include<string>
#include<mutex>
#include<queue>
#include<sstream>
#include<utility>
#include<atomic>
#include<algorithm>

//#define REDUCE_MEMORY_USE

typedef std::pair<int, std::vector<std::pair<int, int>>> sumlist;
typedef std::unordered_map<int, std::vector<std::pair<int, int>>> summap;
typedef std::array<int, 16> solution_space;

class static_solution_state {
public:
    std::array<int, 16> validNumbers;
    summap twosums;
    size_t padding;
    std::string spacing;

    static_solution_state(const std::array<int, 16> & _valid);

    summap gettwovaluesums();
    std::vector<sumlist> gettwovaluesumsvector();
};

static_solution_state::static_solution_state(const std::array<int, 16> & _valid) 
    : validNumbers(_valid) {
    twosums = gettwovaluesums();
    padding = 0;
    for (int i = 0; i < 16; i++) {
        size_t count = std::to_string(validNumbers[i]).size();
        if (padding <= count) padding = count + 1;
    }
    spacing.resize(padding, ' ');
}

class solution_state {
private:
    const static_solution_state * static_state;
public:
    std::array<int, 16> currentSolution;
    std::array<bool, 16> used;
    std::array<int, 7> sums;
    size_t solutions_found;
    size_t permutations_found;
    size_t level;
    std::ostream * log;

    solution_state(const static_solution_state & _sstate);
    solution_state(static_solution_state & _sstate) = delete;
    void setLog(std::ostream & out);
    const int & operator[](size_t index) const;

};

solution_state::solution_state(const static_solution_state & _sstate) {
    static_state = &_sstate;
    sums = { 0 };
    used = { false };
    currentSolution = { -1 };
    solutions_found = 0;
    permutations_found = 0;
    level = 0;
}

void solution_state::setLog(std::ostream & out) {
    log = &out;
}

const int & solution_state::operator[](size_t index) const {
    return static_state->validNumbers[currentSolution[index]];
}

int getincompletetwosum(const static_solution_state & static_state, const solution_state & state);
void permute(const static_solution_state & static_state, solution_state & state, volatile bool & reportProgress, const volatile size_t & total_tests, volatile bool & done);
void setupOutput(std::fstream & out);
void printSolution(const static_solution_state & static_state, const solution_state & state);
constexpr size_t factorial(const size_t iter);

const bool findnext2digits[16]{
    false, false, false,
    true, false,
    false, true, false,
    true, false,
    true, false,
    true, false,
    true, false
};

const int currentsum[16]{
    0, 0, 0,
    1, 1,
    2, 2, 2,
    3, 3,
    4, 4,
    5, 5,
    6, 6
};

const int twosumindexes[7][2]{
    { 0, -1},
    { 2, -1},
    { 5, -1},
    { 5, -1},
    { 0,  7},
    { 11, 4},
    { 10, 9}
};

const std::array<size_t, 17> facttable = [] {
    std::array<size_t, 17> table;
    for (int i = 0; i < 17; i++) table[i] = factorial(i);
    return table;
}();

const int adj = 1;

std::thread::id t1id;

int main(int argc, char** argv) {
    //std::ios_base::sync_with_stdio(false);
    std::array<int, 16> values = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 };
    if (argc == 17) {
        for (int i = 0; i < 16; i++) {
            values[i] = atoi(argv[i + 1]);
        }
    }
    auto start = std::chrono::high_resolution_clock::now();
    const static_solution_state static_state(values);
#if defined(REDUCE_MEMORY_USE)
    const int num_of_threads = max(1u, min(thread::hardware_concurrency(), 16u));
#else
    const int num_of_threads = 16;
#endif
    std::vector<solution_state> states(num_of_threads, static_state);
    for (int i = 0; i < num_of_threads; i++) {
        int start = i * 16 / num_of_threads;
        states[i].permutations_found += start * factorial(16) / 16;
    }
    std::fstream out;
    setupOutput(out);
    std::locale loc("");
    std::cout.imbue(loc);
    volatile bool report = false;
    volatile bool done = false;
    volatile size_t tests = 0;

    std::thread progress([&]() {
        auto now = std::chrono::steady_clock::now();
        while (!done) {
            if (std::chrono::steady_clock::now() - now > std::chrono::seconds(1)) {
                now += std::chrono::seconds(1);

                size_t t_tests = 0;
                for (int i = 0; i < num_of_threads; i++) t_tests += states[i].permutations_found - i * factorial(16) / num_of_threads;
                tests = t_tests;
                report = true;
            }
            std::this_thread::yield();
        }
    });

    if (num_of_threads <= 1) {


        states[0].setLog(out);
        permute(static_state, states[0], report, tests, done);


    } 
    else {
        std::vector<std::thread> threads;
#if defined(REDUCE_MEMORY_USE)
        std::vector<std::fstream> logs(num_of_threads);
#else
        std::vector<std::stringstream> logs(num_of_threads);
#endif
        for (int i = 0; i < num_of_threads; i++) {
            threads.emplace_back([&, i]() {
                if (i == 0) t1id = std::this_thread::get_id();
                int start = i * 16 / num_of_threads;
                int end = (i + 1) * 16 / num_of_threads;
#if defined(REDUCE_MEMORY_USE)
                logs[i].open("T"s + to_string(i) + "log.tmp", ios::out);
#endif
                logs[i].imbue(loc);
                states[i].setLog(logs[i]);

                for (int j = start; j < end; j++) {


                    states[i].currentSolution = { j };
                    states[i].level = 1;
                    states[i].used[j] = true;
                    permute(static_state, states[i], report, tests, done);


                }
            });
        }

        std::string buffer;
        for (int i = 0; i < num_of_threads; i++) {
            threads[i].join();
#if defined(REDUCE_MEMORY_USE)
            logs[i].close();
            logs[i].open("T"s + to_string(i) + "log.tmp", ios::in);
            logs[i].seekg(0, ios::end);
            auto length = logs[i].tellg();
            logs[i].seekg(0, ios::beg);
            buffer.resize(length);
            logs[i].read(&buffer[0], length);
            logs[i].close();
            remove(("T"s + to_string(i) + "log.tmp").c_str());
            out << buffer;
#else
            out << logs[i].str();
#endif
        }
    }
    done = true;
    out.close();

    if (num_of_threads > 1) {
        size_t t_tests = 0;
        for (int i = 0; i < num_of_threads; i++) t_tests += states[i].permutations_found - i * factorial(16) / num_of_threads;
        tests = t_tests;
    }

    size_t solutions = 0;
    for (const auto & state : states) {
        solutions += state.solutions_found;
    }

    auto end = std::chrono::high_resolution_clock::now();

    progress.join();

    auto duration = end - start;
    auto secondsDuration = std::chrono::duration_cast<std::chrono::milliseconds>(duration);
    std::cout << "Total time to process all " << tests << " results: " << std::setprecision(3) << std::setiosflags(std::ostream::fixed) << (secondsDuration.count()/1000.0) << "s" << "\n";
    std::cout << "Solutions found: " << solutions << std::endl;
    //system("pause");
    return 0;
}

void permute(const static_solution_state & static_state, solution_state & state, volatile bool & reportProgress, const volatile size_t & total_tests, volatile bool & done) {
    if (done) return;
    if (state.level >= 16) {
        if (reportProgress) {
            reportProgress = false;
            std::cout << "Current Status:" << "\n";
            std::cout << "Test " << total_tests << "\n";
            std::cout << "Contents: {";
            for (int i = 0; i < 15; i++) std::cout << std::setw(static_state.padding - 1) << state[i] << ",";
            std::cout << std::setw(static_state.padding - 1) << state[15] << "}" << "(Partial Sum: " << state.sums[0] << ")" << "\n";
            std::cout << "=====================" << "\n";
        }
        printSolution(static_state,state);
        state.solutions_found++;
        state.permutations_found++;
    }
    else {
        if (state.level == 3) state.sums[0] = state[0] + state[1] + state[2];

        if (!findnext2digits[state.level]) {
            for (int i = 0; i < 16; i++) {
                if (!state.used[i]) {
                    state.currentSolution[state.level] = i;
                    state.used[i] = true;
                    state.level++;
                    permute(static_state, state, reportProgress, total_tests, done);
                    state.level--;
                    state.used[i] = false;
                }
            }
        }
        else {
            int incompletetwosum = getincompletetwosum(static_state, state);
            if (static_state.twosums.find(incompletetwosum) == static_state.twosums.end()) {
                state.permutations_found += facttable[16 - state.level];
            }
            else {
                size_t successes = 0;
                const std::vector<std::pair<int, int>> & potentialpairs = static_state.twosums.at(incompletetwosum);
                for (const std::pair<int, int> & values : potentialpairs) {
                    if (!state.used[values.first] && !state.used[values.second]) {
                        state.currentSolution[state.level] = values.first;
                        state.currentSolution[state.level + 1] = values.second;
                        state.used[values.first] = true;
                        state.used[values.second] = true;
                        state.level += 2;
                        permute(static_state, state, reportProgress, total_tests, done);
                        state.level -= 2;
                        state.used[values.first] = false;
                        state.used[values.second] = false;

                        successes++;
                    }
                }
                state.permutations_found += facttable[16 - state.level - 2] * ((16 - state.level) * (15 - state.level) - successes); 
            }
        }
    }
}

int getincompletetwosum(const static_solution_state & static_state, const solution_state & state) {
    int retvalue = state.sums[0];
    int thissum = currentsum[state.level];
    for (int i = 0; i < 2 && twosumindexes[thissum][i] >= 0; i++) {
        retvalue -= state[twosumindexes[thissum][i]];
    }
    return retvalue;
}

constexpr size_t factorial(size_t iter) {
    return (iter <= 0) ? 1 : iter * factorial(iter - 1);
}

void setupOutput(std::fstream & out) {
    out.open("puzzle.txt", std::ios::out | std::ios::trunc);
    std::locale loc("");
    out.imbue(loc);
}

void printSolution(const static_solution_state & static_state, const solution_state & state) {
    std::ostream & out = *state.log;
    out << "Test " << state.permutations_found << "\n";
    static const auto format = [](std::ostream & out, const static_solution_state & static_state, const solution_state & state, const std::vector<int> & inputs) {
        for (const int & index : inputs) {
            if (index < 0 || index >= 16) out << static_state.spacing;
            else out
                << std::setw(static_state.padding)
                << state[index];
        }
        out << "\n";
    };
    format(out, static_state, state, { -1, -1, -1,  0,  1,  2 });
    format(out, static_state, state, { 15,  9, 14, 10, -1,  3 });
    format(out, static_state, state, { -1,  8, -1, 11, 12,  4, 13 });
    format(out, static_state, state, { -1,  5,  6,  7});

    out << "Partial Sum: " << (state.sums[0]) << "\n";
    out << "=============================" << "\n";
}

summap static_solution_state::gettwovaluesums() {
    summap sums;
    for (int i = 0; i < 16; i++) {
        for (int j = 0; j < 16; j++) {
            if (i == j) continue;
            std::pair<int,int> values( i, j );
            int sum = validNumbers[values.first] + validNumbers[values.second];
            sums[sum].push_back(values);
        }
    }
    return sums;
}

std::vector<sumlist> static_solution_state::gettwovaluesumsvector() {
    std::vector<sumlist> sums;
    for (auto & key : twosums) {
        sums.push_back(key);
    }

    std::sort(sums.begin(), sums.end(), [](sumlist a, sumlist b) {
        return a.first < b.first;
    });
    return sums;
}
Xirema
источник
Как я уверен, вы знаете, что если вы немного упростите вывод, вы сможете сэкономить приличное количество времени. Вот время для вашего кода: 0.1038s +/- 0.0002 И вот время для вашего кода с упрощенным выводом: 0.0850s +/- 0.0001 Итак, вы можете сэкономить ~ 18 мс, по крайней мере, на моей машине. Я запускал обе версии 500+ раз, выбросив выбросы, используя дамбенч
Эндрю Эпштейн
1

Пролог - 3 минуты

Этот вид головоломки кажется идеальным вариантом использования Пролога. Итак, я кодировал решение в Прологе! Вот:

:- use_module(library(clpfd)).

puzzle(P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15) :-
    Vars = [P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15],
    Vars ins 1..16,
    all_different(Vars),
    29 #= P0 + P1 + P2,
    29 #= P3 + P4 + P5 + P6,
    29 #= P9 + P10 + P11 + P12,
    29 #= P13 + P14 + P15,
    29 #= P0 + P6 + P9 + P15,
    29 #= P2 + P7 + P11,
    29 #= P4 + P8 + P13.

К сожалению, это не так быстро, как я ожидал. Может быть, кто-то более опытный в декларативном программировании (или, в частности, в Прологе) может предложить несколько советов по оптимизации. Вы можете вызвать правилоpuzzle с помощью следующей команды:

time(aggregate_all(count, (puzzle(P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15), labeling([leftmost, up, enum], [P9, P15, P13, P0, P4, P2, P6, P11, P1, P5, P3, P7, P14, P12, P10, P8])), Count)).

Попробуйте это онлайн здесь . Вы можете заменить любое число вместо 29s в коде, чтобы сгенерировать все решения. В его нынешнем виде все 29 решений найдены примерно за 30 секунд, поэтому найти все возможные решения нужно примерно за 3 минуты.

Эндрю Эпштейн
источник