How to compare C++ string using qsort in c? - c++

I tried to learn the qsort function of the c-library stdlib. This is provided even in c++. But i dont understand how to use them for sorting c++ strings. I am not sure of what the parameters should be for the sizeof() operator and whether my compare_str code is right. I tried this code:
#include<iostream>
#include<cstdlib>
using namespace std;
#include<string>
int compare_str( const void *a, const void *b){
string obj = (const char*)a;
string obj1 = (const char*)b;
return obj.compare(obj1);
}
int main(){
string obj[4] = {"fine", "ppoq", "tri", "get"};
qsort(obj, 4, sizeof(obj[0].length()), compare_str);
for( int i=0; i<4; i++)
cout<<obj[i]<<endl;
return 0;
}
My output was:
ppoq
tri
get
fine
I am not able to make out the error. Please help.

You cannot and must not use qsort on an array of std::strings. The elements must be of trivial type, which strings are not, and thus the behaviour is undefined. From 25.5/4 ("qsort"):
The behavior is undefined unless the objects in the array pointed to by base are of trivial type.
The reason is that qsort will memcpy the array elements around, which is not possible for C++ objects in general (unless they're sufficiently trivial).
If you do have a trivial type, you can use this generic qsorter-comparator (but of course this is a terrible idea, and the inlined std::sort is always preferable):
template <typename T>
int qsort_comp(void const * pa, void const * pb)
{
static_assert<std::is_trivial<T>::value, "Can only use qsort with trivial type!");
T const & a = *static_cast<T const *>(pa);
T const & b = *static_cast<T const *>(pb);
if (a < b) { return -1; }
if (b < a) { return +1; }
return 0;
}
Use: T arr[N]; qsort(arr, N, sizeof *arr, qsort_comp<T>);
Don't use this. Use std::sort instead.

Better be C++ oriented and use std::sort for your array:
#include <iostream>
#include <string>
#include <iterator>
#include <algorithm>
int main() {
std::string obj[4] = {"fine", "ppoq", "tri", "get"};
std::sort(obj, obj + 4);
std::copy(obj, obj + 4, std::ostream_iterator<std::string>(std::cout, "\n"));
}
AFAIK - std::sort uses quick sort.
[UPDATE] See comments, std::sort is not always pure quick sort.
[UPDATE2]
If you want to learn qsort - change std::string to const char* and define function based on strcmp. Remember that qsort passes pointers to elements in an array - so dereference const void* to get const char*. See:
#include <stdlib.h>
#include <string.h>
int compare_cstr(const void* c1, const void* c2)
{
return strcmp(*(const char**)(c1), *(const char**)(c2));
}
int main() {
const char* obj[4] = {"fine", "ppoq", "tri", "get"};
qsort(obj, 4, sizeof(obj[0]), compare_cstr);
std::copy(obj, obj + 4, std::ostream_iterator<const char*>(std::cout, "\n"));
}

The problem is that you give qsort an array of C++ strings. In your comparison function, you seem to except C strings, since you cast them to (const char*).
Also, the third parameter of qsort, the size of data, you actually give wrong value. sizeof(obj[0].length()) will result in sizeof(size_t), which is obviously wrong. sizeof(obj[0]) would be more correct, but remember that qsort won't call copy constructor of string, which might lead to problems.
I would suggest not to use qsort with C++ strings.
See answer provided by PiotrNycz for correct solution.

You should use the std::sort template function provided by the C++ standard library (in the <algorithm> header file). By default, std::sort uses the less than comparison operator to order elements (std::string already implements operator<). If you need to specify an ordering condition (for example, case insensitive string compare), std::sort allows you to specify an ordering function object.
Example:
#include <string>
#include <algorithm>
bool caseInsensitiveOrdering(const std::string& lhs, const std::string& rhs)
{
// return true if lowercase lhs is less than lowercase rhs
}
int main()
{
std::string names[] = {"chuck", "amy", "bob", "donna"};
size_t nameCount = sizeof(names) / sizeof(names[0]);
// Sort using built-in operator<
std::sort(names, names + nameCount);
// Sort using comparison function
std::sort(names, names + nameCount, &caseInsensitiveOrdering);
}

Your error is in the declaration of the size in qsort. What is expected is the size of a member, which is, in your case, a string. So you want to use:
qsort(obj, 4, sizeof(string), compare_str);
However, you need to work with pointer to string, rather than strings themselves. Then, the code should look like:
int compare_str( const void *a, const void *b){
const string* obj = (const string*)a;
const string* obj1 = (const string*)b;
return obj->compare(*obj1);
}
// ...
string* obj[4] = { new string("fine"), new string("ppoq"),
new string("tri"), new string("get") };
qsort(obj, 4, sizeof(string*), compare_str);
// And delete the objects
for(int i = 0 ; i < 4 ; ++i) delete obj[i];

Works for me:
#include<iostream>
#include<cstdlib>
using namespace std;
#include<string>
int compare_str( const void *a, const void *b){
string* obj = (string*)a;
string* obj1 = (string*)b;
return obj->compare(*obj1);
}
int main(){
string obj[4] = {"fine", "ppoq", "tri", "get"};
qsort(obj, 4, sizeof(string), compare_str);
for( int i=0; i<4; i++)
cout<<obj[i]<<endl;
return 0;
}

Related

How to pass a vector of strings to a function with a C-style pointer-to-C-string parameter

I need to call the getList function below which comes from a library I cannot change.
#include <iostream>
#include <vector>
#include <string>
//function already exists in a separate library, can't be changed
void getList(const char* list[], int count){};
int main()
{
std::vector<std::string> vectorOfStrings = {"123" , "abc", "def", "456"};
//call getList and pass vectorOfStrings and vectorOfStrings.size()
getList(/*convert std::vector<std::string> to const char** */, vectorOfStrings.size());
return 0;
}
I already asked a similar question here and got an answer but I thought there might be a C++ way of doing this.
Use a std::vector<const char*> to store the pointers of the string data stored in the original std::vector<std::string>.
This is a fast operation because you're copying the original data but mind that: this requires that the const char* passed to the function are not stored anywhere because they'll become INVALID once the original string is deallocated (you'll get undefined behavior).
std::vector<std::string> strings = { "foo", "bar", "baz" };
std::vector<const char*> cstrings;
cstrings.reserve(strings.size());
std::transform(
strings.begin(),
strings.end(),
std::back_inserter(cstrings),
[] (const auto& string) { return string.c_str();}
);
getList(cstrings.data(), cstrings.size());
Given that the vector elements are not a C type, you'll need to construct an array to pass to the function:
const char **a = new const char *[vectorOfStrings.size()];
for (int i=0;i<vectorOfStrings.size();i++) {
a[i] = vectorOfStrings[i].c_str();
}
getList(a, vectorOfStrings.size());
delete[] a;
Or, in a more C++ way:
std::vector<const char *> v2;
for (int i=0;i<vectorOfStrings.size();i++) {
v2.push_back(vectorOfStrings[i].c_str());
}
getList(v2.data(), vectorOfStrings.size());

Changing vector in function by pointer

Newbie in cpp, cannot understand what's going wrong:
void addElem(vector<someCustomByteType> *result, string str_to_add) {
someCustomByteType *elem = (someCustomByteType*)str_to_add.data();
int elem_size = str_to_add.size();
memcpy(&(*result).back(), elem, elem_size);
}
int main(int argc, _TCHAR* argv[])
{
vector<someCustomByteType> vec(5);
vector<someCustomByteType> *vec_ptr = &vec;
addElem(signed_string_ptr, "abcd");
return 0;
}
outside, in main:
vector not changed outside. But memcpy works. What's going on?
Thank you!
The function does not make sense.
For starters it is unclear why you are using a pointer to the vector instead of a reference to the vector.
Nevertheles, this declaration
vector<unsigned char> vec(5);
does not reseeve a memory for the vector. It initializes the vector with 5 zero-characters which will be appended with other characters in the function.
The expression
&(*result).back()
returns reference to the last element of the vector. And this call
memcpy(&(*result).back(), elem, elem_size);
will try to overwrite memory that does not belong to the vector. As result the function has undefined behaviour.
Youy can imagine the situation the following way
vector
|0|0|0|0|0|
|a|b|c|d|
string
so as it is seen there is an attempt to copy characters 'b', 'c', and 'd' to the memory that does not belong to the vector.
You should use methods of the class template std::vector to append new elements.
The function can look the following way as it is shown in the demonstrative program (provided that you want to use a pointer to vector).
#include <iostream>
#include <vector>
#include <string>
void addElem( std::vector<unsigned char> *result, const std::string &str_to_add )
{
result->reserve( result->size() + str_to_add.size() );
result->insert( result->end(), str_to_add.begin(), str_to_add.end() );
}
int main()
{
std::vector<unsigned char> vec;
std::vector<unsigned char> *vec_ptr = &vec;
addElem( vec_ptr, "abcd" );
for ( auto c : *vec_ptr ) std::cout << c;
std::cout << std::endl;
return 0;
}
Its output is
abcd
What you are doing is undefined behavior. Neither std::vector or std::string are trivially copyable which is a requirement of std::memcpy. If you want to copy a std::string into a std::vector you can just do something like
void addString(vector<unsigned char>& vec, const std::string& str)
{
vec.assign(str.begin(), str.end());
}

Visual C++ - qSort with Strings

Yes, I want to use the qsort() function with two strings, most likely those will be tested as character arrays. When I run it,I get an unhandled exception in qsort.c at the line(151):
if (__COMPARE(context, lo, mid) > 0) {
swap(lo, mid, width);
}
This is my code:
#include <iostream>
#include <iomanip>
using namespace std;
struct SaleSlip{
char name[20];
int ProdID;
double value;
};
int compare(void const *a, void const *b);
ostream& operator<<(ostream& out, SaleSlip& sales);
int main(){
SaleSlip sales[17] = {
{"Eric", 1, 200000.00},
{"Sookie", 2, 200.00},
{"Sookie", 4, 200.50},
{"Bill", 3, 5000.00},
{"Bill", 5, 7500.00},
{"Tara", 4, 350.50},
{"Eric", 2, 200.00},
{"Tara", 2, 200.00},
{"Tara", 4, 350.50},
{"Bill", 5, 2500.00},
{"Sookie", 1, 50000.00},
{"Sookie", 2, 200.00},
{"Eric", 5, 10000.00},
{"Tara", 2, 200.00},
{"Tara", 4, 150.50},
{"Bill", 5, 1000.00},
{"Sookie", 4, 400.50}
};
cout << "The array before sorting is: " << endl;
for(int i = 0; i < 17; i++)
cout << sales[i];
qsort(sales[0].name, 17, (sizeof(sales)/sizeof(char*)), compare);
cout << "The array after sorting is: ";
system("pause");
return 0;
}
ostream& operator<<(ostream& out, SaleSlip& sales){
out << setiosflags(ios::left | ios::fixed) << setw(7) << sales.name << setw(3) << sales.ProdID
<< setprecision(2) << sales.value << endl;
return out;
}
int compare(void const *a, void const *b) {
return strcmp(*(const char **)a, *(const char **)b);
}
I am testing in compare properly? Am I calling qsort properly?
You probably want your compare function to take SaleSlip pointers as the argument. In the compare function do strcmp(a->name,b->name). Of course your arguments to qsort will change to become SaleSlip structs.
What you are doing wrong is:
I. You pass in 17 as both the element size and the element count to qsort(). That's wrong, the 2nd argument is the number of elements in the array, the 3rd one is the size of one individual element.
II. you want to sort the array, but you don't pass in the address of its first element, but a pointer to the name member of the first element. Take this and the incorrect element size, and from now on, all the pointers qsort() operates on are quite arbitrary, and using them is not any good. What you could do is:
I. Write a proper comparator function and don't try to include nasty tricks:
qsort(sales, sizeof(sales) / sizeof(sales[0]), sizeof(sales[0]), comp);
int comp(const void *a, const void *b)
{
// the two lines below are the aesthetic reason
// for NOT using qsort() in C++. The ugly cast is not needed in C.
const SaleSlip *s1 = static_cast<const SaleSlip *>(a);
const SaleSlip *s2 = static_cast<const SaleSlip *>(b);
return strcmp(s1->name, s2->name);
}
II. Or, even better: use std::sort, std::string and define SaleSlip::operator <:
class SaleSlip {
bool operator <(const SaleSlip &that) {
return this->name < that.name;
}
};
std::sort(sales, sales + sizeof(sales) / sizeof(sales[0]));
Bonus: don't use hard-coded sizes and types. 17 is dangerous, sizeof(array) / sizeof(SaleSlip) is better but still not perfect, sizeof(array) / sizeof(array[0]) is entirely safe, no matter what you do with the base type and the element count of the array.
Three problems:
The first argument should just be sales - the array you want to sort.
The size argument should be sizeof(SaleSlip) - the size of each element.
The comparison is doing something very strange. To get a pointer to the name, you want static_cast<const SaleSlip*>(a)->name (or (const char*)a if you like to live dangerously).
These problems can be avoided by using the C++ std::sort function (and std::string for the string), which is type-safe and (often) more efficient than qsort. Then you simply do
std::sort(std::begin(sales), std::end(sales),
[](SaleSlip const & a, SaleSlip const & b) {return a.name < b.name;});
Use std::sort : qsort is harder to use in almost every case, and less efficient.
First, write a order function:
struct SaleSlip{
char name[20];
int ProdID;
double value;
};
bool order( SaleSlip const& lhs, SaleSlip const& rhs ) {
return strncmp(lhs.name, rhs.name, sizeof(lhs.name))<0;
}
then feed this to std::sort:
std::sort( &sales[0], &sales[sizeof(sales)/sizeof(sales[0])], order );
and your are done.
In C++11, that line is better:
std::sort( std::begin(sales), std::end(sales), order );
You can also replace name with a std::string rather than work with a fixed sized buffer, but I understand there are reasons to work with raw char buffers. If you do so, simply change order to return lhs.name < rhs.name.
The problems in your original code, above and beyond the choice to use qsort, is that the blocks of memory you are sorting are SaleSlip objects, not char* objects. First, fix your compare function (and give it a better name):
int compare_pvoid_SaleSlips(void const *a, void const *b) {
SalesSlip const* lhs = static_cast<SalesSlip const*>(a);
SalesSlip const* rhs = static_cast<SalesSlip const*>(b);
return strncmp(lhs->name, rhs->name, sizeof(lhs->name)/sizeof(lhs->name[0]));
}
Next, fix your call to qsort:
qsort(&sales[0], 17, (sizeof(sales)/sizeof(sales[0])), compare_pvoid_SaleSlips);
which should work. However, following this method will be slower, more error prone, more brittle, and in every way worse in a C++ program than the std::sort solution.

Insertion sort c++ vectors

void insertion_sort(int *data, unsigned int n) {
for (unsigned int uns = 1; uns < n; ++uns ) {
int next = data[uns];
unsigned int idx;
for (idx = uns; idx > 0 && data[idx - 1] > next; --idx) {
data[idx] = data[idx - 1];
}
data[idx] = next;
}
}
int main()
{
vector<Person> crew= ucitaj_osobe("osobe.txt"); /*this reads the file osobe.tx and stores it in vector crew,this works */
Person o;
insertion_sort(polje, 100); // ???
ispisi_osobe(popis); /* this prints out the vector,this works too*/
return 0;
}
how do I send this vector to insertion sort,and sort it?
please help,the code for insertion sort was implemented from another source
Your function insertion_sort is implemented to sort arrays of int and the function will not work for sorting of your vector of Person objects.
If you want to sort your vector of Person objects I suggest you use std::sort from the standard library. To use it you must implement the < operator for Person objects.
Example:
// Only to demonstrate.
struct Person {
std::string name;
int age;
};
// Implement according to your needs.
bool operator< (const Person& lhs, const Person& rhs) {
return lhs.name < rhs.name;
}
int main() {
vector<Person> crew = ucitaj_osobe("osobe.txt");
std::sort(begin(crew), end(crew));
ispisi_osobe(popis);
// return 0; Unnecessary in main function.
}
Live example: http://ideone.com/YEL7IV
Note that std::sort is not guaranteed to use insertion sort.
You can pass a pointer to the array within a vector by passing the address of the first element within the vector.
insertion_sort(&crew[0], crew.size());
Your insertion_sort is designed to sort arrays of int, and
only arrays of int. You cannot use it on arrays of Person.
You don't say why you want to use this insertion sort, instead
of std::sort. But if you want to use it on vector of
Person, you'll have to change its first argument to Person*,
and pass it &crew[0], crew.size(). A better solution would be
to convert it to take an std::vector<Person> directly, rather
than a pointer and a size. An even better solution would be
a template taking two bidirectional iterators, and invoke it
with crew.begin(), crew.end().

Sorting strings in C++ using operator <

The following array is given in C++ code:
char strings[105][105];
What is the correct way to write operator< to sort the strings using STL sort function and is it possible at all?
That code actually looks suspiciously like C code, not C++ which would use std::string.
There's no way to write an operator< that will work with std::sort because there's no swap that will work right unless you write that TOO.
Using std::string would make this pretty trivial, otherwise you'll have to write your own operator< (look at the C function strcmp) and swap functions.
EDIT: Note that swapping std::strings will almost certainly be faster than swapping huge swaths of memory in a char array.
It's not possible to write an operator< to work with char arrays.
Assuming you really do need to sort a 2D array row-wise, it's a bit difficult to make std::sort() do this for you, even given a working comparer functor: it would need some sort of iterator adapter.
However, you can easily use other in-place sorting algorithms, such as selection sort:
#include <iostream>
#include <algorithm>
#include <string>
template<int N>
bool char_array_less(const char(&l)[N], const char(&r)[N])
{
return std::char_traits<char>::compare(&l[0], &r[0], N) < 0;
// for a more general solution
// return std::lexicographical_compare(&l[0], &l[0]+N, &r[0], &r[0]+N);
}
template<int N>
void swap_char_arrays( char(*l)[N], char(*r)[N])
{
std::swap_ranges(&(*l)[0], &(*l)[0]+N, &(*r)[0]);
}
const int ROWS = 105;
const int COLS = 105;
int main()
{
char a[ROWS][COLS] = {"foo", "bar", "whatever" };
for(char(*i)[COLS] = a; i != a+ROWS; ++i)
swap_char_arrays(i,
std::min_element(i, a+ROWS, char_array_less<COLS>));
for(int i=0; i<ROWS; ++i)
std::cout << a[i] << '\n';
}
test run: https://ideone.com/15hRB
You can't overload operator< for pointers, but you don't need to, since std::sort can accept any comparison function (or functor).
Another problem is that the sort algorithm cannot swap arrays, because they are not assignable. But you can sort an array of pointers into the two-dimensional array (leaving the original array as it is).
#include <algorithm>
#include <cstring>
#include <cstdio>
bool compare_cstring(const char* a, const char* b)
{
return strcmp(a, b) < 0;
}
int main()
{
const int count = 5;
char strings[count][10] = { "One", "Two", "Three", "Four", "Five" };
char* sorted_view[count];
for (int i = 0; i != count; ++i) {
sorted_view[i] = strings[i];
}
std::sort(sorted_view, sorted_view + count, compare_cstring);
for (int i = 0; i != count; ++i) {
puts(sorted_view[i]);
}
}