Sorting Array of Struct's based on String - c++

I've been reading all the topics related to sorting arrays of structs, but haven't had any luck as of yet, so I'll just ask. I have a struct:
struct question{
string programNum;
string programDesc;
string programPoints;
string programInput;
string programQuestion;
};
And I populate an array of question in main, and now have an array called questions[] so now I need to write a sort that will sort questions[] based on question.programQuestion. Based on what I've read, this is where I'm at, but I'm not sure if its even close:
int myCompare (const void *v1, const void *v2 ) {
const struct question* p1 = static_cast<const struct question*>(v1);
const struct question* p2 = static_cast<const struct question*>(v2);
if (p1->programQuestion > p2->programQuestion){
return(+1);}
else if (p1->programQuestion < p2->programQuestion){
return(-1);}
else{
return(0);}
}
If this is right I'm not sure how to call it in main. Thanks for any help!

If you're intending to use std::sort to sort this array, you likely want to declare an operator< as a method in this struct. Something like this:
struct question{
string programNum;
string programDesc;
string programPoints;
string programInput;
string programQuestion;
bool operator<( const question &rhs) const;
};
bool question::operator<( const question &rhs ) const
{
return programQuestion < rhs.programQuestion;
}
The comparison function you were attempting to declare above appears to be the type qsort expects, and I would not recommend trying to qsort an array of these struct questions.
Just use std::sort. It's safer, nearly always faster (sometimes by huge margins), and generally easier to get right.

Unless there is some important reason not to do so, I would use a std::vector instead of a plain array. It is easier and safer. You could use the following code to sort your vector:
std::vector<question> questions;
// add some elements to the vector
std::sort(begin(questions), end(questions),
[](const question& q1, const question& q2) {
return q1.programQuestion < q2.programQuestion;
});
This code use some C++11 features. But you could achieve the same in previous versions of C++ by using a function object, or simply by implementing operator< in the struct (assuming you always want to sort such a struct based on that field).

Related

unordered_multiset pointer in C++

Iam beginner in C++, and I want to use insert function of unordered multiset pointer below to add new element:
struct Customer {
size_t operator()(const char& c) const;
};
unordered_multiset<char, Customer>* ms
can any one help?
void populate_multiset(const string& s, unordered_multiset<char, CustomHasher>* ms)
Given this function accepts a string and your unordered_multiset accepts a char, You can only insert a char
for(size_t i = 0; i<s.size(); i++) {
ms->insert(s[i]); // insert each individual char
}
Or use the iterators to insert a range of char
ms->insert(s.begin(), s.end());
Also, since the standard library already provides a way to hash a char. You can simply declare
unordered_multiset<char> ms;
However, if you do want to provide a custom hash function, you can. And the syntax is exactly like what you have in your question.
And a far more common way to pass in a container to a function is through reference. e.g.
void populate_multiset(const string& s, unordered_multiset<char, CustomHasher>& ms)
Then, you can use . instead of -> to do the exact same thing.

Comparing a vector with an array assuming the elements are in different order

I would like to compare a vector with an array assuming that elements are in different order.
I have got a struct like below:
struct A
{
int index;
A() : index(0) {}
};
The size of the vector and the array is the same:
std::vector<A> l_v = {A(1), A(2), A(3)};
A l_a[3] = {A(3), A(1), A(2)};
The function to compare elements is:
bool isTheSame()
{
return std::equal(l_v.begin(), l_v.end(), l_a,
[](const A& lhs, const A& rhs){
return lhs.index == rhs.index;
});
}
The problem is that my function will return false, because the elements are the same, but not in the same order.
A solution is to sort the elements in the vector and array before "std::equal", but is there any better solution?
Using sort would be the way to go. Sorting in general is a good idea. And as far as I know it would result in the best performance.
Note: I would recommend passing the vectors as arguments. Rather than using the member variables. After doing that this would be a typical function that would be very well suited to inline. Also you might also want to consider taking it out of the class and/or making it static.

How to use std::string as key in stxxl::map

I am trying to use std::string as a key in the stxxl::map
The insertion was fine for small number of strings about 10-100.
But while trying to insert large number of strings about 100000 in it, I am getting segmentation fault.
The code is as follows:
struct CompareGreaterString {
bool operator () (const std::string& a, const std::string& b) const {
return a > b;
}
static std::string max_value() {
return "";
}
};
// template parameter <KeyType, DataType, CompareType, RawNodeSize, RawLeafSize, PDAllocStrategy (optional)>
typedef stxxl::map<std::string, unsigned int, CompareGreaterString, DATA_NODE_BLOCK_SIZE, DATA_LEAF_BLOCK_SIZE> name_map;
name_map strMap((name_map::node_block_type::raw_size)*3, (name_map::leaf_block_type::raw_size)*3);
for (unsigned int i = 0; i < 1000000; i++) { /// Inserting 1 million strings
std::stringstream strStream;
strStream << (i);
Console::println("Inserting: " + strStream.str());
strMap[strStream.str()]=i;
}
In here I am unable to identify why I am unable to insert more number of strings. I am getting segmentation fault exactly while inserting "1377". Plus I am able to add any number of integers as key. I feel that the variable size of string might be causing this trouble.
Also I am unable to understand what to return for max_value of the string. I simply returned a blank string.
According to documentation:
CompareType must also provide a static max_value method, that returns a value of type KeyType that is larger than any key stored in map
Because empty string happens to compare as smaller than any other string, it breaks this precondition and may thus cause unspecified behaviour.
Here's a max_value that should work. MAX_KEY_LEN is just an integer which is larger or equal to the length of the longest possible string key that the map can have.
struct CompareGreaterString {
// ...
static std::string max_value() {
return std::string(MAX_KEY_LEN, std::numeric_limits<unsigned char>::max());
}
};
I have finally found the solution to my problem with great help from Timo bingmann, user2079303 and Martin Ba. Thank you.
I would like to share it with you.
Firstly stxxl supports POD only. That means it stores fixed sized structures only. Hence std::string cannot be a key. stxxl::map worked for about 100-1000 strings because they were contained in the physical memory itself. When more strings are inserted it has to write on disk which is internally causing some problems.
Hence we need to use a fixed string using char[] as follows:
static const int MAX_KEY_LEN = 16;
class FixedString {
public:
char charStr[MAX_KEY_LEN];
bool operator< (const FixedString& fixedString) const {
return std::lexicographical_compare(charStr, charStr+MAX_KEY_LEN,
fixedString.charStr, fixedString.charStr+MAX_KEY_LEN);
}
bool operator==(const FixedString& fixedString) const {
return std::equal(charStr, charStr+MAX_KEY_LEN, fixedString.charStr);
}
bool operator!=(const FixedString& fixedString) const {
return !std::equal(charStr, charStr+MAX_KEY_LEN, fixedString.charStr);
}
};
struct comp_type : public std::less<FixedString> {
static FixedString max_value()
{
FixedString s;
std::fill(s.charStr, s.charStr+MAX_KEY_LEN, 0x7f);
return s;
}
};
Please note that all the operators mainly((), ==, !=) need to be overriden for all the stxxl::map functions to work
Now we may define fixed_name_map for map as follows:
typedef stxxl::map<FixedString, unsigned int, comp_type, DATA_NODE_BLOCK_SIZE, DATA_LEAF_BLOCK_SIZE> fixed_name_map;
fixed_name_map myFixedMap((fixed_name_map::node_block_type::raw_size)*5, (fixed_name_map::leaf_block_type::raw_size)*5);
Now the program is compiling fine and is accepting about 10^8 strings without any problem.
also we can use myFixedMap like std::map itself. {for ex: myFixedMap[fixedString] = 10}
If you are using C++11, then as an alternative to the FixedString class you could use std::array<char, MAX_KEY_LEN>. It is an STL layer on top of an ordinary fixed-size C array, implementing comparisons and iterators as you are used to from std::string, but it's a POD type, so STXXL should support it.
Alternatively, you can use serialization_sort in TPIE. It can sort elements of type std::pair<std::string, unsigned int> just fine, so if all you need is to insert everything in bulk and then access it in bulk, this will be sufficient for your case (and probably faster depending on the exact case).

How to convert vector to set?

I have a vector, in which I save objects. I need to convert it to set. I have been reading about sets, but I still have a couple of questions:
How to correctly initialize it? Honestly, some tutorials say it is fine to initialize it like set<ObjectName> something. Others say that you need an iterator there too, like set<Iterator, ObjectName> something.
How to insert them correctly. Again, is it enough to just write something.insert(object) and that's all?
How to get a specific object (for example, an object which has a named variable in it, which is equal to "ben") from the set?
I have to convert the vector itself to be a set (a.k.a. I have to use a set rather than a vector).
Suppose you have a vector of strings, to convert it to a set you can:
std::vector<std::string> v;
std::set<std::string> s(v.begin(), v.end());
For other types, you must have operator< defined.
All of the answers so far have copied a vector to a set. Since you asked to 'convert' a vector to a set, I'll show a more optimized method which moves each element into a set instead of copying each element:
std::vector<T> v = /*...*/;
std::set<T> s(std::make_move_iterator(v.begin()),
std::make_move_iterator(v.end()));
Note, you need C++11 support for this.
You can initialize a set using the objects in a vector in the following manner:
vector<T> a;
... some stuff ...
set<T> s(a.begin(), a.end());
This is the easy part. Now, you have to realize that in order to have elements stored in a set, you need to have bool operator<(const T&a, const T& b) operator overloaded. Also in a set you can have no more then one element with a given value acording to the operator definition. So in the set s you can not have two elements for which neither operator<(a,b) nor operator<(b,a) is true. As long as you know and realize that you should be good to go.
If all you want to do is store the elements you already have in a vector, in a set:
std::vector<int> vec;
// fill the vector
std::set<int> myset(vec.begin(), vec.end());
You haven't told us much about your objects, but suppose you have a class like this:
class Thing
{
public:
int n;
double x;
string name;
};
You want to put some Things into a set, so you try this:
Thing A;
set<Thing> S;
S.insert(A);
This fails, because sets are sorted, and there's no way to sort Things, because there's no way to compare two of them. You must provide either an operator<:
class Thing
{
public:
int n;
double x;
string name;
bool operator<(const Thing &Other) const;
};
bool Thing::operator<(const Thing &Other) const
{
return(Other.n<n);
}
...
set<Thing> S;
or a comparison function object:
class Thing
{
public:
int n;
double x;
string name;
};
struct ltThing
{
bool operator()(const Thing &T1, const Thing &T2) const
{
return(T1.x < T2.x);
}
};
...
set<Thing, ltThing> S;
To find the Thing whose name is "ben", you can iterate over the set, but it would really help if you told us more specifically what you want to do.
How to correctly initialize it?
std::set<YourType> set;
The only condition is that YourType must have bool operator<(const YourType&) const and by copyable (default constructor + assignment operator). For std::vector copyable is enough.
How to insert them correctly.
set.insert(my_elem);
How to get specific object (for example object, which has name variable in it, which is equal to "ben") from set?
That's maybe the point. A set is just a bunch of object, if you can just check that an object is inside or iterate throught the whole set.
Creating a set is just like creating a vector. Where you have
std::vector<int> my_vec;
(or some other type rather than int) replace it with
std::set<int> my_set;
To add elements to the set, use insert:
my_set.insert(3);
my_set.insert(2);
my_set.insert(1);

How to sort vector of pointer-to-struct

I'm trying to sort a concurrent_vector type, where hits_object is:
struct hits_object{
unsigned long int hash;
int position;
};
Here is the code I'm using:
concurrent_vector<hits_object*> hits;
for(i=0;...){
hits_object *obj=(hits_object*)malloc(sizeof(hits_object));
obj->position=i;
obj->hash=_prevHash[tid];
hits[i]=obj;
}
Now I have filled up a concurrent_vector<hits_object*> called hits.
But I want to sort this concurrent_vector on position property!!!
Here is an example of what's inside a typical hits object:
0 1106579628979812621
4237 1978650773053442200
512 3993899825106178560
4749 739461489314544830
1024 1629056397321528633
5261 593672691728388007
1536 5320457688954994196
5773 9017584181485751685
2048 4321435111178287982
6285 7119721556722067586
2560 7464213275487369093
6797 5363778283295017380
3072 255404511111217936
7309 5944699400741478979
3584 1069999863423687408
7821 3050974832468442286
4096 5230358938835592022
8333 5235649807131532071
I want to sort this based on the first column ("position" of type int). The second column is "hash" of type unsigned long int.
Now I've tried to do the following:
std::sort(hits.begin(),hits.end(),compareByPosition);
where compareByPosition is defined as:
int compareByPosition(const void *elem1,const void *elem2 )
{
return ((hits_object*)elem1)->position > ((hits_object*)elem2)->position? 1 : -1;
}
but I keep getting segmentation faults when I put in the line std::sort(hits.begin(),hits.end(),compareByPosition);
Please help!
Your compare function needs to return a boolean 0 or 1, not an integer 1 or -1, and it should have a strongly-typed signature:
bool compareByPosition(const hits_object *elem1, const hits_object *elem2 )
{
return elem1->position < elem2->position;
}
The error you were seeing are due to std::sort interpreting everything non-zero returned from the comp function as true, meaning that the left-hand side is less than the right-hand side.
NOTE : This answer has been heavily edited as the result of conversations with sbi and Mike Seymour.
int (*)(void*, void*) is the comparator for C qsort() function. In C++ std::sort() the prototype to the comparator is:
bool cmp(const hits_object* lhs, const hits_object* rhs)
{
return lhs->position < rhs->position;
}
std::sort(hits.begin(), hits.end(), &cmp);
On the other hand, you can use std::pair struct, which by default compares its first fields:
typedef std::pair<int position, unsigned long int hash> hits_object;
// ...
std::sort(hits.begin(), hits.end());
Without knowing what concurrent_vector is, I can't be sure what's causing the segmentation fault. Assuming it's similar to std::vector, you need to populate it with hits.push_back(obj) rather than hits[i] = j; you cannot use [] to access elements beyond the end of a vector, or to access an empty vector at all.
The comparison function should be equivalent to a < b, returning a boolean value; it's not a C-style comparison function returning negative, positive, or zero. Also, since sort is a template, there's no need for C-style void * arguments; everything is strongly typed:
bool compareByPosition(hits_object const * elem1, hits_object const * elem2) {
return elem1->position < elem2->position;
}
Also, you usually don't want to use new (and certainly never malloc) to create objects to store in a vector; the simplest and safest container would be vector<hits_object> (and a comparator that takes references, rather than pointers, as arguments). If you really must store pointers (because the objects are expensive to copy and not movable, or because you need polymorphism - neither of which apply to your example), either use smart pointers such as std::unique_ptr, or make sure you delete them once you're done with them.
The third argument you pass to std::sort() must have a signature similar to, and the semantics of, operator<():
bool is_smaller_position(const hits_object* lhs, const hits_object* rhs)
{
return lhs->position < rhs->position;
}
When you store pointers in a vector, you cannot overload operator<(), because smaller-than is fixed for all built-in types.
On a sidenote: Do not use malloc() in C++, use new instead. Also, I wonder why you are not using objects, rather than pointers. Finally, if concurrent_vector is anything like std::vector, you need to explicitly make it expand to accommodate new objects. This is what your code would then look like:
concurrent_vector<hits_object*> hits;
for(i=0;...){
hits_object obj;
obj.position=i;
obj.hash=_prevHash[tid];
hits.push_back(obj);
}
This doesn't look right:
for(i=0;...){
hits_object *obj=(hits_object*)malloc(sizeof(hits_object));
obj->position=i;
obj->hash=_prevHash[tid];
hits[i]=obj;
}
here you already are sorting the array based on 'i' because you set position to i as well as it becomes the index of hits!
also why using malloc, you should use new(/delete) instead. You could then create a simple constructor for the structure to initialize the hits_object
e.g.
struct hits_object
{
int position;
unsigned int hash;
hits_object( int p, unsigned int h ) : position(p), hash(h) {;}
};
then later write instead
hits_object* obj = new hits_object( i, _prevHash[tid] );
or even
hits.push_back( new hits_object( i, _prevHash[tid] ) );
Finally, your compare function should use the same data type as vector for its arguments
bool cmp( hits_object* p1, hits_object* p2 )
{
return p1->position < p2->position;
}
You can add a Lambda instead of a function to std::sort.
struct test
{
int x;
};
std::vector<test> tests;
std::sort(tests.begin(), tests.end(),
[](const test* a, const test* b)
{
return a->x < b->x;
});