C ++ sıralama ve dizinleri izleme


216

C ++ ve umarım standart kütüphane kullanarak, artan bir sırayla örnekleri sıralamak istiyorum, ama aynı zamanda yeni örneklerin orijinal dizinlerini hatırlamak istiyorum.

Örneğin, bir set, vektör veya örnek matrisim var A : [5, 2, 1, 4, 3]. Bunları sıralamak istiyorum B : [1,2,3,4,5], ama aynı zamanda değerlerin orijinal dizinlerini hatırlamak istiyorum, böylece olacak başka bir set alabilirsiniz: C : [2, 1, 4, 3, 0 ]- hangi 'B' her öğenin dizinine karşılık gelen orijinal ' A'.

Örneğin, Matlab'da şunları yapabilirsiniz:

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

Bunu yapmak için iyi bir yol görebilir mi?

Yanıtlar:


298

C++11 lambda kullanma :

#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;
}

Artık döndürülen dizin vektörünü aşağıdaki gibi yinelemelerde kullanabilirsiniz:

for (auto i: sort_indexes(v)) {
  cout << v[i] << endl;
}

Ek bir vektör kullanarak orijinal dizin vektörünüzü, sıralama işlevini, karşılaştırıcıyı veya sort_indexes işlevinde v'yi otomatik olarak yeniden sıralamayı da seçebilirsiniz.


4
Derleyiciniz lambdas'ı desteklemiyorsa, bir sınıf kullanabilirsiniz: template <typename T> class CompareIndicesByAnotherVectorValues ​​{std :: vector <T> * _values; public: CompareIndicesByAnotherVectorValues ​​(std :: vektör <T> * değerler): _values ​​(değerler) {} public: bool operatörü () (const int & a, const int & b) const {return ( _values) [a]> ( _values) [ b]; }};
Yoav

2
Ben de bu cevabı seviyorum, çift vektörü oluşturmak için orijinal vektörü kopyalamaya gerek yok.
headmyshoulder

29
El yapımı yerine for (size_t i = 0; i != idx.size(); ++i) idx[i] = i;standart tercih ediyorumstd::iota( idx.begin(), idx.end(), 0 );
Wyck

6
#include <numeric>iota () için kullanın
kartikag01

6
iotaC ++ standart kütüphanesinin tamamında en az açıkça adlandırılan algoritmadır.
Seth Johnson

87

Sadece int yerine std :: pair sıralayabilirsiniz - ilk int orijinal verilerdir, ikinci int orijinal indekstir. Sonra yalnızca ilk int'de sıralanan bir karşılaştırıcı sağlayın. Misal:

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

Yeni sorun örneğini aşağıdaki gibi bir karşılaştırıcı kullanarak sıralayın:

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

Bu karşılaştırıcıyı kullanarak v_prime'da std :: sort'in sonucu şöyle olmalıdır:

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

Her std :: çiftinden .second alarak vektörü yürüterek indeksleri soyabilirsiniz.


1
Ben de aynen böyle yapardım. Temel sıralama işlevi eski ve yeni konumları izlemez, çünkü bu gereksiz ek yük getirir.
the_mandrill

8
Bu işlevin dezavantajı, tüm değerler için belleği yeniden tahsis etmenizi gerektirmesidir.
Yoav

1
Bu açık bir şekilde uygulanabilir bir yaklaşımdır, ancak orijinal kapsayıcısını "sayılar kapsayıcısından" "çift kapsayıcısına" değiştirmeniz dezavantajı vardır.
Ruslan

19

Varsayalım ki verilen vektör

A=[2,4,3]

Yeni bir vektör oluştur

V=[0,1,2] // indicating positions

V'yi sıralayın ve V öğelerini karşılaştırmak yerine sıralama yaparken, A'nın karşılık gelen öğelerini karşılaştırın

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

Cevabınızı seviyorum. hatta std::iota()daha zarif bir başlangıç ​​için kullanabilirsinizmap
Nimrod Morag

Evet kullanabiliriz! Öneri için teşekkürler
MysticForce

12

Dizin sıralamasının genel sürümünü yazdım.

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

Sıralı dizinleri almak için bir dizin kapsayıcısı dışında kullanım std :: sort ile aynıdır. test yapmak:

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 almalısınız. c ++ 0x desteği olmayan derleyiciler için lamba ifadesini sınıf şablonu olarak değiştirin:

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 :: yeniden sırala

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

Merhaba hkyi! Bu şablon işlevini nasıl başlatırız? İki şablon türü vardır ve bunlardan biri bu durumu çok nadir yapan bir yineleyicidir. Yardım edebilir misin?
Scott Yang

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";
}

Şimdi ahem değerlerimizi hem de sıralı indekslerini içerir.

a[i].first = valueEn i'th.

a[i].second = idx başlangıç ​​dizisinde.


Bu yayını ziyaret eden kullanıcıların nasıl çalıştığını anlayabilmeleri için kodunuzun açıklamasını eklemeyi düşünün .
MeşgulProgramcı

Aslında bu çözümü en çok sevdim - vektörüm 4 boyutta ve C ++ 11'den önce sıkıştım ve lambdas kullanamıyorum. Teşekkürler Aditya Aswal.
stephanmg

6

Bu soruya rastladım ve iteratörleri doğrudan sıralamanın, değerleri sıralamanın ve endeksleri takip etmenin bir yolu olacağını anladım; pairDeğerler büyük nesneler olduğunda yardımcı olan fazladan ( s, değer, dizin) bir kap tanımlamanıza gerek yoktur ; Yineleyiciler, hem değere hem de dizine erişim sağlar:

/*
 * 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 ) );
}

kullanım örneği gelince:

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

şimdi, örneğin, sıralanmış vektördeki 5. en küçük eleman değere sahip olacak **idx[ 5 ]ve orijinal vektördeki indeksi distance( A.begin( ), *idx[ 5 ] )ya basitçe olacaktır *idx[ 5 ] - A.begin( ).


3

Bir harita kullanarak bunu çözmenin başka bir yolu var:

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

Bu, benzersiz olmayan öğeleri ortadan kaldıracaktır. Bu kabul edilebilir değilse, bir multimap kullanın:

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

Endekslerin çıktısını almak için harita veya çoklu harita üzerinde tekrarlayın:

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

3

@Lukasz Wiklendt tarafından güzel bir çözüm! Her ne kadar benim durumumda daha genel bir şeye ihtiyacım olmasına rağmen biraz değiştirdim:

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

Örnek: Bir kukla olan ilk öğe hariç, bir dizgenin vektörünü uzunluğa göre sıralayan indeksleri bulun.

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;

baskılar:

abc
ab
a

3

std::multimap@Ulrich Eckhardt tarafından önerildiği gibi kullanmayı düşünün . Sadece kod daha da basitleştirilebilir.

verilmiş

std::vector<int> a = {5, 2, 1, 4, 3};  // a: 5 2 1 4 3

Ortalama yerleştirme süresini sıralamak için

std::multimap<int, std::size_t> mm;
for (std::size_t i = 0; i != a.size(); ++i)
    mm.insert({a[i], i});

Değerleri ve orijinal indeksleri almak için

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
}

Nedeni tercih std::multimapa std::mapözgün vektörler eşit değerlere izin vermektir. Ayrıca için farklı olarak, o notu memnun std::map, operator[]için tanımlanmamıştır std::multimap.


2

Bir std::pairişlev oluşturun ve ardından çifti sıralayın:

genel sürüm:

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

Vektördeki öğeler benzersiz mi? Eğer öyleyse, vektörü kopyalayın, kopyalardan birini STL Sıralaması ile sıralayın, ardından her bir öğenin orijinal vektörde hangi dizine sahip olduğunu bulabilirsiniz.

Vektörün yinelenen öğeleri işlemesi gerekiyorsa, kendi sıralama rutininizi uygulamaktan daha iyi olduğunuzu düşünüyorum.


1

Benim çözümüm kalıntı tekniği kullanıyor. Değerleri üst 2 bayta ve öğelerin indekslerine göre alt 2 bayta sıralayabiliriz:

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

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

Ardından diziyi myintsher zamanki gibi sıralayın :

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

Bundan sonra elementlerin indekslerine rezidü yoluyla erişebilirsiniz. Aşağıdaki kod, artan sırada sıralanmış değerlerin indekslerini yazdırır:

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

Tabii ki, bu teknik sadece orijinal dizideki nispeten küçük değerler için çalışır myints(yani üst 2 bayta sığabilecek olanlar int). Ancak, aynı değerlerin ayırt edilmesinin ek bir yararı vardır myints: endeksleri doğru sırayla yazdırılacaktır.


1

Mümkünse, find işlevini kullanarak konum dizisini oluşturabilir ve sonra diziyi sıralayabilirsiniz.

Ya da anahtarın öğe olacağı bir harita kullanabilirsiniz ve yaklaşan dizilerdeki konumunun bir listesini (A, B ve C) değerleri kullanabilirsiniz

Bu dizilerin sonraki kullanımlarına bağlıdır.


0

Bu tür bir soru için Orignal dizi verilerini yeni bir veride saklayın ve ardından ikili dizide sıralanan dizinin ilk öğesini çoğaltılmış diziye koyun ve bu indeks bir vektör veya dizide saklanmalıdır.

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

Burada binarysearch, diziyi, dizi boyutunu, arama öğesini alan ve aranan öğenin konumunu döndüren bir işlevdir


-1

Birçok yolu var. Oldukça basit bir çözüm, bir 2D vektör kullanmaktır.

#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;
}

İşte çıktı:

   Index   Value
   3       1
   4       3
   1       4
   0       5
   2       7
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.