Sorting multiple data members [duplicate] - c++

This question already has answers here:
Multi-Key Custom Sorting in C++
(4 answers)
Closed 3 years ago.
What should be the best approach to make multiple sortings in vector of pointers (std::vector<SomeObject*)? Should I make one sorting function for multiple data members and what would be the best way to do it? Or maybe I should make separate functions for each data member? Or maybe i should use some things from std::sort()?
Let's say i have some class Vehicle with some data members
class Vehicle{
private:
unsigned int _maxSpeed;
unsigned short _numberOfHorsePower;
unsigned short _numberOfGears;
unsigned short _numberOfWheels;
unsigned int _weight;
}
And I would like to have option to sort those objects by each data member e.g SortByMaxSpeedAsc(),SortByWeightAsc(),SortByWeightDesc().
sample sorting of one data member
void Vehicles::sortVehiclesByWeightAsc()
{
Vehicle* tmp;
size_t n = _VehiclesCollection.size();
int i, j, minIndex;
for (i = 0; i < n - 1; i++)
{
minIndex = i;
for (j = i + 1; j < n; j++)
{
if (_VehiclesCollection[j].weight < _VehiclesCollection[minIndex].weight)
{
minIndex = j;
}
}
if (minIndex != i)
{
tmp = _VehiclesCollection[i];
_VehiclesCollection[i] = _VehicleCollection[minIndex];
_VehiclesCollection[minIndex] = tmp;
}
}
}

Use std::sort and write a Compare functor for each of your cases. If you need to combine multiple fields, use std::tie.
Here's an example. You don't need to make the sorting functions static members like I've done here but it makes it possible to use std::tie with private members.
#include <algorithm>
#include <iostream>
#include <vector>
class Vehicle {
public:
Vehicle(unsigned maxSpeed, unsigned short numberOfHorsePower,
unsigned short numberOfGears, unsigned short numberOfWheels,
unsigned weight) :
maxSpeed_(maxSpeed),
numberOfHorsePower_(numberOfHorsePower), numberOfGears_(numberOfGears),
numberOfWheels_(numberOfWheels), weight_(weight) {}
// you can use the accessor functions with loose sort statements if you don't
// need to tie many columns together
inline unsigned get_maxSpeed() const { return maxSpeed_; }
inline unsigned short get_numberOfHorsePower() const {
return numberOfHorsePower_;
}
inline unsigned short get_numberOfGears() const { return numberOfGears_; }
inline unsigned short get_numberOfWheels() const { return numberOfWheels_; }
inline unsigned get_weight() const { return weight_; }
// sorting functions
template<typename It>
static void sort_on_speed(It first, It last) {
std::sort(first, last,
// a lambda receiving references to two Vehicle objects to compare
[](const Vehicle& a, const Vehicle& b) {
// return true if a is less than b (using the field you want)
return a.maxSpeed_ < b.maxSpeed_;
}
);
}
template<typename It>
static void sort_on_hp_plus_speed(It first, It last) {
std::sort(first, last,
// another lambda using std::tie to sort primarily on hp
// and secondary on speed
[](const Vehicle& a, const Vehicle& b) {
return std::tie(a.numberOfHorsePower_, a.maxSpeed_) <
std::tie(b.numberOfHorsePower_, b.maxSpeed_);
});
}
private:
unsigned int maxSpeed_;
unsigned short numberOfHorsePower_;
unsigned short numberOfGears_;
unsigned short numberOfWheels_;
unsigned int weight_;
};
int main() {
std::vector<Vehicle> cars = {
{1, 2, 3, 4, 5}, {2, 3, 4, 5, 1}, {3, 4, 5, 1, 2}
};
// sort on speed
Vehicle::sort_on_speed(cars.begin(), cars.end());
// sort on hp + speed
Vehicle::sort_on_hp_plus_speed(cars.begin(), cars.end());
}

boost.multiindex should be very useful.

Related

Pass a parameter to an unordered_map (or set) hash struct

I have the following struct:
array<int, 2> map_size;
struct Node{
int id;
array<int, 2> pos;
vector<Node*> nbs;
bool in_open;
bool in_closed;
};
Every Node has a position (pos) that is in range of map_size (global variable).
And I'm using the following unordered map
struct PosHash{
public:
size_t operator()(array<int, 2> const& pos) const{
return pos[1] + pos[0]*map_size[1];
}
};
struct PosCompare{
bool operator()(const array<int, 2>& pos1, const array<int, 2>& pos2) const{
if (pos1[0] == pos2[0] and pos1[1] == pos2[1]){
return true;
}
else{
return false;
}
}
};
unordered_map<array<int, 2>, Node*, PosHash, PosCompare> nodes;
map_size is a global variable used in the hash function and allows me to get a perfect hash.
However, I would like to pass map_size as a parameter to PosHash and this way, avoid using global variables. How can I do this?
PD: map_size value is determined in execution time
Here's a complete program showing how to construct a PosHash object storing a specific value. Note that you don't need a custom comparison - std::arrays are compared the same way anyway.
#include <iostream>
#include <array>
#include <unordered_map>
using Pos = std::array<int, 2>;
struct PosHash {
PosHash(int m) : m_{m} { }
size_t operator()(const Pos& pos) const {
return pos[1] + pos[0] * m_;
}
int m_;
};
int main()
{
static constexpr size_t initial_bucket_count = 5;
// for illustrative purposes, will just map to ints
std::unordered_map<Pos, int, PosHash> m{initial_bucket_count, PosHash{4}};
m[Pos{2, 3}] = 23;
m[Pos{1, 1}] = 11;
m[Pos{3, 1}] = 11;
m[Pos{3, 2}] = 32;
for (const auto& x : m)
std::cout << x.first[0] << ',' << x.first[1] << '=' << x.second << '\n';
}
More generally, this is a "weak" hash function. It may be "perfect" in producing hash values spread out enough to avoid collisions in the hash value space, but that doesn't mean you won't get collisions if you're folding that space into a lesser number of buckets.
You can have map_size as a data member inside PosHash struct.
struct PosHash{
public:
size_t operator()(array<int, 2> const& pos) const{
return pos[1] + pos[0]*map_size[1];
}
// Contructor that initializes the data member
PosHash(std::intializer_list<int> map_dim) : map_size(map_dim) {
// Other stuff
};
std::array<int, 2> map_size; // data member of the struct
};
Then you can construct a PosHash object with required map_size array. Pass this object to your unordered_map
PosHash myPosHash({1, 2});
std::unordered_map<array<int, 2>, Node*, PosHash, PosCompare> nodes(\\intial args\\, myPosHash);
Check out the different available constructors for std::unordered_map here. You need to explicitly specify all the arguments before the hasher argument. Those would depend on your needs.
The simplest I can think of is
nodes(0, myPosHash); //initial #buckets, hasher object

use n_th element in a container, but with another key

I have two vectors. One that actually holds the data (let's say floats) and one that holds the indices. I want to pass at nth_element the indices vector, but I want the comparison to be done by the vector that actually holds the data. I was thinking about a functor, but this provides only the () operator I guess. I achieved that by making the data vector a global one, but of course that's not desired.
std::vector<float> v; // data vector (global)
bool myfunction (int i,int j) { return (v[i]<v[j]); }
int find_median(std::vector<int> &v_i)
{
size_t n = v_i.size() / 2;
nth_element(v_i.begin(), v_i.begin()+n, v_i.end(), myfunction);
return v_i[n];
}
You may use a functor like:
class comp_with_indirection
{
public:
explicit comp_with_indirection(const std::vector<float>& floats) :
floats(floats)
{}
bool operator() (int lhs, int rhs) const { return floats[lhs] < floats[rhs]; }
private:
const std::vector<float>& floats;
};
And then you may use it like:
int find_median(const std::vector<float>& v_f, std::vector<int> &v_i)
{
assert(!v_i.empty());
assert(v_i.size() <= v_f.size());
const size_t n = v_i.size() / 2;
std::nth_element(v_i.begin(), v_i.begin() + n, v_i.end(), comp_with_indirection(v_f));
return v_i[n];
}
Note: with C++11, you may use lambda instead of named functor class.
int find_median(const std::vector<float>& v_f, std::vector<int> &v_i)
{
assert(!v_i.empty());
assert(v_i.size() <= v_f.size());
const size_t n = v_i.size() / 2;
std::nth_element(
v_i.begin(), v_i.begin() + n, v_i.end(),
[&v_f](int lhs, int rhs) {
return v_f[lhs] < v_f[rhs];
});
return v_i[n];
}

Functors and vector of strings

I'm new to functors theme, so I hope this question will be constructive.
I have array of strings (). I need to calculate the sum of lenghts of these strings with help of functors.
My code:
class LengthFinder{
private:
size_t sum;
public:
LengthFinder():sum(0){}
void operator()(string elem)
{
sum += elem.size();
}
operator int()
{
return sum;
}
};
int main(int argc, char* argv[])
{
vector< string > array;
array.push_back("string");
array.push_back("string1");
array.push_back("string11");
string elem;
int sum = std::for_each(array.begin(), array.end(), LengthFinder(/*??*/));
return 0;
}
What should I pass to LengthFinder(), to get each string and take it size?
Don't use for_each for this. It can be forced to do the job, but it's a fair amount of extra work because it's not really the right tool for the job.
What you want to use is std::accumulate, which is built for exactly the sort of thing you're doing.
struct length : std::binary_function<size_t, size_t, std::string> {
size_t operator()(size_t a, std::string const &b) {
return a+b.length();
}
};
// ...
int sum = std::accumulate(array.begin(), array.end(), 0, length());
As stated by the original poster of the question:
SOLUTION:
Nothing should go to the parameters of LengthFinder:
int sum = std::for_each(array.begin(), array.end(), LengthFinder());

sort one array and other array following?

here is the C++ sample
int a[1000] = {3,1,5,4}
int b[1000] = {7,9,11,3}
how do i make it so if i sort array a, array b also following array a
example
a[1000] = {1,3,4,5}
b[1000] = {9,7,3,11}
is it possible using sort function
sort(a,a+4)
but also sort array b aswell ?
edit: what if there are 3 arrays ?
Instead of using two arrays, can you use an array of pairs and then sort THAT using a special comparison functor rather than the default less-than operator?
The simplest way is to rearrange your data into an array-of-structs instead of a pair of arrays so that each datum is contiguous; then, you can use an appropriate comparator. For example:
struct CompareFirst
{
bool operator() (const std::pair<int,int>& lhs, const std::pair<int,int>& rhs)
{
return lhs.first < rhs.first;
}
};
// c[i].first contains a[i], c[i].second contains b[i] for all i
std::pair<int, int> c[1000];
std::sort(c, c+1000, CompareFirst());
If you can't refactor your data like that, then you need to define a custom class that acts as a RandomAccessIterator:
struct ParallalArraySortHelper
{
ParallelArraySortHelper(int *first, int *second)
: a(first), b(second)
{
}
int& operator[] (int index) { return a[index]; }
int operator[] const (int index) { return a[index]; }
ParallelArraySortHelper operator += (int distance)
{
a += distance;
b += distance;
return *this;
}
// etc.
// Rest of the RandomAccessIterator requirements left as an exercise
int *a;
int *b;
};
...
int a[1000] = {...};
int b[1000] = {...};
std::sort(ParallalArraySortHelper(a, b), ParallelArraySortHelper(a+1000, b+1000));
Generate an array the same size as the original, containing the indexes into the array: {0, 1, 2, 3}. Now use a custom comparator functor that compares the elements in an associated array rather than the indexes themselves.
template<typename T>
class CompareIndices
{
public:
CompareIndices(const T * array) : m_AssociatedArray(array) {}
bool operator() (int left, int right) const
{
return std::less(m_AssociatedArray[left], m_AssociatedArray[right]);
}
private:
const T * m_AssociatedArray;
};
std::sort(i, i+4, CompareIndices(a));
Once you have a sorted list of indices, you can apply it to the original array a, or any other b array you want.

C++ STL make_heap and pop_heap not working

I need to use a Heap, so i've searched about the STL one, but it doesn't seem to work, i wrote some code to explain what i mean:
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <algorithm>
struct data
{
int indice;
int tamanho;
};
bool comparator2(const data* a, const data* b)
{
return (a->tamanho < b->tamanho);
}
int main()
{
std::vector<data*> mesas;
data x1, x2, x3, x4, x5;
x1.indice = 1;
x1.tamanho = 3;
x2.indice = 2;
x2.tamanho = 5;
x3.indice = 3;
x3.tamanho = 2;
x4.indice = 4;
x4.tamanho = 6;
x5.indice = 5;
x5.tamanho = 4;
mesas.push_back(&x1);
mesas.push_back(&x2);
mesas.push_back(&x3);
mesas.push_back(&x4);
mesas.push_back(&x5);
make_heap(mesas.begin(), mesas.end(), comparator2);
for(int i = 0 ; i < 5 ; i++)
{
data* mesa = mesas.front();
pop_heap(mesas.begin(),mesas.end());
mesas.pop_back();
printf("%d, %d\n", mesa->indice, mesa->tamanho);
}
return 0;
};
and this is what i get:
4, 6
2, 5
1, 3
3, 2
5, 4
So it's not working as a heap, as the maximum element on the vector is not being returned right.
Or am i doing something wrong?
You need to pass comparator2 to std::pop_heap since that is how you created the heap. Otherwise, it will use the default less than operator for pointers, which simply compares the pointer values.
MSN's answer is correct. However, either of a couple style guidelines can prevent this error:
Declare the comparator to work on references, not objects, as operator< would. Use a vector of objects, not pointers.
bool comparator2(const data& a, const data& b)
{
return (a.tamanho < b.tamanho);
}
You might really need the vector of pointers, in which case this doesn't apply.
Use std::priority_queue (from <queue>), which ties together pop_heap and pop_back for you, remembering your comparator. This requires a functor comparator:
struct comparator2 { bool operator()(const data& a, const data& b)
{
return (a.tamanho < b.tamanho);
} };
 
std::priority_queue<data, vector<data>, comparator2> mesas;
// or std::priority_queue<data, vector<data>, comparator2>
mesas.push(x1);
Most elegant way is to make this the default comparison for data:
struct data
{
int indice;
int tamanho;
 
friend bool operator<(const data& a, const data& b)
{
return (a.tamanho < b.tamanho);
}
};
std::priority_queue<data> mesas;
mesas.push(x1);
priority_queue can also take a prefilled unsorted container, which it will copy.
What about std::set
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <algorithm>
#include <set>
struct data
{
// Always put constructors on.
// When the constructor is finished the object is ready to be used.
data(int i,int t)
:indice(i)
,tamanho(t)
{}
int indice;
int tamanho;
// Add the comparator to the class.
// Then you know where to look for it.
bool operator<(data const& b) const
{
return (tamanho < b.tamanho);
}
};
int main()
{
std::set<data> mesas;
// Dont declare all your variables on the same line.
// One per line otherwise it is hard to read.
data x1(1,3);
data x2(2,5);
data x3(3,2);
data x4(4,6);
data x5(5,4);
mesas.insert(x1);
mesas.insert(x2);
mesas.insert(x3);
mesas.insert(x4);
mesas.insert(x5);
// You don't actually need the variables.
// You could have done it in place.
mesas.insert(data(6,100));
// Use iterator to loop over containers.
for(std::set<data>::iterator loop = mesas.begin(); loop != mesas.end(); ++loop)
{
printf("%d, %d\n", loop->indice, loop->tamanho);
}
return 0;
};
I had a similar problem and was able to solve it with something like this:
struct comparator2 { bool operator()(data const * const a, data const * const b)
{
return (a->tamanho < b->tamanho);
} };
std::priority_queue<data*, std::vector<data*>, comparator2> mesas;