C ++ sortowanie i śledzenie indeksów

216

Korzystając z C ++ i, mam nadzieję, ze standardowej biblioteki, chcę posortować sekwencję próbek w kolejności rosnącej, ale chcę również zapamiętać oryginalne indeksy nowych próbek.

Na przykład mam zestaw lub wektor lub macierz próbek A : [5, 2, 1, 4, 3]. Chcę je posortować B : [1,2,3,4,5], ale chcę też pamiętać oryginalne indeksy wartości, aby uzyskać inny zestaw, który byłby: C : [2, 1, 4, 3, 0 ]- który odpowiada indeksowi każdego elementu w „B”, w oryginalnym „ ZA'.

Na przykład w Matlab możesz wykonać:

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

Czy ktoś może zobaczyć dobry sposób na zrobienie tego?

Mingus
źródło

Odpowiedzi:

298

Używanie 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;
}

Teraz możesz użyć zwróconego wektora indeksu w iteracjach takich jak

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

Możesz także podać oryginalny wektor indeksu, funkcję sortowania, komparator lub automatycznie zmienić kolejność v w funkcji sort_indexes za pomocą dodatkowego wektora.

Łukasz Wiklendt
źródło
4
Podoba mi się ta odpowiedź. Jeśli twój kompilator nie obsługuje lambdas, możesz użyć klasy: template <typename T> klasa CompareIndicesByAnotherVectorValues ​​{std :: vector <T> * _values; public: CompareIndicesByAnotherVectorValues ​​(std :: vector <T> * wartości): _values ​​(wartości) {} public: bool operator () (const int & a, const int & b) const {return ( _values) [a]> ( _values) [ b]; }};
Yoav
2
Uwielbiam również tę odpowiedź, nie ma potrzeby kopiowania oryginalnego wektora, aby utworzyć wektor par.
headmyshoulder
29
Zamiast ręcznie wykonanego for (size_t i = 0; i != idx.size(); ++i) idx[i] = i;wolę standardstd::iota( idx.begin(), idx.end(), 0 );
Wyck
6
use #include <numeric>for iota ()
kartikag01
6
iotajest najmniej oczywistym nazwanym algorytmem w całej standardowej bibliotece C ++.
Seth Johnson
87

Możesz sortować std :: pair zamiast tylko ints - pierwsza int to oryginalne dane, druga int to oryginalny indeks. Następnie podaj komparator, który sortuje tylko na pierwszej int. Przykład:

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

Posortuj nowe wystąpienie problemu za pomocą komparatora, takiego jak:

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

Wynikiem użycia std :: sort na v_prime przy użyciu tego komparatora powinno być:

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

Możesz obrać indeksy, chodząc po wektorze, chwytając .second z każdej std :: pair.

RAC
źródło
1
Właśnie tak bym to zrobił. Podstawowa funkcja sortowania nie śledzi starych i nowych pozycji, ponieważ spowodowałoby to dodatkowe niepotrzebne obciążenie.
the_mandrill
8
Wadą tej funkcji jest to, że wymaga ona ponownego przydzielenia pamięci dla wszystkich wartości.
Yoav
1
Jest to oczywiście wykonalne podejście, ale ma tę wadę, że musisz zmienić oryginalny pojemnik z „pojemnika z liczbami” na „pojemnik z parami”.
Ruslan
19

Załóżmy, że dany wektor to

A=[2,4,3]

Utwórz nowy wektor

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

Posortuj V i podczas sortowania zamiast porównywania elementów V, porównaj odpowiednie elementy 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];} );
MysticForce
źródło
Uwielbiam twoją odpowiedź. możesz nawet użyć std::iota()do bardziej eleganckiej inicjalizacjimap
Nimrod Morag
Tak, możemy go użyć! Dzięki za sugestię
MysticForce
12

Napisałem ogólną wersję sortowania indeksu.

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

Użycie jest takie samo, jak w przypadku std :: sort, z wyjątkiem kontenera indeksów do odbierania posortowanych indeksów. testowanie:

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

powinieneś dostać 2 1 0 3. dla kompilatorów bez obsługi c ++ 0x, zamień wyrażenie lamba jako szablon klasy:

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

i przepisz std :: sort as

std::sort(pv.begin(), pv.end(), PairComp(comp)()) ;
hkyi
źródło
Cześć hej! Jak utworzyć instancję tej funkcji szablonu? Ma dwa typy nazw szablonów, a jeden z nich jest iteratorem, co czyni tę sytuację bardzo rzadką. Możesz mi pomóc?
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";
}

Teraz azawiera zarówno nasze wartości, jak i ich odpowiednie wskaźniki w posortowanym.

a[i].first = valueo godz i.

a[i].second = idx w początkowej tablicy.

Aditya Aswal
źródło
Rozważ dodanie opisu kodu, aby użytkownicy odwiedzający ten post mogli zrozumieć, jak to działa.
BusyProgrammer
Najbardziej podoba mi się to rozwiązanie - mój wektor ma rozmiar około 4 i utknąłem przed C ++ 11 i nie mogę używać lambd. Dzięki Aditya Aswal.
stephanmg
6

Natknąłem się na to pytanie i doszedłem do wniosku, że bezpośrednie sortowanie iteratorów byłoby sposobem na uporządkowanie wartości i śledzenie indeksów; Nie ma potrzeby definiowania dodatkowego kontenera pairs (wartości, indeksu), który jest pomocny, gdy wartości są dużymi obiektami; Iteratory zapewniają dostęp zarówno do wartości, jak i do indeksu:

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

jak w przykładzie użycia:

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

teraz na przykład piąty najmniejszy element w posortowanym wektorze miałby wartość, **idx[ 5 ]a jego indeks w oryginalnym wektorze byłby distance( A.begin( ), *idx[ 5 ] )lub po prostu *idx[ 5 ] - A.begin( ).

behzad.nouri
źródło
3

Istnieje inny sposób rozwiązania tego problemu za pomocą mapy:

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

Spowoduje to jednak usunięcie nie unikalnych elementów. Jeśli nie jest to możliwe, użyj mapy wielopunktowej:

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

Aby wygenerować wskaźniki, iteruj po mapie lub multimapie:

for (auto it = m.begin(); it != m.end(); ++it)
    cout << it->second << endl;
Ulrich Eckhardt
źródło
3

Piękne rozwiązanie autorstwa @L Łukasz Wiklendt! Chociaż w moim przypadku potrzebowałem czegoś bardziej ogólnego, więc nieco go zmodyfikowałem:

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

Przykład: Znajdź indeksy sortujące wektor ciągów według długości, z wyjątkiem pierwszego elementu, który jest atrapą.

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;

drukuje:

abc
ab
a
Sigvaldm
źródło
3

Rozważ użycie std::multimapsugerowane przez @Ulricha Eckhardta. Wystarczy, że kod może być jeszcze prostszy.

Dany

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

Aby posortować według średniego czasu wstawienia

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

Aby pobrać wartości i oryginalne indeksy

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
}

Powodem wolą std::multimapDo std::mapto, aby umożliwić równe wartości w oryginalnych wektorów. Należy również pamiętać, że w przeciwieństwie do std::map, operator[]nie jest zdefiniowane dla std::multimap.

aafulei
źródło
2

Utwórz funkcję std::pairin, a następnie posortuj parę:

wersja ogólna:

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

uchar
źródło
1

Czy elementy w wektorze są unikalne? Jeśli tak, skopiuj wektor, posortuj jedną z kopii za pomocą STL Sort, a następnie możesz znaleźć indeks, który każdy element miał w oryginalnym wektorze.

Jeśli wektor ma obsługiwać zduplikowane elementy, myślę, że lepiej wdrożyć własną procedurę sortowania.

Mizipzor
źródło
1

Moje rozwiązanie wykorzystuje technikę pozostałości. Możemy umieszczać wartości w sortowaniu w górnych 2 bajtach, a indeksy elementów - w dolnych 2 bajtach:

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

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

Następnie posortuj tablicę myintsjak zwykle:

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

Następnie możesz uzyskać dostęp do indeksów elementów za pośrednictwem residuum. Poniższy kod drukuje indeksy wartości posortowane w porządku rosnącym:

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

Oczywiście ta technika działa tylko dla względnie małych wartości w oryginalnej tablicy myints(tj. Tych, które mogą zmieścić się w 2 górnych bajtach int). Ma jednak dodatkową zaletę polegającą na odróżnianiu identycznych wartości myints: ich indeksy będą drukowane we właściwej kolejności.

Macmep
źródło
1

Jeśli to możliwe, możesz zbudować tablicę pozycji za pomocą funkcji find, a następnie posortować tablicę.

A może możesz użyć mapy, w której kluczem byłby element, a wartościuje listę jego pozycji w nadchodzących tablicach (A, B i C)

To zależy od późniejszych zastosowań tych tablic.

HyLian
źródło
0

W przypadku tego typu pytań należy zapisać dane z tablicy pierwotnej w nowych danych, a następnie przeszukać binarnie pierwszy element posortowanej tablicy w zduplikowanej tablicy, a ten indeks należy zapisać w wektorze lub tablicy.

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

Tutaj binarysearch jest funkcją, która pobiera tablicę, rozmiar tablicy, szukany element i zwraca pozycję szukanego elementu

Mohit Vachhani
źródło
-1

Jest wiele sposobów. Dość prostym rozwiązaniem jest użycie wektora 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;
}

Oto wynik:

   Index   Value
   3       1
   4       3
   1       4
   0       5
   2       7
Gokul
źródło