Tri C ++ et suivi des index


216

En utilisant C ++ et, espérons-le, la bibliothèque standard, je veux trier une séquence d'échantillons dans l'ordre croissant, mais je veux aussi me souvenir des index originaux des nouveaux échantillons.

Par exemple, j'ai un ensemble, ou un vecteur, ou une matrice d'échantillons A : [5, 2, 1, 4, 3]. Je veux les trier B : [1,2,3,4,5], mais je veux aussi me souvenir des index originaux des valeurs, donc je peux obtenir un autre ensemble qui serait: C : [2, 1, 4, 3, 0 ]- qui correspond à l'index de chaque élément dans 'B', dans l'original ' UNE'.

Par exemple, dans Matlab, vous pouvez faire:

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

Quelqu'un peut-il voir une bonne façon de procéder?

Réponses:


298

Utilisation de C++11 lambdas:

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

Vous pouvez maintenant utiliser le vecteur d'index renvoyé dans des itérations telles que

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

Vous pouvez également choisir de fournir votre vecteur d'index d'origine, la fonction de tri, le comparateur ou de réorganiser automatiquement v dans la fonction sort_indexes à l'aide d'un vecteur supplémentaire.


4
J'adore cette réponse. Si votre compilateur ne prend pas en charge les lambdas, vous pouvez utiliser une classe: 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
J'aime aussi cette réponse, il n'est pas nécessaire de copier le vecteur d'origine pour créer le vecteur de paires.
headmyshoulder

29
Plutôt que le fait à la main, for (size_t i = 0; i != idx.size(); ++i) idx[i] = i;je préfère le standardstd::iota( idx.begin(), idx.end(), 0 );
Wyck

6
utiliser #include <numeric>pour iota ()
kartikag01

6
iotaest l'algorithme le moins clairement nommé de toute la bibliothèque standard C ++.
Seth Johnson

87

Vous pouvez trier std :: pair au lieu de simplement des entiers - le premier entier est les données d'origine, le second entier est l'index d'origine. Fournissez ensuite un comparateur qui ne trie que le premier entier. Exemple:

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

Triez la nouvelle instance de problème à l'aide d'un comparateur comme:

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

Le résultat de std :: sort sur v_prime, en utilisant ce comparateur, devrait être:

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

Vous pouvez décoller les indices en parcourant le vecteur, en saisissant .second de chaque std :: pair.


1
C'est exactement comme ça que je le ferais aussi. La fonction de tri de base ne suit pas les anciennes et les nouvelles positions car cela ajouterait des frais généraux inutiles supplémentaires.
the_mandrill

8
L'inconvénient de cette fonction est qu'elle vous oblige à réallouer la mémoire pour toutes les valeurs.
Yoav

1
C'est évidemment une approche viable, mais elle a un inconvénient que vous devez changer votre conteneur d'origine de "conteneur de nombres" à "conteneur de paires".
Ruslan

18

Supposons que le vecteur donné soit

A=[2,4,3]

Créer un nouveau vecteur

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

Triez V et pendant le tri au lieu de comparer les éléments de V, comparez les éléments correspondants de 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];} );

Aimez votre réponse. vous pouvez même utiliser std::iota()pour une initialisation plus élégante demap
Nimrod Morag

Oui, nous pouvons l'utiliser! Merci pour la suggestion
MysticForce

12

J'ai écrit une version générique du tri d'index.

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

L'utilisation est la même que celle de std :: sort à l'exception d'un conteneur d'index pour recevoir les index triés. essai:

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

vous devriez obtenir 2 1 0 3. pour les compilateurs sans prise en charge de c ++ 0x, remplacez l'expression lamba comme modèle de classe:

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

et réécrire std :: sort as

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

Salut hkyi! Comment instancier cette fonction de modèle? Il a deux noms de type de modèle et l'un d'eux est un itérateur qui rend cette situation très rare. Pourriez-vous aider?
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";
}

Maintenant aContient à la fois nos valeurs et leurs indices respectifs dans le tri.

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

a[i].second = idx dans le tableau initial.


Pensez à ajouter une description de votre code afin que les utilisateurs qui visitent ce message puissent comprendre comment il fonctionne.
BusyProgrammer

En fait, je préfère cette solution - mon vecteur est de taille 4 environ et je suis bloqué avant C ++ 11 et je ne peux pas utiliser de lambdas. Merci Aditya Aswal.
stephanmg

6

Je suis tombé sur cette question et j'ai compris que trier les itérateurs directement serait un moyen de trier les valeurs et de garder une trace des indices; Il n'est pas nécessaire de définir un conteneur supplémentaire de pairs de (valeur, index) qui est utile lorsque les valeurs sont de gros objets; Les itérateurs fournissent l'accès à la fois à la valeur et à l'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 ) );
}

comme pour l'exemple d'utilisation:

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

maintenant, par exemple, le 5e plus petit élément du vecteur trié aurait une valeur **idx[ 5 ]et son indice dans le vecteur d'origine serait distance( A.begin( ), *idx[ 5 ] )ou tout simplement *idx[ 5 ] - A.begin( ).


3

Il existe une autre façon de résoudre ce problème, en utilisant une carte:

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

Cela supprimera cependant les éléments non uniques. Si ce n'est pas acceptable, utilisez une carte multiple:

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

Pour afficher les indices, parcourez la carte ou la carte multiple:

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

3

Belle solution par @Lukasz Wiklendt! Bien que dans mon cas, j'avais besoin de quelque chose de plus générique, je l'ai donc modifié un peu:

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

Exemple: Trouvez des indices triant un vecteur de chaînes par longueur, à l'exception du premier élément qui est un mannequin.

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;

impressions:

abc
ab
a

3

Pensez à utiliser std::multimap comme suggéré par @Ulrich Eckhardt. Juste que le code pourrait être encore plus simple.

Donné

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

Trier dans le temps moyen d'insertion

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

Pour récupérer des valeurs et des indices d'origine

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
}

La raison de préférer a std::multimapà a std::mapest de permettre des valeurs égales dans les vecteurs d'origine. Veuillez également noter que, contrairement à std::map, operator[]n'est pas défini pour std::multimap.


2

Faire un std::pair fonction in puis triez la paire:

version générique:

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

idéone


1

Les éléments du vecteur sont-ils uniques? Si oui, copiez le vecteur, triez l'une des copies avec STL Sort puis vous pourrez trouver l'index de chaque élément dans le vecteur d'origine.

Si le vecteur est censé gérer les éléments en double, je pense que vous feriez mieux d'implémenter votre propre routine de tri.


1

Eh bien, ma solution utilise la technique des résidus. Nous pouvons placer les valeurs sous tri dans les 2 octets supérieurs et les indices des éléments - dans les 2 octets inférieurs:

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

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

Triez ensuite le tableau myintscomme d'habitude:

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

Après cela, vous pouvez accéder aux indices des éléments via residuum. Le code suivant imprime les indices des valeurs triées dans l'ordre croissant:

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

Bien sûr, cette technique ne fonctionne que pour les valeurs relativement petites du tableau d'origine myints(c'est-à-dire celles qui peuvent tenir dans les 2 octets supérieurs de int). Mais il a l'avantage supplémentaire de distinguer des valeurs identiques de myints: leurs indices seront imprimés dans le bon ordre.


1

Si c'est possible, vous pouvez créer le tableau de positions à l'aide de la fonction find, puis trier le tableau.

Ou peut-être pouvez-vous utiliser une carte où la clé serait l'élément, et les valeurs une liste de sa position dans les tableaux à venir (A, B et C)

Cela dépend des utilisations ultérieures de ces tableaux.


0

Pour ce type de question Stockez les données du tableau d'origine dans de nouvelles données, puis recherchez en binaire le premier élément du tableau trié dans le tableau dupliqué et cet indice doit être stocké dans un vecteur ou un tableau.

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

Ici, la recherche binaire est une fonction qui prend le tableau, la taille du tableau, l'élément de recherche et retournerait la position de l'élément recherché


-1

Il y a plusieurs façons. Une solution assez simple consiste à utiliser un vecteur 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;
}

Voici la sortie:

   Index   Value
   3       1
   4       3
   1       4
   0       5
   2       7
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.