Related
How can I sort two vectors in the same way, with criteria that uses only one of the vectors?
For example, suppose I have two vectors of the same size:
vector<MyObject> vectorA;
vector<int> vectorB;
I then sort vectorA using some comparison function. That sorting reordered vectorA. How can I have the same reordering applied to vectorB?
One option is to create a struct:
struct ExampleStruct {
MyObject mo;
int i;
};
and then sort a vector that contains the contents of vectorA and vectorB zipped up into a single vector:
// vectorC[i] is vectorA[i] and vectorB[i] combined
vector<ExampleStruct> vectorC;
This doesn't seem like an ideal solution. Are there other options, especially in C++11?
Finding a sort permutation
Given a std::vector<T> and a comparison for T's, we want to be able to find the permutation you would use if you were to sort the vector using this comparison.
template <typename T, typename Compare>
std::vector<std::size_t> sort_permutation(
const std::vector<T>& vec,
Compare& compare)
{
std::vector<std::size_t> p(vec.size());
std::iota(p.begin(), p.end(), 0);
std::sort(p.begin(), p.end(),
[&](std::size_t i, std::size_t j){ return compare(vec[i], vec[j]); });
return p;
}
Applying a sort permutation
Given a std::vector<T> and a permutation, we want to be able to build a new std::vector<T> that is reordered according to the permutation.
template <typename T>
std::vector<T> apply_permutation(
const std::vector<T>& vec,
const std::vector<std::size_t>& p)
{
std::vector<T> sorted_vec(vec.size());
std::transform(p.begin(), p.end(), sorted_vec.begin(),
[&](std::size_t i){ return vec[i]; });
return sorted_vec;
}
You could of course modify apply_permutation to mutate the vector you give it rather than returning a new sorted copy. This approach is still linear time complexity and uses one bit per item in your vector. Theoretically, it's still linear space complexity; but, in practice, when sizeof(T) is large the reduction in memory usage can be dramatic. (See details)
template <typename T>
void apply_permutation_in_place(
std::vector<T>& vec,
const std::vector<std::size_t>& p)
{
std::vector<bool> done(vec.size());
for (std::size_t i = 0; i < vec.size(); ++i)
{
if (done[i])
{
continue;
}
done[i] = true;
std::size_t prev_j = i;
std::size_t j = p[i];
while (i != j)
{
std::swap(vec[prev_j], vec[j]);
done[j] = true;
prev_j = j;
j = p[j];
}
}
}
Example
vector<MyObject> vectorA;
vector<int> vectorB;
auto p = sort_permutation(vectorA,
[](T const& a, T const& b){ /*some comparison*/ });
vectorA = apply_permutation(vectorA, p);
vectorB = apply_permutation(vectorB, p);
Resources
std::vector
std::iota
std::sort
std::swap
std::transform
With range-v3, it is simple, sort a zip view:
std::vector<MyObject> vectorA = /*..*/;
std::vector<int> vectorB = /*..*/;
ranges::v3::sort(ranges::view::zip(vectorA, vectorB));
or explicitly use projection:
ranges::v3::sort(ranges::view::zip(vectorA, vectorB),
std::less<>{},
[](const auto& t) -> decltype(auto) { return std::get<0>(t); });
Demo
I would like to contribute with a extension I came up with.
The goal is to be able to sort multiple vectors at the same time using a simple syntax.
sortVectorsAscending(criteriaVec, vec1, vec2, ...)
The algorithm is the same as the one Timothy proposed but using variadic templates, so we can sort multiple vectors of arbitrary types at the same time.
Here's the code snippet:
template <typename T, typename Compare>
void getSortPermutation(
std::vector<unsigned>& out,
const std::vector<T>& v,
Compare compare = std::less<T>())
{
out.resize(v.size());
std::iota(out.begin(), out.end(), 0);
std::sort(out.begin(), out.end(),
[&](unsigned i, unsigned j){ return compare(v[i], v[j]); });
}
template <typename T>
void applyPermutation(
const std::vector<unsigned>& order,
std::vector<T>& t)
{
assert(order.size() == t.size());
std::vector<T> st(t.size());
for(unsigned i=0; i<t.size(); i++)
{
st[i] = t[order[i]];
}
t = st;
}
template <typename T, typename... S>
void applyPermutation(
const std::vector<unsigned>& order,
std::vector<T>& t,
std::vector<S>&... s)
{
applyPermutation(order, t);
applyPermutation(order, s...);
}
template<typename T, typename Compare, typename... SS>
void sortVectors(
const std::vector<T>& t,
Compare comp,
std::vector<SS>&... ss)
{
std::vector<unsigned> order;
getSortPermutation(order, t, comp);
applyPermutation(order, ss...);
}
// make less verbose for the usual ascending order
template<typename T, typename... SS>
void sortVectorsAscending(
const std::vector<T>& t,
std::vector<SS>&... ss)
{
sortVectors(t, std::less<T>(), ss...);
}
Test it in Ideone.
I explain this a little bit better in this blog post.
In-place sorting using permutation
I would use a permutation like Timothy, although if your data is too large and you don't want to allocate more memory for the sorted vector you should do it in-place. Here is a example of a O(n) (linear complexity) in-place sorting using permutation:
The trick is to get the permutation and the reverse permutation to know where to put the data overwritten by the last sorting step.
template <class K, class T>
void sortByKey(K * keys, T * data, size_t size){
std::vector<size_t> p(size,0);
std::vector<size_t> rp(size);
std::vector<bool> sorted(size, false);
size_t i = 0;
// Sort
std::iota(p.begin(), p.end(), 0);
std::sort(p.begin(), p.end(),
[&](size_t i, size_t j){ return keys[i] < keys[j]; });
// ----------- Apply permutation in-place ---------- //
// Get reverse permutation item>position
for (i = 0; i < size; ++i){
rp[p[i]] = i;
}
i = 0;
K savedKey;
T savedData;
while ( i < size){
size_t pos = i;
// Save This element;
if ( ! sorted[pos] ){
savedKey = keys[p[pos]];
savedData = data[p[pos]];
}
while ( ! sorted[pos] ){
// Hold item to be replaced
K heldKey = keys[pos];
T heldData = data[pos];
// Save where it should go
size_t heldPos = rp[pos];
// Replace
keys[pos] = savedKey;
data[pos] = savedData;
// Get last item to be the pivot
savedKey = heldKey;
savedData = heldData;
// Mark this item as sorted
sorted[pos] = true;
// Go to the held item proper location
pos = heldPos;
}
++i;
}
}
I have recently wrote a proper zip iterator which works with the stl algorithms.
It allows you to produce code like this:
std::vector<int> a{3,1,4,2};
std::vector<std::string> b{"Alice","Bob","Charles","David"};
auto zip = Zip(a,b);
std::sort(zip.begin(), zip.end());
for (const auto & z: zip) std::cout << z << std::endl;
It is contained in a single header and the only requirement is C++17.
Check it out on GitHub.
There is also a post on codereview which contains all the source code.
Make a vector of pairs out of your individual vectors.
initialize vector of pairs
Adding to a vector of pair
Make a custom sort comparator:
Sorting a vector of custom objects
http://rosettacode.org/wiki/Sort_using_a_custom_comparator#C.2B.2B
Sort your vector of pairs.
Separate your vector of pairs into individual vectors.
Put all of these into a function.
Code:
std::vector<MyObject> vectorA;
std::vector<int> vectorB;
struct less_than_int
{
inline bool operator() (const std::pair<MyObject,int>& a, const std::pair<MyObject,int>& b)
{
return (a.second < b.second);
}
};
sortVecPair(vectorA, vectorB, less_than_int());
// make sure vectorA and vectorB are of the same size, before calling function
template <typename T, typename R, typename Compare>
sortVecPair(std::vector<T>& vecA, std::vector<R>& vecB, Compare cmp)
{
std::vector<pair<T,R>> vecC;
vecC.reserve(vecA.size());
for(int i=0; i<vecA.size(); i++)
{
vecC.push_back(std::make_pair(vecA[i],vecB[i]);
}
std::sort(vecC.begin(), vecC.end(), cmp);
vecA.clear();
vecB.clear();
vecA.reserve(vecC.size());
vecB.reserve(vecC.size());
for(int i=0; i<vecC.size(); i++)
{
vecA.push_back(vecC[i].first);
vecB.push_back(vecC[i].second);
}
}
I'm assuming that vectorA and vectorB have equal lengths. You could create another vector, let's call it pos, where:
pos[i] = the position of vectorA[i] after sorting phase
and then, you can sort vectorB using pos, i.e create vectorBsorted where:
vectorBsorted[pos[i]] = vectorB[i]
and then vectorBsorted is sorted by the same permutation of indexes as vectorA is.
I am not sure if this works but i would use something like this. For example to sort two vectors i would use descending bubble sort method and vector pairs.
For descending bubble sort, i would create a function that requires a vector pair.
void bubbleSort(vector< pair<MyObject,int> >& a)
{
bool swapp = true;
while (swapp) {
int key;
MyObject temp_obj;
swapp = false;
for (size_t i = 0; i < a.size() - 1; i++) {
if (a[i].first < a[i + 1].first) {
temp_obj = a[i].first;
key = a[i].second;
a[i].first = a[i + 1].first;
a[i + 1].first = temp_obj;
a[i].second = a[i + 1].second;
a[i + 1].second = key;
swapp = true;
}
}
}
}
After that i would put your 2 vector values into one vector pair. If you are able to add values at the same time use this one and than call the bubble sort function.
vector< pair<MyObject,int> > my_vector;
my_vector.push_back( pair<MyObject,int> (object_value,int_value));
bubbleSort(my_vector);
If you want to use values after adding to your 2 vectors, you can use this one and than call the bubble sort function.
vector< pair<MyObject,int> > temp_vector;
for (size_t i = 0; i < vectorA.size(); i++) {
temp_vector.push_back(pair<MyObject,int> (vectorA[i],vectorB[i]));
}
bubbleSort(temp_vector);
I hope this helps.
Regards,
Caner
Based on Timothy Shields answer.
With a small tweak to apply_permutaion you can apply the permutation to multiple vectors of different types at once with use of a fold expression.
template <typename T, typename... Ts>
void apply_permutation(const std::vector<size_t>& perm, std::vector<T>& v, std::vector<Ts>&... vs) {
std::vector<bool> done(v.size());
for(size_t i = 0; i < v.size(); ++i) {
if(done[i]) continue;
done[i] = true;
size_t prev = i;
size_t curr = perm[i];
while(i != curr) {
std::swap(v[prev], v[curr]);
(std::swap(vs[prev], vs[curr]), ...);
done[curr] = true;
prev = curr;
curr = perm[curr];
}
}
}
This answer demonstrates how to efficiently obtain an indices vector using std::sort on a vector of values using the nice new-ish C++11 functionality (there's also a variety of duplicates of that question as well). It also hints that you can obtain the double output of both the sorted vector and the sorted indices by "using an extra vector." However, the only way I can achieve this is by calling std:sort a second time. I'm working with arrays with lengths of tens, maybe hundreds, of thousands of elements trying to focus on efficiency. Is it possible to obtain both the sorted vector and the indices of the sort from a single call to std::sort?
More generally, my question is: can one sort multiple vectors with a single sort call? The assumption is the sorting order is based on only one of the supplied vectors.
What I've come up with in the meantime is below (a slight modification to the code in the linked answer). As you can see, it requires a call to std::sort for each vector being sorted, even though they are all to be ordered according to the sorting of a single vector. I suspect there may be a way to do this by passing references to the lambda compare function, but I can't seem to make it work.
#include <numeric>
#include <algorithm>
using std;
void sort_vectors(vector<size_t> idx, vector<double> &v) {
// 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];});
// Sort the actual vector
sort(v.begin(), v.end());
return idx;
}
std::sort takes iterators: Although a custom sort could likely take both indexes and the values in a single sort step, it's unlikely to be of much use (and may require different algorithms, making it slower).
Algorithm Design
Why? Because std::sort performs in O(n*logn) time. Moving elements from the sorted indexes will take O(n) time, which is relatively cheap in comparison.
Using the example from above, in the link given, we have this existing code:
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
sort(idx.begin(), idx.end(),
[&v](size_t i1, size_t i2) {return v[i1] < v[i2];});
return idx;
}
We can now create a sorted array from these indexes, a cheap step:
template <typename T>
vector<T> sorted_array(const vector<T> &v, const vector<size_t>& i)
{
vector<T> out;
out.reserve(v.size())
for (auto j: i) {
out.emplace_back(v[j]);
}
}
If copying the values is too prohibitive, you can use a std::reference_wrapper to create a non-nullable wrapper.
template <typename T>
vector<reference_wrapper<const T>> sorted_array(const vector<T> &v, const vector<size_t>& i)
{
vector<reference_wrapper<const T>> out;
out.reserve(v.size())
for (auto j: i) {
out.emplace_back(std::cref(v[j]));
}
}
Even for large arrays, this should be pretty efficient.
Caution
Don't try to sort two arrays at once. Don't try to move items in your value array when sorting the index array. Why? Because the comparison is index-based for the value array: moving items will destroy the sort in the original array. Since moving the items to the correct position is so cheap once you have the sorted indexes, don't worry about performance here: the sort is the bottleneck.
The reorder of the array(s) or vector(s) according to sorted indices can be done in place in O(n) time. This example sorts two arrays using a third array of indices. During the reorder, the array of indices is restored back to it's original state of going from 0 to n-1. I manually did the iota part in this example, and it doesn't use templates, but could be easily converted to templates and vectors:
#include <algorithm>
#include <iostream>
int main()
{
int A[8] = {8,6,1,7,5,3,4,2};
char B[8] = {'h','f','a','g','e','c','d','b'};
size_t I[8];
size_t i, j, k;
int ta;
char tb;
// create array of indices to A[]
for(i = 0; i < sizeof(A)/sizeof(A[0]); i++)
I[i] = i;
// sort array of indices according to A[]
std::sort(I, I+sizeof(I)/sizeof(I[0]),
[&A](int i, int j) {return A[i] < A[j];});
// reorder A[] B[] I[] according to I[]
for(i = 0; i < sizeof(A)/sizeof(A[0]); i++){
if(i != I[i]){
ta = A[i];
tb = B[i];
k = i;
while(i != (j = I[k])){
A[k] = A[j];
B[k] = B[j];
I[k] = k;
k = j;
}
A[k] = ta;
B[k] = tb;
I[k] = k;
}
}
for(i = 0; i < sizeof(A)/sizeof(A[0]); i++)
std::cout << A[i] << ' ';
std::cout << std::endl;
for(i = 0; i < sizeof(B)/sizeof(B[0]); i++)
std::cout << B[i] << ' ';
std::cout << std::endl;
return 0;
}
or an array of pointers can be used instead of an array of indices, which allows a normal compare function instead of a lambda compare function.
#include <algorithm>
#include <iostream>
bool compare(const int *p0, const int *p1)
{
return *p0 < *p1;
}
int main()
{
int A[8] = {8,6,1,7,5,3,4,2};
char B[8] = {'h','f','a','g','e','c','d','b'};
int *pA[8];
size_t i, j, k;
int ta;
char tb;
// create array of pointers to A[]
for(i = 0; i < sizeof(A)/sizeof(A[0]); i++)
pA[i] = &A[i];
// sort array of pointers according to A[]
std::sort(pA, pA+sizeof(A)/sizeof(A[0]), compare);
// reorder A[] B[] pA[] according to pA[]
for(i = 0; i < sizeof(A)/sizeof(A[0]); i++){
if(i != pA[i]-A){
ta = A[i];
tb = B[i];
k = i;
while(i != (j = pA[k]-A)){
A[k] = A[j];
B[k] = B[j];
pA[k] = &A[k];
k = j;
}
A[k] = ta;
B[k] = tb;
pA[k] = &A[k];
}
}
for(i = 0; i < sizeof(A)/sizeof(A[0]); i++)
std::cout << A[i] << ' ';
std::cout << std::endl;
for(i = 0; i < sizeof(B)/sizeof(B[0]); i++)
std::cout << B[i] << ' ';
std::cout << std::endl;
return 0;
}
Rather than constructing an index vector, and applying it to each of the vectors you want to sort, you can instead make a range that references all the vectors to sort, and sort that by the appropriate element.
template <typename OrderBy, typename... Others>
void sort_multiple(OrderBy& order_by, Others&... others) {
auto range = ranges::views::zip(order_by, others...);
ranges::actions::sort(range.begin(), range.end(), std::less{}, [](auto & tuple){ return get<0>(tuple); });
}
The only unfortunate thing about this formulation is that there isn't a simple way of specifying a custom compare, because ... arguments are greedy.
struct custom_compare_t {} custom_compare;
template <typename Compare, typename OrderBy, typename... Others>
void sort_multiple(custom_compare_t, Compare compare, OrderBy& order_by, Others&... others) {
auto range = ranges::views::zip(order_by, others...);
ranges::actions::sort(range.begin(), range.end(), compare, [](auto & tuple){ return get<0>(tuple); });
}
I worked out an algorithm that can be used recursively, which works in a non-recursive manner, however, I cannot test it in recursive form because I cannot create a n-1 dimensional variable from the template.For help I write down the variable names and its meaning:
v: the n-dimensional vector
dims: n long vector with every dimension (e.g: if it was an array looking like int x[3][6][4], then dims looks like: {3, 6, 4})
p: product of the dimensions, used to get the size of the flat vector (e.g: 3*6*4)
ret: the returned flat vector
sub_dims: same as dims execpt without the first dimension (e.g: {6, 4})
sub_p: same as p except without the first factor (e.g: 6*4)
sub_ret: the returned flat vector for the n-1 dimensional vector
the code:
template <typename T>
vector<int> dim_flat(vector<T> v, vector<int> dims)
{
// creating variables and vectors
int n = dims.size();
int p = dims[0];
for (int i = 1; i < n; i++)
{
p *= dims[i];
}
int sub_p = p / dims[0];
vector<int> sub_dims;
sub_dims.assign(dims.begin() + 1, dims.end());
vector<int> ret(p);
// algorithm
if (n > 1)
{
for (int i = 0; i < dims[0]; i++)
{
vector<int> sub_ret = dim_flat(v[i], sub_dims);
for (int j = 0; j < sub_p; j++)
{
ret[i * sub_p + j] = sub_ret[j];
}
}
}
else
{
// case for the input is 1D
// not yet written
}
return ret;
}
With this code the project build, however if I call it in main:
vector<int> ret = dim_flat(v, dims);
where v is e.g. an 4D vector and dims is a vector containing {3, 3, 3, 3}, then I get the following when trying to build:
error C2784: 'std::vector<_Ty> dim_flat(std::vector<T>,std::vector<_Ty>)' : could not deduce template argument for 'std::vector<T>' from 'int'
for the line
vector<int> sub_ret = dim_flat(v[i], sub_dims);
I kinda (but not really) understand the meaning of this error, so I expected the same to happen, which it did for this:
T x = v[i];
vector<int> sub_ret = dim_flat(x, sub_dims);
This is the part where I don't really understand the error anymore, because I thought that with the argument vector<T> v I specified that the input e.g. 4D vector will be understood as a vector of T where T is a 3D vector that can be also indexed since its a vector<T>. So following this logic I thought that if I give the recursion the first T being a 3D vector, then a step deeper this 3D vector will now be understood as a vector of T' where T' is a 2D vector, and so on.
Clearly either my logic is flawed, or I used the wrong method (, or both), so the question is: How can I solve / fix this?
EDIT:Credit to Max66 for the solution.
The code probably could be more optimized, but at least now it works.
Code:
//// for integer only
// case input is 0-dimensional (simply a variable)
template <typename T>
vector<int> dim_flat (const T &v, const vector<int> &dims)
{
return vector<int>(1, v);
}
// case input is n-dimensional
template <typename T>
vector<int> dim_flat(const vector<T> &v, const vector<int> &dims)
{
// creating variables and vectors
int n = dims.size();
int p = dims[0];
for (int i = 1; i < n; i++)
{
p *= dims[i];
}
int sub_p = p / dims[0];
vector<int> sub_dims;
sub_dims.assign(dims.begin() + 1, dims.end());
vector<int> ret(p);
// algorithm
if (n > 1) // case n-dimensional
{
for (int i = 0; i < dims[0]; i++)
{
vector<int> sub_ret = dim_flat(v[i], sub_dims);
for (int j = 0; j < sub_p; j++)
{
ret[i * sub_p + j] = sub_ret[j];
}
}
}
else // case 1-dimensional
{
for (int i = 0; i < p; i++)
{
vector<int> sub_ret = dim_flat(v[i], sub_dims);
ret[i] = sub_ret[0];
}
}
return ret;
}
Short answer: add the function
std::vector<int> dim_flat (int v, std::vector<int> const &)
{ return {v}; }
if you can use C++11 or newer, or
std::vector<int> dim_flat (int v, std::vector<int> const &)
{ return std::vector<int>(1, v); }
if you have to use C++98
Long answer: if I'm not wrong, the problem is that, when you call dim_flat() with an std::vector<int> (when T is int), you have n == 1, so dim_flat() isn't called (is executed the "case for the input 1D") but the compiler isn't so smart to understand that there is no need to call dim_flat() with int (instead std::vector<T>) so look for dim_flat(int, std::vector<int>) and doesn't find it.
So, to make the compiler happy, you have to implement dim_flat(int, std::vector<int>).
Can be a dummy function (that return an empty vector), but I suggest you to implement it correctly.
A little of topic: please, avoid unusefull copies of vectors; in your dim_flat(), v and dims are only readed, not modified; so you can receive they as const reference, as follows
template <typename T>
std::vector<int> dim_flat(std::vector<T> const & v,
std::vector<int> const & dims)
P.s.: why don't you simply write as follows ?
std::vector<int> dim_flat (std::vector<int> const & v)
{ return v; }
template <typename T>
std::vector<int> dim_flat(std::vector<std::vector<T>> const & v)
{
std::vector<int> ret;
for ( auto const & e : v )
{
auto s = dim_flat(e);
ret.reserve( ret.size() + s.size() );
ret.insert( ret.end(), s.cbegin(), s.cend() );
}
return ret;
}
I am trying to construct a function take takes a vector, ranks it, sorts it and outputs the sorted and ranked vector with the original positioning of the values. For example: Input: [10,332,42,0.9,0] Output: [3, 5, 4, 2, 1]
I used this stack overflow question (specifically Marius' answer) as a reference guide, however I am stuck with my code now and do not understand where the issue is.
I am running a C++03.
One of the errors I get is
error: invalid types ‘const float*[float]’ for array subscript’ for array subscript on my if statement.
//Rank the values in a vector
std::vector<float> rankSort(const float *v_temp, size_t size)
{
vector <float> v_sort;
//create a new array with increasing values from 0 to n-1
for(unsigned i = 0; i < size; i++)
{
v_sort.push_back(i);
}
bool swapped = false;
do
{
for(unsigned i = 0; i < size; i++)
{
if(v_temp[v_sort[i]] > v_temp[v_sort[i+1]]) //error line
{
float temp = v_sort[i];
v_sort[i] = v_sort[i+1];
v_sort[i+1] = temp;
swapped = true;
}
}
}
while(swapped);
return v_sort;
}
std::vector<float> rankSort(const std::vector<float> &v_temp)
{
return rankSort(&v_temp[0], v_temp.size());
}
Your problem is a misconception on rankings. Array indices are of size_t not float, so you'll need to return a vector<size_t> not a vector<float>.
That said your sort is O(n2). If you're willing to use more memory we can get that time down to O(n log(n)):
vector<size_t> rankSort(const float* v_temp, const size_t size) {
vector<pair<float, size_t> > v_sort(size);
for (size_t i = 0U; i < size; ++i) {
v_sort[i] = make_pair(v_temp[i], i);
}
sort(v_sort.begin(), v_sort.end());
pair<double, size_t> rank;
vector<size_t> result(size);
for (size_t i = 0U; i < size; ++i) {
if (v_sort[i].first != rank.first) {
rank = make_pair(v_sort[i].first, i);
}
result[v_sort[i].second] = rank.second;
}
return result;
}
Live Example
EDIT:
Yeah this actually gets a little simpler when taking a vector<float> instead of a float[]:
vector<size_t> rankSort(const vector<float>& v_temp) {
vector<pair<float, size_t> > v_sort(v_temp.size());
for (size_t i = 0U; i < v_sort.size(); ++i) {
v_sort[i] = make_pair(v_temp[i], i);
}
sort(v_sort.begin(), v_sort.end());
pair<double, size_t> rank;
vector<size_t> result(v_temp.size());
for (size_t i = 0U; i < v_sort.size(); ++i) {
if (v_sort[i].first != rank.first) {
rank = make_pair(v_sort[i].first, i);
}
result[v_sort[i].second] = rank.second;
}
return result;
}
Live Example
//Rank the values in a vector
std::vector<size_t> rankSort(const std::vector<float> &v_temp)
{
vector <size_t> v_sort;
//create a new array with increasing values from 0 to size-1
for(size_t i = 0; i < v_temp.size(); i++)
v_sort.push_back(i);
bool swapped = false;
do
{
swapped = false; //it's important to reset swapped
for(size_t i = 0; i < v_temp.size()-1; i++) // size-2 should be the last, since it is compared to next element (size-1)
if(v_temp[v_sort[i]] > v_temp[v_sort[i+1]])
{
size_t temp = v_sort[i]; // we swap indexing array elements, not original array elements
v_sort[i] = v_sort[i+1];
v_sort[i+1] = temp;
swapped = true;
}
}
while(swapped);
return v_sort;
}
v_sort[i] is a float (it's just an element of v_sort vector) while only integral types can be used as array subscripts.
Probably you meant v_sort as an array of indices, thus, you should declare it as std::vector<size_t> or std::vector<int> something like that.
UP: Also, given that you change the values of the array passed, it's not an elegant way of pass it by const reference.
To sum up, the following code compiles correctly on my machine:
std::vector<unsigned> rankSort(float *v_temp, size_t size)
{
vector <unsigned> v_sort;
//create a new array with increasing values from 0 to n-1
for(unsigned i = 0; i < size; i++)
{
v_sort.push_back(i);
}
bool swapped = false;
do
{
for(unsigned i = 0; i < size; i++)
{
if(v_temp[v_sort[i]] > v_temp[v_sort[i+1]]) //error line
{
unsigned temp = v_sort[i];
v_sort[i] = v_sort[i+1];
v_sort[i+1] = temp;
swapped = true;
}
}
}
while(swapped);
return v_sort;
}
std::vector<unsigned> rankSort(std::vector<float> &v_temp)
{
return rankSort(&v_temp[0], v_temp.size());
}
I suggest you adopt a more robust solution by taking advantage of what you have in the STL. To do so, we will first make an "index vector", ie. a std::vector<std::size_t> vsuch that for any i, v[i] == i is true:
// I'm sure there's a more elegant solution to generate this vector
// But this will do
std::vector<std::size_t> make_index_vector(std::size_t n) {
std::vector<std::size_t> result(n, 0);
for (std::size_t i = 0; i < n; ++i) {
result[i] = i;
}
return result;
}
Now all we have to do is to sort this vector according to a specific comparison function that will use the input vector. Furthermore, to allow for the most generic approach we will give the user the opportunity to use any comparison functor:
template <typename T, typename A, typename Cmp>
struct idx_compare {
std::vector<T, A> const& v;
Cmp& cmp;
idx_compare(std::vector<T, A> const& vec, Cmp& comp) : v(vec), cmp(comp) {}
bool operator()(std::size_t i, std::size_t j) {
return cmp(v[i], v[j]);
}
};
template <typename T, typename A, typename Cmp>
std::vector<std::size_t> sorted_index_vector(std::vector<T, A> const& vec, Cmp comp) {
std::vector<std::size_t> index = make_index_vector(vec.size());
std::sort(index.begin(), index.end(),
idx_compare<T, A, Cmp>(vec, comp));
return index;
}
In the sorted index vector, index[0] is the index of the lowest value in the input vector, index[1] the second lowest and so on. Therefore, we need one additional step to get the rank vector from this one:
std::vector<std::size_t> get_rank_vector(std::vector<std::size_t> const& index) {
std::vector<std::size_t> rank(index.size());
for (std::size_t i = 0; i < index.size(); ++i) {
// We add 1 since you want your rank to start at 1 instead of 0
// Just remove it if you want 0-based ranks
rank[index[i]] = i + 1;
}
return rank;
}
Now we combine all the pieces together:
template <typename T, typename A, typename Cmp>
std::vector<std::size_t> make_rank_vector(
std::vector<T, A> const& vec, Cmp comp) {
return get_rank_vector(sorted_index_vector(vec, comp));
}
// I had to stop using default template parameters since early gcc version did not support it (4.3.6)
// So I simply made another overload to handle the basic usage.
template <typename T, typename A>
std::vector<std::size_t> make_rank_vector(
std::vector<T, A> const& vec) {
return make_rank_vector(vec, std::less<T>());
}
Result with [10, 332, 42, 0.9, 0]: [3, 5, 4, 2, 1].
You can find a Live Demo on gcc 4.3.6 to explicit this behavior.
Here is my codes using STL to achieve this in a concise way to get the rank.
template <typename T>
vector<size_t> calRank(const vector<T> & var) {
vector<size_t> result(var.size(),0);
//sorted index
vector<size_t> indx(var.size());
iota(indx.begin(),indx.end(),0);
sort(indx.begin(),indx.end(),[&var](int i1, int i2){return var[i1]<var[i2];});
//return ranking
for(size_t iter=0;iter<var.size();++iter){
result[indx[iter]]=iter+1;
}
return result;
}
template<typename T, typename F>
auto mapV(vector<T> v, F f) {
vector<T> ret = {};
for(int i=0; i < v.size(); i++) {
ret[i] = f(v[i]);
}
return ret;
}
int main () {
vector<int> v = {1,2,3,4,5,6};
vector<int> b = mapV(v, [](int &t){ return t*2; });
for (int n : b) {
cout << "is " << n << endl;
}
return 0;
}
this code compiles but when run generates a segfault. What is the correct way to accept a function generically using templates? I would like to define the structure of the function also, being able to say that I only accept a function that takes a T and returns a T, but I'm not quite sure how to express that.
I tried with std::function<T(T)> but that didn't work either
vector<T> ret = {};
This creates an empty vector.
for(int i=0; i < v.size(); i++) {
ret[i] = f(v[i]);
This assigns something to the values in the vector ret. There are no existing values in the vector ret. There is no ret[0]. There is no ret[1], and so on. This is undefined behavior. operator[] does not add new values to the vector, it only modifies existing ones.
Replace this with the push_back() method.