Trouble Using Template Bubblesort with Array of Structs - c++

So my goal is to read in some data and sort it by population, but I have to use a sort that can accept multiple data types. I was instructed to use a template to do this, but every time I pass the array "results[i].pop" to my bubblesort function I receive the error
no matching function for call to ‘bubblesort(std::string&)’
bubblesort(results[i].pop);"
note: candidate is:
election.cpp:32:3: note: template T bubblesort(T*)
T bubblesort(T ar[])
^
election.cpp:32:3: note: template argument deduction/substitution failed:
election.cpp:106:34: note: cannot convert ‘results[i].election::pop’ (type ‘std::string {aka std::basic_string}’) to type ‘std::basic_string*’
bubblesort(results[i].pop);
Here's the code:
#include <iostream>
#include <iomanip>
#include <string>
#include <cstdlib>
#include <fstream>
#include <stdlib.h>
using namespace std;
struct election {
string party;
string state;
string pop;
string reps;
int ratio;
};
template <typename T>
void bubblesort(T ar[])
{
//Bubblesort
int n = 51;
int swaps = 1;
while(swaps)
{
swaps = 0;
for (int i = 0; i < n - 1; i++)
{
if (ar[i] > ar[i + 1])
{
swap(ar[i],ar[i+1]);
swaps = 1;
}
}
}
//End Bubblesort
}
void delete_chars(string & st, string ch)
{
int i = st.find(ch);
while (i > -1)
{
st.replace(i,1,"");
i = st.find(ch);
}
}
int main()
{
int i = 0;
int n = 51;
election results[n];
int population[n];
int electoralVotes[n];
int ratio[n];
string st;
fstream inData;
//Read in Data from Text File
inData.open("electionresults.txt");
//Print Array as is
cout << "Array Printed As is" << endl;
cout << left << setw(10) << "Party" << setw(20) << "State" << setw(20) << "Population" << setw(15) << "Representatives" << endl;
for (int i = 0; i < n; i++)
{
getline(inData,st);
results[i].party = st.substr(0,1);
results[i].state = st.substr(8,14);
results[i].pop = st.substr(24,10);
results[i].reps = st.substr(40,2);
cout << left << setw(10) << results[i].party << setw(20) << results[i].state << setw(20) << results[i].pop << setw(15) << results[i].reps << endl;
}
//Array Sorted by Population
cout << "Array Sorted By Population" << endl;
cout << endl;
cout << endl;
cout << left << setw(10) << "Party" << setw(20) << "State" << setw(20) << "Population" << setw(15) << "Representatives" << endl;
for(int i = 0; i < n; i++){
bubblesort<string>(results[i].pop);
}

For your bubblesort to work, you need to implement the greater than operator(>) for the election struct:
struct election
{
string party;
string state;
string pop;
string reps;
int ratio;
bool operator>( election a)
{
return pop > a.pop;
}
};
Now call the bubblesort by passing the results array:
bubblesort<election>(results);
A side note your function should pass in the size rather than hardcoding the size in the function(void bubblesort(T ar[], int size)). This gives your function much more functionality and adaptability.

The other answer addressed the issue if you only wanted to sort on pop. However, it is a limited solution, and won't address the real issue of sorting on any field (today it's "pop", but what if this isn't the case tomorrow, where you want to sort on "ratio"?). The issue is that you cannot provide more than one operator > to do this and you're basically stuck only sorting on pop.
Another solution is to provide the bubblesort function with an additional template parameter that defines what to do when given two T's, whether one T should be placed before the other T in the sorted array.
#include <functional>
#include <algorithm>
//...
template <typename T, typename cmp>
void bubblesort(T ar[], int n, cmp compare_fn)
{
int swaps = 1;
while (swaps)
{
swaps = 0;
for (int i = 0; i < n - 1; i++)
{
if (!compare_fn(ar[i], ar[i + 1]))
{
std::swap(ar[i], ar[i + 1]);
swaps = 1;
}
}
}
}
// keep our original 2 param bubble sort, but let it call the one above
template <typename T>
void bubblesort(T ar[], int n)
{
// call general version using <
bubblesort(ar, n, std::less<T>());
}
We basically have two functions, where the two parameter bubblesort function calls the general 3 parameter bubblesort version that takes a third parameter, which describes the comparison.
The two parameter version of bubblesort is used when you want to call bubblesort for the "simple" cases, where your items are
In an array and
You can compare T using < and
You want to sort in ascending order (which is why we used < and not > for the general case).
For example, an array of int needs to be sorted, and you simply want to sort it in ascending order:
int someArray[10];
//...
bubblesort<int>(someArray, 10); // sort ascending
However, we don't want to do a "simple" sort on int, or even std::string. We want to sort on election, and not only that, on election.pop.
If you look at the first bubblesort function above, note that we replaced the comparison using > with a call to a function compare_fn. Note that the parameter is defaulted to the std::less function object. This is why the second bubblesort function works for simple types, since std::less uses < to compare.
However, if you tried to call the bubblesort using only two parameters using election, you come across another compiler error, basically stating that election has no operator < to compare with. The solution to that is either
1) to provide such an operator < (similar to the other answer given) to the election struct or
2) Write a custom comparison function.
So let's go over each of these solutions.
Solution 1:
If we use 1), the election struct will look like this:
struct election
{
std::string party;
std::string state;
std::string pop;
std::string reps;
int ratio;
bool operator <(const election& e) const { return pop < e.pop; }
};
int main()
{
//...
bubblesort<election>(results, n);
}
This will now sort on results using pop as the item to sort on due to the operator < defined in election being used by std::less<>.
Here is an example using overloaded < in election
However, this solution has the same issues as the other answer, in that you can only define one operator < that takes a const election& as a parameter. If you wanted to sort on ratio, for example, you're out of luck, or if you want to sort pop in descending order, you're out of luck. This is where option 2) above will be used.
Solution 2:
We can define what we want to sort on, the sort order, etc. by providing a custom comparison function, function object, or lambda function that returns true if the first T should come before the second T that's passed into the comparison function, false otherwise.
Let's try a function:
bool compare_pop(const election& e1, const election& e2)
{
return e1.pop < e2.pop; // if e1.pop comes before e2.pop, return true, else false
}
int main()
{
//...
bubblesort<election>(results, n, compare_pop);
}
What will happen now is that this will call the first version of bubblesort that takes a comparison function as a parameter. The bubblesort template function will now call compare_pop to determine if the items are out of order. If compare_pop returns false the bubblesort function will swap the items, otherwise it will leave them alone.
Here is a live example with an array of 3 elections, sorted on pop
If you wanted to use a lambda function instead of writing another compare function, that will work too:
int main()
{
//...
bubblesort<election>(results, n, [&](const element& e1, const element& e2) { return e1.pop < e2.pop; });
}
The above will do the same thing as the function example, except that you no longer need to write a separate function as the lambda syntax is used as the function.
Example using lambda syntax
So now, what if we want to sort on pop, but descending and not ascending? Simple -- call bubblesort with a different function or lambda:
bool compare_pop_up(const election& e1, const election& e2)
{
return e1.pop > e2.pop; // if e1.pop comes after e2.pop, return true, else false
}
int main()
{
//...
bubblesort<election>(results, n, compare_pop_up);
}
or using lambda:
int main()
{
//...
bubblesort<election>(results, n,
[&](const element&e1, const element& e2)
{ return e1.pop > e2.pop;});
}
and magically, the bubblesort does the job, sorting on pop in descending order.
Here is a live example with an array of 3 elections, sorted on pop, descending
What if you want to sort on ratio? Same thing -- provide a different function or lambda:
bool compare_ratio(const election& e1, const election& e2)
{
return e1.ratio < e2.ratio;
}
int main()
{
//...
bubblesort<election>(results, n, compare_ratio);
}
or using lambda:
int main()
{
//...
bubblesort<election>(results, n,
[&](const element&e1, const element& e2)
{ return e1.ratio < e2.ratio;});
}
This will sort on ratio in ascending order of the ratio.
The other issue with your code is that you are using non-standard C++ syntax in defining your arrays. You're doing this:
election results[n];
This is not standard C++ syntax, as C++ only allows arrays to be created using a compile-time expression to denote the number of items. You're using something called Variable Length Arrays, which is not standard.
Instead, you can use std::vector, which is standard C++.
#include <vector>
//...
std::vector<election> results(n);
//...
bubblesort<election>(results.data(), results.size(), compare_pop)

Related

Using STL functions with a struct?

I've recently read up on STL functions in C++. I understand the basic uses of the functions, but I am struggling getting them to use member variables of a struct.
I have this struct:
struct Apples
{
double weight; // oz
string color; // red or green
void print() const { cout << color << ", " << weight << endl; }
};
Basically, I insert Apples into a vector storing random weights and random color. Now, I want to use a count_if function to determine how many apples are greater than a given weight. I want to convert a function like this:
int cnt = 0;
for(auto it = crate.cbegin(); it != crate.cend(); ++it)
if(it->weight > toFind)
cnt++;
to a count_if() version (this does not work):
int cnt = count_if(crate.begin(), crate,end(), isGreater())
With isGreater() being like this:
void isGreater()
{
if(it->weight > toFind)
return it->weight > toFind;
}
What I don't understand about STL functions and a struct is how to use the member variables inside of the struct with the STL functions. I'm not sure what to pass inside of the STL function, either. Would it be better to use a lambda function in this case? If so, why?
Here is all the current code, if it doesn't make sense:
#include <iostream>
#include <ctime>
#include <cstdlib>
#include <vector>
#include <deque>
#include <string>
using namespace std;
struct Apples
{
double weight; // oz
string color; // red or green
void print() const { cout << color << ", " << weight << endl; }
};
void isGreater()
{
if(it->weight > toFind)
return it->weight > toFind;
}
int main()
{
srand(time(nullptr));
const double minWeight = 8.;
const double maxWeight = 3.;
cout << "Input crate size: ";
int size;
cin >> size;
vector <Apples> crate(size);
for(auto it = crate.begin(); it != crate.end(); ++it)
{
it->weight = minWeight + static_cast<double>(rand())/RAND_MAX*(maxWeight - minWeight);
it->color = rand() % 2 == 1 ? "green" : "red";
}
cout << "Enter weight to find: ";
double toFind;
cin >> toFind;
//this is what I want to convert to count if
int cnt = 0;
for(auto it = crate.cbegin(); it != crate.cend(); ++it)
if(it->weight > toFind)
cnt++;
std::count_if takes unary predicate as the third argument. In this case unary predicate is a function taking one object and returning true if object matches find criterion or false if not.
Since your criterion depends on toFind, it seems more laconic to use lambda capturing toFind:
int cnt = count_if(crate.begin(), crate.end(), [toFind](const Apple& apple) {
return it->weight > toFind;
});
If you want a standalone function, you can use:
bool isGreater(double toFind, const Apple& apple) {
return it->weight > toFind;
}
...
int cnt = count_if(crate.begin(), crate.end(),
std::bind(&isGreater, toFind, std::placeholders::_1));
Note, that you don't need to call function, you need to pass it:
int cnt = count_if(crate.begin(), crate,end(), isGreater())
// ^^ remove parentheses
you are not storing the apples in the vector.
you have to initialize inside a loop each apple and then store them in the vector.
crate.push_back(newApple).
so run a loop from 0 to size.
inside that loop initialize new apples and give them weights and colors
then push_back in vector:
for(int i = 0; i < size ++i)
{
apples newApple;
newApple.weight = ...;
newApple.color = ...;
crate.push_back(newApple);
}
This is usually accomplished by creating a "functor" class, a class whose objects can be called like a function. Each instance call hold the reference weight:
struct IsGreater {
double w;
IsGreater(double weight) : w{weight} {}
bool operator()(const Apples& A) const {
return A.weight > w;
}
};
Then we just need to create an instance of the class holding the reference weight and pass it to count_if:
const int count = std::count_if(crate.begin(), crate.end(), IsGreater(toFind));
You can avoid creating an explicit class using a lambda:
const int count = std::count_if(crate.begin(), crate.end(),
[=](const Apples& A) -> bool {
return A.weight > toFind;
});
Here the reference value toFind is captured by value.

getting wrong output while sorting in C++

The following C++ code sorts an array in descending order using qsort:
#include<iostream>
#include<cstdio>
#include <stdlib.h>
using namespace std;
struct player {
double data;
int index;
};
struct player A[] = {{0.690277,0}, {0.517857,1}, {0.780762,2}, {0.0416667,3}, {0.0416667,4}};
int compare (const void * a, const void * b)
{
return ( ((struct player*)b)->data - ((struct player*)a)->data );
}
int main ()
{
int n;
qsort (A, 5, sizeof(struct player), compare);
for (n=0; n<5; n++)
printf ("data=%lf, index=%d\n", A[n].data, A[n].index);
return 0;
}
But I am getting output like this:
data=0.517857, index=1
data=0.780762, index=2
data=0.041667, index=3
data=0.041667, index=4
data=0.690277, index=0
Is there anything wrong in the code?
In compare, you are subtracting two sub-1 doubles and casting them to an int, the result will in most cases be 0. Instead of subtracting you should compare them and return -1/1.
Consider using this compare instead:
int compare (const void * a, const void * b)
{
auto x = reinterpret_cast<const player*>(a);
auto y = reinterpret_cast<const player*>(b);
if(x->data < y->data)
return -1;
if(x->data > y->data)
return 1;
return 0;
}
That said, this style of coding is ancient/deprecated/bad practice.
Consider writing similar to this instead:
#include<iostream>
#include <algorithm>
struct player {
double data;
int index;
bool operator < (const player& p) const
{
return data < p.data;
}
};
auto A = std::vector<player>{
{0.690277,0}, {0.517857,1},
{0.780762,2}, {0.0416667,3}, {0.0416667,4}
};
int main ()
{
std::sort(std::begin(A), std::end(A));
for(const auto& x: A)
std::cout << "data=" << x.data << ", "
<< "index=" << x.index << "\n";
}
Suggested changes:
don't import std names globally
don't mix cstdio and iostreams (only include one of them)
use std::vector or std::array instead of native array
define the sorting order in the interface of the class (bool operator <). (this should also imply that you define the other arithmetic operators - it is good practice and avoids subtle bugs later, but it is not required for this particular implementation to compile and work)
use std::sort instead of qsort
don't use raw pointers (using them like this is a source for bugs)

C++ code improvement, array out of bounds

This is a class template for an Array. I overloaded the [ ] operator in hopes it would fix the "out of bounds" issue. The print outs work well, except if it falls out of range, the compiler enables the range by default and it displays a 6 digit number.
Perhaps looking for a better way to initialize the arrays with the appropriate element number for a better check and if it does fall out of range when looking up the element, display an error.
// implement the class myArray that solves the array index
// "out of bounds" problem.
#include <iostream>
#include <string>
#include <cmath>
using namespace std;
template <class T>
class myArray
{
private:
T* array;
int begin;
int end;
int size;
public:
myArray(int);
myArray(int, int);
~myArray() { };
void printResults();
// attempting to overload the [ ] operator to find correct elements.
int operator[] (int position)
{if (position < 0)
return array[position + abs(begin)];
else
return array[position - begin];
}
};
template <class T>
myArray<T>::myArray(int newSize)
{
size = newSize;
end = newSize-1;
begin = 0;
array = new T[size] {0};
}
template <class T>
myArray<T>::myArray(int newBegin, int newEnd)
{
begin = newBegin;
end = newEnd;
size = ((end - begin)+1);
array = new T[size] {0};
}
// used for checking purposes.
template <class T>
void myArray<T>::printResults()
{
cout << "Your Array is " << size << " elements long" << endl;
cout << "It begins at element " << begin << ", and ends at element " << end << endl;
cout << endl;
}
int main()
{
int begin;
int end;
myArray<int> list(5);
myArray<int> myList(2, 13);
myArray<int> yourList(-5, 9);
list.printResults();
myList.printResults();
yourList.printResults();
cout << list[0] << endl;
cout << myList[2] << endl;
cout << yourList[9] << endl;
return 0;
}
First of all, your operator[] is not correct. It is defined to always return int. You will get compile-time error as soon as you instantiate array of something, that is not implicitly convertible to int.
It should rather be:
T& operator[] (int position)
{
//...
}
and, of course:
const T& operator[] (int position) const
{
//you may want to also access arrays declared as const, don't you?
}
Now:
I overloaded the [ ] operator in hopes it would fix the "out of bounds" issue.
You didn't fix anything. You only allowed clients of your array to define custom boundaries, nothing more. Consider:
myArray<int> yourList(-5, 9);
yourList[88] = 0;
Does your code check for out-of-bounds cases like this one? No.
You should do it:
int operator[] (int position)
{
if((position < begin) || (position > end)) //invalid position
throw std::out_of_range("Invalid position!");
//Ok, now safely return desired element
}
Note, that throwing exception is usually the best solution in such case. Quote from std::out_of_range doc:
It is a standard exception that can be thrown by programs. Some components of the standard library, such as vector, deque, string and bitset also throw exceptions of this type to signal arguments out of range.
An better option to redefining an array class is to use the containers from the std library. Vector and array(supported by c++11). They both have an overloaded operator [] so you can access the data. But adding elements using the push_back(for vector) method and using the at method to access them eliminates the chance or getting out of range errors, because the at method performs a check and push_back resizes the vector if needed.

Sort() leads to Segmentation Fault

I have to implement a shuffleString(string s,int d) method where the first step involves getting the count of each character in the string and arranging them in the decreasing order of their count. I implemented the required functionality as follows:
struct node{
char ch;
int ctr;
};
bool myfunc(const struct node x,const struct node y){
return (x.ctr>=y.ctr?true:false);
}
string shuffleString(string s,int d){
int i,j;
int len=s.size();
vector<struct node> counter(26);
for(i=0;i<26;i++){
counter[i].ch='a'+i;
counter[i].ctr=0;
}
for(i=0;i<len;i++){
counter[s[i]-'a'].ctr++;
}
sort(counter.begin(),counter.end(),myfunc);//From STL's algorithm
/*
Remaining Functionality
*/
}
However, the code generates a segmentation fault when the sort() method is called. What is wrong with my code? Please help.
P.S. The string only contains lowercase ASCII characters.
try this
bool myfunc(const struct node x,const struct node y){
return (x.ctr>y.ctr) || (x.ctr == y.ctr && x.ch < y.ch);
}
EDITED:
you should handle the break tie case (separate > and == cases instead of >=)
Your algorithm will fail when input has any char other than a-z (even A-Z)
A small snippet is here:
struct node{
char ch;
int ctr;
};
bool myfunc(const struct node x,const struct node y){
return (x.ctr>y.ctr) || (x.ctr == y.ctr && x.ch < y.ch);
}
void shuffleString(string s){
int i,j;
int len=s.size();
vector<struct node> counter(26);
for(i=0;i<26;i++){
counter[i].ch='a'+i;
counter[i].ctr=0;
}
for(i=0;i<len;i++){
counter[s[i]-'a'].ctr++;
}
for(int i=0; i<26;i++) cout << counter[i].ch <<"," << counter[i].ctr << endl;
cout << endl << endl;
sort(counter.begin(),counter.end(), myfunc);//From STL's algorithm
for(int i=0; i<26;i++) cout << counter[i].ch <<"," << counter[i].ctr << endl;
}
int main()
{
shuffleString("hello");
return 0;
}
You likely have got memory corruption in the line: counter[s[i]-'a'].ctr++;
s[i]-'a' can be more than 25, what is out of the vector boundary.
stl sort uses operator< by default - it expects the function you pass in to behave in the same way.
This means that if it calls the function on two identical values it should get false, but in your case it would get true. Because of this it will not correctly sort.
When you remove the = the function no longer has this issues since operator> is non reflexive like operator <
If you run with the debug c runtime STL will first assert that the passed in function is well formed.

c++ odd behaviour of an array of iterators

So searching to see if someone has already asked this I see lots of questions about iterating over arrays. But what I've got is an array of iterators. Essentially here's what I'm up to:
I have a sorted std::list of a custom object. The object just contains an int and a double, and has methods for the sorts of things you would expect (constructors, setters, getters, operator< to make it sortable by the double, a toSting() method). That all works, including sorting.
Now I want a bunch of iterators that point into the list at different points. There will be one to the head of the list, one to the tail and several pointing into various points in the middle. I'm doing this using an old-style array (this may be the problem - I'll try it with a std::array, but I still want to understand why this hasn't worked). So I've got a subroutine that initializes this array. It almost works. I can build the array and output from within the subroutine and everything looks good. Outputting from outside the subroutine the last element of the array has changed and no longer appears to point into the list. Here's the relevant code:
using namespace std;
#include <iostream>
#include <list>
#include <cmath>
#include <algorithm>
#include "random.h"
#include "Double_list_struct.h"
/**********************************Subroutine declarations***************************/
template <typename Tplt>
void output_list(list<Tplt> to_out);
template <typename Tplt>
void initialize_list(list<Tplt> &alist, int size);
template <typename Tplt>
void initialize_iter_array(typename list<Tplt>::iterator* itar, int size, list<Tplt> alist);
/***************************************Main routine*******************************/
int main(void)
{
int list_size = 16;
// Make the list that will be tested.
list<Double_list_struct> list_to_play_with;
initialize_list(list_to_play_with, list_size);
list_to_play_with.sort();
cout << "Sorted list is: " << endl;
output_list(list_to_play_with);
// Make an array of list<Double_list_struct>::iterator of size floor(sqrt(N))
int iter_array_size = floor(sqrt(list_size));
list<Double_list_struct>::iterator* iter_array;
iter_array = new list<Double_list_struct>::iterator[iter_array_size];
// Initialize the iterators in iter_array to point to roughly evenly spaced locations in the list
initialize_iter_array(iter_array, iter_array_size, list_to_play_with);
for (int i = 0; i < iter_array_size; i++)
{
cout << "In main routine, iter_array[" << i << "]:" << (*(iter_array[i])).toString() << endl;
}
cout << "Reset it, and redo the output loop??" << endl;
iter_array[iter_array_size-1] = list_to_play_with.end();
iter_array[iter_array_size-1]--;
for (int i = 0; i < iter_array_size; i++)
{
cout << "In main routine, iter_array[" << i << "]:" << (*(iter_array[i])).toString() << endl;
}
}
/************************************************Subroutine code**************************************/
// Output all elements of a list to cout.
template <typename Tplt>
void output_list(list<Tplt> to_out)
{
...not important here
}
template <typename Tplt>
void initialize_list(list<Tplt> &alist, int size)
{
...not important here
}
template <typename Tplt>
void initialize_iter_array(typename list<Tplt>::iterator* itar, int size, list<Tplt> alist)
{
itar[0] = alist.begin();
itar[size-1] = alist.end();
itar[size-1]--; // Recall that .end() makes an iterator point *past* the end...
// Find out how big the list is
int listsize = 0;
for (typename list<Tplt>::iterator it = itar[0]; it != itar[size-1]; it++)
{
listsize = listsize + 1;
}
int spacing = floor(listsize/(size-1));
cout << "In initialize_iter_array(): created itar[0]: " << (*itar[0]).toString() << endl;
for (int i = 1; i < size-1 ; i++)
{
itar[i] = itar[i-1];
for (int j = 0; j < spacing; j++)
{
itar[i]++;
}
cout << "In initialize_iter_array(): created itar[" << i << "]: " << (*itar[i]).toString() << endl;
}
cout << "In initialize_iter_array(): created itar[" << size-1 << "]: " << (*itar[size-1]).toString() << endl;
}
This generates output
Sorted list is:
struct[15] = 0.135837
struct[1] = 0.200995
struct[12] = 0.217693
...SNIP...
struct[8] = 0.863816
struct[14] = 0.887851
struct[2] = 0.893622
struct[10] = 0.925875
In initialize_iter_array(): created itar[0]: struct[15] = 0.135837
In initialize_iter_array(): created itar[1]: struct[5] = 0.314127
In initialize_iter_array(): created itar[2]: struct[11] = 0.704419
In initialize_iter_array(): created itar[3]: struct[10] = 0.925875
In main routine, iter_array[0]:struct[15] = 0.135837
In main routine, iter_array[1]:struct[5] = 0.314127
In main routine, iter_array[2]:struct[11] = 0.704419
In main routine, iter_array[3]:struct[-1] = 6.21551e-71
Reset it, and redo the output loop??
In main routine, iter_array[0]:struct[15] = 0.135837
In main routine, iter_array[1]:struct[5] = 0.314127
In main routine, iter_array[2]:struct[11] = 0.704419
In main routine, iter_array[3]:struct[10] = 0.925875
So, you see, iter_array[3] is correct inside the initialization subroutine, but has "moved" after the subroutine exits. I then reset it from outside the subroutine, but obviously I'd like to not have to do that...
My best guess is that there is something subtle going on here with how the assignment operator works for iterators. But I'm very puzzled.
initialize_iter_array takes the list by value, which means it's putting iterators that point into the parameter copy of the list, not the original list. You probably meant to pass the list by const& instead.