1

At the moment I am working on an iOS-application in which I need to draw random cards from a carddeck. At the moment my Code looks like this:

- (PlayingCard*) drawRandomCard
{
    PlayingCard * randomCard = nil;

    NSUInteger index = arc4random_uniform(self.cards.count);
    randomCard = self.cards[index]; //self.cards is an NSArray of PlayingCard's
    while (randomCard.isUsed) {
        index = arc4random_uniform(self.cards.count);
        randomCard = self.cards[index];
    }
    randomCard.used = YES;
    return randomCard;
}

This methods gets called a lot (50'000 - 1'000'000 times for one monte-carlo-simulation)!
At the moment the whole thing is way to slow and I need really to optimize it. I have some Ideas:

  • faster random number generator
  • change the whole high-level representation of the deck (Objective-C Class including an NSArray of PlayingCards) and cards (Objective-C Class) to a representation in normal C-Arrays and structs
  • change the whole representation of the deck and cards to the bitwise level and do everything down there

What do you think?
Do you have any other ideas?
Do you know a better suited (faster) random number generator?

Thanks in advance!

Robin des Bois
  • 355
  • 2
  • 6
  • 20

3 Answers3

2

By randomly looking at the full deck in the loop

while (randomCard.isUsed) {

and not only the ones still in play, you will get a lot of retries when you reach the end of the deck. With the last card (number 52) giving > 25 misses on avareage. Treversing a full deck gives a total of more than 600 misses on average.

In adition to this, you also need to reset your deck before you can use it again. I guess you have a methode that resets the deck by looping through all the cards, and doing a used = NO. This gives 52 operations on the deck even if you only need to deal one card.

You can avoid both problems by this simple solution.

Store all your cards in an array. Then dedicate one end to cards not yet dealt, and the other end to cards that have been dealt:

                                                                                             <---------------------- not yet dealt ---------------------->
[ ah 2h 3h 4h 5h 6h 7h 8h 9h 10h jh qh kh ad 2d  ..... jk qk kk ]
                                                               ^
                                                            dealt 
                                                            cards

Randomly pick a card (7h) in the range of not yet dealt cards:

[ ah 2h 3h 4h 5h 6h 7h 8h 9h 10h jh qh kh ad 2d  ..... jk qk kk ]
                    ^^

Switch it with the last not yet dealt, and move the dealt card pointer on place left.

  <-------------------- not yet dealt --------------------->
[ ah 2h 3h 4h 5h 6h kk 8h 9h 10h jh qh kh ad 2d  ..... jk qk 7h ]
                                                            ^
                                                           dealt
                                                           cards

Repeat for as long as needed:

  <------------------ not yet dealt ----------------->
[ ah 2h qk 4h 5h 6h kk 8h 9h 10h jh jk kh ad 2d  ..... qh 3h 7h ]
                                                      ^
                                                    dealt 
                                                    cards

When you need a new deck, just move the dealt cards pointer back to the end, and the deck is ready for a new use.

  <----------------------- not yet dealt ---------------------->
[ ah 2h qk 4h 5h 6h kk 8h 9h 10h jh jk kh ad 2d  ..... qh 3h 7h ]
                                                               ^
                                                             dealt 
                                                             cards

The new order of the deck just adds to the randomness...

Ebbe M. Pedersen
  • 7,250
  • 3
  • 27
  • 47
0

I assume you are calling this method multiple times to draw cards from a deck, setting the used property of the drawn card to YES so that it doesn't get drawn again.

Instead of setting the property, you can just remove the card from the deck and place it in a usedCards array, saving you the potentially never ending while loop.

Another approach would be to shuffle the deck initially, and then draw cards from the beginning or the end of the array.

Sebastian
  • 7,670
  • 5
  • 38
  • 50
  • The reason for this implementation was, that I have to draw some (2 - 23) cards out of the deck, do some computation and then reset the deck and do all this again and again and again (50'000 - 1'000'000 times)... So I don't really know if your suggestion leads to a speed up. Because I think that the overhead of creating an NSArray, coping some elements, copy elements back (reset) is bigger than just setting some bools, and setting the bools back for reset... Can you explain why you think it would help? – Robin des Bois Sep 28 '13 at 11:35
  • It seems that your while loop isn't as much of a bottleneck as I thought (unless the number of drawn cards gets very close to the deck size). – Sebastian Sep 28 '13 at 12:43
0

Keep a static index pointing to the last card drawn (initially set to -1). Every time the method is invoked, advance the index modulo self.cards.count, and if the result is zero shuffle the deck. Then return the card at index. Since a decent shuffle such as Fisher-Yates/Knuth is Theta(self.cards.count) but gets invoked only one time out of self.cards.count, the result is amortized constant time per draw. This should avoid any overhead due to identifying already drawn values and rejecting. You could add a reset method if you want to avoid dealing out the whole deck before reshuffling.

pjs
  • 18,696
  • 4
  • 27
  • 56