Encontre todas as soluções para este quebra-cabeça numérico no menor tempo possível

16

História

Minha empresa envia um boletim semanal a todos dentro da empresa. Incluído nesses boletins há um enigma, juntamente com uma mensagem para quem foi o primeiro a enviar por e-mail / fornecer uma solução para o enigma da semana passada. A maioria desses enigmas é bastante trivial e honestamente bastante chata para uma empresa de tecnologia, mas houve um, vários meses atrás, que chamou minha atenção.

O enigma original:

Dada a forma abaixo:

Imagem do quebra-cabeça

Você tem os números naturais de 1 a 16. Coloque-os todos nessa forma, de modo que todas as linhas e colunas contíguas totalizem 29.

Por exemplo, uma dessas soluções para esse quebra-cabeça (que era a solução "canônica" que enviei para o boletim) foi a seguinte:

Imagem de quebra-cabeça resolvida

No entanto, no curso da solução, encontrei algumas informações bastante interessantes:

  • Existem significativamente mais soluções do que apenas essa; de fato, existem 9.368 soluções.
  • Se você expandir o conjunto de regras para exigir apenas que linhas e colunas sejam iguais umas às outras, não necessariamente 29, você obtém 33.608 soluções:
    • 4.440 Soluções para uma soma de 27.
    • 7.400 Soluções, num total de 28.
    • 9.368 Soluções para uma soma de 29.
    • 6.096 Soluções para uma soma de 30.
    • 5.104 Soluções para uma soma de 31.
    • 1.200 soluções, totalizando 32.

Então, eu e meus colegas de trabalho (embora principalmente apenas meu gerente, já que ele era a única outra pessoa além de mim com habilidades de programação para "Propósitos Gerais") partimos para um desafio que durou a maior parte do mês - tivemos outros empregos reais. obrigações relacionadas que tínhamos que cumprir - para tentar escrever um programa que encontrasse todas as soluções da maneira mais rápida possível.

Estatísticas originais

O primeiro programa que escrevi para resolver o problema simplesmente checava soluções aleatórias repetidas vezes e parava quando encontrava uma solução. Se você fez uma análise matemática sobre esse problema, provavelmente já sabe que isso não deveria ter funcionado; mas, de alguma forma, tive sorte e o programa levou apenas um minuto para encontrar uma única solução (a que postei acima). As execuções repetidas do programa costumavam levar 10 ou 20 minutos, portanto, obviamente, essa não era uma solução rigorosa para o problema.

Mudei para uma solução recursiva que iterava em todas as permutações possíveis do quebra-cabeça e descartava muitas soluções ao mesmo tempo, eliminando somas que não estavam sendo adicionadas. Ou seja, se a primeira linha / coluna que eu estava comparando já não fosse igual, eu poderia parar de verificar esse ramo imediatamente, sabendo que nada mais permeado no quebra-cabeça mudaria isso.

Usando esse algoritmo, obtive o primeiro sucesso "adequado": o programa poderia gerar e cuspir todas as 33.608 soluções em cerca de 5 minutos.

Meu gerente teve uma abordagem diferente: sabendo, com base no meu trabalho, que as únicas soluções possíveis tinham somas de 27, 28, 29, 30, 31 ou 32, ele escreveu uma solução multiencadeada que verificava somas possíveis apenas para esses valores específicos. Ele conseguiu que seu programa fosse executado em apenas 2 minutos. Então eu iterou novamente; Misturei todas as somas possíveis de 3/4 dígitos (no início do programa; é contado no tempo de execução total) e usei a "soma parcial" de uma linha para pesquisar o valor restante com base em uma linha concluída anteriormente, em vez de testando todos os valores restantes e diminuindo o tempo para 72 segundos. Então, com alguma lógica de multiencadeamento, reduzi para 40 segundos. Meu gerente levou o programa para casa, realizou algumas otimizações na execução do programa e reduziu o tempo para 12 segundos. Reordenei a avaliação das linhas e colunas,

O mais rápido que qualquer um de nós conseguiu nossos programas após um mês foi de 0,15 segundo para meu gerente e 0,33 segundos para mim. Acabei afirmando que meu programa era mais rápido, pois o programa do meu gerente, embora encontrasse todas as soluções, não as imprimia em um arquivo de texto. Se ele adicionou essa lógica ao seu código, muitas vezes levava mais de 0,4-0,5 segundos.

Desde então, permitimos que nosso desafio intra-pessoal subsistisse, mas, é claro, a questão permanece: esse programa pode ser feito mais rápido?

Esse é o desafio que vou fazer para vocês.

O seu desafio

Os parâmetros sob os quais trabalhamos relaxaram a regra da "soma de 29" para serem "todas as somas de linhas / colunas iguais", e vou definir essa regra para vocês também. O desafio, portanto, é: Escreva um programa que encontre (e imprima!) Todas as soluções para esse enigma no menor tempo possível. Vou estabelecer um limite para as soluções enviadas: se o programa demorar mais de 10 segundos em um computador relativamente decente (<8 anos), provavelmente será muito lento para ser contado.

Além disso, tenho alguns bônus para o quebra-cabeça:

  • Você pode generalizar a solução para que funcione para qualquer conjunto de 16 números, não apenas int[1,16]? A pontuação de tempo seria avaliada com base no conjunto de números de prompt original, mas passaria por esse caminho de código. (-10%)
  • Você pode escrever o código de uma maneira que ele manipule e resolva normalmente com números duplicados? Isso não é tão simples quanto pode parecer! Soluções "visualmente idênticas" devem ser exclusivas no conjunto de resultados. (-5%)
  • Você consegue lidar com números negativos? (-5%)

Você também pode tentar gerar uma solução que lide com números de ponto flutuante, mas é claro, não fique chocado se isso falhar completamente. Se você encontrar uma solução robusta, isso pode valer um grande bônus!

Para todos os efeitos, "Rotações" são consideradas soluções únicas. Portanto, uma solução que é apenas uma rotação de uma solução diferente conta como sua própria solução.

Os IDEs que tenho trabalhando no meu computador são Java e C ++. Posso aceitar respostas de outros idiomas, mas talvez seja necessário fornecer um link para onde eu possa obter um ambiente de tempo de execução fácil de configurar para o seu código.

Xirema
fonte
3
Gatos sagrados, boa primeira pergunta! ... exceto para o bônus, que sorta-desencorajam (principalmente em code-golfe perguntas, então eles devem estar bem aqui)
cat
4
@cat Acho que os bônus fazem sentido aqui, porque quando resolvi esses problemas em meu próprio código, eles costumavam fazer com que o código diminuísse significativamente. Então eu acho que um bônus é para justificar a inclusão.
Xirema
2
BTW, existem empregos em sua casa? parece que você tem um chefe descontraído e muito tempo em suas mãos :-)
Level River St
1
Com números duplicados, não há problema em imprimir soluções duplicadas em que os dois números idênticos são trocados? isso faria uma grande diferença na maneira como esse bônus é interpretado. Esclareça, mas eu consideraria eliminar esse bônus completamente.
Level River St
1
Além disso, as rotações de 180 graus são consideradas a mesma solução ou soluções diferentes?
Level River St

Respostas:

7

C - perto de 0,5 s

Este programa muito ingênuo fornece todas as soluções em meio segundo no meu laptop de 4 anos. Sem multithread, sem hash.

Windows 10, Visual Studio 2010, núcleo da CPU I7 de 64 bits

Experimente online no 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
fonte
Santo doce Mal Vampiric Jesus, aqueles aninhados para loops. Aposto que isso deixou seu compilador muito feliz. XD
Xirema
@ edc65, FYI você pode substituir int inuse[16];por just int inuse;e depois usar operadores bit a bit para manipulá-lo. Ele não parece aumentar a velocidade que muito, mas ajuda um pouco.
Andrew Epstein
@AndrewEpstein pode mesmo tornar-se mais lento - bitshift vs indexação
edc65
@ edc65, tomei a liberdade de usar o dumbbench para testar sua versão original vs. a versão bitshift. Aqui estão os resultados: Indexação: 0,2253 +/- 5,7590e-05 Deslocamento de bits: 0,2093 +/- 6.6595e-05 Portanto, aproximadamente uma aceleração de 16ms na minha máquina. O comando que usei foi:dumbbench --precision=.01 -vvv --initial=500 ./solve
Andrew Epstein
3

C ++ - 300 milissegundos

Por solicitação, adicionei meu próprio código para resolver esse quebra-cabeça. No meu computador, ele funciona a uma média de 0,310 segundos (310 milissegundos), mas dependendo da variação pode ser executado tão rapidamente quanto 287 milissegundos. Raramente vejo isso subir acima de 350 milissegundos, geralmente apenas se meu sistema estiver atolado com uma tarefa diferente.

Esses horários são baseados no auto-relatório usado no programa, mas eu também testei usando um timer externo e obtive resultados semelhantes. A sobrecarga no programa parece adicionar cerca de 10 milissegundos.

Além disso, o meu código não completamente lidar com duplicatas corretamente. Ele pode resolver usá-los, mas não elimina soluções "visualmente idênticas" do conjunto de soluções.

#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
fonte
Como tenho certeza de que você sabe, se você simplificar um pouco a saída, poderá economizar uma quantidade decente de tempo. Aqui está a hora do seu código: 0.1038s +/- 0.0002 E aqui está a hora do seu código com saída simplificada: 0.0850s +/- 0.0001 Então, você pode economizar ~ 18ms, pelo menos na minha máquina. Corri as duas versões mais de 500 vezes com valores atípicos jogados fora, usando dumbbench
Andrew Epstein
1

Prolog - 3 minutos

Esse tipo de quebra-cabeça parece ser um caso de uso perfeito para o Prolog. Então, eu codifiquei uma solução no Prolog! Aqui está:

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

Infelizmente, não é tão rápido quanto eu esperava. Talvez alguém mais versado em programação declarativa (ou Prolog especificamente) possa oferecer algumas dicas de otimização. Você pode invocar a regra puzzlecom o seguinte comando:

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

Experimente online aqui . Você pode substituir qualquer número no lugar de 29s no código para gerar todas as soluções. Tal como está, todas as 29 soluções são encontradas em aproximadamente 30 segundos; portanto, para encontrar todas as soluções possíveis, deve ser de aproximadamente 3 minutos.

Andrew Epstein
fonte