提问人: 提问时间:12/12/2012 最后编辑:16 revs, 3 users 99%gnzlbg 更新时间:1/1/2021 访问量:4965
使用 boost 或 STL 在 C++ 中对压缩(锁定)容器进行排序
Sorting zipped (locked) containers in C++ using boost or the STL
问:
我想做什么:我想对锁定在一起的 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_iterator 和 boost::zip_range(带有 boost::combine range),但 std::sort 和 boost::range::algorithm::sort 都抱怨迭代器/范围是只读的,而不是随机访问......
问题:如何在锁定步骤(zipped)中对 N 个向量进行排序?这个问题看起来很通用和常见,所以我想通过一个可能非常复杂的库一定有一个简单的解决方案,但我就是找不到它......
备注:是的,stackoverflow 中也有类似的问题,这个问题以不同的形式被问到很多。但是,它们总是以下列答案之一关闭:
- 将您的向量复制到一对/元组中,并对该元组进行排序...
- 将向量复制到每个向量一个成员的结构中,并对结构的向量进行排序...
- 针对您的特定问题实现您自己的排序功能...
- 使用索引的辅助数组...
- 使用不带示例的 boost::zip_iterator 或带有产生不良结果的示例。
提示:
- 我在boost邮件列表中找到了这个帖子,它指向了Anthony Williams的这篇论文。虽然这似乎只适用于成对,但它们也讨论了 TupleIteratorType,但我无法找到它。
- user673679 发现这篇文章包含针对两个容器案例的一个很好的解决方案。它还确定了问题(重点是我的):
[...]根本问题是数组引用的“对”行为不符合它们应有的 [...]我只是决定滥用迭代器的符号并编写一些有效的东西。这实际上涉及编写一个不合格的迭代器,其中值类型的引用与引用类型不同。
答:请参阅下面 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;
}
未来的问题:前面的答案适用于序列容器。我们能否让它也适用于可排序的容器(例如序列和列表)?这将需要random_access和双向 TupleIterator 以及适用于双向迭代器的排序算法。
更新:这适用于类似序列的容器的组合。但是,混合列表需要 std::sort 支持 BidirectionalIterators(不支持)。
答:
创建一个包含索引 0..N-1 的辅助数组。使用自定义比较器对该数组进行排序,该比较器实际上通过比较其中一个主数组的元素返回结果。然后使用排序的辅助阵列以正确的顺序打印出主阵列。
评论
很高兴见到一位互联网考古学家!
如何在锁定步骤(zipped)中对 N 个向量进行排序?问题看起来 非常通用和常见,所以我想一定有一个简单的解决方案 通过一个可能非常复杂的库,但我就是找不到它。
有时,我带着类似的假设进行了同样的寻宝活动......
从未找到宝藏:(
我遵循与你相同的轨道:
- 通过通常的嫌疑人 boost.iterator/boost.range/boost.fusion/boost.oven,经过大量的实验和研究,意识到他们无法解决这个特定问题。
- 在 SO 上浏览了许多问题,却发现每个问题都以不正确的答案关闭(例如,推荐 boost::zip_iterator 正如您指出的那样,在这种情况下不起作用),或者使用一些避免问题核心的解决方法。
- 浏览了许多博客文章、邮件列表,才意识到没有人真正解决了这个问题,除了......
- 经过大量研究,最终挖掘出了安东尼乌斯·威廉(Antonius Wilhelm)的旧手抄本,他声称已经制作了一个通用解决方案“TupleIterator”,并将其锁定在某个存档“tupleit.zip中。关于这个档案的历史资料非常稀缺,我仍然不确定这个档案是神话、传说,还是仍然埋藏在互联网:)的某个失落层中
好吧,更严重的是,Anthony Williams 的论文表明这个问题实际上真的很困难,所以发现没有像 boost 这样的现有库可以解决这个问题也就不足为奇了。
对于两个容器的情况,这里有一个基于上述论文在 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);
}
评论
下面是一个基于 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';
}
实时示例(需要具有良好 C++14 支持的最新编译器,而不是 VS 2015)。
评论
我很高兴地说,经过类似的寻宝活动,我找到了解决方案。range-v3 是一个好主意,如果你能使用它,但如果你真的需要一个迭代器,HPX 项目已经创建了一个,它与排序完美配合。
由于疏忽,希望可以修复,它仍然需要您与 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";
}
}
评论
std::sort
hpx::sort
std::sort
auto zip_v = view::zip(rows, cols); auto b = ranges::begin(zip_v); auto e = ranges::end(zip_v);
std::sort
评论
std::sort
std::iter_swap
zip_iterator
boost::zip_iterator