std ::按一元映射排序

时间:2017-04-16 13:51:50

标签: c++ sorting c++11 std

C ++标准库提供将比较器传递给std::sort。但是,我的代码中有很多情况,我希望通过函数Tf个对象的列表进行排序。像这样的比较器将是一个有效的选项:

bool compare(const T& a, const T& b) {
  return f(a) < f(b);
}

但这不是最佳选择。 f评估缓慢,但会为具有相同T对象的每个调用返回相同的值。所以我宁愿做的是为范围内的每个对象计算一次f,然后使用这些结果对它们进行排序。

我的目标是编写这个函数(我无法做到):

template <typename IterT, typename Transformation>
void sort(IterT left, IterT right, Transformation f) { /* ? */ }

在此次通话后,f(*iter) <= f(*std::next(iter))代表序列iterleft中的所有right

此外,该功能应满足这些要求:

  • 不分配T类型的任何其他对象。
  • 多次评估f完全std::distance(left, right)
  • 维持O(n log n)的整体复杂性。
  • 应该用std :: sort来实现。当然,我可以通过实现自己的合并排序来解决这个问题,但这是我想避免的。

(C ++ 11是首选; C ++ 14也可以)

2 个答案:

答案 0 :(得分:3)

所以你想要的是Schwartzian transform的C ++实现。我在几行代码中没有简单的解决方案,但我确实在我的C ++ 14库中实现了Schwartzian transform utility。不幸的是,它依赖于代理迭代器,它不由std::sort处理(至少在Ranges TS之前),但您可以使用库中的任何其他分类器。以下是您可以编写问题中提到的sort函数的方法:

#include <cpp-sort/adapters/schwartz_adapter.h>
#include <cpp-sort/sorters/default_sorter.h>

template <typename IterT, typename Transformation>
void sort(IterT left, IterT right, Transformation f)
{
    using sorter = cppsort::schwartz_adapter<cppsort::default_sorter>;
    sorter{}(left, right, f);
}

以这种方式调用时,分拣机将跨越[left, right)并创建将迭代器std::distance(left, right)it相关联的f(*it)对。然后它将使用传递的排序器(在上面的示例中为default_sorter,在编写本文时为pattern-defeating quicksort)来对对的集合进行排序。引擎盖下使用Proxy iterators,以便在交换对时交换原始集合的元素。

我不会说这很简单,但它应该可以解决你的问题。如果您不想依赖外部图书馆,您仍然可以从the soure code获取灵感。它是在许可许可下,所以如果你需要使用它,你可以用它做任何你想做的事。

无论如何,看起来它主要满足你的要求:

  • 它没有分配T的其他实例(除非f返回T的新实例,因为它存储了f的返回值。 / LI>
  • 在实际排序之前,f正好适用std::distance(left, right)
  • 如果与O(n log n)分拣机一起使用,它会维持O(n log n)的整体复杂性。
  • 这个最新的子弹是唯一一个不满意的:它没有使用std::sort,因为std::sort目前还不够聪明,但它可以使用等效的算法而不用你必须自己写。

答案 1 :(得分:0)

如果您想坚持std::sort,只需编写一个比较器,为T的每个实例缓存函数的值。

示例:

struct Foo {
    int value;
};

// Replace with your CPU time intensive f() function
int evaluate(const Foo& foo) {
    std::cout << "Evaluating complex function on an instance of Foo..." << std::endl;
    return foo.value;
}

bool compare(const Foo& left, const Foo& right) {
    static std::unordered_map<Foo, int> cache;
    auto leftIt = cache.find(left);
    auto rightIt = cache.find(right);
    if (leftIt == cache.end())
        leftIt = cache.emplace(left, evaluate(left)).first;
    if (rightIt == cache.end())
        rightIt = cache.emplace(right, evaluate(right)).first;
    return (*leftIt).second < (*rightIt).second;
}

您可以在此处找到完整示例:https://gist.github.com/PandarinDev/ee75b095c4cc256a88496f1985bf57ba

这种方式evaluate(const Foo&);(在您的情况下为f(T))只会运行N次,其中N = the number of unique instances of Foo

编辑如下面评论中所述,如果T实例的副本进入地图是您的问题,您可以使用唯一标识符 - 例如地址你的对象 - 作为一个键而不是对象本身。