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

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

Например, у меня есть набор, вектор или матрица образцов 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

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

Ответ 1

Использование С++ 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;
}

Теперь вы можете использовать возвращенный индексный вектор в итерациях, таких как

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

Очевидно, что вы также можете указать собственный исходный индексный вектор, функцию сортировки, компаратор или автоматически изменить порядок v в функции sort_indexes, используя дополнительный вектор.

Ответ 2

Вы можете отсортировать std:: pair вместо просто ints - first 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>]

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

Ответ 3

Я написал общую версию сортировки индекса.

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. для компиляторов без поддержки С++ 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)()) ;

Ответ 4

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 at i 'th.

a[i].second = idx в исходном массиве.

Ответ 5

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

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

Ответ 6

Это проще, чем кажется.

Предположим, что данный вектор

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

Ответ 7

Создайте функцию std::pair, затем выполните парную сортировку:

общая версия:

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

Ответ 8

Красивое решение от @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

Ответ 9

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

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

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

Ответ 10

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

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

Ответ 11

Существует еще один способ решить эту проблему, используя карту:

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

Это приведет к уничтожению неповторимых элементов. Если это неприемлемо, используйте multimap:

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;

Ответ 12

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

Ответ 13

За этот вопрос Храните данные массива orignal в новые данные, а затем двоичный поиск первого элемента отсортированного массива в дублированный массив и чтобы индексы были сохранены в вектор или массив.

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 - это функция, которая принимает массив, размер массива, поиск элемента и возвращает позицию искомого элемента.

Ответ 14

Подумайте об использовании 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.

Ответ 15

Есть много способов. Довольно простое решение - использовать 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

Ответ 16

Вы также можете сделать это, используя карту или кортежи!

    // Example program
          #include <iostream>
          #include <string>
          #include <vector>
          #include <tuple>
          #include <algorithm>
          #include <random>


          typedef std::tuple<double, int> mytuple;
          bool comparator(const mytuple& l, const mytuple& r)
          { 
           return std::get<0>(l) < std::get<0>(r);
          }

int main()
{
       // declare vector of tuples double and int
        std::vector<std::tuple<double, int> > vtA; 
        //vector of doubles
        std::vector<double> vB;

        //for exemple, fill "vB" with something
        int j = 0;
        for(int i = 10; i < 20 ; i++)
        {
            j = rand()% i;
            vB.push_back(j);
        }

        for (int k = 0; k < vB.size(); k++)
        {
            //make a tuple with double and int (int is a indexis you want to save)
            vtA.emplace_back(vB[k], k);
            //print members before ordering
            std::cout << std::get<0>(vtA[k]) << " - " << std::get<1>(vtA[k]) << std::endl;
        }

        std::cout << "\n";
        std::cout << "\n";
        std::sort(vtA.begin(), vtA.end(), comparator); //call function to increasing order
        std::cout << "\n";
        std::cout << "\n";

        //prints vector with the old indices
        for (int k = 0; k < vB.size(); k++)
        {
            std::cout << std::get<0>(vtA[k]) << " - " << std::get<1>(vtA[k]) << std::endl;
        }
return(0);
}