Encuentre todas las soluciones para este rompecabezas numérico en el menor tiempo posible

16

Historia

Mi empresa envía un boletín semanal a todos los miembros de la empresa. En estos boletines se incluye un acertijo, junto con un saludo a quien en la empresa fue el primero en enviar un correo electrónico / proporcionar una solución al enigma de la semana pasada. La mayoría de estos acertijos son bastante triviales, y honestamente bastante aburridos para una empresa de tecnología, pero hubo uno, varios meses atrás, que me llamó la atención.

El acertijo original:

Dada la forma a continuación:

Imagen de rompecabezas

Tiene los números naturales del 1 al 16. Ajústelos a todos en esta forma, de modo que todas las filas y columnas contiguas sumen 29.

Por ejemplo, una de esas soluciones a este rompecabezas (que fue la solución "canónica" que envié al boletín) fue la siguiente:

Imagen del rompecabezas resuelto

Sin embargo, en el curso de resolverlo, encontré información bastante interesante:

  • Hay significativamente más soluciones que solo esa; de hecho, hay 9.368 soluciones.
  • Si expande el conjunto de reglas para requerir solo que las filas y columnas sean iguales entre sí, no necesariamente 29, obtendrá 33,608 soluciones:
    • 4.440 soluciones por una suma de 27.
    • 7.400 soluciones por una suma de 28.
    • 9.368 soluciones por una suma de 29.
    • 6.096 soluciones por una suma de 30.
    • 5.104 Soluciones por un total de 31.
    • 1.200 soluciones por una suma de 32.

Así que yo y mis compañeros de trabajo (aunque principalmente solo mi gerente, ya que él era la única persona que no era yo con habilidades de programación de "Propósito general") nos embarcamos en un desafío que duró la mayor parte del mes: tuvimos otro trabajo real. obligaciones relacionadas a las que teníamos que atender, para tratar de escribir un programa que encontrara todas las soluciones de la manera más rápida posible.

Estadísticas originales

El primer programa que escribí para resolver el problema simplemente verificó soluciones aleatorias una y otra vez, y se detuvo cuando encontró una solución. Si ha realizado un análisis matemático sobre este problema, probablemente ya sepa que esto no debería haber funcionado; pero de alguna manera tuve suerte, y el programa tardó solo un minuto en encontrar una solución única (la que publiqué anteriormente). Las ejecuciones repetidas del programa a menudo tomaron tanto como 10 o 20 minutos, por lo que obviamente esta no era una solución rigurosa para el problema.

Me cambié a una Solución Recursiva que iteraba a través de cada permutación posible del rompecabezas, y descarté muchas soluciones a la vez al eliminar sumas que no estaban sumando. Es decir, si la primera fila / columna que estaba comparando ya no fuera igual, podría dejar de verificar esa rama inmediatamente, sabiendo que nada más permutado en el rompecabezas cambiaría eso.

Usando este algoritmo, obtuve el primer éxito "adecuado": el programa podía generar y escupir todas las 33,608 soluciones en aproximadamente 5 minutos.

Mi gerente tenía un enfoque diferente: sabiendo en base a mi trabajo que las únicas soluciones posibles tenían sumas de 27, 28, 29, 30, 31 o 32, escribió una solución multiproceso que verificaba las sumas posibles solo para esos valores específicos. Logró ejecutar su programa en solo 2 minutos. Entonces volví a repetir; Calculé todas las sumas posibles de 3/4 dígitos (al inicio del programa; se cuenta en el tiempo de ejecución total) y utilicé la "suma parcial" de una fila para buscar el valor restante en función de una fila completada previamente, en lugar de probando todos los valores restantes, y redujo el tiempo a 72 segundos. Luego, con un poco de lógica de subprocesos múltiples, lo reduje a 40 segundos. Mi gerente se llevó el programa a casa, realizó algunas optimizaciones sobre cómo se ejecutó el programa y lo redujo a 12 segundos. Reordené la evaluación de las filas y columnas,

Lo más rápido que obtuvimos nuestros programas después de un mes fue de 0,15 segundos para mi gerente y de 0,33 segundos para mí. Sin embargo, terminé afirmando que mi programa era más rápido, ya que el programa de mi gerente, aunque encontró todas las soluciones, no las imprimió en un archivo de texto. Si agregaba esa lógica a su código, a menudo tomaba más de 0.4-0.5 segundos.

Desde entonces hemos permitido que nuestro desafío intrapersonal subsista, pero, por supuesto, la pregunta sigue siendo: ¿se puede hacer este programa más rápido?

Ese es el desafío que les voy a plantear.

Su desafío

Los parámetros con los que trabajamos relajaron la regla de "suma de 29" para ser "todas las sumas de filas / columnas iguales", y voy a establecer esa regla para ustedes también. El desafío, por lo tanto, es: escribir un programa que encuentre (¡e imprima!) Todas las soluciones a este acertijo en el menor tiempo posible. Voy a establecer un límite en las soluciones enviadas: si el programa tarda más de 10 segundos en una computadora relativamente decente (<8 años), probablemente sea demasiado lento para contarlo.

Además, tengo algunas bonificaciones para el rompecabezas:

  • ¿Puede generalizar la solución para que funcione para cualquier conjunto de 16 números, no solo int[1,16]? La puntuación de tiempo se evaluaría en función del conjunto de números de solicitud original, pero se pasaría por esta ruta de código. (-10%)
  • ¿Puedes escribir el código de una manera que maneje y resuelva con gracia con números duplicados? ¡Esto no es tan sencillo como parece! Las soluciones que son "visualmente idénticas" deberían ser únicas en el conjunto de resultados. (-5%)
  • ¿Puedes manejar números negativos? (-5%)

También puede intentar generar una solución que maneje los números de coma flotante, pero, por supuesto, no se sorprenda si eso falla por completo. Sin embargo, si encuentra una solución sólida, ¡podría valer una gran ventaja!

Para todos los efectos, las "rotaciones" se consideran soluciones únicas. Entonces, una solución que es solo una rotación de una solución diferente cuenta como su propia solución.

Los IDEs que tengo trabajando en mi computadora son Java y C ++. Puedo aceptar respuestas de otros idiomas, pero es posible que también deba proporcionar un enlace para obtener un entorno de tiempo de ejecución fácil de configurar para su código.

Xirema
fuente
3
¡Santos gatos, buena primera pregunta! ... a excepción de los bonos, que desaconsejamos (sobre todo en las preguntas de código de golf , por lo que deberían estar bien aquí)
gato
44
@cat Creo que los bonos tienen sentido aquí, porque cuando resolví esos problemas en mi propio código, tendían a hacer que el código se ralentizara de manera significativa. Así que creo que una bonificación es para justificar la inclusión.
Xirema
2
Por cierto, ¿hay algún trabajo en tu casa? parece que tienes un jefe tranquilo y mucho tiempo libre :-)
Level River St
1
Con números duplicados, ¿está bien imprimir soluciones duplicadas donde se intercambian los dos números idénticos? eso marcaría una gran diferencia en cuanto a cómo se interpreta esa bonificación. Por favor aclare, pero consideraría eliminar esa bonificación por completo.
Level River St
1
Además, ¿las rotaciones de 180 grados se consideran la misma solución o diferentes soluciones?
Level River St

Respuestas:

7

C - cerca de 0.5 segundos

Este programa muy ingenuo da todas las soluciones en medio segundo en mi computadora portátil de 4 años. Sin subprocesos múltiples, sin hashing.

Windows 10, Visual Studio 2010, CPU core I7 64 bit

Probar en línea en 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
fuente
Santo dulce malvado vampiro Jesús, aquellos anidados para bucles. Apuesto a que eso hizo que tu compilador sea realmente feliz. XD
Xirema
@ edc65, FYI, puede reemplazarlo int inuse[16];por solo int inuse;y luego usar operadores bit a bit para manipularlo. No parece aumentar la velocidad que es mucho, pero ayuda un poco.
Andrew Epstein
@AndrewEpstein incluso podría volverse más lento - bithift vs indexación
edc65
@ edc65, me tomé la libertad de usar dumbbench para probar su versión original en comparación con la versión bithift. Aquí están los resultados: Indización: 0.2253 +/- 5.7590e-05 Bitshifting: 0.2093 +/- 6.6595e-05 Entonces, aproximadamente una velocidad de 16 ms en mi máquina. El comando que usé fue:dumbbench --precision=.01 -vvv --initial=500 ./solve
Andrew Epstein
3

C ++ - 300 milisegundos

Por solicitud, agregué mi propio código para resolver este rompecabezas. En mi computadora, registra un promedio de 0.310 segundos (310 milisegundos) pero, dependiendo de la variación, puede ejecutarse tan rápido como 287 milisegundos. Muy rara vez veo que se eleve por encima de 350 milisegundos, generalmente solo si mi sistema está bloqueado con una tarea diferente.

Estos tiempos se basan en el autoinforme utilizado en el programa, pero también probé usando un temporizador externo y obtuve resultados similares. Los gastos generales en el programa parecen agregar unos 10 milisegundos.

Además, mi código no bastante manejar duplicados correctamente. Puede resolver su uso, pero no elimina las soluciones "visualmente idénticas" del conjunto de soluciones.

#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
fuente
Como estoy seguro de que sabe, si simplifica un poco su salida, puede ahorrar una cantidad de tiempo decente. Este es el momento de su código: 0.1038s +/- 0.0002 Y este es el momento de su código con salida simplificada: 0.0850s +/- 0.0001 Entonces, puede guardar ~ 18 ms, al menos en mi máquina. Ejecuté
Andrew Epstein
1

Prolog - 3 minutos

Este tipo de rompecabezas parece un caso de uso perfecto para Prolog. Entonces, codifiqué una solución en Prolog! Aquí 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.

Lamentablemente, no es tan rápido como esperaba. Quizás alguien más versado en programación declarativa (o Prolog específicamente) pueda ofrecer algunos consejos de optimización. Puede invocar la regla puzzlecon el siguiente 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)).

Pruébelo en línea aquí . Puede sustituir cualquier número en lugar de 29s en el código para generar todas las soluciones. Tal como está, las 29 soluciones se encuentran en aproximadamente 30 segundos, por lo que encontrar todas las soluciones posibles debe ser de aproximadamente 3 minutos.

Andrew Epstein
fuente