4

I was implementing a recursive function with memoization for speed ups. The point of the program is as follows:

I shuffle a deck of cards (with an equal number of red and black cards) and start dealing them face up. After any card you can say “stop”, at which point I pay you $1 for every red card dealt and you pay me $1 for every black card dealt. What is your optimal strategy, and how much would you pay to play this game?

My recursive function is as follows:

double Game::Value_of_game(double number_of_red_cards, double number_of_black_cards)
{
    double value, key;


    if(number_of_red_cards == 0)
    {
    Card_values.insert(Card_values.begin(), pair<double, double> (Key_hash_table(number_of_red_cards, number_of_black_cards), number_of_black_cards));
    return number_of_black_cards;
    }
    else if(number_of_black_cards == 0)
    {
    Card_values.insert(Card_values.begin(), pair<double, double> (Key_hash_table(number_of_red_cards, number_of_black_cards), 0));
    return 0;
    }

    card_iter = Card_values.find(Key_hash_table(number_of_red_cards, number_of_black_cards));
    if(card_iter != Card_values.end())
    {
        cout << endl << "Debug: [" << number_of_red_cards << ", " << number_of_black_cards << "] and value = " << card_iter->second << endl;
    return card_iter->second; 
    }

    else 
    {
    number_of_total_cards = number_of_red_cards + number_of_black_cards;
        prob_red_card = number_of_red_cards/number_of_total_cards;
        prob_black_card = number_of_black_cards/number_of_total_cards;

    value = max(((prob_red_card*Value_of_game(number_of_red_cards - 1, number_of_black_cards)) + 
             (prob_black_card*Value_of_game(number_of_red_cards, number_of_black_cards - 1))), 
             (number_of_black_cards - number_of_red_cards));
    cout << "Check: value = " << value << endl;

    Card_values.insert(Card_values.begin(), pair<double, double> (Key_hash_table(number_of_red_cards, number_of_black_cards), value));

        card_iter = Card_values.find(Key_hash_table(number_of_red_cards , number_of_black_cards ));
        if(card_iter != Card_values.end());
        return card_iter->second;
     } 
}

double Game::Key_hash_table(double number_of_red_cards, double number_of_black_cards)
{
    double key = number_of_red_cards + (number_of_black_cards*91);
    return key; 
}

The third if statement is the "memoization" part of the code, it stores all the necessary values. The values that are kept in the map can be thought of as a matrix, these values will correspond to a certain #red cards and #black cards. What is really werid is that when I execute the code for 8 cards in total (4 blacks and 4 reds), I get an incorrect answer. But when I execute the code for 10 cards, my answer is wrong, but now my answer for 4 blacks and 4 reds are correct (8 cards)! Same can be said for 12 cards, where I get the wrong answer for 12 cards, but the correct answer for 10 cards, so on and so forth. There is some bug in the code, however, I can't figure it out.

Chen Li
  • 151
  • 1
  • 2
  • 9
  • 1
    Why are you using `double` to store integers? – nneonneo Oct 07 '12 at 02:43
  • Just like that, I don't think it would make any difference? – Chen Li Oct 07 '12 at 02:48
  • No, that wouldn't be the *problem* (otherwise I'd post it as an answer), but it's usually poor form to use a floating-point value for integral data. – nneonneo Oct 07 '12 at 02:54
  • I'll change it, I know it's not proper to do so, but I wanted to find the problem first – Chen Li Oct 07 '12 at 02:58
  • 1
    Where are `prob_red_card` and `prob_black_card` declared? Are they global? – nneonneo Oct 07 '12 at 03:05
  • 1
    You cannot use `double`s as a hash table key because two values of type double can be effectively equal, but not hash the same. I don't think that's your problem, but that is a problem that will render your hash table memoizer much less useful than you would like. And comparing them to '0' to decide if recursion should end IS possibly your problem. Never compare doubles for equality. – Omnifarious Oct 07 '12 at 03:10
  • @nneonneo: I bet you found the issue. However they are declared, it's clearly wrong for this recursive algorithm. They have to be local variables for it to work in any sane way. – Omnifarious Oct 07 '12 at 03:15
  • @Omnifarious: Yes, exactly what I was thinking w.r.t the global variables. Also, note that `double`s can exactly hold integers up to 50 or so bits in size, and not lose precision (or fail to compare exactly equal). Therefore, it is safe to stick 32-bit integer values in a `double` and do strictly integer operations on it (but certainly not something you *should* do). – nneonneo Oct 07 '12 at 03:18
  • So I should change my "double #ofredcards" to "int" and same for the black ones? – Chen Li Oct 07 '12 at 03:27
  • @nneonneo - "`double`s can exactly hold" should be replaced with "usually the implementation of `double` on most systems can exactly hold". :-) – Omnifarious Oct 07 '12 at 06:50
  • @nneonneo: even then its not safe, since division introduces parts ints cant do, and depending on what operations you do, they still might not compare equal. 8.0000000 != 7.999999999 – Mooing Duck Oct 07 '12 at 07:47
  • If you divide two doubles, then yes. Things break. But, at least with IEEE floats, adding, multiplying and subtracting doubles should always retain the integer property. Obviously this should never be depended upon. – nneonneo Oct 07 '12 at 07:49

1 Answers1

3

Nobody actually answered this question with an answer. So I will give it a try, though nneonneo actually put his or her finger on the likely source of your problem.

The first problem that's probably not actually a problem in this case, but sticks out like a sore thumb... you are using double to hold a value that you mostly treat as an integer. In this case, on most systems, this is probably OK. But as a general practice, it is very bad. In particular because you check if a double is exactly equal to 0. It probably will be as, on most systems, with most compilers, a double can hold integers values up to a fairly large size with perfect precision as long as you restrict yourself to adding, subtracting and multiplying by other integers or doubles masquerading as integers to get a new value.

But, that's likely not the source of the error you're seeing, it's just trips every good programmer's alarm bells for smelly code. It should be fixed. The only time you really need them to be doubles is when you're calculating the relative probability of red or black.

And that brings me to the thing that probably is your problem. You have these two statements in your code:

number_of_total_cards = number_of_red_cards + number_of_black_cards;
prob_red_card = number_of_red_cards/number_of_total_cards;
prob_black_card = number_of_black_cards/number_of_total_cards;

which, of course, should read:

number_of_total_cards = number_of_red_cards + number_of_black_cards;
prob_red_card = number_of_red_cards/double(number_of_total_cards);
prob_black_card = number_of_black_cards/double(number_of_total_cards);

because you've been a good programmer and declared those variables as integers.

Presumably prob_red_card and prob_black_card are variables of type double. But they are not declared anywhere in the code you show us. This means that no matter where they are declared, or what their types are, they must be effectively shared by all sub-calls in the recursive call tree for Game::Value_of_game.

The is almost certainly not what you want. It makes it extremely difficult to reason about what values those variables have and what those values represent during any given call in the recursive call tree for your function. They really have to be local variables in order for the algorithm to be tractable to analyze. Luckily, they seem to only be used within the else clause of a particular if statement. So they can be declared when they are initially assigned values. Here is probably what this code should read:

unsigned const int number_of_total_cards = number_of_red_cards + number_of_black_cards;
const double prob_red_card = number_of_red_cards/double(number_of_total_cards);
const double prob_black_card = number_of_black_cards/double(number_of_total_cards);

Note that I also declare them const. It is good practice to declare any variable who's value you don't expect to change during the lifetime of the variable as const. It helps you write code that is more correct by asking the compiler to tell you when you accidentally write code that is incorrect. It also can help the compiler generate better code, though in this case even a trivial analysis of the code reveals that they are not modified during their lifetimes and can be treated as const, so most decent optimizers will essentially put the const in for you for the purposes of code optimization, though that still will not give you the benefit of having the compiler tell you if you accidentally use them in a non-const way.

Community
  • 1
  • 1
Omnifarious
  • 54,333
  • 19
  • 131
  • 194
  • Thank you so much!! You are correct. I don't understand why that I HAD to declare the 3 data types in my else statement even thought I never used them anywhere else? – Chen Li Oct 07 '12 at 12:55
  • @ChenLi: You didn't have to declare them in your else statement. You just had to declare them as local variables for the function. That's because each recursive invocation of the function needs its own copy of those variables. I declared them in the else statement because another really good programming practice is to always declare variables with the smallest possible scope. It's good to have fewer variables you have to worry about at any given point in the program when you're trying to read and understand it. – Omnifarious Oct 07 '12 at 13:32