Optimize my code? C++ Compare arrays & calculate prize - c++

I'm doing this slot machine game where a 3x3 2D-array is being generated with random letters.
I have successfully made the game work as I want but I wonder if you have any tips on how I can optimize or improve my code.
What I've gotten my code to do:
Generate an 2D-array (3x3) and randomly assign chars out of 3
letters.
An "if" that will compare and see what elements in the array belong
to each other (same char next to eachother for getting
columns/rows/diagonals).
An "if else" that will take total amount of columns/rows/diagonals
and make a prize out of it, depending on total amounts of row in the
slot machine and the bet.
So I'm now wondering if you have any suggestions on how I can improve the "if" code where the program checks if there are any rows/columns/diagonals? The game works as it should but I just wonder if there's any way of optimizing it - Perhaps with a "for-loop"?
I also wonder if you have any tips on the "prize" code where the code calculates total amout of rows/columns/diagonals and multiplies that with the bet.
I mean, there must be a way to optimize this. If I was to do a 100x100 array, the code where the elements are compared would be awfully long :)
I'm new to C++ (this is a course) so I'm looking forward to optimize this.
PS! I'm not asking for a solution but rather suggestions/tips of methods I can use to optimize it.
This is a homework so no solutions please, only suggestions/tips!
My code for the array comparison and prize calculation:

To optimize, running a profiler would give you a lot of information. If you're talking about general guidelines to optimizing your application, here are some:
1 - use threads to process in parallel
2 - reduce cache miss by keeping the data properly aligned depending on the processing done on it. For instance, if you need to use the speed to process the position, keeping them both near each other in memory will reduce cache-misses.
ie:
struct Particle
{
float position;
float speed;
};
Particle particles[NUM_PARTICLES];
vs
float positions[NUM_PARTICLES];
float speeds[NUM_PARTICLES];
3- Don't process what you don't need to process or user can't see. For instance, some stuff may not affect the current states - no need to process it (in graphics, we use scene management like octtrees but the same applies to all - if you don't need it, don't process it).
4- Reduce the amount of floating point operations.
See this post as well - it provices with some good C++ references for optimizations: C++ Optimization Techniques.

About optimizing:
Don't optimize prematurely - it won't help anything. I'm too lazy to write about that, but search internet, read "Code Complete" and "C++ Coding Standards: 101 Rules, Guidelines, and Best Practices" books.
Don't waste - if optimization won't take more time and is at same readability level, than you can use it.
Optimize AFTER a speed problem arise.
About your problem:
You are absolutely right that there should be better ways to write a code. What you wrote is what workers do, but you need to be smart programmer to make it more easy.
But what you need is more knowledge about language.
Yes, there is a looping possibility for C++. For example following code checks whether a line contains same values:
const int rowCount = 3; // Number of rows
const int colCount = 3; // Number of columns
// Variable for counting same rows
int sameRowsCount = 0;
// Following line is loop: first it sets variable row to 0
// and for each pass it increments it until rowCount is reached
for(int row = 0; row < rowCount; ++row)
{
// This variable stores whether the row contains same values.
// At beginning we assume that it does.
bool isSame = true;
// Now we will check each column in current row. Note that
// we begin with 1 and not 0 - at 0 position is value which
// we check against all others.
for(int col = 1; (col < colCount) && isSame; ++col)
{
if(matrix[0] != matrix[col])
{
// We found different values
isSame = false;
}
}
// If row contains same values, isSame remained true and
// we increment same-rows counter.
if(isSame)
{
++sameRowsCount;
}
}
cout << "Number of same rows: " << sameRowsCount << "." << endl;

Depends on the array size(s) as you mentioned. With small arrays the if statements may be more efficient than using a loop (or two nested) to iterate over all the elements (this is also called 'loop unrolling' and is considered a performance improvement).
To 'optimize' (I'd better say generalize) your code for any array sizes you should use for loops of course to iterate over the x/y indices.

Completed code:
//Check all horiztonal and vertical locations
for(int i = 0; i <= 2; i++)
{
if(matris[i][0] == matris[i][1] && matris[i][1] == matris[i][2])
rows++;
if(matris[0][i] == matris[1][i] && matris[1][i] == matris[2][i])
rows++;
}
//Now check diagonals
if(matris[0][0] == matris[1][1] && matris[1][1] == matris[2][2])
if(matris[0][2] == matris[1][1] && matris[1][1] == matris[2][0])
//Calculate prize
prize = g_satsning*(1 << rows);

In terms of speed, what you have is not going to be inefficient. If you are looking to generalize the code and make it scalable (e.g. if you wanted to add 2 more rows/columns), there are several things you could do (e.g. looping and a more mathematical form of prize calculation).
The looping has already been discussed, but the prize calculation could be simplified a bit using something like the following:
if (rows > 0 && rows < SOMEMAXIMUMVALUE)
{
prize = g_satsning * (1 << rows);
}
else
{
prize = 0;
}
Since your multiplier is an exponent of 2, the math is fairly straight forward. SOMEMAXIMUMVALUE should be declared to be the maximum number of matching rows you expect. For a 3x3 setup, there would be 8 potential matches (3 rows, 3 columns, 2 diagonals), so SOMEMAXIMUMVALUE should be set to 8.

Related

most efficient way to have a if statement in C++

I am trying to do some Monte Carlo simulation, and as it is with this kind of simulation, it requires a lot of iterations, even for the smallest system. Now I want to do some tweaks with my previous code but it increases the wall time or running time, by 10 fold, which makes a week of calculations to more than two months. I wonder whether I am doing the most efficient way to do the simulation.
Before that, I was using a set of fixed intervals to get the properties of the simulations, but now I want to record a set of random intervals to get the system information as it is the most logical thing to do. However I don't know how to do it.
The code that I was using was basically something like that:
for(long long int it=0; it<numIterations; ++it)
{
if((numIterations>=10) && (it%1000==0))
{
exportedStates = system.GetStates();
Export2D(exportedStates, outputStatesFile1000, it);
}
}
As you see, before the tweaks made it was going through the simulation and only record the data, every 1000th iterations.
Now I want to do something like this
for(long long int it=0; it<numIterations; ++it)
{
for(int j = 1; j <= n_graph_points; ++j){
for (int i = 0; i < n_data_per_graph_points; ++i){
if (it == initial_position_array[j][i] || it == (initial_position_array[j][i] + delta_time_arr[j])) {
exportedStates = system.GetStates();
Export2D(exportedStates, outputStatesFile, it);
}
}
}
}
In this part, the initial position array is just an array with lots of random numbers. The two for loop inside of each other checks every iteration and if the iterations is equal to that random number, it starts recording. I know this is not the best method as it is checking lots of iterations that are not necessary. But, I don't know how can I improve my code. I am a little helpless at this point, so any comment would be appreciated
This does not answer the implied question
[What is the] most efficient way to have [an] if statement in C++ (all of them should be equivalent), but
Supposing varying intervals between exports were logical, how do I code that adequately?
Keep a sane Monte Carlo control, initialise a nextExport variable to a random value to your liking, and whenever it equals nextExport, export and increase nextExport by the next random interval.
if (it == initial_position_array[j][i] || it == (initial_position_array[j][i] + delta_time_arr[j]))
you can use references for both expressions.(please use meaningful names as per your convinience)
int& i_p_a = initial_position_array[j][i];
int& i_p_a_d = (initial_position_array[j][i] + delta_time_arr[j]);
now you final if statement will be readable and maintainable.
if (it == i_p_a || it == i_p_a_d) {
exportedStates = system.GetStates();
Export2D(exportedStates, outputStatesFile, it);
}

Comparing Values in a Single Vector

I'm working on a GA and seem to be having problems with the tournament selection. I think this is due to the fact that I'm not comparing what I want to compare (in terms of fitness values)
srand(static_cast <unsigned> (time(0)));
population Pop;
vector<population> popvector;
vector<population> survivors;
population *ptrP;
for (int i = 0; i <= 102; i++)
{
ptrP = new population;
ptrP->generatefit;
ptrP->findfit;
popvector.push_back(*ptrP);
//include finding the persons "overall". WIP
}
cout << "The fit values of the population are listed here: " << endl;
vector<population> ::iterator it; //iterator to print everything in the vector
for (it = popvector.begin(); it != popvector.end(); ++it)
{
it->printinfo();
}
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count(); // generate a seed for the shuffle process of the vector.
cout << "Beggining selection process" << endl;
shuffle(popvector.begin(), popvector.end(), std::default_random_engine(seed));
//Shuffling done to randomize the parents I will be taking.
// I also want want to pick consecutive parents
for (int i = 0; i <= 102; i = i + 3)
{
if (popvector[i] >= popvector[i++]);
}
}
Now what I think my problem is, is that when im trying to compare the Overall values (Not found yet, working on how to properly model them to give me accurate Overall fitness values) I'm not comparing what I should be.
I'm thinking that once I find the persons "Overall" I should store it in a Float vector and proceed from there, but I'm unsure if this is the right way to proceed if I wish to create a new "parent" pool, since (I think) the "parent pool" has to be part of my population class.
Any feedback is appreciated.
srand(static_cast <unsigned> (time(0)));
This is useless: you're calling std::shuffle in a form not based on std::rand:
shuffle(popvector.begin(), popvector.end(), std::default_random_engine(seed));
If somewhere else in the program you need to generate random numbers, do it via functions / distributions / engines in random pseudo-random number generation library (do not use std::rand).
Also consider that, for debugging purpose, you should have a way to initialize the random engine with a fixed seed (debug needs repeatable results).
for (int i = 0; i <= 102; i++)
Do not use magic numbers.
Why 102? If it's the population size, store it in a constant / variable (populationSize?), document the variable use and "enjoy" the fact that when you need to change the value you haven't to remember the locations where it's used (just in this simple snippet there are two distinct use points).
Also consider that the population size is one of those parameters you need to change quite often in GA.
ptrP = new population;
ptrP->generatefit;
ptrP->findfit;
popvector.push_back(*ptrP);
Absolutely consider Sam Varshavchik's and paddy's remarks.
for (int i = 0; i <= 102; i = i + 3)
{
if (popvector[i] >= popvector[i++]);
// ...
Generally it's not a good practice to change the index variable inside the body of a for loop (in some languages, not C / C++, the loop variable is immutable within the scope of the loop body).
Here you also have an undefined behaviour:
popvector[i] >= popvector[i++]
is equivalent to
operator>=(popvector[i], popvector[i++])
The order that function parameters are evaluated is unspecified. So you may have:
auto a = popvector[i];
auto b = popvector[i++];
operator>=(a, b); // i.e. popvector[i] >= popvector[i]
or
auto b = popvector[i++];
auto a = popvector[i];
operator>=(a, b); // i.e. popvector[i + 1] >= popvector[i]
Both cases are wrong.
In the first case you're comparing the same elements and the expression is always true.
In the second case the comparison probably is the opposite of what you were thinking.
Take a look at:
Undefined behavior and sequence points
What are all the common undefined behaviours that a C++ programmer should know about?
and always compile source code with -Wall -Wextra (or their equivalent).
I'm not sure to correctly understand the role of the class population. It may be that the name is misleading.
Other questions / answers you could find interesting:
C++: "std::endl" vs "\n"
http://herbsutter.com/2013/05/13/gotw-2-solution-temporary-objects/ (the section about premature pessimization)

Writing two versions of a function, one for "clarity" and one for "speed"

My professor assigned homework to write a function that takes in an array of integers and sorts all zeros to the end of the array while maintaining the current order of non-zero ints. The constraints are:
Cannot use the STL or other templated containers.
Must have two solutions: one that emphasizes speed and another that emphasizes clarity.
I wrote up this function attempting for speed:
#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
void sortArray(int array[], int size)
{
int i = 0;
int j = 1;
int n = 0;
for (i = j; i < size;)
{
if (array[i] == 0)
{
n++;
i++;
}
else if (array[i] != 0 && j != i)
{
array[j++] = array[i++];
}
else
{
i++;
n++;
}
}
while (j < size)
{
array[j++] = 0;
}
}
int main()
{
//Example 1
int array[]{20, 0, 0, 3, 14, 0, 5, 11, 0, 0};
int size = sizeof(array) / sizeof(array[0]);
sortArray(array, size);
cout << "Result :\n";
for (int i = 0; i < size; i++)
{
cout << array[i] << " ";
}
cout << endl << "Press any key to exit...";
cin.get();
return 0;
}
It outputs correctly, but;
I don't know what the speed of it actually is, can anyone help me figure out how to calculate that?
I have no idea how to go about writing a function for "clarity"; any ideas?
I my experience, unless you have very complicated algorithm, speed and clarity come together:
void sortArray(int array[], int size)
{
int item;
int dst = 0;
int src = 0;
// collect all non-zero elements
while (src < size) {
if (item = array[src++]) {
array[dst++] = item;
}
}
// fill the rest with zeroes
while (dst < size) {
array[dst++] = 0;
}
}
Speed comes from a good algorithm. Clarity comes from formatting, naming variables and commenting.
Speed as in complexity?
Since you are, and need, to look at all the elements in the array — and as such have a single loop going through the indexes in the range [0, N)—where N denotes the size of the input—your solution is O(N).
Further reading:
Plain English explanation of big O
Determining big O Notation
Regarding clearity
In my honest opinion there shouldn't need to be two alternatives when implementing such functionality as you are presenting. If you rename your variables to more suitable (descriptive) names your current solution should be clear enough to count as both performant and clear.
Your current approach can be written in plain english in a very clear fashion:
pseudo-explanation
set write_index to 0
set number_of_zeroes to 0
For each element in array
If element is 0
increase number_of_zeros by one
otherwise
write element value to position denoted by write_index
increase write_index by one
write number_of_zeroes 0s at the end of array
Having stated the explanation above we can quickly see that sortArray is not a descriptive name for your function, a more suitable name would probably be partition_zeroes or similar.
Adding comments could improve readability, but you current focus should lie in renaming your variables to better express the intent of the code.
(I feel your question is almost off-topic; I am answering it from a Linux perspective; I recommend using Linux to learn C++ programming; you'll adapt my advices to your operating system if you are using something else....)
speed
Regarding speed, you should have two complementary approaches.
The first (somehow "theoretical") is to analyze (i.e. think on) your algorithm and give (with some proof) its asymptotic time complexity.
The second approach (only "practical", and often pragmatical) is to benchmark and profile your program. Don't forget to compile with optimizations enabled (e.g. using g++ -Wall -O2 with GCC). Have a benchmark which runs for more than half of a second (so processes a large amount of data, e.g. several million numbers) and repeat it several times (e.g. using time(1) command on Linux). You could also measure some time inside your program using e.g. <chrono> in C++11, or just clock(3) (if you read a large array from some file, or build a large array of pseudo-random numbers with <random> or with random(3) you certainly want to measure separately the time to read or fill the array with the time to move zeros out of it). See also time(7).
(You need to process a large amount of data - more than a million items, perhaps many millions of them - because computer are very fast; a typical "elementary" operation -a machine instruction- takes less than a nanosecond, and you have lot of uncertainty on a single run, see this)
clarity
Regarding clarity, it is a bit subjective, but you might try to make your code readable and concise. Adding a few good comments could also help.
Be careful about naming: sorting is not exactly what your program is doing (it is more moving zeros than sorting the array)...
I think this is the best - Of course you may wish to use doxygen or some other
// Shift the non-zeros to the front and put zero in the rest of the array
void moveNonZerosTofront(int *list, unsigned int length)
{
unsigned int from = 0, to = 0;
// This will move the non-zeros
for (; from < length; ++from) {
if (list[from] != 0) {
list[to] = list[from];
to++;
}
}
// So the rest of the array needs to be assigned zero (as we found those on the way)
for (; to < length; +=to) {
list[to] = 0;
}
}

Possible segmentation fault: Am I using the "this->" operator correctly?

I am doing a homework problem that I have a question about. If you don't feel comfortable assisting with a homework problem, I should say that my instructor has encouraged us to ask for help on this site when we are completely stumped. Also, I have completed the basic portion of the assignment on my own, and am now doing an optional challenge problem. Anyway, on to the problem!
Being new to OOP and C++ in general, I am having trouble understanding the "this->" operator. We haven't covered it in class, but I have seen it elsewhere and I am sort-of guessing how it is meant to be used.
For the assignment, I have to create a console based Tic-Tac-Toe game. Only the challenge portion of the assignment wants us to create an AI opponent, and we don't get any extra credit for doing the challenge, I just want to know how to do it. I am studying things like minimax and game trees, but for now I just wanted to create a "pick a random, open spot" function.
I have a class called TicTacToe which is basically the entire program. I will post it below with the parts that are relevant to the question, but part that is giving me an error is this subroutine:
void TicTacToe::makeAutoMove(){
srand(time(NULL));
int row = rand() % 3 + 1;
int col = rand() % 3 + 1;
if(this->isValidMove(row, col)){
this->makeMove(row, col);
}else{
this->makeAutoMove();
}
}
The only thing that this function is meant to do is make a move on the board, assuming that it is open. The board is set up like:
char board[4][4];
and when I print it, it looks like:
1 2 3
1 - - -
2 - - -
3 - - -
The problem, is that on occasion a move is made by the computer that gives me an error that is difficult to track down because of the random nature of the function. I believe it is a segfault error, but I can't tell because I can't replicate it in my debugger.
I think that the "this->" operator functions as a pointer, and if a pointer is NULL and it is accessed it could give me this problem. Is this correct? Is there a way to fix this?
I understand that this may be a very low-level question to many of the members of the community, but I would appreciate your help as long as it doesn't come with snide remarks about how trivial this is, or how stupid I must be. I'm LEARNING, which means that I am going to have some silly questions sometimes.
Here is more of my .cpp file if it helps:
TicTacToe::TicTacToe()
{
for(int row = 0; row < kNumRows; row++){
for(int col = 0; col < kNumCols; col++){
if(col == 0 && row == 0){
board[row][col] = ' ';
}else if(col == 0){
board[row][col] = static_cast<char>('0' + row);
}else if(row == 0){
board[row][col] = static_cast<char>('0' + col);
}else{
board[row][col] = '-';
}
}
}
currentPlayer = 'X';
}
char TicTacToe::getCurrentPlayer(){
return currentPlayer;
}
char TicTacToe::getWinner(){
//Check for diagonals (Only the middle square can do this)
char middle = board[2][2];
if(board[1][1] == middle && board[3][3] == middle && middle != '-'){
return middle;
}else if(middle == board[3][1] && middle == board[1][3] && middle != '-'){
return middle;
}
//Check for horizontal wins
for(int row = 1; row < kNumRows; row++){
if(board[row][1] == board[row][2] && board[row][2] == board[row][3] && board[row][1] != '-'){
return board[row][1];
}
}
//Check for vertical wins
for(int col = 1; col < kNumCols; col++){
if(board[1][col] == board[2][col] && board[2][col] == board[3][col] && board[1][col] != '-'){
return board[1][col];
}
}
//Otherwise, in the case of a tie game, return a dash.
return '-';
}
void TicTacToe::makeMove(int row, int col){
board[row][col] = currentPlayer;
if(currentPlayer == 'X'){
currentPlayer = 'O';
}else if(currentPlayer == 'O'){
currentPlayer = 'X';
}
}
//TODO: Make sure this works after you make the make-move function
bool TicTacToe::isDone(){
bool fullBoard = true;
//First check to see if the board is full
for(int col = 1; col < kNumCols; col++){
for(int row = 1; row < kNumRows; row++){
if(board[row][col] == '-'){
fullBoard = false;
}
}
}
//If the board is full, the game is done. Otherwise check for consecutives.
if(fullBoard){
return true;
}else{
//Check for diagonals (Only the middle square can do this)
char middle = board[2][2];
if(board[1][1] == middle && board[3][3] == middle && middle != '-'){
return true;
}else if(middle == board[3][1] && middle == board[1][3] && middle != '-'){
return true;
}
//Check for horizontal wins
for(int row = 1; row < kNumRows; row++){
if(board[row][1] == board[row][2] && board[row][2] == board[row][3] && board[row][1] != '-'){
return true;
}
}
//Check for vertical wins
for(int col = 1; col < kNumCols; col++){
if(board[1][col] == board[2][col] && board[2][col] == board[3][col] && board[1][col] != '-'){
return true;
}
}
}
//If all other tests fail, then the game is not done
return false;
}
bool TicTacToe::isValidMove(int row, int col){
if(board[row][col] == '-' && row <= 3 && col <= 3){
return true;
}else{
//cout << "That is an invalid move" << endl;
return false;
}
}
void TicTacToe::print(){
for(int row = 0; row < kNumRows; row++){
for(int col = 0; col < kNumCols; col++){
cout << setw(3) << board[row][col];
}
cout << endl;
}
}
A general preface: you almost never need to use this explicitly. In a member function, in order to refer to member variables or member methods, you simply name the variable or method. As with:
class Foo
{
int mN;
public:
int getIt()
{
return mN; // this->mN legal but not needed
}
};
I think that the "this->" operator functions as a pointer, and if a
pointer is NULL and it is accessed it could give me this problem. Is
this correct? Is there a way to fix this?
this is a pointer, yes. (Actually, it's a keyword.) If you call a non-static member function of a class, this points to the object. For instance, if we were to call getIt() above:
int main()
{
Foo f;
int a = f.getIt();
}
then this would point to f from main().
Static member functions do not have a this pointer. this cannot be NULL, and you cannot change the value of this.
There are several cases in C++ where using this is one way to solve a problem, and other cases where this must be used. See this post for a list of these situations.
I could reproduce the bug on coliru's g++4.8.1 when not compiling with optimizations. As I said in a comment, the problem is the srand combined with time and the recursion:
The return value of time is often the Unix time, in seconds. That is, if you call time within the same second, you'll get the same return value. When using this return value to seed srand (via srand(time(NULL))), you'll therefore set the same seed within this second.
void TicTacToe::makeAutoMove(){
srand(time(NULL));
int row = rand() % 3 + 1;
int col = rand() % 3 + 1;
if(this->isValidMove(row, col)){
this->makeMove(row, col);
}else{
this->makeAutoMove();
}
}
If you don't compile with optimizations, or the compiler otherwise needs to use stack space to do an iteration of makeAutoMove, each call will occupy a bit of the stack. Therefore, when called often enough, this will produce a Stack Overflow (luckily, you went to the right site).
As the seed doesn't change within the same second, the calls to rand will also produce the same values within that second - for each iteration, the first rand will always produce some value X and the second always some value Y within that second.
If X and Y lead to an invalid move, you'll get infinite recursion until the seeding changes. If your computer is fast enough, it might call makeAutoMove often enough to occupy enough stack space within that second to cause a Stack Overflow.
Note that it's not required to seed the Pseudo-Random Number Generator used by rand more than once. Typically, you do only seed once, to initialize the PRNG. Subsequent calls to rand then produce pseudo-random numbers.
From cppreference:
Each time rand() is seeded with srand(), it must produce the same sequence of values.
cppreference: rand, srand
Here is the first pass:
Arrays start counting from zero. So you do not need the +1 in lines like rand() % 3 + 1;
Indeed this is a point to the current object. Usually you do not need to use it. i.e. this->makeMove(row, col); and makeMove(row, col); work the same
char board[4][4];1 should bechar board[3][3];` as you want a 3x3 board. See 1) above
board[row][col] = static_cast<char>('0' + row); - You do not need the static cast '0' + row will suffice
You need to take account of (1) in the rest of your code
If you get segmentation problems it is best to use the debugger. A very skill to learn
Anyway - Good luck with your studies. It is refreshing to get a new poster on this web site that is keen to learn
Just a side note about recursion, efficiency, robust coding and how being paranoid can help.
Here is a "cleaned up" version of your problematic function.
See other answers for explanations about what went wrong with the original.
void TicTacToe::makeAutoMove() {
// pick a random position
int row = rand() % 3;
int col = rand() % 3;
// if it corresponds to a valid move
if (isValidMove(row, col)){
// play it
makeMove(row, col);
}else{
// try again
makeAutoMove(); // <-- makeAutoMove is calling itself
}
}
Recursion
In plain English you could describe what the code does as:
pick a random (row, col) couple.
if this couple represents a valid move position, play that move
else try again
Calling makeAutoMove() is indeed a very logical way of trying again, but a not so efficient one programming-wise.
Each new call will cause some memory allocation on the stack:
4 bytes for each local variable (8 bytes in total)
4 bytes for the return address
So the stack consumption will look like:
makeAutoMove <-- 12 bytes
makeAutoMove <-- 24
makeAutoMove <-- 36
makeAutoMove <-- 48
<-- etc.
Imagine for a second that you inadvertently call this function in a situation where it cannot succeed (when a game has ended and no more valid moves are available).
The function will then call itself endlessly. It will be only a matter of time before stack memory gets exhausted and the program crashes. And, given the computing power of your average PC, the crash will occur in the blink of an eye.
This extreme case illustrates the (hidden) cost of using recursive calls. But even if the function eventually succeeds, the cost of each retry is still there.
The things we can learn from there:
recursive calls have a cost
they can lead to crashes when the termination conditions are not met
a lot of them (but not all of them) can easily be replaced by loops, as we will see
As a side note within the side note, as dyp duly noted, modern compilers are so smart they can, for various reasons, detect some patterns within the code that allow them to eliminate such kind of recursive calls.
Nevertheless, you never know if your particular compiler will be smart enough to remove banana peels from under your sloppy feets, so better avoid slopiness altogether, if you ask me.
Avoiding recursion
To get rid of that naughty recursion, we could implement the try again like so:
void TicTacToe::makeAutoMove() {
try_again:
int row = rand() % 3;
int col = rand() % 3;
if (isValidMove(row, col)){
makeMove(row, col);
}else{
goto try_again; // <-- try again by jumping to function start
}
}
After all, we don't really need to call our function again. Jumping back to the start of it will be enough. That's what the goto does.
Good news is, we got rid of the recursion without changing much of the code.
Not so good news is, we used an ugly construct to do so.
Preserving regular program flow
We don't want to keep that ungainly goto since it breaks the usual control flow and makes the code very difficult to understand, maintain and debug *.
We can, however, replace it easily with a conditional loop:
void TicTacToe::makeAutoMove() {
// while a valid move has not been found
bool move_found = false;
while (! move_found) {
// pick a random position
int row = rand() % 3;
int col = rand() % 3;
// if it corresponds to a valid move
if (isValidMove(row, col)){
// play it
makeMove(row, col);
move_found = true; // <-- stop trying
}
}
}
The good: bye bye Mr goto
The bad : hello Mrs move_found
Keeping the code sleek
We swapped the goto for a flag.
It's already better (the program flow is not broken anymore), but we have added some complexity to the code.
We can relatively easily get rid of the flag:
while (true) { // no way out ?!?
// pick a random position
int row = rand() % 3;
int col = rand() % 3;
// if it corresponds to a valid move
if (isValidMove(row, col)){
// play it
makeMove(row, col);
break; // <-- found the door!
}
}
}
The good: bye bye Mrs move_found
The bad : we use a break, that is little more than a tamed goto (something like "goto the end of the loop").
We could end the improvements there, but there is still something annoying with this version: the exit condition of the loop is hidden within the code, which makes it more difficult to understand at first glance.
Using explicit exit conditions
Exit conditions are especially important to figure whether a piece of code will work or not (the reason why our function gets stuck forever is precisely that there are some cases where the exit condition is never met).
So it's always a good idea to make exit conditions stand out as clearly as possible.
Here is a way to make the exit condition more apparent:
void TicTacToe::makeAutoMove() {
// pick a random valid move
int row, col;
do {
row = rand() % 3;
col = rand() % 3;
} while (!isValidMove (row, col)); // <-- if something goes wrong, it's here
// play it
makeMove(row, col);
}
You could probably do it a bit differently. It does not matter as long as we achieve all of these goals:
no recursion
no extraneous variables
meaningful exit condition
sleek code
When you compare the latest refinement with the original version, you can see that it has mutated to something significantly different.
Code robustness
As we have seen, this function can never succeed in case no more legal moves are available (i.e. the game has ended).
This design can work, but it requires the rest of your algorithm to make sure end game conditions are properly checked before this function is called.
This makes your function dependent on external conditions, with nasty consequences if these conditions are not met (a program hangup and/or crash).
This makes this solution a fragile design choice.
Paranoia to the rescue
You might want to keep this fragile design for various reasons. For instance, you might prefer to wine & dine your g/f rather than dedicating your evening to software robustness improvements.
Even if your g/f eventually learns how to cope with geeks, there will be cases when the best solution you can think of will have inherent potential inconsistencies.
This is perfectly OK, as long as these inconsistencies are spotted and guarded against.
A first step toward code robustness is to make sure a potentially dangerous design choice will be detected, if not corrected altogether.
A way of doing so is to enter a paranoid state of mind, imagining that every system call will fail, the caller of any of your function will do its best to make it crash, every user input will come from a rabid Russian hacker, etc.
In our case, we don't need to hire a rabid Russian hacker and there is no system call in sight. Still, we know how an evil programmer could get us in trouble, so we will try to guard against that:
void TicTacToe::makeAutoMove() {
// pick a random valid move
int row, col;
int watchdog = 0; // <-- let's get paranoid
do {
row = rand() % 3;
col = rand() % 3;
assert (watchdog++ < 1000); // <-- inconsistency detection
} while (!isValidMove (row, col));
// play it
makeMove(row, col);
}
assert is a macro that will force a program exit if the condition passed as parameter is not met, with a console message and/or popup window saying something like assertion "watchdog++ < 1000" failed in tictactoe.cpp line 238.
You can see it as a way to bail out of a program if a fatal algorithmic flaw (i.e. the kind of flaw that will require a source code overhaul, so there is little point in keeping this inconsistent version of the program running) has been detected.
By adding the watchdog, we make sure the program will explicitely exit if it detects an abnormal condition, indicating gracefully the location of the potential problem (tictactoe.cpp line 238 in our case).
While refactoring your code to eliminate inconsistencies can be difficult or even impossible, detecting inconsistencies is most often very easy and cheap.
The condition have not to be very precise, the only point is to make sure your code is executing in a "reasonably" consistent context.
In this example, the actual number of trials to get a legit move is not easy to estimate (it's based on cumulative probabilities to hit a cell where a move is forbidden), but we can easily figure that failing to find a legit move after 1000 tries means something went seriously wrong with the algorithm.
Since this code is just there to increase robustness, it does not have to be efficient. It's just a means to go from the "why the hell does my program hang?!?" situation to the "dang, I must have called makeAutoMove after end game" (near) immediate realization.
Once you've tested and proved your program, and if you have really good reasons for that (namely, if your paranoid checks cause serious performance issues) you can take the decision to cleanup that paranoid code, leaving very explicit comments in your source about the way this particular piece of code shall be used.
Actually there are means to keep the paranoid code live without sacrificing efficiency, but that's another story.
What it boils down to is:
get used to notice potential inconsistencies in your code, especially when these inconsistencies can have serious consequences
try to make sure as many pieces of your code as possible can detect inconsistencies
sprinkle your code with paranoid checks to increase your chances of detecting wrong moves early
Code refactoring
In an ideal world, each function should give a consistent result and leave the system in a consistent state. That rarely happens in real life, unless you accept some limitations to your creativity.
However, it could be interesting to see what you could achieve if you designed a tic-tac-toe 2.0 with these guidelines in mind. I'm sure you would find a lot of helpful reviewers here on StackOverflow.
Feel free to ask if you found some points of interest in all these rants, and welcome to the wonderful world of geeks :)
(kuroi dot neko at wanadoo dot fr)
* goto might look harmless enough in such a small example, but you can trust me on this: abusing goto will lead you to a world of pain. Just don't do it unless you have a very, very good reason.

How to keep only the last duplicate when iterating through rows

Following code iterates through many data-rows, calcs some score per row and then sorts the rows according to that score:
unsigned count = 0;
score_pair* scores = new score_pair[num_rows];
while ((row = data.next_row())) {
float score = calc_score(data.next_feature())
scores[count].score = score;
scores[count].doc_id = row->docid;
count++;
}
assert(count <= num_rows);
qsort(scores, count, sizeof(score_pair), score_cmp);
Unfortunately, there are many duplicate rows with the same docid but different score. Now i like to keep the last score for any docid only. The docids are unsigned int, but usually big (=> no lookup-array) - using a HashMap to lookup the last count for a docid would probably be too slow (many millions of rows, should only take seconds not minutes...).
Ok, i modified my code to use a std:map:
map<int, int> docid_lookup;
unsigned count = 0;
score_pair* scores = new score_pair[num_rows];
while ((row = data.next_row())) {
float score = calc_score(data.next_feature())
map<int, int>::iterator iter;
iter = docid_lookup.find(row->docid);
if (iter != docid_lookup.end()) {
scores[iter->second].score = score;
scores[iter->second].doc_id = row->docid;
} else {
scores[count].score = score;
scores[count].doc_id = row->docid;
docid_lookup[row->docid] = count;
count++;
}
}
It works and the performance hit is not as bad as i expected - now it runs a minute instead of 16 seconds, so it's about a factor of 3. Memory usage has also gone up from about 1Gb to 4Gb.
The first thing I'd try would be a map or unordered_map: I'd be surprised if performance is a factor of 60 slower than what you did without any unique-ification. If the performance there isn't acceptable, another option is something like this:
// get the computed data into a vector
std::vector<score_pair>::size_type count = 0;
std::vector<score_pair> scores;
scores.reserve(num_rows);
while ((row = data.next_row())) {
float score = calc_score(data.next_feature())
scores.push_back(score_pair(score, row->docid));
}
assert(scores.size() <= num_rows);
// remove duplicate doc_ids
std::reverse(scores.begin(), scores.end());
std::stable_sort(scores.begin(), scores.end(), docid_cmp);
scores.erase(
std::unique(scores.begin(), scores.end(), docid_eq),
scores.end()
);
// order by score
std::sort(scores.begin(), scores.end(), score_cmp);
Note that the use of reverse and stable_sort is because you want the last score for each doc_id, but std::unique keeps the first. If you wanted the first score you could just use stable_sort, and if you didn't care what score, you could just use sort.
The best way of handling this is probably to pass reverse iterators into std::unique, rather than a separate reverse operation. But I'm not confident I can write that correctly without testing, and errors might be really confusing, so you get the unoptimised code...
Edit: just for comparison with your code, here's how I'd use the map:
std::map<int, float> scoremap;
while ((row = data.next_row())) {
scoremap[row->docid] = calc_score(data.next_feature());
}
std::vector<score_pair> scores(scoremap.begin(), scoremap.end());
std::sort(scores.begin(), scores.end(), score_cmp);
Note that score_pair would need a constructor taking a std::pair<int,float>, which makes it non-POD. If that's not acceptable, use std::transform, with a function to do the conversion.
Finally, if there is much duplication (say, on average 2 or more entries per doc_id), and if calc_score is non-trivial, then I would be looking to see whether it's possible to iterate the rows of data in reverse order. If it is, then it will speed up the map/unordered_map approach, because when you get a hit for the doc_id you don't need to calculate the score for that row, just drop it and move on.
I'd go for a std::map of docids. If you could create an appropriate hashing function, a hash-map would be preferable. But I guess it's too difficult. And no - the std::map ist not too slow. Access is O(log n), which is nearly as good as O(1). O(1) is array access time (and Hashmap btw).
Btw, if std::map is too slow, qsort O(n log n) is too slow as well. And, using a std::map and iterating over it's contents, you can perhaps save your qsort.
Some additions for the comment (by onebyone):
I did not go for the implementation
details, since there wasn't enough
information on that.
qsort may behave bad with sorted data
(depending on the implementation).
Std::map may not. This is a real
advantage, especially if you read the
values from a database that might
output them ordered by key.
There was no word on the memory allocation strategy. Changing to a memory allocator with fast allocation of small objects may improve the performance.
Still - the fastest would be a hash map with an appropriate hash function. Since there's not enough information about the distribution of the keys, presenting one in this answer is not possible.
Short - if you ask general questions, you get general answers. This means - at least for me, looking at the time complexity in the O-Notation. Still you were right, depending on different factors, the std::map may be too slow while qsort is still fast enough - it may also be the other way round in the worst case of qsort, where it has n^2 complexity.
Unless I've misunderstood the question, the solution can be simplified considerably. At least as I understand it, you have a few million docid's (which are of type unsigned int) and for each unique docid, you want to store one 'score' (which is a float). If the same docid occurs more than once in the input, you want to keep the score from the last one. If that's correct, the code can be reduced to this:
std::map<unsigned, float> scores;
while ((row = data.next_row()))
scores[row->docid] = calc_score(data.next_feature());
This will probably be somewhat slower than your original version since it allocates a lot of individual blocks rather than one big block of memory. Given your statement that there are a lot of duplicates in the docid's, I'd expect this to save quite a bit of memory, since it only stores data for each unique docid rather than for every row in the original data.
If you wanted to optimize this, you could almost certainly do so -- since it uses a lot of small blocks, a custom allocator designed for that purpose would probably help quite a bit. One possibility would be to take a look at the small-block allocator in Andrei Alexandrescu's Loki library. He's done more work on the problem since, but the one in Loki is probably sufficient for the task at hand -- it'll almost certainly save a fair amount of memory and run faster as well.
If your C++ implementation has it, and most do, try hash_map instead of std::map (it's sometimes available under std::hash_map).
If the lookups themselves are your computational bottleneck, this could be a significant speedup over std::map's binary tree.
Why not sort by doc id first, calculate scores, then for any subset of duplicates use the max score?
On re-reading the question; I'd suggest a modification to how scores are read in. Keep in mind C++ isn't my native language, so this won't quite be compilable.
unsigned count = 0;
pair<int, score_pair>* scores = new pair<int, score_pair>[num_rows];
while ((row = data.next_row())) {
float score = calc_score(data.next_feature())
scores[count].second.score = score;
scores[count].second.doc_id = row->docid;
scores[count].first = count;
count++;
}
assert(count <= num_rows);
qsort(scores, count, sizeof(score_pair), pair_docid_cmp);
//getting number of unique scores
int scoreCount = 0;
for(int i=1; i<num_rows; i++)
if(scores[i-1].second.docId != scores[i].second.docId) scoreCount++;
score_pair* actualScores=new score_pair[scoreCount];
int at=-1;
int lastId = -1;
for(int i=0; i<num_rows; i++)
{
//if in first entry of new doc id; has the last read time by pair_docid_cmp
if(lastId!=scores[i].second.docId)
actualScores[++at]=scores[i].second;
}
qsort(actualScores, count, sizeof(score_pair), score_cmp);
Where pair_docid_cmp would compare first on docid; grouping same docs together, then second by reverse order read; such that the last item read is the first in the sublist of items with the same docid. Should only be ~5/2x memory usage, and ~double the execution speed.