it-swarm.com.de

C++ Sortieren und Verfolgen von Indizes

Mit C++ und hoffentlich der Standardbibliothek möchte ich eine Folge von Samples aufsteigend sortieren, aber ich möchte auch die ursprünglichen Indizes der neuen Samples berücksichtigen.

Zum Beispiel habe ich eine Menge oder einen Vektor oder eine Matrix von Proben A : [5, 2, 1, 4, 3]. Ich möchte diese sortieren, um B : [1,2,3,4,5] zu sein, aber ich möchte auch die ursprünglichen Indizes der Werte erinnern, damit ich einen anderen Satz erhalten kann, der sein würde: C : [2, 1, 4, 3, 0 ] - entspricht dem Index jedes Elements in 'B', im ursprünglichen 'A'.

In Matlab können Sie beispielsweise Folgendes tun:

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

Kann jemand einen guten Weg sehen, dies zu tun?

182
Mingus

C++ 11-Lambdas verwenden

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

Jetzt können Sie den zurückgegebenen Indexvektor in Iterationen wie verwenden

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

Natürlich können Sie auch Ihren eigenen ursprünglichen Indexvektor, eine Sortierfunktion, einen Vergleicher angeben oder v in der sort_indexes-Funktion mithilfe eines zusätzlichen Vektors automatisch neu anordnen.

234
Lukasz Wiklendt

Sie können std :: pair statt nur ints sortieren - das erste int ist die Originaldaten, das zweite int ist der Originalindex. Geben Sie dann einen Komparator an, der nur nach dem ersten int sortiert. Beispiel:

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

Sortieren Sie die neue Probleminstanz mit einem Vergleicher wie:

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

Das Ergebnis von std :: sort auf v_prime mit diesem Komparator sollte lauten:

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

Sie können die Indizes herausziehen, indem Sie den Vektor durchlaufen und von jedem std :: -Paar die .sekunden.

81
RAC

Ich habe eine generische Version von Index Sort geschrieben.

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

Die Verwendung ist die gleiche wie die von std :: sort, mit Ausnahme eines Index-Containers, der sortierte Indizes erhalten soll.

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

sie sollten 2 1 0 3 . für die Compiler ohne C++ 0x-Unterstützung erhalten, ersetzen Sie den Lamba-Ausdruck als Klassenvorlage:

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

und schreiben Sie std :: sort as neu

std::sort(pv.begin(), pv.end(), PairComp(comp)()) ;
11
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";
}

Jetzt enthält a sowohl unsere Werte als auch ihre jeweiligen Indizes in der Sortierung.

a[i].first = value bei i 'th.

a[i].second = idx im ursprünglichen Array.

8
Aditya Aswal

Ich bin auf diese Frage gestoßen, und herauszufinden, dass das Sortieren der Iteratoren direkt eine Möglichkeit wäre, die Werte zu sortieren und die Indizes zu verfolgen; Es ist nicht erforderlich, einen zusätzlichen Container mit pairs von (Wert, Index) zu definieren, was hilfreich ist, wenn die Werte große Objekte sind. Die Iteratoren bieten Zugriff auf den Wert und den 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 ) );
}

wie für das Anwendungsbeispiel:

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

das fünftkleinste Element im sortierten Vektor hätte beispielsweise den Wert **idx[ 5 ] und sein Index im ursprünglichen Vektor wäre distance( A.begin( ), *idx[ 5 ] ) oder einfach *idx[ 5 ] - A.begin( ).

6
behzad.nouri

Es ist einfacher als es scheint.

Angenommen, der angegebene Vektor ist 

A=[2,4,3]

Einen neuen Vektor erstellen

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

Sortieren Sie V und vergleichen Sie die entsprechenden Elemente von A, und sortieren Sie stattdessen Elemente von V

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

Schöne Lösung von @Lukasz Wiklendt! Obwohl ich in meinem Fall etwas generischeres brauchte, modifizierte ich es ein wenig:

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

Beispiel: Suchen Sie Indizes, indem Sie einen Vektor von Strings nach Länge sortieren, mit Ausnahme des ersten Elements, das ein Dummy ist.

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;

druckt:

abc
ab
a
2
sigvaldm

Erstellen Sie einen std::pair in der Funktion und sortieren Sie dann das Paar: 

generische Version: 

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

Wenn möglich, können Sie das Positionsarray mithilfe der Suchfunktion erstellen und das Array anschließend sortieren.

Oder vielleicht können Sie eine Karte verwenden, bei der der Schlüssel das Element ist und die Werte eine Liste seiner Position in den kommenden Arrays (A, B und C)

Dies hängt von der späteren Verwendung dieser Arrays ab.

1
HyLian

Für diesen FragetypSpeichern Sie die Daten des ursprünglichen Arrays in neue Daten und suchen Sie dann binär nach dem ersten Element des sortierten Arrays im duplizierten Array, und dieser Index sollte in einem Vektor oder Array gespeichert werden.

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

Hier ist binarysearch eine Funktion, die das Array, die Größe des Arrays, das Suchelement und die Position des gesuchten Elements zurückgibt

1
Mohit Vachhani

Sind die Elemente im Vektor eindeutig? Wenn ja, kopieren Sie den Vektor, sortieren Sie eine der Kopien mit STL Sort , und Sie können herausfinden, welchen Index jedes Element im ursprünglichen Vektor hatte.

Wenn der Vektor mit doppelten Elementen umgehen soll, sollten Sie Ihre eigene Sortierroutine besser implementieren.

1
Mizipzor

Es gibt eine andere Möglichkeit, dies mithilfe einer Karte zu lösen:

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

Dadurch werden jedoch nicht eindeutige Elemente beseitigt. Wenn dies nicht akzeptabel ist, verwenden Sie eine Multimap:

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

Um die Indizes auszugeben, iterieren Sie über die Karte oder Multimap:

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

Nun, meine Lösung verwendet Reststofftechnik. Wir können die Werte unter die Sortierung in die oberen 2 Bytes und die Indizes der Elemente - in die unteren 2 Bytes setzen:

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

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

Dann sortieren Sie das Array myints wie gewohnt:

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

Danach können Sie über Residuum auf die Indizes der Elemente zugreifen. Mit dem folgenden Code werden die Indizes der Werte in aufsteigender Reihenfolge sortiert angezeigt:

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

Natürlich funktioniert diese Technik nur für die relativ kleinen Werte in dem ursprünglichen Array myints (d. H. Diejenigen, die in die oberen 2 Bytes von int passen können). Es hat jedoch den zusätzlichen Vorteil, identische Werte von myints zu unterscheiden: Ihre Indizes werden in der richtigen Reihenfolge gedruckt.

1
Macmep

Es gibt viele Wege. Eine ziemlich einfache Lösung ist die Verwendung eines 2D-Vektors.

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

Hier ist die Ausgabe:

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

Erwägen Sie die Verwendung von std::multimap, wie von @Ulrich Eckhardt vorgeschlagen. Nur dass der Code noch einfacher gemacht werden könnte.

Gegeben

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

In der mittleren Zeit des Einfügens sortieren

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

Werte und ursprüngliche Indizes abrufen

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
}

Der Grund, einen std::multimap einem std::map vorzuziehen, besteht darin, gleiche Werte in Originalvektoren zuzulassen. Beachten Sie auch, dass std::map im Gegensatz zu operator[] nicht für std::multimap definiert ist.

0
fleix