Transposition Table is causing tests to fail(but runs fine in game) - c++

I have added a transposition table to my TicTacToe minmax algorithm
int AI::findBestMove()
{
hash = tTable->recalculateHash();
int bestMove = minMax().second;
return bestMove;
}
std::pair<int, int> AI::minMax(int reverseDepth, std::pair<int, int> bestScoreMove, player currentPlayer, int alpha, int beta, int lastPlay)
{
Entry e = (*tTable)[hash];
if (e && e.depth == reverseDepth)
return e.scoreMove;
if (reverseDepth == 0)
return { 0, -2 };
else if (field->canDrawOrWin() && lastPlay != -1)
{
if (field->hasWon(lastPlay))
return { evaluateScore(currentPlayer), -1 };
else if (field->isDraw())
return { 0, -1 };
}
bestScoreMove.first = currentPlayer == player::AI ? INT_MIN : INT_MAX;
for (int i = 0; i < field->size(); i++)
{
if ((*field)[i] == player::None && field->isCoordWorthChecking(i))
{
(*field)[i] = currentPlayer;
hash = tTable->calculateHash(hash, i);
std::pair<int, int> scoreMove = minMax(reverseDepth - 1, bestScoreMove, getOpponent(currentPlayer), alpha, beta, i);
if (currentPlayer == player::AI)
{
alpha = std::max(alpha, scoreMove.first);
if (bestScoreMove.first < scoreMove.first)
bestScoreMove = { scoreMove.first, i };
}
else
{
beta = std::min(beta, scoreMove.first);
if (bestScoreMove.first > scoreMove.first)
bestScoreMove = { scoreMove.first, i };
}
hash = tTable->calculateHash(hash, i);
(*field)[i] = player::None;
if (beta <= alpha)
break;
}
}
tTable->placeEntry(hash, bestScoreMove, reverseDepth);
return bestScoreMove;
}
To test it I made an acceptance test that plays every possible board and checks for human wins
TEST(AcceptanceTest, EveryBoard)
{
int winstate = 0;
std::shared_ptr<Field> field = std::make_shared<Field>(4);
AI ai(field);
playEveryBoard(ai, field, winstate);
std::cout <<"Human wins: " << winstate << std::endl;
}
void playEveryBoard(AI& ai, std::shared_ptr<Field> f, int& winstate)
{
int bestMove = 0;
auto it = f->begin();
while (true)
{
it = std::find(it, f->end(), player::None);
if (it == f->end())
break;
*it = player::Human;
if (f->hasWon())
winstate++;
EXPECT_TRUE(!f->hasWon());
bestMove = ai.findBestMove();
if (bestMove == -1)//TIE
{
*it = player::None;
break;
}
(*f)[bestMove] = player::AI;
if (f->hasWon())//AI WIN
{
*it = player::None;
(*f)[bestMove] = player::None;
break;
}
playEveryBoard(ai, f, winstate);
*it = player::None;
(*f)[bestMove] = player::None;
if (it == f->end())
break;
it++;
}
}
The test never returned any loosing states until I added the transposition table, to test when the loosing state appears I made a test that plays every permutation of the loosing field, but it never found a loosing state, what could cause the AI to loose only in the EveryBoard test?
TEST(LoosePossible, AllPermutations)
{
std::vector<int> loosingField = { 2, 3, 7, 11, 12, 13, 15 };
do{
std::shared_ptr<Field> field = std::make_shared<Field>(4);
AI *ai = new AI(field);
for (auto i : loosingField)
{
if ((*field)[i] != player::None || field->hasWon())
break;
(*field)[i] = player::Human;
EXPECT_TRUE(!field->hasWon());
(*field)[ai->findBestMove()] = player::AI;
}
delete ai;
} while (next_permutation(loosingField.begin(), loosingField.end()));
}

I see at least two places these errors could be arising.
One potential problem is in this line:
Entry e = (*tTable)[hash];
if (e && e.depth == reverseDepth)
return e.scoreMove;
In addition to checking if the transposition table stores the result of a search that is the same depth, you also need to check that the stored bounds in the table are compatible with the bounds in the table.
I addressed this as part of an answer to another question:
When you store values in the transposition table, you also need to store the alpha and beta bounds used during the search. When you get a value back at a node mid-search it is either an upper bound on the true value (because value = beta), a lower bound on the true value (because value = alpha) or the actual value of the node (alpha < value < beta). You need to store this in your transposition table. Then, when you want to re-use the value, you have to check that you can use the value given your current alpha and beta bounds. (You can validate this by actually doing the search after finding the value in the transposition table to see if you get the same value from search that you got in the table.)
The way to test this is to modify AI::minMax. Set a flag to true when you have a value returned from the transposition table. Then, each time you return a value, if the transposition table flag is true, compare the value you are about to return to the value that was found in the transposition table. If they are not the same, then something is wrong.
In addition, minimax is typically used with zero-sum games, which means that the sum of scores for the two players should add to 0. I don't know what all the returned values mean in your code, but sometimes you are returning {0, -1} and sometimes {0, -2}. This is problematic, because now you have a non-zero-sum game and much of the theory falls apart.
In particular, the max player may treat {0, -1} and {0, -2} the same, but the min player will not. Thus, if the move ordering changes in any way you may see these in different orders, and thus the value at the root of the tree will not be stable.
As an aside, this is a fundamental issue in multi-player games. Practically speaking it arises when one player is a king-maker. They can't win the game themselves, but they can decide who does.

Related

Linear Search on Vector

I have tests to pass online using my created methods. I have a feeling there is an issue with one of the tests. The final one i cannot pass.
Here is the test-
TEST_CASE ("Linear Search With Self-Organization 3") {
int searchKey = 191;
vector<int> searchArray(500);
for (int i = 0; i < 500; i++) {
searchArray[i] = i + 1;
}
random_shuffle(searchArray.begin(), searchArray.end());
bool result, result2;
result = linearSearchSO(searchArray, searchKey);
int searchKey2 = 243;
result2 = linearSearchSO(searchArray, searchKey2);
REQUIRE (result == true);
REQUIRE (result2 == true);
REQUIRE (verifySearchArray(searchArray) == true);
REQUIRE (searchArray[0] == searchKey2);
REQUIRE (searchArray[1] == searchKey);
REQUIRE (searchArray.size() == 500);
}
The method in question here is linearSearchSO.
bool linearSearchSO(vector<int> & inputArr, int searchKey) {
printArray(inputArr);
for(int i=0; i < inputArr.size(); i++) {
int temp = inputArr[0];
if (inputArr[i] == searchKey) {
inputArr[0] = inputArr[i];
inputArr[i] = temp;
printArray(inputArr);
return true;
}
}
return false;
}
Worth noting that this method has passed all 3 of the other tests required. As you can see in the test, my tutor has called this method twice passing two different values.The idea is that there is a vector of 500 numbers.. In this instance he randomises the numbers. The best way for me to explain what is happening is that if he didn't randomise and the numbers were simply listed 1-500. The method gets called and I begin with the requested number 191, I move this to front of the vector.
Now it reads 191, 2, 3, 4 etc. 190, 1, 192 etc.
So he then calls the method again, and wants 243 to be moved to the front. His test wants the result to be 243, 191, 2, 3, 4. However what my code does is swap 191 to 243's position.
My result now reads 243, 2, 3, 4 etc. 242, 191, 244, 245 etc.
Every other test is simply taking one number and moving it to the front, the test then checks that each number is in the correct position. My question is, is there a way for me to achieve 243, 191, 2, 3.. without messing up every other test I've passed only using this one linearSearch function? or is there a problem with the test, and hes simply made a mistake.
EDIT- The actual question asked for this test.
Question 4
A self-organising search algorithm is one that rearranges items in a collection such that those items that are searched frequently are likely to be found sooner in the search. Modify the learning algorithm for linear search such that every time an item is found in the array, that item is exchanged with the item at the beginning of the array.
If I have understood correctly you need something like the following
#include <iostream>
#include <vector>
bool linearSearchSO( std::vector<int> & inputArr, int searchKey )
{
bool success = false;
auto it = inputArr.begin();
while ( it != inputArr.end() && *it != searchKey ) ++it;
if ( ( success = it != inputArr.end() ) )
{
int value = *it;
inputArr.erase( it );
inputArr.insert( inputArr.begin(), value );
}
return success;
}
int main()
{
std::vector<int> inputArr = { 1, 2, 3, 4, 5 };
for ( const auto &item : inputArr )
{
std::cout << item << ' ';
}
std::cout << '\n';
linearSearchSO( inputArr, 3 );
for ( const auto &item : inputArr )
{
std::cout << item << ' ';
}
std::cout << '\n';
}
The program output is
1 2 3 4 5
3 1 2 4 5
Pay attention to that instead of writing manually a loop in the function you could use the standard algorithm std::find.

Increment the value of a map

need your help and better if you can help me fast. It is very trivial problem but still can't understand what exactly i need to put in one line.
The following code i have
for (busRequest = apointCollection.begin(); busRequest != apointCollection.end(); busRequest++)
{
double Min = DBL_MAX;
int station = 0;
for (int i = 0; i < newStations; i++)
{
distance = sqrt(pow((apointCollection2[i].x - busRequest->x1), 2) + pow((apointCollection2[i].y - busRequest->y1), 2));
if (distance < Min)
{
Min = distance;
station = i;
}
}
if (people.find(station) == people.end())
{
people.insert(pair<int, int>(station, i));
}
else
{
how can i increment "i" if the key of my statation is already in the map.
}
}
Just briefly what i do , i take the first busrequest go to the second loop take the first station and find the minimum distance. After i go over the second loop , i add that station with minimum distance to my map . After i proceed with all my loops and if there is the same station , i need to increment it , so it means that that station is using two times and etc.
I need the help just give me hint or provide the line that i need to add.
I thank you in advance and waiting for your help.
And I think you meant Min Distance instead of i? Check and let me know.
for (busRequest = apointCollection.begin(); busRequest != apointCollection.end(); busRequest++)
{
double Min = DBL_MAX;
int station = 0;
for (int i = 0; i < newStations; i++)
{
distance = sqrt(pow((apointCollection2[i].x - busRequest->x1), 2) + pow((apointCollection2[i].y - busRequest->y1), 2));
if (distance < Min)
{
Min = distance;
station = i;
}
}
if (people.find(station) == people.end())
{
people.insert(pair<int, int>(station, i)); // here???
}
else
{
// This routine will increment the value if the key already exists. If it doesn't exist it will create it for you
YourMap[YourKey]++;
}
}
In C++ you can directly access a map key without inserting it. C++ will automatically create it with default value.
In your case, if a station is not present in people map and you will access people[station] then people[station] will automatically be set to 0 ( default value of int is 0 ).
So you can just do this:
if (people[station] == 0)
{
// Do something
people[station] = station; // NOTE: i is not accessible here! check ur logic
}
else
{
people[station]++;
}
Also: In your code i cannot be accessed inside IF condition to insert into people map.

Checking if reducing iterator points to a valid element

I need to know if I can reduce the iterator and have a valid object. The below errors out because I reduce the iterator by 1 which doesn't exist. How can I know that so I don't get the error?
ticks.push_front(Tick(Vec3(0, 0, 5), 0));
ticks.push_front(Tick(Vec3(0, 0, 8), 100));
ticks.push_front(Tick(Vec3(0, 0, 10), 200));
bool found = false;
list<Tick, allocator<Tick>>::iterator iter;
for (iter = ticks.begin(); iter != ticks.end(); ++iter)
{
Tick t = (*iter);
if (214>= t.timestamp)
{
prior = t;
if (--iter != ticks.end())
{
next = (*--iter);
found = true;
break;
}
}
}
I'm trying to find the entries directly "above" and directly "below" the value 214 in the list. If only 1 exists then I don't care. I need above and below to exist.
After your edits to the question, I think I can write a better answer than what I had before.
First, write a comparison function for Ticks that uses their timestamps:
bool CompareTicks(const Tick& l, const Tick& r)
{
return l.timestamp < r.timestamp;
}
Now use the function with std::upper_bound:
// Get an iterator pointing to the first element in ticks that is > 214
// I'm assuming the second parameter to Tick's ctor is the timestamp
auto itAbove = std::upper_bound(ticks.begin(), ticks.end(), Tick(Vec3(0, 0, 0), 214), CompareTicks);
if(itAbove == ticks.end())
; // there is nothing in ticks > 214. I don't know what you want to do in this case.
This will give you the first element in ticks that is > 214. Next, you can use lower_bound to find the first element that is >= 214:
// get an iterator pointing to the first element in ticks that is >= 214
// I'm assuming the second parameter to Tick's ctor is the timestamp
auto itBelow = std::lower_bound(ticks.begin(), ticks.end(), Tick(Vec3(0, 0, 0), 214), CompareTicks);
You have to do one extra step with itBelow now to get the first element before 214, taking care not to go past the beginning of the list:
if(itBelow == ticks.begin())
; // there is nothing in ticks < 214. I don't know what you want to do in this case.
else
--itBelow;
Now, assuming you didn't hit any of the error cases, itAbove is pointing to the first element > 214, and itBelow is pointing to the last element < 214.
This assumes your Ticks are in order by timestamp, which seems to be the case. Note also that this technique will work even if there are multiple 214s in the list. Finally, you said the list is short so it's not really worth worrying about time complexity, but this technique could get you logarithmic performance if you also replaced the list with a vector, as opposed to linear for iterative approaches.
The answer to your core question is simple. Don't increment if you are at the end. Don't decrement if you are at the start.
Before incrementing, check.
if ( iter == ticks.end() )
Before decrementig, check.
if ( iter == ticks.begin() )
Your particular example
Looking at what you are trying to accomplish, I suspect you meant to use:
if (iter != ticks.begin())
instead of
if (--iter != ticks.end())
Update
It seems you are relying on the contents of your list being sorted by timestamp.
After your comment, I think what you need is:
if (214>= t.timestamp)
{
prior = t;
if (++iter != ticks.end())
{
next = *iter;
if ( 214 <= next.timestep )
{
found = true;
break;
}
}
}
Update 2
I agree with the comment made by #crashmstr. Your logic can be:
if (214 <= t.timestamp)
{
next = t;
if ( iter != ticks.begin())
{
prior = *--(iter);
found = true;
break;
}
}
I think you can do what you want with std::adjacent_find from the standard library <algorithm>. By default std::adjacent_find looks for two consecutive identical elements but you can provide your own function to define the relationship you are interested in.
Here's a simplified example:
#include <algorithm>
#include <iostream>
#include <list>
struct matcher
{
matcher(int value) : target(value) {}
bool operator()(int lo, int hi) const {
return (lo < target) && (target < hi);
}
int target;
};
int main()
{
std::list<int> ticks = { 0, 100, 200, 300 };
auto it = std::adjacent_find(ticks.begin(), ticks.end(), matcher(214));
if (it != ticks.end()) {
std::cout << *it << ' ' << *std::next(it) << '\n';
} else {
std::cout << "not found\n";
}
}
This outputs 200 300, the two "surrounding" values it found.

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.