使用c++(最好是标准库),我想对一个样本序列进行升序排序,但我也想记住新样本的原始索引。
例如,我有一个集合,或向量,或样本a的矩阵:[5,2,1,4,3]。我想把它们排序为B:[1,2,3,4,5],但我也想记住这些值的原始索引,所以我可以得到另一个集合,它将是:
C:[2,1,4,3,0] -这对应于'B'中每个元素的索引,在原始'A'中。
例如,在Matlab中,你可以这样做:
[a,b]=sort([5, 8, 7])
a = 5 7 8
b = 1 3 2
有谁能想到一个好办法吗?
我遇到了这个问题,并发现直接对迭代器排序是一种对值排序并跟踪下标的方法;不需要定义一个额外的(value, index)对容器,这在值是大对象时很有用;迭代器提供了对值和索引的访问:
/*
* a function object that allows to compare
* the iterators by the value they point to
*/
template < class RAIter, class Compare >
class IterSortComp
{
public:
IterSortComp ( Compare comp ): m_comp ( comp ) { }
inline bool operator( ) ( const RAIter & i, const RAIter & j ) const
{
return m_comp ( * i, * j );
}
private:
const Compare m_comp;
};
template <class INIter, class RAIter, class Compare>
void itersort ( INIter first, INIter last, std::vector < RAIter > & idx, Compare comp )
{
idx.resize ( std::distance ( first, last ) );
for ( typename std::vector < RAIter >::iterator j = idx.begin( ); first != last; ++ j, ++ first )
* j = first;
std::sort ( idx.begin( ), idx.end( ), IterSortComp< RAIter, Compare > ( comp ) );
}
关于使用示例:
std::vector < int > A ( n );
// populate A with some random values
std::generate ( A.begin( ), A.end( ), rand );
std::vector < std::vector < int >::const_iterator > idx;
itersort ( A.begin( ), A.end( ), idx, std::less < int > ( ) );
现在,例如,排序向量中第5小的元素的值为**idx[5],它在原始向量中的下标为distance(A.begin(), *idx[5])或简单地称为*idx[5] - A.begin()。
我最近接触了c++ 20 <ranges>的优雅投影特性,它允许编写更短/更清晰的代码:
std::vector<std::size_t> B(std::size(A));
std::iota(begin(B), end(B), 0);
std::ranges::sort(B, {}, [&](std::size_t i){ return A[i]; });
{}指通常的std::less<std::size_t>。因此,正如您所看到的,我们定义了一个函数,在任何比较之前调用每个元素。这个投影特性实际上是非常强大的,因为这个函数可以是,就像这里,或者它甚至可以是一个方法,或者一个成员值。例如:
struct Item {
float price;
float weight;
float efficiency() const { return price / weight; }
};
int main() {
std::vector<Item> items{{7, 9}, {3, 4}, {5, 3}, {9, 7}};
std::ranges::sort(items, std::greater<>(), &Item::efficiency);
// now items are sorted by their efficiency in decreasing order:
// items = {{5, 3}, {9, 7}, {7, 9}, {3, 4}}
}
如果我们想通过增加价格来排序:
std::ranges::sort(items, {}, &Item::price);
不要定义操作符<或使用lambda,使用投影!
假设给定向量为
A=[2,4,3]
创建一个新向量
V=[0,1,2] // indicating positions
对V进行排序,而不是比较V中的元素,比较A中对应的元素
//Assume A is a given vector with N elements
vector<int> V(N);
std::iota(V.begin(),V.end(),0); //Initializing
sort( V.begin(),V.end(), [&](int i,int j){return A[i]<A[j];} );
使用c++ 11 lambdas:
#include <iostream>
#include <vector>
#include <numeric> // std::iota
#include <algorithm> // std::sort, std::stable_sort
using namespace std;
template <typename T>
vector<size_t> sort_indexes(const vector<T> &v) {
// initialize original index locations
vector<size_t> idx(v.size());
iota(idx.begin(), idx.end(), 0);
// sort indexes based on comparing values in v
// using std::stable_sort instead of std::sort
// to avoid unnecessary index re-orderings
// when v contains elements of equal values
stable_sort(idx.begin(), idx.end(),
[&v](size_t i1, size_t i2) {return v[i1] < v[i2];});
return idx;
}
现在您可以在迭代中使用返回的索引向量,例如
for (auto i: sort_indexes(v)) {
cout << v[i] << endl;
}
您还可以选择提供原始索引向量、排序函数、比较器,或者使用额外的向量在sort_indexes函数中自动重新排序v。
考虑使用@Ulrich Eckhardt建议的std::multimap。只是代码可以变得更简单。
鉴于
std::vector<int> a = {5, 2, 1, 4, 3}; // a: 5 2 1 4 3
在插入的平均时间内排序
std::multimap<int, std::size_t> mm;
for (std::size_t i = 0; i != a.size(); ++i)
mm.insert({a[i], i});
检索值和原始索引
std::vector<int> b;
std::vector<std::size_t> c;
for (const auto & kv : mm) {
b.push_back(kv.first); // b: 1 2 3 4 5
c.push_back(kv.second); // c: 2 1 4 3 0
}
首选std::multimap而不是std::map的原因是允许原始向量的值相等。另外请注意,与std::map不同,操作符[]没有为std::multimap定义。