将置换函数从Python移植到C ++

时间:2017-07-04 09:09:50

标签: c++ visual-studio python-3.x

我一直在使用Stack Overflow答案中的算法:https://stackoverflow.com/a/9650593/4771889 即permute2()函数。

为了我的目的,我改变了一些东西,它生成了我需要的正确数据。

这是我的Python版本:

def permute(list, nextFixable, num, begin, end):
    if end == begin + 1:
        yield list
    else:
        for i in range(begin, end):
            if nextFixable[list[i]] == num[i]:
                nextFixable[list[i]] += 1
                num[begin], num[i] = num[i], num[begin]
                list[begin], list[i] = list[i], list[begin]
                for p in permute(list, nextFixable, num, begin + 1, end):
                    yield p
                list[begin], list[i] = list[i], list[begin]
                num[begin], num[i] = num[i], num[begin]
                nextFixable[list[i]] -= 1


if __name__ == "__main__":    
    list = [0, 1, 2]         
    nextFixable = {0:0, 1:0, 2:0}
    num = [0, 0, 0]

    for p in permute(list, nextFixable, num, 0, len(list)):
        print(p)

我已经简化了一些用法,以提供一个基本的例子。

此示例的输出为:

[0, 1, 2]
[0, 2, 1]
[1, 0, 2]
[1, 2, 0]
[2, 1, 0]
[2, 0, 1]

由于我对此的实际用法是针对较长的列表(不是简单的“0,1,2”),我真的很喜欢这里使用的生成器功能(yield语句)。

问题是Python很慢,我有时需要等几天才能得到结果。

我不是C ++专家,但之前我发现与Python相比,C ++要快得多。所以你可以猜到我接下来做了什么 - 我试图用C ++(Visual Studio 2015)重写它。

以下是我提出的建议:

#include "stdafx.h"

#include <iostream> // for std::cout
#include <vector> // for std::vector
#include <map> // for std::map

// https://blogs.msdn.microsoft.com/vcblog/2014/11/12/resumable-functions-in-c/
#include <experimental/generator>
using namespace std::experimental;
using namespace std;

using std::cout;
using std::vector;
using std::map;

void print(vector<unsigned short> data) {
    cout << "[";
    for (unsigned short i = 0; i < data.size(); ++i) {
        cout << (int)data[i];
        if (i < data.size() - 1) {
            cout << ", ";
        }
    }
    cout << "]\n";
}

generator<vector<unsigned short>> permute(
    vector<unsigned short> list, 
    map<unsigned short, unsigned short> nextFixable, 
    vector<unsigned short> num, 
    unsigned short begin, 
    unsigned short end
) {
    if (end == begin + 1) {
        __yield_value list;
    }
    else {
        for (unsigned short i = begin; i < end; ++i) {
            if (nextFixable[list[i]] == num[i]) {
                nextFixable[list[i]]++;
                swap(num[begin], num[i]);
                swap(list[begin], list[i]);
                for (auto p : permute(list, nextFixable, num, begin + 1, end)) {
                    __yield_value p;
                    swap(list[begin], list[i]);
                    swap(num[begin], num[i]);
                    nextFixable[list[i]]--;
                }
            }
        }
    }
}

int main()
{
    vector<unsigned short> list = { 0, 1, 2 };
    map<unsigned short, unsigned short> nextFixable = { {0, 0}, {1, 0}, {2, 0} };
    vector<unsigned short> num = { 0, 0, 0 };

    for (auto p : permute(list, nextFixable, num, 0, (unsigned short)list.size())) {
        print(p);
    }

    // Keep output window open.
    std::getchar();
    return 0;
}

你可以看到我引用了https://blogs.msdn.microsoft.com/vcblog/2014/11/12/resumable-functions-in-c/ 这就是给我 __ yield_value 功能的原因。 (我认为这不是可移植的,这对我来说确实会成为一个问题,因为我更喜欢在linux下运行这个问题,但是对于一些早期的实验它可以解决问题。)

代码运行正常,但输出为:

[0, 1, 2]
[0, 2, 1]
[1, 2, 0]
[2, 1, 0]

因此,让我们与Python输出进行比较 - 似乎我们缺少两行:

[0, 1, 2] - Yes
[0, 2, 1] - Yes
[1, 0, 2] - Missing
[1, 2, 0] - Yes
[2, 1, 0] - Yes
[2, 0, 1] - Missing

通常在撰写SO问题的这一点上,答案从我不得不解释问题中跳出来。在这种情况下它没有。

我可能做出了错误的语言假设,而且我没有足够的知识来纠正自己。

所以要把问题解决 - 为什么我的permute()函数的端口从Python到C ++没有给我相同的输出?

2 个答案:

答案 0 :(得分:0)

我会像这样构建它(c ++ 14):

#include <iostream>
#include <algorithm>
#include <vector>


// version which mutates the vector
template<class Vec, class F>
auto permute_over_inplace(Vec& vec, F&& f)
{
    auto first = std::begin(vec);
    auto last = std::end(vec);

    // permutation reequires sorted ranges
    if (not std::is_sorted(first, last)) {
        std::sort(first, last);
    }

    // remove duplicates
    last = vec.erase(std::unique(first, last), last);

    // call the functor for each permutation
    do {
        f(vec);
    } while (std::next_permutation(first, last));
};

// version which does not mutate the vector
template<class Vec, class F>
auto permute_over_copy(Vec vec, F&& f)
{
    return permute_over_inplace(vec, std::forward<F>(f));
};



int main()
{
    std::vector<int> list = {0, 1, 2};

    // an operation to perform on each permutation
    auto print_list = [](auto&& c)
    {
        const char* sep = " ";
        auto& os = std::cout;

        os << "[";
        for(auto&& item : c) {
            os << sep << item;
            sep = ", ";
        }
        os << " ]";
        os << "\n";
    };

    permute_over_copy(list, print_list);

    list = { 9, 8, 7, 9, 6 };
    permute_over_inplace(list, print_list);
}

预期产出:

[ 0, 1, 2 ]
[ 0, 2, 1 ]
[ 1, 0, 2 ]
[ 1, 2, 0 ]
[ 2, 0, 1 ]
[ 2, 1, 0 ]

[ 6, 7, 8, 9 ]
[ 6, 7, 9, 8 ]
[ 6, 8, 7, 9 ]
[ 6, 8, 9, 7 ]
[ 6, 9, 7, 8 ]
[ 6, 9, 8, 7 ]
[ 7, 6, 8, 9 ]
[ 7, 6, 9, 8 ]
[ 7, 8, 6, 9 ]
[ 7, 8, 9, 6 ]
[ 7, 9, 6, 8 ]
[ 7, 9, 8, 6 ]
[ 8, 6, 7, 9 ]
[ 8, 6, 9, 7 ]
[ 8, 7, 6, 9 ]
[ 8, 7, 9, 6 ]
[ 8, 9, 6, 7 ]
[ 8, 9, 7, 6 ]
[ 9, 6, 7, 8 ]
[ 9, 6, 8, 7 ]
[ 9, 7, 6, 8 ]
[ 9, 7, 8, 6 ]
[ 9, 8, 6, 7 ]
[ 9, 8, 7, 6 ]

答案 1 :(得分:0)

我决定今晚不去睡觉,通过在算法的两个版本中添加详细调试并研究它们的不同之处来找到答案。

问题在于这部分代码:

        for (auto p : permute(list, nextFixable, num, begin + 1, end)) {
            __yield_value p;
            swap(list[begin], list[i]);
            swap(num[begin], num[i]);
            nextFixable[list[i]]--;
        }

要匹配python,它应该是:

        for (auto p : permute(list, nextFixable, num, begin + 1, end)) {
            __yield_value p;
        }
        swap(list[begin], list[i]);
        swap(num[begin], num[i]);
        nextFixable[list[i]]--;

可能在将Python代码粘贴到VS中时,它有助于排列所有内容,并且由于Python方便的缺少括号,在再次比较代码时,这一点未被注意。

这引发了第二个问题,其中nextFixable [无论如何]在无符号整数上会减少0以下并且溢出 - 但这就是放弃它的原因。

(但正如评论中所述,我已经考虑过在do-while循环中结合std:sort使用std:next_permutation的建议。该解决方案在一个简单的实验中被证明是大约5-6比这个算法快一倍。)