C ++排序和跟踪索引

时间:2009-10-16 11:18:24

标签: c++ sorting stl indexing

使用C ++,希望是标准库,我想按升序排序一系列样本,但我也想记住新样本的原始索引。

例如,我有一个样本集{/ 1}}的集合,矢量或矩阵。我想将它们排序为A : [5, 2, 1, 4, 3],但我也想记住值的原始索引,所以我可以得到另一个集合:  B : [1,2,3,4,5] - 对应于原始'A'中'B'中每个元素的索引。

例如,在Matlab中你可以这样做:

C : [2, 1, 4, 3, 0 ]

有人能看到这样做的好方法吗?

16 个答案:

答案 0 :(得分:248)

使用C ++ 11 lambdas

#include <iostream>
#include <vector>
#include <numeric>      // std::iota
#include <algorithm>    // std::sort

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
  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。

答案 1 :(得分:83)

你可以对std :: pair进行排序而不仅仅是整数 - 第一个int是原始数据,第二个int是原始索引。然后提供一个只对第一个int进行排序的比较器。例如:

Your problem instance: v = [5 7 8]
New problem instance: v_prime = [<5,0>, <8,1>, <7,2>]

使用比较器对新问题实例进行排序:

typedef std::pair<int,int> mypair;
bool comparator ( const mypair& l, const mypair& r)
   { return l.first < r.first; }
// forgetting the syntax here but intent is clear enough

使用该比较器的st_ :: sort on v_prime的结果应为:

v_prime = [<5,0>, <7,2>, <8,1>]

您可以通过遍历向量来剥离索引,从每个std :: pair中获取.second。

答案 2 :(得分:11)

我写了索引排序的通用版本。

template <class RAIter, class Compare>
void argsort(RAIter iterBegin, RAIter iterEnd, Compare comp, 
    std::vector<size_t>& indexes) {

    std::vector< std::pair<size_t,RAIter> > pv ;
    pv.reserve(iterEnd - iterBegin) ;

    RAIter iter ;
    size_t k ;
    for (iter = iterBegin, k = 0 ; iter != iterEnd ; iter++, k++) {
        pv.push_back( std::pair<int,RAIter>(k,iter) ) ;
    }

    std::sort(pv.begin(), pv.end(), 
        [&comp](const std::pair<size_t,RAIter>& a, const std::pair<size_t,RAIter>& b) -> bool 
        { return comp(*a.second, *b.second) ; }) ;

    indexes.resize(pv.size()) ;
    std::transform(pv.begin(), pv.end(), indexes.begin(), 
        [](const std::pair<size_t,RAIter>& a) -> size_t { return a.first ; }) ;
}

除了用于接收已排序索引的索引容器外,用法与std :: sort的用法相同。 测试:

int a[] = { 3, 1, 0, 4 } ;
std::vector<size_t> indexes ;
argsort(a, a + sizeof(a) / sizeof(a[0]), std::less<int>(), indexes) ;
for (size_t i : indexes) printf("%d\n", int(i)) ;
你应该得到2 1 0 3。 对于没有c ++ 0x支持的编译器,将lamba表达式替换为类模板:

template <class RAIter, class Compare> 
class PairComp {
public:
  Compare comp ;
  PairComp(Compare comp_) : comp(comp_) {}
  bool operator() (const std::pair<size_t,RAIter>& a, 
    const std::pair<size_t,RAIter>& b) const { return comp(*a.second, *b.second) ; }        
} ;

并将std :: sort重写为

std::sort(pv.begin(), pv.end(), PairComp(comp)()) ;

答案 3 :(得分:11)

vector<pair<int,int> >a;

for (i = 0 ;i < n ; i++) {
    // filling the original array
    cin >> k;
    a.push_back (make_pair (k,i)); // k = value, i = original index
}

sort (a.begin(),a.end());

for (i = 0 ; i < n ; i++){
    cout << a[i].first << " " << a[i].second << "\n";
}

现在a包含我们的值以及它们在排序中的相应索引。

a[i].first = value&#39;

i

a[i].second = idx在初始数组中。

答案 4 :(得分:6)

我遇到了这个问题,并想出直接对迭代器进行排序将是一种对值进行排序并跟踪索引的方法;没有必要定义pair的(值,索引)的额外容器,当值是大对象时,这是有用的;迭代器提供对值和索引的访问:

/*
 * 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( )

答案 5 :(得分:6)

它比看起来容易。

假设给定的向量是

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);
 int x=0;
 std::iota(V.begin(),V.end(),x++); //Initializing
 sort( V.begin(),V.end(), [&](int i,int j){return A[i]<A[j];} );

答案 6 :(得分:2)

在函数中创建std::pair然后排序对:

通用版本:

template< class RandomAccessIterator,class Compare >
auto sort2(RandomAccessIterator begin,RandomAccessIterator end,Compare cmp) ->
   std::vector<std::pair<std::uint32_t,RandomAccessIterator>>
{
    using valueType=typename std::iterator_traits<RandomAccessIterator>::value_type;
    using Pair=std::pair<std::uint32_t,RandomAccessIterator>;

    std::vector<Pair> index_pair;
    index_pair.reserve(std::distance(begin,end));

    for(uint32_t idx=0;begin!=end;++begin,++idx){
        index_pair.push_back(Pair(idx,begin));
    }

    std::sort( index_pair.begin(),index_pair.end(),[&](const Pair& lhs,const Pair& rhs){
          return cmp(*lhs.second,*rhs.second);
    });

    return index_pair;
}

ideone

答案 7 :(得分:2)

@Lukasz Wiklendt的美丽解决方案!虽然在我的情况下我需要更通用的东西,所以我稍微修改了一下:

template <class RAIter, class Compare>
vector<size_t> argSort(RAIter first, RAIter last, Compare comp) {

  vector<size_t> idx(last-first);
  iota(idx.begin(), idx.end(), 0);

  auto idxComp = [&first,comp](size_t i1, size_t i2) {
      return comp(first[i1], first[i2]);
  };

  sort(idx.begin(), idx.end(), idxComp);

  return idx;
}

示例:查找按长度排序字符串向量的索引,除了第一个虚拟元素。

vector<string> test = {"dummy", "a", "abc", "ab"};

auto comp = [](const string &a, const string& b) {
    return a.length() > b.length();
};

const auto& beginIt = test.begin() + 1;
vector<size_t> ind = argSort(beginIt, test.end(), comp);

for(auto i : ind)
    cout << beginIt[i] << endl;

打印:

abc
ab
a

答案 8 :(得分:1)

如果可能,您可以使用find函数构建位置数组,然后对数组进行排序。

或许您可以使用键作为元素的地图,以及值在即将到来的数组中的位置列表(A,B和C)

这取决于后来对这些数组的使用。

答案 9 :(得分:1)

向量中的项目是否唯一?如果是这样,复制矢量,使用STL Sort对其中一个副本进行排序,然后您就可以找到每个项目在原始矢量中的索引。

如果向量应该处理重复的项目,我认为你最好实现自己的排序例程。

答案 10 :(得分:1)

还有另一种解决方法,使用地图:

vector<double> v = {...}; // input data
map<double, unsigned> m; // mapping from value to its index
for (auto it = v.begin(); it != v.end(); ++it)
    m[*it] = it - v.begin();

这将消除非独特元素。如果这不可接受,请使用多重映射:

vector<double> v = {...}; // input data
multimap<double, unsigned> m; // mapping from value to its index
for (auto it = v.begin(); it != v.end(); ++it)
    m.insert(make_pair(*it, it - v.begin()));

为了输出索引,迭代地图或多图:

for (auto it = m.begin(); it != m.end(); ++it)
    cout << it->second << endl;

答案 11 :(得分:1)

好吧,我的解决方案使用残留技术。我们可以将排序值放在上面的2个字节和元素的索引中 - 在较低的2个字节中:

int myints[] = {32,71,12,45,26,80,53,33};

for (int i = 0; i < 8; i++)
   myints[i] = myints[i]*(1 << 16) + i;

然后像往常一样对数组myints进行排序:

std::vector<int> myvector(myints, myints+8);
sort(myvector.begin(), myvector.begin()+8, std::less<int>());

之后,您可以通过残差访问元素的索引。以下代码打印按升序排序的值的索引:

for (std::vector<int>::iterator it = myvector.begin(); it != myvector.end(); ++it)
   std::cout << ' ' << (*it)%(1 << 16);

当然,这种技术仅适用于原始数组myints中相对较小的值(即那些可以适合int的高2字节的值)。但它具有区分myints的相同值的额外好处:它们的索引将以正确的顺序打印。

答案 12 :(得分:1)

对于这类问题 将原始数组数据存储到新数据中,然后将已排序数组的第一个元素二进制搜索到重复数组中,并将该indice存储到向量或数组中。

SELECT
  DATE(SEC_TO_TIMESTAMP(created_utc)) date_submission,
  SUM((LOWER(body) CONTAINS ('apples') AND LOWER(body) CONTAINS ('oranges'))) AS num_apples_oranges_submissions
FROM
  [fh-bigquery:reddit_comments.2008]
GROUP BY
  date_submission
ORDER BY
  date_submission 

这里binarysearch是一个函数,它接受数组,数组大小,搜索项并返回搜索项的位置

答案 13 :(得分:1)

按照@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不同,operator[]并未为std::multimap定义。

答案 14 :(得分:0)

有很多方法。一个相当简单的解决方案是使用2D向量。

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

int main() {
 vector<vector<double>> val_and_id;
 val_and_id.resize(5);
 for (int i = 0; i < 5; i++) {
   val_and_id[i].resize(2); // one to store value, the other for index.
 }
 // Store value in dimension 1, and index in the other:
 // say values are 5,4,7,1,3.
 val_and_id[0][0] = 5.0;
 val_and_id[1][0] = 4.0;
 val_and_id[2][0] = 7.0;
 val_and_id[3][0] = 1.0;
 val_and_id[4][0] = 3.0;

 val_and_id[0][1] = 0.0;
 val_and_id[1][1] = 1.0;
 val_and_id[2][1] = 2.0;
 val_and_id[3][1] = 3.0;
 val_and_id[4][1] = 4.0;

 sort(val_and_id.begin(), val_and_id.end());
 // display them:
 cout << "Index \t" << "Value \n";
 for (int i = 0; i < 5; i++) {
  cout << val_and_id[i][1] << "\t" << val_and_id[i][0] << "\n";
 }
 return 0;
}

以下是输出:

   Index   Value
   3       1
   4       3
   1       4
   0       5
   2       7

答案 15 :(得分:0)

我最近使用了 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>。正如你所看到的,我们定义了一个函数来在任何比较之前调用每个元素。这个投影功能实际上非常强大,因为这个函数可以是一个 lambda,也可以是一个方法,或者一个成员值。例如:

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);

不要定义 operator< 或使用 lambdas,使用投影!