【问题标题】:Sorting zipped (locked) containers in C++ using boost or the STL使用 boost 或 STL 对 C++ 中的压缩(锁定)容器进行排序
【发布时间】:2012-11-30 05:31:38
【问题描述】:

我想做的事:我想将 2 个、3 个或 N 个向量排序,锁定在一起,不将它们复制到一个元组中。也就是说,抛开冗长,例如:

vector<int>    v1 = {  1,   2,   3,   4,   5};
vector<double> v2 = { 11,  22,  33,  44,  55};
vector<long>   v3 = {111, 222, 333, 444, 555};

typedef tuple<int&,double&,long&> tup_t;
sort(zip(v1,v2,v3),[](tup_t t1, tup_t t2){ return t1.get<0>() > t2.get<0>(); });

for(auto& t : zip(v1,v2,v3))
  cout << t.get<0>() << " " << t.get<1>() << " " << t.get<2>() << endl;

这应该输出:

5 55 555
4 44 444
...
1 11 111

我现在是怎么做的:我已经实现了自己的快速排序,其中我传递的第一个数组用于比较,排列应用于所有其他数组。我只是不知道如何重用 std::sort 来解决我的问题(例如提取排列)。

我尝试过的: boost::zip_iteratorboost::zip_range(带有 boost::combine 范围),但 std::sort 和 boost::range::algorithm::sort 都抱怨读取了迭代器/范围仅而非随机访问...

问题:如何在锁步(压缩)中对 N 个向量进行排序?这个问题看起来很笼统也很常见,所以我想必须通过一个可能非常复杂的库找到一个简单的解决方案,但我就是找不到它......

备注: 是的,stackoverflow 中有类似的问题,这个问题以不同的形式被问了很多。但是,它们总是以以下答案之一结束:

  • 将您的向量复制到一对/元组中并对该元组进行排序...
  • 将向量复制到每个向量一个成员的结构中,并对结构向量进行排序...
  • 针对您的特定问题实现您自己的排序功能...
  • 使用索引的辅助数组...
  • 在没有示例的情况下使用 boost::zip_iterator 或使用产生不良结果的示例。

提示:

  • 我在boost mailing list 中找到了这个线程,它指向来自Anthony Williams 的paper。虽然这似乎只适用于对,但他们也讨论了 TupleIteratorType 但我一直没能找到它。
  • user673679 找到 this 帖子,其中包含两个容器案例的不错解决方案。它还确定了问题(重点是我的):

[...] 根本问题是数组引用的“对”的行为不像它们应该的那样[...] 我只是决定滥用迭代器的符号并编写一些有效的东西。这涉及到编写一个非一致性迭代器,其中值类型的引用与引用类型不同。

答案: 见下面 interjay 的评论(这也部分回答了未来的问题):

#include "tupleit.hh"
#include <vector>
#include <iostream>
#include <boost/range.hpp>
#include <boost/range/algorithm/sort.hpp>
#include <boost/range/algorithm/for_each.hpp>

template <typename... T>
auto zip(T&... containers)
    -> boost::iterator_range<decltype(iterators::makeTupleIterator(std::begin(containers)...))> {
  return boost::make_iterator_range(iterators::makeTupleIterator(std::begin(containers)...),
                                      iterators::makeTupleIterator(std::end(containers)...));
}

int main() {

  typedef boost::tuple<int&,double&,long&> tup_t;

  std::vector<int>    a = {   1,   2,   3,   4 };
  std::vector<double> b = {  11,  22,  33,  44 };
  std::vector<long>   c = { 111, 222, 333, 444 };

  auto print = [](tup_t t){ std::cout << t.get<0>() << " " << t.get<1>() << " " << t.get<2>() << std::endl; };

  boost::for_each( zip(a, b, c), print);

  boost::sort( zip(a, b, c), [](tup_t i, tup_t j){ return i.get<0>() > j.get<0>(); });

  for ( auto tup : zip(a, b, c) ) print(tup);

  return 0;
}

未来的问题:之前的答案适用于序列容器。我们能否让它也适用于 sortable 容器(例如序列和列表)?这将需要 random_access 和双向 TupleIterators 以及适用于双向迭代器的排序算法。

更新:这适用于类似序列的容器的组合。然而,混合列表需要 std::sort 支持 BidirectionalIterators(它不支持)。

【问题讨论】:

  • 考虑std::sort如何重新排列元素:与std::iter_swap。你的zip_iterator 应该能够支持它。
  • 这是您为自己设定的一项非常有趣的任务 :)
  • 你错了 zip 迭代器 - 如果你做对了你可以编译你的第一个代码 - 但它实际上不会因为其他原因给你正确的结果(见这里:stackoverflow.com/questions/9343846/…) .然而,这看起来或多或少是您正在寻找的:stanford.edu/~dgleich/notebook/2006/03/…(如果我正确理解它在做什么,您可能需要添加更多 PermuteIter 值(可能使用包含它们的元组)。
  • 这是 Anthony Williams 的代码:tupleit.hh, testtupleit.cpp。从 tupleit.zip here 得到它(需要加入)。
  • @interjay 谢谢!这回答了问题!

标签: c++ algorithm boost c++11 std


【解决方案1】:

这是一个基于 range-v3 库的工作示例,已被提议用于标准化

#include <range/v3/all.hpp>
#include <iostream>

using namespace ranges;

int main() 
{
    std::vector<int> a1{15, 7, 3,  5};
    std::vector<int> a2{ 1, 2, 6, 21};
    sort(view::zip(a1, a2), std::less<>{}, &std::pair<int, int>::first); 
    std::cout << view::all(a1) << '\n';
    std::cout << view::all(a2) << '\n';
}

Live Example(需要具有良好 C++14 支持的最新编译器,而不是 VS 2015)。

【讨论】:

  • 我删除了临时变量 lambda,并使用了投影。现在这可能是最好的答案,所以我正在更新这个答案。
  • @gnzlbg 啊是的,预测仍然需要一些时间来适应
  • 看来你是对的,但是错误列表很长,也许@EricNiebler 知道最近版本的 range-v3 发生了什么改变,破坏了压缩视图的排序?
  • @mrks 见上面的评论
【解决方案2】:

对于两个容器的情况,这里有一个在 gcc 4.4.6 上编译的版本,基于上面提到的论文。在更高版本的 gcc 中,您可以将 boost::tuple 替换为 std::tuple

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

# include <boost/iterator/iterator_facade.hpp>
# include <boost/tuple/tuple.hpp> 

using namespace std;

template <class T, class T2>
struct helper_type {
  typedef boost::tuple<typename iterator_traits<T>::value_type, typename iterator_traits<T2>::value_type> value_type;
  typedef boost::tuple<typename iterator_traits<T>::value_type&, typename iterator_traits<T2>::value_type&> ref_type;
};

template <typename T1, typename T2>
class dual_iterator : public boost::iterator_facade<dual_iterator<T1, T2>,
                                                    typename helper_type<T1, T2>::value_type,
                                                    boost::random_access_traversal_tag,
                                                    typename helper_type<T1, T2>::ref_type> {
public:
   explicit dual_iterator(T1 iter1, T2 iter2) : mIter1(iter1), mIter2(iter2) {}
   typedef typename iterator_traits<T1>::difference_type difference_type;
private:
   void increment() { ++mIter1; ++mIter2; }
   void decrement() { --mIter1; --mIter2; }
   bool equal(dual_iterator const& other) const { return mIter1 == other.mIter1; }
   typename helper_type<T1, T2>::ref_type dereference() const { return (typename helper_type<T1, T2>::ref_type(*mIter1, *mIter2)); }
   difference_type distance_to(dual_iterator const& other) const { return other.mIter1 - mIter1; }
   void advance(difference_type n) { mIter1 += n; mIter2 += n; }

   T1 mIter1;
   T2 mIter2;
   friend class boost::iterator_core_access;
};

template <typename T1, typename T2>
dual_iterator<T1, T2> make_iter(T1 t1, T2 t2) { return dual_iterator<T1, T2>(t1, t2); }

template <class T1, class T2> struct iter_comp {
  typedef typename helper_type<T1, T2>::value_type T;
  bool operator()(const T& t1, const T& t2) { return get<0>(t1) < get<0>(t2); }
};

template <class T1, class T2> iter_comp<T1, T2> make_comp(T1 t1, T2 t2) { return iter_comp<T1, T2>(); }

template<class T> void print(T& items) {
  copy(items.begin(), items.end(), ostream_iterator<typename T::value_type>(cout, " ")); cout << endl;
}

int main() {
  vector<double> nums1 = {3, 2, 1, 0};
  vector<char> nums2 = {'D','C', 'B', 'A'};
  sort(make_iter(nums1.begin(), nums2.begin()), 
       make_iter(nums1.end(), nums2.end()), 
       make_comp(nums1.begin(), nums2.begin()));
  print(nums1);
  print(nums2);
}

【讨论】:

  • 此代码不可移植,GCC 无法编译
【解决方案3】:

创建一个包含索引 0..N-1 的辅助数组。使用自定义比较器对该数组进行排序,该比较器实际上返回比较您的一个主数组的元素的结果。然后使用已排序的辅助数组以正确的顺序打印出您的主数组。

【讨论】:

  • 不需要使用 O(n) 额外存储空间。
【解决方案4】:

很高兴认识一位互联网考古学家!

如何在锁步(压缩)中对 N 个向量进行排序?问题看起来 非常通用和常见,所以我想一定有一个简单的解决方案 通过一个可能非常复杂的库,但我找不到它。

以前,我带着类似的假设进行了同样的寻宝活动......
没找到宝藏:(

我和你走的一样:

  • 查看常见的疑点 boost.iterator/boost.range/boost.fusion/boost.oven,经过大量实验和研究后,发现它们无法解决这个特定问题。
  • 通过许多关于 SO 的问题,才发现每一个问题都已被关闭,或者答案不正确(例如推荐 boost::zip_iterator,正如您所指出的那样,在这种情况下它不起作用),或者有一些解决方法避免问题的核心。
  • 浏览了许多博客文章、邮件列表,才发现除了......之外没有人真正解决了这个问题。
  • 经过大量研究,最终挖掘出 Antonius Wilhelm 的旧法典,他声称已经制作了通用解决方案“TupleIterator”并将其锁定在某个存档“tupleit.zip”中。这个档案的历史资料非常稀缺,我仍然不确定这个档案是神话还是传说,或者它是否仍然埋在互联网的某个失落层中:)

好吧,更严重的是,Anthony Williams 的论文表明这个问题实际上非常困难,所以发现没有像 boost 这样的现有库解决它也就不足为奇了。

【讨论】:

    【解决方案5】:

    我很高兴地说,在进行了类似的寻宝之后,我找到了解决方案。如果您可以使用 range-v3 是一个好主意,但如果您真的需要一个 迭代器,HPX 项目 has created one 它与排序完美配合。

    由于疏忽,希望得到修复,它仍然需要您链接 HPX 库,但这对我来说没关系,因为重点是使用 C++17 并行算法,HPX 提供了该算法的实现。

    #include <hpx/util/zip_iterator.hpp>
    
    using zip_it = 
        hpx::util::zip_iterator<std::vector<std::size_t>::iterator,
                                std::vector<std::size_t>::iterator,
                                std::vector<double>::iterator>;
    
    int main() {
        std::vector<std::size_t> rows{3, 2, 1};
        std::vector<std::size_t> cols{1, 2, 3};
        std::vector<double> values{4.0, 5.0, 6.0};
    
        auto start = hpx::util::make_zip_iterator(rows.begin(), cols.begin(), values.begin());
        auto stop  = hpx::util::make_zip_iterator(rows.end(), cols.end(), values.end());
    
        std::sort(start, stop);
    
        for ( int i = 0; i < 3; ++i ) {
            std::cerr << rows[i] << ", " << cols[i] << ", " << values[i] << "\n";
        }
    }
    

    【讨论】:

    • 您应该在答案中添加多个注释。首先,range-v3 仅迭代器解决方案是微不足道的。其次,您使用std::sort,它要求迭代器对标准迭代器概念进行建模。但是,hpx 的 zip 迭代器不会对它们进行建模。也就是说,检查其输入的“好”STL 实现将拒绝此代码。使用hpx::sort 代替可能解决这个问题,但是使用std::sort 这个代码是不可移植的,只能偶然工作。
    • 感谢您的反馈。你能解释一下 range-v3 迭代器吗?它只是应用于范围的 std::begin/std::end 吗?至于迭代器建模,我的印象是 HPX 符合要求,但我不知道。希望@K-ballo 可以进一步评论。
    • range-v3 迭代器解决方案开始/结束应用于范围适配器:auto zip_v = view::zip(rows, cols); auto b = ranges::begin(zip_v); auto e = ranges::end(zip_v);。您可以像往常一样将这些迭代器与所有 range-v3 算法一起使用(它们都有迭代器版本)。 HPX zip 迭代器不能符合标准,因为 C++ 标准不支持 zip 迭代器(因此使用它们调用 std::sort 是未定义的行为)。
    猜你喜欢
    • 1970-01-01
    • 2015-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多