С++ сортировка и отслеживание индексов
Используя С++ и, надеюсь, стандартную библиотеку, я хочу сортировать последовательность выборок в порядке возрастания, но я также хочу запомнить исходные индексы новых образцов.
Например, у меня есть набор, вектор или матрица образцов 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);
}