web-dev-qa-db-fra.com

Tri C++ et suivi des index

En utilisant C++ et, espérons-le, la bibliothèque standard, je souhaite trier une séquence d'échantillons par ordre croissant, mais je souhaite également garder en mémoire les index d'origine des nouveaux échantillons.

Par exemple, j'ai un ensemble, un vecteur ou une matrice d'échantillons A : [5, 2, 1, 4, 3]. Je veux trier ces pour être B : [1,2,3,4,5], mais je veux aussi me rappeler les index originaux des valeurs, afin que je puisse 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 'A'.

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 un bon moyen de faire cela?

182
Mingus

Utilisation de lambdas 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;
}

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

De toute évidence, vous pouvez également choisir de fournir votre propre vecteur d'index, votre fonction de tri, votre comparateur ou de réorganiser automatiquement v dans la fonction sort_indexes à l'aide d'un vecteur supplémentaire.

234
Lukasz Wiklendt

Vous pouvez trier std :: pair au lieu de simplement ints - first int correspond aux données d'origine, second int à l'index d'origine. Ensuite, fournissez un comparateur qui effectue uniquement le tri sur le premier int. Exemple:

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

Triez la nouvelle instance à l'aide d'un comparateur tel que:

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, à l'aide de ce comparateur, devrait être:

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

Vous pouvez extraire les index en parcourant le vecteur, en récupérant .second de chaque paire std ::.

81
RAC

J'ai écrit la version générique de la sorte 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, sauf qu'un conteneur d'index reçoit des index triés . Testing:

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 par un 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)()) ;
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";
}

Maintenant, a contient à la fois nos valeurs et leurs index respectifs dans les éléments triés.

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

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

8
Aditya Aswal

Je suis tombé sur cette question et j'ai compris que le tri direct des itérateurs 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 of (valeur, index), ce qui est utile lorsque les valeurs sont des objets volumineux. Les itérateurs fournissent l'accès à 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 cinquième plus petit élément du vecteur trié aurait la valeur **idx[ 5 ] et son index dans le vecteur d'origine serait distance( A.begin( ), *idx[ 5 ] ) ou simplement *idx[ 5 ] - A.begin( ).

6
behzad.nouri

C'est plus facile qu'il n'y paraît.

Supposons que le vecteur donné est 

A=[2,4,3]

Créer un nouveau vecteur

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

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

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

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: recherchez des index 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
2
sigvaldm

Créez une fonction std::pair 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;
}

ideone

2
Omid

Si cela est possible, vous pouvez construire le tableau de positions à l'aide de la fonction de recherche, 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.

1
HyLian

Pour ce type de questionStorez les données du tableau initial dans une nouvelle donnée, puis recherchez 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, binarysearch est une fonction qui prend le tableau, la taille du tableau, l'élément de recherche et renvoie la position de l'élément recherché.

1
Mohit Vachhani

Les éléments du vecteur sont-ils uniques? Si tel est le cas, 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 de mettre en œuvre votre propre routine de tri.

1
Mizipzor

Il existe un autre moyen 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 des é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 sortir les indices, parcourez la carte ou la multi-carte:

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

Eh bien, ma solution utilise la technique des résidus. Nous pouvons placer les valeurs en cours de tri dans les 2 octets supérieurs et les index 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;

Puis, triez le tableau myints comme 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 index des valeurs triées par 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 original myints (c'est-à-dire celles pouvant correspondre aux 2 octets supérieurs de int). Mais il présente l’avantage supplémentaire de distinguer les valeurs identiques de myints: leurs indices seront imprimés dans le bon ordre.

1
Macmep

Il y a beaucoup de 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
0
Gokul

Envisagez d'utiliser std::multimap comme suggéré par @Ulrich Eckhardt. Juste que le code pourrait être rendu encore plus simple.

Donné

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

Pour trier 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 index 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 pour préférer un std::multimap à un std::map est d'autoriser des valeurs égales dans les vecteurs d'origine. Veuillez également noter que, contrairement à std::map, operator[] n'est pas défini pour std::multimap.

0
fleix