web-dev-qa-db-ja.com

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

C++と、できれば標準ライブラリを使用して、サンプルのシーケンスを昇順で並べ替えたいが、新しいサンプルの元のインデックスも覚えておきたい。

たとえば、サンプルのセット、ベクトル、または行列A : [5, 2, 1, 4, 3]があります。これらをB : [1,2,3,4,5]にソートしたいのですが、値の元のインデックスを覚えておきたいので、次のような別のセットを取得できます:C : [2, 1, 4, 3, 0 ]-それぞれのインデックスに対応元の「A」の「B」の要素。

たとえば、Matlabでは次のことができます。

 [a,b]=sort([5, 8, 7])
 a = 5 7 8
 b = 1 3 2

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

195
Mingus

C++ 11ラムダの使用

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

263
Lukasz Wiklendt

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を取得することで、インデックスを剥離できます。

85
RAC

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

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)()) ;
12
hkyi
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 at i 'th。

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

11
Aditya Aswal

それはそうであるよりも簡単です。

与えられたベクトルが

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

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

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

6
behzad.nouri

@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
2
sigvaldm

関数で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

2
Omid

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

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

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

1
HyLian

@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に対して定義されています。

1
fleix

このタイプの質問の場合、元の配列データを新しいデータに保存してから、ソートされた配列の最初の要素を複製配列にバイナリ検索し、そのインデックスをベクトルまたは配列に保存します。

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
Mohit Vachhani

ベクター内のアイテムは一意ですか?その場合、ベクターをコピーし、コピーの1つを STL Sort でソートすると、元のベクターで各アイテムがどのインデックスを持っているかがわかります。

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

1
Mizipzor

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

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;
1
Ulrich Eckhardt

まあ、私のソリューションは残留物のテクニックを使用しています。ソートの対象となる値を上位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の同一の値を区別するという追加の利点があります。それらのインデックスは正しい順序で印刷されます。

1
Macmep

多くの方法があります。かなり簡単な解決策は、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
0
Gokul