在尽可能短的时间内找到该数字难题的所有解决方案


16

历史

我的公司每周向公司内的每个人发送时事通讯。这些新闻通讯中包括一个谜语,并向公司中第一个向上一周的谜语发送电子邮件/提供解决方案的人大喊大叫。对于科技公司来说,这些谜语大多数都不是小事,而且平淡无奇,但是几个月前,有一个引起了我的注意。

原始谜语:

鉴于以下形状:

拼图图片

您具有从1到16的自然数。将它们全部适合此形状,以使所有连续的行和连续的列总计为29。

例如,以下是该难题的一种解决方案(这是我提交给时事通讯的“规范”解决方案):

解决的拼图图片

但是,在解决此问题的过程中,我发现了一些相当有趣的信息:

  • 解决方案远远不止一种。实际上,有9,368解决方案。
  • 如果将规则集扩展为仅要求行和列彼此相等,而不必等于29,则会得到33,608个解决方案:
    • 4,440个解决方案,总计27个。
    • 7,400个解决方案,总计28个。
    • 9,368个解决方案,总计29个。
    • 6,096个解决方案,总计30个。
    • 5,104个解决方案,总计31个。
    • 1,200个解决方案,总计32个。

因此,我和我的同事们(尽管大多数情况下只是我的经理,因为他是我之外唯一拥有“通用”编程技能的人)开始了一个挑战,这个挑战持续了一个月的大部分时间-我们还有其他实际工作,我们必须承担的相关义务—尝试编写一个程序,以最快的方式找到每个解决方案。

原始统计

我编写的用于解决该问题的第一个程序只是反复检查随机解,并在找到解时停止运行。如果您已经对这个问题进行了数学分析,那么您可能已经知道这不应该起作用。但是以某种方式我很幸运,该程序只花了一分钟时间就找到了一个解决方案(我在上面发布了一个解决方案)。重复运行该程序通常要花费10到20分钟,因此显然这不是严格解决问题的方法。

我切换到了“递归解决方案”,该解决方案遍历了难题的每个可能排列,并通过消除未加总和而一次丢弃了很多解决方案。IE,如果我正在比较的第一行/列已经不相等,那么我可以立即停止检查该分支,因为知道拼图中没有其他排列会改变这一点。

使用这种算法,我获得了第一个“正确”的成功:该程序可以在大约5分钟内生成并吐出所有33,608个解决方案。

我的经理采用了另一种方法:根据我的工作,得知唯一可能的解决方案的总和为27、28、29、30、31或32,他编写了一个多线程解决方案,仅检查那些特定值的可能总和。他设法使程序仅在2分钟内运行。所以我又重复了一次。我对所有可能的3/4位和进行了哈希处理(在程序开始时;在总运行时中进行了计数),并使用行的“部分和”来基于先前完成的行查找剩余值,而不是测试所有剩余值,并将时间减少到72秒。然后,使用一些多线程逻辑,我将其缩短到40秒。我的经理将程序带回家,对程序的运行方式进行了一些优化,并将其降低到12秒。我对行和列的评估重新排序,

一个月后,我们中每个人获得程序最快的是我的经理0.15秒,我的0.33秒。最后,我声称我的程序更快,因为我经理的程序虽然找到了所有解决方案,但并未将它们打印到文本文件中。如果他在代码中添加了该逻辑,则通常需要花费0.4-0.5秒以上的时间。

我们既然允许我们内部的个人挑战生存,当然,问题依然存在:这一计划进行得更快?

这就是我要对你们提出的挑战。

您的挑战

我们根据“ 29之和”规则放宽的参数改为“所有行/列的总和相等”,我也将为你们设置该规则。因此,面临的挑战是:编写一个程序,以在尽可能短的时间内找到(并打印)此难题的所有解决方案。我将为提交的解决方案设置一个上限:如果该程序在相对不错的计算机(小于8岁)上花费10秒钟以上,则可能太慢而无法计算。

另外,我还有一些难题的奖励:

  • 您能否概括该解决方案,使其适用于任何16个数字集,而不仅是int[1,16]?时间分数将根据原始提示号集进行评估,但会通过此代码路径传递。(-10%)
  • 您是否可以用能很好地处理和解决重复编号的方式编写代码?这并不像看起来那样简单!“视觉上相同”的解决方案在结果集中应该是唯一的。(-5%)
  • 你能处理负数吗?(-5%)

您也可以尝试生成处理浮点数的解决方案,但是,如果完全失败,请不要感到震惊。如果您确实找到了一个可靠的解决方案,那可能值得大笔奖励!

出于所有目的和目的,“轮换”被视为唯一的解决方案。因此,只是一个不同解决方案的轮换解决方案将被视为自己的解决方案。

我在计算机上使用的IDE是Java和C ++。我可以接受其他语言的答案,但您可能还需要提供一个链接,以获取可以为您的代码轻松设置运行时环境的位置。


3
圣猫,很好的第一个问题!......除了奖金,这是我们八九不离十,劝阻(大多代码高尔夫问题,所以他们应该是在这里很好)

4
@cat我认为奖金在这里很有意义,因为当我在自己的代码中解决这些问题时,它们往往会导致代码显着降低速度。因此,我认为奖金是为了证明包容是合理的。
希雷马'16

2
顺便说一句,你那里有工作吗?听起来您老板很随和,而且时间充裕:-)
Level River St

1
使用重复编号,可以打印交换两个相同编号的重复解决方案吗?这将对奖金的解释方式产生重大影响。请澄清一下,但我会考虑完全取消该奖金。
水平河圣

1
另外,将180度旋转视为相同的解决方案还是不同的解决方案?
水平河圣

Answers:


7

C-接近0.5秒

这个非常幼稚的程序可以在我4岁的笔记本电脑上半秒钟内提供所有解决方案。没有多线程,没有哈希。

Windows 10,Visual Studio 2010,CPU核心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);
}

圣洁的甜美邪恶的吸血鬼耶稣,那些嵌套循环。我敢打赌,这会让您的编译器真正感到高兴。XD
Xirema

@ edc65,仅供参考,您可以int inuse[16];用just 替换,int inuse;然后使用按位运算符对其进行操作。它似乎并没有增加的速度多少,但它有助于一点点。
Andrew Epstein

@AndrewEpstein它甚至可能变得更慢
移位

@ edc65,我使用了dumbbench来测试您的原始版本和bitshift版本。结果如下:索引:0.2253 +/- 5.7590e-05移位:0.2093 +/- 6.6595e-05因此,我的机器上的加速时间约为16毫秒。我使用的命令是:dumbbench --precision=.01 -vvv --initial=500 ./solve
Andrew Epstein 2016年

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;
}

如您所知,如果您稍微简化一下输出,就可以节省很多时间。这是编写代码的时间: 0.1038s +/- 0.0002 这是简化输出代码的时间: 0.0850s +/- 0.0001 因此,至少在我的机器上,您可以节省〜18ms。我使用dumbbench
Andrew Epstein

1

Prolog-3分钟

这种难题似乎是Prolog的完美用例。因此,我在Prolog中编写了一个解决方案!这里是:

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

不幸的是,它没有我预期的那么快。也许更精通声明式编程(或Prolog)的人可以提供一些优化技巧。您可以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)).

在这里在线尝试。您可以用任何数字代替29代码中的来生成所有解决方案。就目前而言,所有29种解决方案都是在大约30秒内找到的,因此要找到所有可能的解决方案应该是大约3分钟。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.