Can this if-else statement be made cleaner - c++

I am trying to improve a C++ assignment to make it more efficient. I am a beginner with the language (and programming in general too), so I am only using what I know so far (if, else). I have a function that converts scores into levels, so anything under 30 = 1, 30-49 = 2, 50-79 = 3 and so on...
Here is how I am doing it:
if (score1 <= 30) level1 = 1;
else if (score1 <= 49) level1 = 2;
else level1 = 3;
if (score2 <= 30) level2 = 1;
else if (score2 <= 49) level2 = 2;
else level2 = 3;
//etc...
Is there a better way to do this, as I am aware this will require a new line for every single score I have.

This rather depends on what you mean by efficiency. You could keep the limits for each level in an array
int level_limits[] = {0, 30, 49, 79, [...]};
int getLevel(int score)
{
int level;
for (level = 0; level < N_LEVELS; ++level)
if (level_limits[level] > score)
return level;
return level; // or whatever should happen when you exceed the score of the top level
}
...
level1 = getLevel(score1);
level2 = getLevel(score2);
... or something like that.

Create a function where you pass in the score and it returns the level. Also, if there are going to be a lot of them you should create an array of scores and levels.
for (x=0;x < num_scores;x++)
{
level[x] = get_level(score[x]);
}
something like that.

First of all factor out the code for computing the level into a separate function, say get_level:
level1 = get_level(score1);
level2 = get_level(score2);
You can implement get_level in different ways.
If the number of levels is small you can use the linear search:
const int bounds[] = {30, 49, 79}; // Add more level bounds here.
int get_level(int score)
{
const size_t NUM_BOUNDS = sizeof(bounds) / sizeof(*bounds);
for (size_t i = 0; i < NUM_BOUNDS; ++i)
if (score <= bounds[i])
return i + 1;
return NUM_BOUNDS + 1;
}
Or, if you are an STL fan:
#include <algorithm>
#include <functional>
const int bounds[] = {30, 49, 79}; // Add more level bounds here.
int get_level(int score)
{
return std::find_if(bounds,
bounds + sizeof(bounds) / sizeof(*bounds),
std::bind2nd(std::greater_equal<int>(), score)) - bounds + 1;
}
If you have many levels binary search may be more appropriate:
#include <algorithm>
const int bounds[] = {30, 49, 79}; // Add more level bounds here.
int get_level(int score)
{
return std::lower_bound(bounds,
bounds + sizeof(bounds) / sizeof(*bounds), score) - bounds + 1;
}
Or if you have relatively small number of levels then use if-else chain similar to your original version:
int get_level(int score)
{
if (score <= 30)
return 1;
else if (score <= 49)
return 2;
else if (score <= 79)
return 3;
return 4;
}
Note that putting returns on separate lines can make your program easier to trace in the debugger.

No, In terms of efficiency it's already optimized.
On another stylistic note. I would recommend putting the conditions and statements on separate lines:
if (score1 <= 30) {
level1 = 1;
} else if (score1 <= 49) {
level1 = 2;
} else if (score1 <= 79) {
level1 = 3;
}
and as the other answer suggest, another stylistic plus would be to extract out the common behavior into a function

The absolute fastest way:
Create an array of 80 values, one for each possible score.
Fill in the array with the level for each possible score.
Example:
int score_array[80] = {1,1,1,1,...};
The following code gets you the level for each score:
level2 = score_array[score2];
This will compile down to one machine instruction. Doesn't get much faster.

If the score has a "manageable" range, how about the following?
// Convert score to level. Valid scores are in the range [0-79]
int score2level( int score ) {
static const int level_table[ 80 ] = {
1, 1, 1, ... // 31 "1"s for range 0-30
2, 2, 2, ... // 19 "2"s for range 31-49
3, 3, 3, ... // 30 "3"s for range 50-79
};
assert( (0 <= score) && (score <= 79) );
return level_table[ score ];
}
The motivation is to avoid conditional code (the if, elses) at the cost of a pre-populated table. May be its too much for 3 levels, but may help when the number of levels increase.

int getLevel(int score)
{
if(score<=30)
return 1;
int level=2,limit=49;
while(score > limit)
{
limit+=30;
level++;
}
return level;
}
int score[]={35,25,67,56,78};
int level[5];
for(int i=0;i<5;i++)
level[i]=getLevel(score[i]);
Cheers :)

You can utilize modulo and a hash table in order to achieve some code style elegance. Pseudo code:
int getLevel(int num)
{
hash_table = (30=>1, 49=>2, 79=>3);
foreach threshold (keys hash_table) {
if (num modulo (threshold + 1) == num) {
return hash_table[threshold];
}
}
}

If you have less levels than fingers on your hands, you should use Paul's suggestion. However, if the number of levels are bigger, you could use binary search if your level thresholds are fairly uniform (and strictly increasing). Then you can get close to logarithmic runtime (I mean, you were asking for efficiency).
This is a good compromise between the lookuptable-suggestion and the linear search, but again, it only pays if you have a lot of levels.
Oh, by the way, if there is a pattern in how the thresholds are chosen, you should use that instead of searching for the right level.

Related

Binary Search avoid unreadable entry (hole in list)

I have implemented a binary search function but I have an issue with a list entry that may become unreadable. It's implemented in C++ but ill just use some pseudo code to make it easier. Please to not focus on the unreadable or string implementation, it's just pseudo code. What matter is that there are unreadable entries in the list that have to be navigated around.
int i = 0;
int imin = 0;
int imax = 99;
string search = "test";
while(imin <= imax)
{
i = imin + (imax - imin) / 2;
string text = vector.at(i);
if(text.isUnreadable())
{
continue;
}
if(compare(text, search) = 0)
{
break;
}
else if(compare(text, search) < 0)
{
imin = i + 1;
}
else if(compare(text, search) > 0)
{
imax = i - 1;
}
}
The searching itself is working pretty well, but the problem I have is how to avoid getting an endless loop if the text is unreadable. Anyone has a time tested approach for this? The loop should not just exit when unreadable but rather navigate around the hole.
I had similar task in one of projects - lookup on sequence where some of items are non-comparable.
I am not sure is this the best possible implementation, in my case it looks like this:
int low = first_comparable(0,env);
int high = last_comparable(env.total() - 1,env);
while (low < high)
{
int mid = low + ((high - low) / 2);
int tmid = last_comparable(mid,env);
if( tmid < low )
{
tmid = first_comparable(mid,env);
if( tmid == high )
return high;
if( tmid > high )
return -1;
}
mid = tmid;
...
}
If vector.at(mid) item is non-comparable it does lookup in its neighborhood to find closest comparable.
first/last_comparable() functions return index of first comparable element from given index. Difference is in direction.
inline int first_comparable( int n, E& env)
{
int n_elements = env.total();
for( ; n < n_elements; ++n )
if( env.is_comparable(n) )
return n;
return n;
}
Create a list of pointers to your data items. Do not add "unreadable" ones. Search the resulting list of pointers.
the problem I have is how to avoid getting an endless loop if the text is unreadable.
Seems like that continue should be break instead, so that you break out of the loop. You'd probably want to set a flag or something to indicate the error to whatever code follows the loop.
Another option is to throw an exception.
Really, you should do almost anything other than what you're doing. Currently, when you read one of these 'unreadable' states, you simply continue the loop. But imin and imax still have the same values, so you end up reading the same string from the same place in the vector, and find that it's unreadable again, and so on. You need to decide how you want to respond to one of these 'unreadable' states. I guessed above that you'd want to stop the search, in which case either setting a flag and breaking out of the loop or throwing an exception to accomplish the same thing would be reasonable choices.

Vector changes value unexpected

I am making a little game (just console) and my vectors don't act like I think they have to. One Value of the vector just changes and I don't know why. The Code shown below is part where this bug come from, I have deleted the rest of the code where these 2 vectors show up and the bug still appears and I DONT KNOW WHY!!
This part of the code is responsible for letting the Enemy spread to a random direction.
/**in this part the first value of the 2 vectors are created
(only once, I've tested it)**/
if(moves == 0){
int randomNum1 = (rand() % HEIGHT)+1;
int randomNum2 = (rand() % WIDTH)+1;
_EnemysY.push_back(randomNum1);
_EnemysX.push_back(randomNum2);
}
/**_Enemy vectors have normal values. For instance: _EnemysX[0]
is 23 and _EnemysY[0] is 12**/
/**In this part, the Enemy spreads in a random direction**/
if (moves > 3){
//save Enemys at the border here (those who can move)
std::vector<int> topX;
std::vector<int> topY;
std::vector<int> botX;
std::vector<int> botY;
std::vector<int> rigX;
std::vector<int> rigY;
std::vector<int> lefX;
std::vector<int> lefY;
/**here, I wanna save all Fields of the Enemy where it can spread to:**/
for (Uint it = 0; it < _EnemysY.size(); it++){
/**_EnemysY is still normal, but _EnemysX is like: 86BF163E0**/
if (_map[_EnemysY[it]-1][_EnemysX[it]] == _Grenade || _map[_EnemysY[it]-1][_EnemysX[it]] == _Field){
topY.push_back(_EnemysY[it]);
topX.push_back(_EnemysX[it]);
}
if (_map[_EnemysY[it]+1][_EnemysX[it]] == _Grenade || _map[_EnemysY[it]+1][_EnemysX[it]] == _Field){
botY.push_back(_EnemysY[it]);
botX.push_back(_EnemysX[it]);
}
if (_map[_EnemysY[it]][_EnemysX[it]-1] == _Grenade || _map[_EnemysY[it]][_EnemysX[it]-1] == _Field){
lefX.push_back(_EnemysX[it]);
lefY.push_back(_EnemysY[it]);
}
if (_map[_EnemysY[it]][_EnemysX[it]+1] == _Grenade || _map[_EnemysY[it]][_EnemysX[it]+1] == _Field){
rigX.push_back(_EnemysX[it]);
rigY.push_back(_EnemysY[it]);
}
}
/**and here is a random direction created and the programm
chooses which Field it will spread to: **/
for (;;){
int ranDir = (rand() % 4)+1;
if (ranDir == 1 && !topY.empty()){
int temp = (rand() % topY.size())+1;
_EnemysY.push_back(topY[temp]);
_EnemysX.push_back(topX[temp]);
return true;
}
if (ranDir == 2 && !botY.empty()){
int temp = (rand() % botY.size())+1;
_EnemysY.push_back(botY[temp]);
_EnemysX.push_back(botX[temp]);
return true;
}
if (ranDir == 3 && !lefY.empty()){
int temp = (rand() % lefY.size())+1;
_EnemysY.push_back(lefY[temp]);
_EnemysX.push_back(lefX[temp]);
return true;
}
if (ranDir == 4 && !rigY.empty()){
int temp = (rand() % rigY.size())+1;
_EnemysY.push_back(rigY[temp]);
_EnemysX.push_back(rigX[temp]);
return true;
}
}
}
First off, why not have a struct describing the "enemy", including its position (via two fields X and Y)? It would improve the code a bit and avoid the need for two vectors that are meaningless if separated.
On topic:
int temp = (rand() % rigY.size())+1;
_EnemysY.push_back(rigY[temp]);
rand() % rigY.size() will give you a number in the interval [0, rigY.size() ). You then add 1 to both sides so temp would be in the interval [1, rigY.size() ].
But rigY[rigY.size()] is not a valid element in the vector...
First of all: thank you Andrei and PaulMcKenzie for your time, your answers didn't solve the problem, but they were still usefull. :)
Now to the answer of the problem:
Alright! I'm a complete douchebag! Because what I didn't print in the code above, was my std::cout s for my variables and I accidentally wrote: "std::cout << ... << std:cout << ... std::endl" and for some reason the second "std::cout" caused a bug, where it printed out an ridiculously high number. So my code in general (without the cout s) works fine, but I still changed some things which you said.

Random choices of two values

In my algorithm I have two values that I need to choose at random but each one has to be chosen a predetermined number of times.
So far my solution is to put the choices into a vector the correct number of times and then shuffle it. In C++:
// Example choices (can be any positive int)
int choice1 = 3;
int choice2 = 4;
int number_of_choice1s = 5;
int number_of_choice2s = 1;
std::vector<int> choices;
for(int i = 0; i < number_of_choice1s; ++i) choices.push_back(choice1);
for(int i = 0; i < number_of_choice2s; ++i) choices.push_back(choice2);
std::random_shuffle(choices.begin(), choices.end());
Then I keep an iterator to choices and whenever I need a new one I increase the iterator and grab that value.
This works but it seems like there might be a more efficient way. Since I always know how many of each value I'll use I'm wondering if there is a more algorithmic way to go about doing this, rather than just storing the values.
You are unnecessarily using so much memory. You have two variables:
int number_of_choice1s = 5;
int number_of_choice2s = 1;
Now simply randomize:
int result = rand() % (number_of_choice1s + number_of_choice2s);
if(result < number_of_choice1s) {
--number_of_choice1s;
return choice1;
} else {
--number_of_choice2s;
return choice2;
}
This scales very well two millions of random invocations.
You could write this a bit more simply:
std::vector<int> choices(number_of_choice1s, choice1);
choices.resize(number_of_choice1s + number_of_choice2s, choice2);
std::random_shuffle(choices.begin(), choices.end());
A biased random distribution will keep some kind of order over the resulting set ( the choice that was picked the most have lesser and lesser chance to be picked next ), which give a biased result (specially if the number of time you have to pick the first value is large compared to the second value, you'll endup with something like this {1,1,1,2,1,1,1,1,2}.
Here's the code, which looks a lot like the one written by #Tomasz Nurkiewicz but using a simple even/odd which should give about 50/50 chance to pick either values.
int result = rand();
if ( result & 1 && number_of_choice1s > 0)
{
number_of_choice1s--;
return choice1;
}else if (number_of_choice2s>0)
{
number_of_choice2s--;
return choice2;
}
else
{
return -1;
}

Need to find a logic error in a card shuffling method

I'm trying to write a method that takes an array of integers (0-51, in that order), cuts it into two separate arrays (A and B in the below function by using the cut method, which I know for sure works) and then re-fuses the two arrays together by randomly selecting 0, 1 or 2 cards from the BOTTOM of either A or B and then adding them to the deck.
(ps- by "array" I mean linked list, I just said array because I thought it would be conceptually easier)
This is my code so far, it works, but there's a definite bias when it comes to where the cards land. Can anybody spot my logic error?
[code]
void Deck::shuffle(){
IntList *A = new IntList();
IntList *B = new IntList();
cut(A, B);
IntListNode *aMarker = new IntListNode;
aMarker = A->getSentinel()->next;
//cout<< A->getSentinel()->prev->prev->data <<'\n'<<'\n';
IntListNode *bMarker = new IntListNode;
bMarker = B->getSentinel()->next;
//cout<< B->getSentinel()->prev->data;
deckList.clear();
srand(time(NULL));
int randNum = 0, numCards = 0, totalNumCards = 0;
bool selector = true, aisDone = false, bisDone = false;
while(totalNumCards < 52){
randNum = rand() % 3;
if(randNum == 0){
selector = !selector;
continue;
}
numCards = randNum;
if(!aisDone && !bisDone){
if(selector){
for(int i = 0; i < numCards; i++){
deckList.push_back(aMarker->data);
aMarker = (aMarker->next);
if(aMarker == A->getSentinel()){
aisDone = true;
break;
}
}
selector = false;
}else{
for(int i = 0; i < numCards; i++){
deckList.push_back(bMarker->data);
bMarker = (bMarker->next);
if(bMarker == B->getSentinel()){
bisDone = true;
break;
}
}
selector = true;
}
}
if(aisDone && !bisDone){
for(int i = 0; i < (52 - totalNumCards); i++){
deckList.push_back(bMarker->data);
bMarker = (bMarker->next);
if(bMarker == B->getSentinel()){
bisDone = true;
break;
}
}
//return;
}
if(bisDone && !aisDone){
for(int i = 0; i < (52 - totalNumCards); i++){
deckList.push_back(aMarker->data);
aMarker = (aMarker->next);
if(aMarker == A->getSentinel()){
aisDone = true;
break;
}
}
//return;
}
totalNumCards += numCards;
}
int tempSum = 0;
IntListNode *tempNode = deckList.head();
for(int j = 0; j < 52; j++){
//cout<< (tempNode->data) << '\n';
tempSum += (tempNode->data);
tempNode = (tempNode ->next);
}
if(tempSum != 1326)
system("PAUSE");
return;
}
[/code]
What about just using std::random_shuffle? Yeah, it won't work for linked list, but you can change it to vector :)
If your instructor would have the moral to teach you programming the way it should be done then they'd encourage you to solve the problem like so, with four lines of code:
#include<algorithm>
#include<vector>
// ...
std::vector<int> cards; // fill it in ...
std::random_shuffle(cards.begin(), cards.end());
Using the standard library is the right way of doing things. Writing code on your own when you can solve the problem with the standard library is the wrong way of doing things. Your instructor doesn't teach you right. If they want to get a point across (say, have you practice using pointers) then they should be more attentive in selecting the exercise they give you.
That speech given, here is a solution worse than the above but better than your instructor's:
52 times do the following:
Choose two random none-equal integers in the range [0,52).
Swap the values in the array corresponding to these positions.
For most random number generators, the low bits are the least random ones. So your line
randNum = rand() % 3;
should be modified to get its value more from the high- to middle-order bits from rand.
Your expectations may be off. I notice that you swap the selector if your random value is 0. Coupled with the relative non-randomness of randNum, this may be your problem. Perhaps you need to make things less random to make them appear more random, such as swapping the selector every time through the loop, and always taking 1 or more cards from the selected deck.
Comments:
srand(time(NULL));
This should only be called once during an applications run. This it is usally best to call it in main() as you start.
int randNum = 0, numCards = 0, totalNumCards = 0;
bool selector = true, aisDone = false, bisDone = false;
One identifier per line. Every coding standard written has this rule. It also prevents some subtle errors that can creep in when using pointers. Get used to it.
randNum = rand() % 3;
The bottom bits of rand are the lest random.
rand Num = rand() / (MAX_RAND / 3.0);
Question:
if(!aisDone && !bisDone)
{
This can execute
and set one of the above to isDone
Example:
Exit state aisDone == false bsiDone == false // OK
Exit state aisDone == true bsiDone == false // Will run below
Exit state aisDone == false bsiDone == ture // Will run below
}
if(aisDone && !bisDone)
{
Is this allowed to run if the first block above is run?
}
if(bisDone && !aisDone)
{
Is this allowed to run if the first block above is run?
}
The rest is too complicated and I don't understand.
I can think of simpler techniques to get a good shuffle of a deck of cards:
for(loop = 0 .. 51)
{
rand = rand(51 - loop);
swap(loop, loop+rand);
}
The above simulates picking a card at random from the deck A and putting it on the top of deck B (deck B initially being empty). When the loop completes B is now A (as it was done in place).
Thus each card (from A) has the same probability of being placed at any position in B.

Custom sorting, always force 0 to back of ascending order?

Premise
This problem has a known solution (shown below actually), I'm just wondering if anyone has a more elegant algorithm or any other ideas/suggestions on how to make this more readable, efficient, or robust.
Background
I have a list of sports competitions that I need to sort in an array. Due to the nature of this array's population, 95% of the time the list will be pre sorted, so I use an improved bubble sort algorithm to sort it (since it approaches O(n) with nearly sorted lists).
The bubble sort has a helper function called CompareCompetitions that compares two competitions and returns >0 if comp1 is greater, <0 if comp2 is greater, 0 if the two are equal. The competitions are compared first by a priority field, then by game start time, and then by Home Team Name.
The priority field is the trick to this problem. It is an int that holds a positve value or 0. They are sorted with 1 being first, 2 being second, and so on with the exception that 0 or invalid values are always last.
e.g. the list of priorities
0, 0, 0, 2, 3, 1, 3, 0
would be sorted as
1, 2, 3, 3, 0, 0, 0, 0
The other little quirk, and this is important to the question, is that 95% of the time, priority will be it's default 0, because it is only changed if the user wants to manually change the sort order, which is rarely. So the most frequent case in the compare function is that priorities are equal and 0.
The Code
This is my existing compare algorithm.
int CompareCompetitions(const SWI_COMPETITION &comp1,const SWI_COMPETITION &comp2)
{
if(comp1.nPriority == comp2.nPriority)
{
//Priorities equal
//Compare start time
int ret = comp1.sStartTime24Hrs.CompareNoCase(comp2.sStartTime24Hrs);
if(ret != 0)
{
return ret; //return compare result
}else
{
//Equal so far
//Compare Home team Name
ret = comp1.sHLongName.CompareNoCase(comp2.sHLongName);
return ret;//Home team name is last field to sort by, return that value
}
}
else if(comp1.nPriority > comp2.nPriority)
{
if(comp2.nPriority <= 0)
return -1;
else
return 1;//comp1 has lower priority
}else /*(comp1.nPriority < comp2.nPriority)*/
{
if(comp1.nPriority <= 0)
return 1;
else
return -1;//comp1 one has higher priority
}
}
Question
How can this algorithm be improved?
And more importantly...
Is there a better way to force 0 to the back of the sort order?
I want to emphasize that this code seems to work just fine, but I am wondering if there is a more elegant or efficient algorithm that anyone can suggest. Remember that nPriority will almost always be 0, and the competitions will usually sort by start time or home team name, but priority must always override the other two.
Isn't it just this?
if (a==b) return other_data_compare(a, b);
if (a==0) return 1;
if (b==0) return -1;
return a - b;
You can also reduce some of the code verbosity using the trinary operator like this:
int CompareCompetitions(const SWI_COMPETITION &comp1,const SWI_COMPETITION &comp2)
{
if(comp1.nPriority == comp2.nPriority)
{
//Priorities equal
//Compare start time
int ret = comp1.sStartTime24Hrs.CompareNoCase(comp2.sStartTime24Hrs);
return ret != 0 ? ret : comp1.sHLongName.CompareNoCase(comp2.sHLongName);
}
else if(comp1.nPriority > comp2.nPriority)
return comp2.nPriority <= 0 ? -1 : 1;
else /*(comp1.nPriority < comp2.nPriority)*/
return comp1.nPriority <= 0 ? 1 : -1;
}
See?
This is much shorter and in my opinion easily read.
I know it's not what you asked for but it's also important.
Is it intended that if the case nPriority1 < 0 and nPriority2 < 0 but nPriority1 != nPriority2 the other data aren't compared?
If it isn't, I'd use something like
int nPriority1 = comp1.nPriority <= 0 ? INT_MAX : comp1.nPriority;
int nPriority2 = comp2.nPriority <= 0 ? INT_MAX : comp2.nPriority;
if (nPriority1 == nPriority2) {
// current code
} else {
return nPriority1 - nPriority2;
}
which will consider values less or equal to 0 the same as the maximum possible value.
(Note that optimizing for performance is probably not worthwhile if you consider that there are insensitive comparisons in the most common path.)
If you can, it seems like modifying the priority scheme would be the most elegant, so that you could just sort normally. For example, instead of storing a default priority as 0, store it as 999, and cap user defined priorities at 998. Then you won't have to deal with the special case anymore, and your compare function can have a more straightforward structure, with no nesting of if's:
(pseudocode)
if (priority1 < priority2) return -1;
if (priority1 > priority2) return 1;
if (startTime1 < startTime2) return -1;
if (startTime1 > startTime2) return 1;
if (teamName1 < teamName2) return -1;
if (teamName1 > teamName2) return -1;
return 0; // exact match!
I think the inelegance you feel about your solution comes from duplicate code for the zero priority exception. The Pragmatic Programmer explains that each piece of information in your source should be defined in "one true" place. To the naive programmer reading your function, you want the exception to stand-out, separate from the other logic, in one place, so that it is readily understandable. How about this?
if(comp1.nPriority == comp2.nPriority)
{
// unchanged
}
else
{
int result, lowerPriority;
if(comp1.nPriority > comp2.nPriority)
{
result = 1;
lowerPriority = comp2.nPriority;
}
else
{
result = -1;
lowerPriority = comp1.nPriority;
}
// zero is an exception: always goes last
if(lowerPriority == 0)
result = -result;
return result;
}
I Java-ized it, but the approach will work fine in C++:
int CompareCompetitions(Competition comp1, Competition comp2) {
int n = comparePriorities(comp1.nPriority, comp2.nPriority);
if (n != 0)
return n;
n = comp1.sStartTime24Hrs.compareToIgnoreCase(comp2.sStartTime24Hrs);
if (n != 0)
return n;
n = comp1.sHLongName.compareToIgnoreCase(comp2.sHLongName);
return n;
}
private int comparePriorities(Integer a, Integer b) {
if (a == b)
return 0;
if (a <= 0)
return -1;
if (b <= 0)
return 1;
return a - b;
}
Basically, just extract the special-handling-for-zero behavior into its own function, and iterate along the fields in sort-priority order, returning as soon as you have a nonzero.
As long as the highest priority is not larger than INT_MAX/2, you could do
#include <climits>
const int bound = INT_MAX/2;
int pri1 = (comp1.nPriority + bound) % (bound + 1);
int pri2 = (comp2.nPriority + bound) % (bound + 1);
This will turn priority 0 into bound and shift all other priorities down by 1. The advantage is that you avoid comparisons and make the remainder of the code look more natural.
In response to your comment, here is a complete solution that avoids the translation in the 95% case where priorities are equal. Note, however, that your concern over this is misplaced since this tiny overhead is negligible with respect to the overall complexity of this case, since the equal-priorities case involves at the very least a function call to the time comparison method and at worst an additional call to the name comparator, which is surely at least an order of magnitude slower than whatever you do to compare the priorities. If you are really concerned about efficiency, go ahead and experiment. I predict that the difference between the worst-performing and best-performing suggestions made in this thread won't be more than 2%.
#include <climits>
int CompareCompetitions(const SWI_COMPETITION &comp1,const SWI_COMPETITION &comp2)
{
if(comp1.nPriority == comp2.nPriority)
if(int ret = comp1.sStartTime24Hrs.CompareNoCase(comp2.sStartTime24Hrs))
return ret;
else
return comp1.sHLongName.CompareNoCase(comp2.sHLongName);
const int bound = INT_MAX/2;
int pri1 = (comp1.nPriority + bound) % (bound + 1);
int pri2 = (comp2.nPriority + bound) % (bound + 1);
return pri1 > pri2 ? 1 : -1;
}
Depending on your compiler/hardware, you might be able to squeeze out a few more cycles by replacing the last line with
return (pri1 > pri2) * 2 - 1;
or
return (pri1-pri2 > 0) * 2 - 1;
or (assuming 2's complement)
return ((pri1-pri2) >> (CHAR_BIT*sizeof(int) - 1)) | 1;
Final comment: Do you really want CompareCompetitions to return 1,-1,0 ? If all you need it for is bubble sort, you would be better off with a function returning a bool (true if comp1 is ">=" comp2 and false otherwise). This would simplify (albeit slightly) the code of CompareCompetitions as well as the code of the bubble sorter. On the other hand, it would make CompareCompetitions less general-purpose.