С ++ сортування та відстеження індексів


216

Використовуючи C ++ і, сподіваюся, стандартну бібліотеку, я хочу сортувати послідовність зразків у порядку зростання, але я також хочу запам’ятати початкові індекси нещодавно зразків.

Наприклад, у мене є набір, або вектор, або матриця зразків A : [5, 2, 1, 4, 3]. Я хочу їх сортувати за формою B : [1,2,3,4,5], але я також хочу запам’ятати початкові індекси значень, так що я можу отримати інший набір, який би був: C : [2, 1, 4, 3, 0 ]- який відповідає індексу кожного елемента в 'B', в оригіналі ' A '.

Наприклад, у Matlab ви можете:

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

Хтось може побачити хороший спосіб зробити це?

Відповіді:


298

Використання C++11 лямбда:

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

Тепер ви можете використовувати повернутий вектор індексу в таких ітераціях, як

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

Ви також можете надати свій вихідний вектор індексу, функцію сортування, компаратор або автоматично упорядкувати v у функції sort_indexes, використовуючи додатковий вектор.


4
Подобається ця відповідь. Якщо ваш компілятор не підтримує лямбда, ви можете використовувати клас: шаблон <typename T> class CompareIndicesByAbodyVectorValues ​​{std :: vector <T> * _values; загальнодоступний: CompareIndicesByATHERVectorValues ​​(std :: вектор <T> * значення): _ значень (значень) {} public: bool operator () (const int & a, const int & b) const {return ( _values) [a]> ( _values) [ б]; }};
Йоав

2
Я теж люблю цю відповідь, немає потреби копіювати оригінальний вектор, щоб створити вектор пар.
headmyshoulder

29
Замість ручної роботи for (size_t i = 0; i != idx.size(); ++i) idx[i] = i;я віддаю перевагу стандартуstd::iota( idx.begin(), idx.end(), 0 );
Wyck

6
використовувати #include <numeric>для йоти ()
kartikag01

6
iotaє найменш очевидно названим алгоритмом у всій стандартній бібліотеці C ++.
Сет Джонсон

87

Ви можете сортувати std :: pair замість просто ints - перший int - це вихідні дані, другий int - це вихідний індекс. Потім поставте компаратор, який сортує лише перший ім. Приклад:

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

Сортуйте новий екземпляр проблеми за допомогою порівняльника на зразок:

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

Результатом std :: сортування на v_prime, використовуючи цей компаратор, повинен бути:

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

Ви можете очистити індекси, перейшовши по вектору, захопивши .second від кожної std :: пари.


1
Саме так я б і зробив це. Основна функція сортування не відслідковує старі та нові позиції, оскільки це додасть зайві непотрібні витрати.
the_mandrill

8
Недолік цієї функції полягає в тому, що вона вимагає перерозподілу пам'яті для всіх значень.
Йоав

1
Це, очевидно, працездатний підхід, але він має і мінус, що вам доведеться змінити свій вихідний контейнер з "контейнер чисел" на "контейнер пар".
Руслан

19

Припустимо, даний вектор є

A=[2,4,3]

Створіть новий вектор

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

Сортуйте V і під час сортування замість порівняння елементів V порівнюйте відповідні елементи 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];} );

Люблю свою відповідь. ви навіть можете використовувати std::iota()для більш елегантної ініціалізаціїmap
Німрод Мораг

Так, ми можемо ним скористатися! Дякую за пропозицію
MysticForce

12

Я написав загальну версію сортування індексу.

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

Використання таке ж, як у std :: sort, за винятком контейнера індексів для отримання відсортованих індексів. тестування:

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. для компіляторів без підтримки c ++ 0x, замініть вираз lamba як шаблон класу:

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 :: сортувати як

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

Привіт hkyi! Як ми створимо цю функцію шаблону? Він має два назви типу шаблону, і один з них є ітератором, що робить цю ситуацію дуже рідкісною. Чи можете ви допомогти?
Скотт Ян

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

Тепер a міститься як відсортовані наші значення, так і їх відповідні показники.

a[i].first = valueв i'м.

a[i].second = idx в початковому масиві.


Спробуйте додати опис свого коду, щоб користувачі, які відвідують цю публікацію, зрозуміли, як вона працює.
BusyProgrammer

Мені найбільше подобається це рішення - мій вектор розміром 4 або більше, і я застряг перед C ++ 11 і не можу використовувати лямбда. Дякуємо Aditya Aswal.
stephanmg

6

Я зіткнувся з цим питанням і зрозумів, що сортування ітераторів безпосередньо стане способом сортування значень та відстеження показників; Не потрібно визначати додатковий контейнер pairs (значення, індекс), який є корисним, коли значення великі об'єкти; Ітератори забезпечують доступ як до значення, так і до індексу:

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

що стосується прикладу використання:

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

тепер, наприклад, 5-й найменший елемент у відсортованому векторі мав би значення, **idx[ 5 ]а його індекс у вихідному векторі був би distance( A.begin( ), *idx[ 5 ] )чи просто *idx[ 5 ] - A.begin( ).


3

Є ще один спосіб вирішити це за допомогою карти:

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

Це, однак, викорінить не унікальні елементи. Якщо це не прийнятно, використовуйте мультимап:

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

Щоб вивести індекси, повторіть карту чи мультимап:

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

3

Прекрасне рішення від @Lukasz Wiklendt! Хоча в моєму випадку мені було потрібно щось більш загальне, тому я трохи його змінив:

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

Приклад: Знайдіть індекси, сортуючи вектор рядків за довжиною, за винятком першого елемента, який є манекеном.

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;

відбитки:

abc
ab
a

3

Подумайте про використання std::multimap як запропонував @Ulrich Eckhardt. Просто, що код можна було зробити ще простішим.

Дано

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

Сортувати середній час вставки

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

Для отримання значень та оригінальних індексів

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
}

Причина віддати перевагу a std::multimapдо a std::map- це дозволити рівні значення в початкових векторах. Також врахуйте, що, на відміну від std::map, operator[]не визначено для std::multimap.


2

Зробіть функцію std::pairin, а потім сортуйте пару:

загальна версія:

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

ідеоне


1

Чи унікальні елементи у векторі? Якщо так, скопіюйте вектор, сортуйте одну з копій за допомогою STL Sort тоді ви можете знайти, який індекс кожен елемент мав у вихідному векторі.

Якщо вектор повинен обробляти повторювані елементи, я думаю, ви краще реалізуєте свій власний розпорядок.


1

Ну, моє рішення використовує техніку залишків. Ми можемо розмістити значення під сортуванням у верхньому 2 байті, а індекси елементів - у нижньому 2 байті:

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

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

Потім сортуйте масив myintsяк завжди:

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

Після цього ви можете отримати доступ до індексів елементів через залишок. Наступний код друкує індекси значень, відсортованих у порядку зростання:

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

Звичайно, ця методика працює лише для відносно невеликих значень у вихідному масиві myints(тобто тих, які можуть вміщуватися у верхні 2 байти int). Але це має додаткову вигоду від розрізнення однакових значень myints: їх індекси будуть надруковані в правильному порядку.


1

Якщо це можливо, ви можете побудувати масив позицій за допомогою функції find, а потім сортувати масив.

Або, можливо, ви можете використовувати карту, де ключовим був би елемент, а значення - це список його положення у майбутніх масивах (A, B і C)

Це залежить від подальшого використання цих масивів.


0

Для цього типу запитань Зберігайте вихідні дані масиву в нові дані, а потім двійковою шукайте перший елемент відсортованого масиву в дублюваний масив, і цей індекс повинен зберігатися у векторному чи масиві.

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

Тут бінарний пошук - це функція, яка приймає масив, розмір масиву, елемент пошуку і повертає позицію шуканого елемента


-1

Є багато способів. Досить просте рішення - використовувати 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;
}

Ось вихід:

   Index   Value
   3       1
   4       3
   1       4
   0       5
   2       7
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.