C ++のソートとインデックスの追跡


216

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

誰もがこれを行うための良い方法を見ることができますか?

回答:


298

C++11ラムダを使用:

#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を自動的に並べ替えることもできます。


4
この答えが大好きです。コンパイラがラムダをサポートしていない場合は、クラスを使用できます。template <typename T> class CompareIndicesByAnotherVectorValues {std :: vector <T> * _values; public:CompareIndicesByAnotherVectorValues(std :: vector <T> * values):_values(values){} public:bool operator()(const int&a、const int&b)const {return(_values)[a]>( _values)[ b]; }};
Yoav

2
私もこの答えが大好きです。ペアのベクトルを作成するために元のベクトルをコピーする必要はありません。
headmyshoulder 2013年

29
手作りのものではなく、for (size_t i = 0; i != idx.size(); ++i) idx[i] = i;私は標準を好むstd::iota( idx.begin(), idx.end(), 0 );
Wyck

6
#include <numeric>iota()の使用
kartikag01 2017年

6
iotaC ++標準ライブラリ全体で最も明確に指定されていないアルゴリズムです。
セスジョンソン

87

intだけでなくstd :: pairをソートすることもできます。最初のintは元のデータ、2番目の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

そのコンパレーターを使用したv_primeでのstd :: sortの結果は次のようになります。

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

各std :: pairから.secondを取得してベクトルを歩くことにより、インデックスを取り除くことができます。


1
これもまさに私がやる方法です。基本的なソート機能は、不要なオーバーヘッドを追加するため、古い位置と新しい位置を追跡しません。
the_mandrill 2009年

8
この関数の欠点は、すべての値にメモリを再割り当てする必要があることです。
Yoav、

1
これは明らかに有効なアプローチですが、元のコンテナを「数値のコンテナ」から「ペアのコンテナ」に変更する必要があるという欠点があります。
ルスラン

18

与えられたベクトルが

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

あなたの答えが大好きです。std::iota()さらに洗練された初期化に使用することもできますmap
Nimrod Morag

はい、使用できます。提案をありがとう
MysticForce

12

索引ソートの汎用バージョンを作成しました。

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をサポートしていないコンパイラでは、ランバ式をクラステンプレートとして置き換えます。

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 asに書き換えます

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

こんにちは!このテンプレート関数をどのようにインスタンス化しますか?2つのテンプレートタイプ名があり、そのうちの1つはイテレータであり、この状況は非常にまれです。手伝ってくれる?
スコットヤン

12
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 = valuei

a[i].second = idx 初期配列。


このポストを訪問し、ユーザーが理解できるように、あなたのコードの記述を追加することを検討してくださいどのようにそれが動作します。
BusyProgrammer 2017

私は実際にこのソリューションが一番好きです-ベクトルのサイズは4程度で、C ++ 11の前で立ち往生していて、ラムダを使用できません。Aditya Aswalに感謝します。
stephanmg

6

私はこの質問に出くわし、イテレータを直接ソートすることは、値をソートしてインデックスを追跡する方法になると考えました。pair(value、index)のsの追加のコンテナーを定義する必要はありません。これは、値が大きなオブジェクトである場合に役立ちます。イテレータは値とインデックスの両方へのアクセスを提供します。

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


3

これを解決するには、マップを使用する別の方法があります。

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;

3

@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

3

std::multimap@Ulrich Eckhardtの提案に従って使用することを検討してください。コードをさらに単純化できるということだけです。

与えられた

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::mapoperator[]のために定義されていませんがstd::multimap


2

std::pairin関数を作成してから、ペアをソートします。

一般的なバージョン:

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


1

ベクター内のアイテムは一意ですか?その場合は、ベクターをコピーし、STLソートでコピーの1つをソートすると、元のベクターで各アイテムがどのインデックスにあったかを見つけることができます。

ベクトルが重複するアイテムを処理することになっている場合は、独自のソートルーチンを実装する方がよいと思います。


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

その後、residuumを介して要素のインデックスにアクセスできます。次のコードは、昇順でソートされた値のインデックスを出力します。

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

もちろん、この手法は、元の配列の比較的小さな値myints(つまり、の上位2バイトに収まる値)に対してのみ機能しますint。ただし、myints次の同一の値を区別するという追加の利点があります。それらのインデックスは正しい順序で出力されます。


1

可能であれば、関数findを使用して位置配列を作成し、配列を並べ替えることができます。

または、キーを要素とするマップを使用し、値を次の配列(A、B、C)での位置のリストとして使用することもできます。

それはそれらの配列の後の使用に依存します。


0

このタイプの質問の場合、元の配列データを新しいデータに格納し、ソートされた配列の最初の要素を複製された配列にバイナリ検索し、そのインデックスをベクトルまたは配列に格納する必要があります。

input array=>a
duplicate array=>b
vector=>c(Stores the indices(position) of the orignal array
Syntax:
for(i=0;i<n;i++)
c.push_back(binarysearch(b,n,a[i]));`

ここで、binarysearchは、配列、配列のサイズ、検索項目を受け取り、検索された項目の位置を返す関数です


-1

多くの方法があります。かなり簡単な解決策は、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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.