C ++ сортировка и отслеживание индексов

216

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

Например, у меня есть набор или вектор, или матрица образцов A : [5, 2, 1, 4, 3]. Я хочу отсортировать их так, чтобы они были B : [1,2,3,4,5], но я также хочу запомнить исходные индексы значений, чтобы я мог получить другой набор, который будет: C : [2, 1, 4, 3, 0 ]- который соответствует индексу каждого элемента в 'B', в оригинале ' А».

Например, в Matlab вы можете сделать:

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

Кто-нибудь может увидеть хороший способ сделать это?

Mingus
источник

Ответы:

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
Понравился этот ответ. Если ваш компилятор не поддерживает лямбда-выражения, вы можете использовать класс: template <typename T> class CompareIndicesByAnotherVectorValues ​​{std :: vector <T> * _values; public: CompareIndicesByAnotherVectorValues ​​(std :: vector <T> * values): _values ​​(значения) {} 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 вместо просто int: первый 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 :: sort для v_prime с использованием этого компаратора должен быть:

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

Вы можете очистить индексы, пройдя вектор, захватывая .second из каждой пары std ::.

RAC
источник
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];} );
MysticForce
источник
Люблю твой ответ. Вы даже можете использовать 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 :: sort как

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 и не могу использовать лямбды. Спасибо Адитья Асвал.
stephanmg
6

Я столкнулся с этим вопросом и понял, что сортировка итераторов напрямую будет способом сортировки значений и отслеживания индексов; Нет необходимости определять дополнительный контейнер pairs of (value, 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 ) );
}

как для примера использования:

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

behzad.nouri
источник
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
sigvaldm
источник
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
}

Причина предпочитают std::multimapк std::map, чтобы позволить одинаковые значения в исходных векторов. Также обратите внимание, что, в отличие от std::map, operator[]не определено для std::multimap.

aafulei
источник
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;
}

ideone

Uchar
источник
1

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

Если вектор должен обрабатывать дубликаты, я думаю, вам лучше реализовать собственную процедуру сортировки.

Mizipzor
источник
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: их индексы будут напечатаны в правильном порядке.

Macmep
источник
1

Если это возможно, вы можете построить массив позиций с помощью функции find, а затем отсортировать массив.

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

Это зависит от последующего использования этих массивов.

HyLian
источник
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]));`

Здесь binarysearch - это функция, которая принимает массив, размер массива, элемент поиска и возвращает позицию искомого элемента.

Мохит Вачхани
источник
-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
Gokul
источник