views:

1633

answers:

10
+1  Q: 

C++ Array Shuffle

I'm fairly new to C++ and don't quite understand function parameters with pointers and references. I have an array of Cards that I want to shuffle using the Fisher-Yates shuffle. The deck is declared as

Card *deck[deckSize];

where deckSize has been declared as 24. The array is then initialized. I then call the shuffle function:

void shuffle (Card * deck[]) {
    int deckSize = 24;
    while (deckSize > 1) {
       long int k = lrand48();
       k = k %24;
       deckSize--;
       Card * temp = deck[deckSize];
       deck[deckSize] = deck[k];
       deck[k] = temp;
    }
}

If I try to print the value of a card after calling the shuffle function I get a seg fault. Any pointers on how to do this properly?

+1  A: 

You declared deck as an array of pointers but you didn't allocate any space for it. If you de-reference it without allocating space you will get a seg-fault.

Trent
he said that its initialized in his question
yx
@yz I'm, not sure someone who doesn't "quite understand function parameters with pointers and references" realizes that allocating space for a pointer is part of "initializing" it.
Trent
I loop through the array and initialize each Card with "deck[i] = new Card()". Is that sufficient for allocating space for it?
Everett
@Everett yes, that is fine. but as n8wrl has pointed out in his answer: you probably don't want an array of pointers to begin with. Possibly a pointer to an array: Card (* deck)[deckSize]
Trent
+6  A: 

My C/C++ is rusty but I think your declaration:

Card *deck[deckSize];

is declaring an array of POINTERS to Cards. Don't you want this?

Card deck[deckSize];

and then declare shuffle:

void shuffle (Card deck[])

keep in mind arrays are 0-indexed. Not sure if you'd ever access the 24th element but that would be a boo-boo.

n8wrl
I don't believe Card deck[deckSize]; is valid C++.
Alan
Unless deckSize is a constant.
Alan
Oops - I assumed deckSize was a constant. ASSuME :)
n8wrl
Fair enuff; removed the -1 :D
Alan
Shuffling pointers is probably what you want to do here. If the card objects are large, or have a complicated copy constructor, doing a copy could be very expensive.
Dolphin
I would say shuffling cards not pointers is probably a better idea. How big can a card be? Probably the same size as a pointer.
Martin York
Martin, it might have a high-res texture for all we know.
Aistina
Having a texture or anything in the card class would be bad design. Don't mix GUI with logic. Card classes should have two members: color and value, and maybe coordinates. Think about re-using your card class in a game with 104 cards: you don't want to duplicate textures.A good way to do it is implementing a card renderer, which takes cards on input. At worse, you could have a pointer to a texture (OpenGL ...)
Jem
and in addition, you can use swap two swap cards if they have an expensive resource with them. swapping can almost always be implemented O(1)
Johannes Schaub - litb
+1  A: 

One immediate nitpick, you should always use the top-half of a random number because most implementations of random numbers have poorer randomness on the bottom half. So if long's are 32 bit you could use: k = (k >> 24) % 24 to get better randomness.

Second, the problem here is you are not setting temp. Your code should have a line: temp = deck[deckSize];.

Hope this helps.

Edit:

Further nitpick, your random number generator is also not big enough to sufficiently shuffle a deck of cards regardless of using the high bit or low bit. It only has 48bit long sequence, but to shuffle a deck you'd need at least a 226bit long sequence (52!, the number of ways to shuffle a deck, is a 226bit long number).

Adam Luter
re: random numbers. This sound like pretty marginal advise at best.
Trent
Sorry, it probably doesn't matter if you are playing against a human, but if you are doing simulations, your results will not be reliable at all. He did not state which was the case.
Adam Luter
The advice about the range of the random number generator are valid. As stated, it doesn't matter if you're just playing cards, but if the shuffle is being used to determine who gets into a better school and who doesn't (I speak from experience here), then you have to ask yourself: is this fair? As an aside, you would probably not want to do k = (k >> 24) % 24 - that would leave you with a severly limited random number generator. (of range [0,23]
Thanatos
That's true, it would be best to skip Fisher-Yates all-together and use a 226bit random number to generate the nth-permutation of the deck. If you only need to shuffle a small number of times using the lrand48() call 5 times to generate the larger number would be fine enough. Course nothing's easy with these darn random numbers -- and also, these are all just nitpicks :) .
Adam Luter
Here is an example for generating the nth permutation: http://tafakuri.net/?p=68
Adam Luter
+1  A: 
 Card *deck[deckSize];

I think you want:

Card *deck = new Card[deckSize];
Alan
`deckSize` is a conpile-time constant, so `Card * deck[deckSize]' is perfectly fine, and preferable to dynamic allocation.
No, you're wrong. You assume it's a compile time constant, but you can't be certain. Is the cat alive or is the cat dead?
Alan
+2  A: 

It looks that your problem does not come from the code posted, which looks fine at a first glance, but from the code around it.

What about using a standard container of cards ? You must fill it, print it first to see if it's ok, shuffle, and then print it again.

#include <vector>
std::vector<Card> deck; // Empty for now. Must be filled with cards.


void shuffle (std::vector<Card> & deck)
{
    int deckSize = 24;
    while (deckSize > 1) 
    {
       long int k = lrand48();
       k = k %24;
       deckSize--;
       Card temp = deck[deckSize];
       deck[deckSize] = deck[k];
       deck[k] = temp;
    }
}
Jem
A: 

Basic arrays can't be defined with variable passed as size, as mentioned above.

And be careful there. Last element of

typename array[SIZE];

is array[SIZE-1], not array[SIZE]. It's probably where you getting a segfault.

You really should at lest try to use STL containers. STL has shuffle algorithms too (:

Dark
A: 

I think it might help to see the calling code.


class Card{
public:
    Card(int number):number_(number){}
    int getNumber(){return number_;}
 // ...
private:
    int number_;
};

void shuffle (Card * deck[]) {
    int deckSize = 24;
    while (deckSize > 1) {
       long int k = lrand48();
       k = k %24;
       deckSize--;
       Card * temp = deck[deckSize];
       deck[deckSize] = deck[k];
       deck[k] = temp;
    }
}

int main(int argc, char* argv[]){
{

  const int deckSize=24;
  Card* deck[deckSize];
  for(int i = 0 ; i getNumber()

That should work just fine.

Dolphin
A: 

People are complaining that you're not using containers and not declaring the size of your array. Don't worry about that, that's not the problem. Someone also said you're going past array boundaries, which you aren't. It's okay to have arrays with size not declared. It's also okay to have an array of pointers to Card. But the thing I don't get is why it crashes. Here's some sample code I wrote, based on your code:

#include <stdio.h>
#include <stdlib.h>
#define DECK_SIZE 24
void shuffle(int deck[]) {
    int n = DECK_SIZE, t;
    while (n > 1) {
        long k = lrand48() % DECK_SIZE;
        n--;
        t = deck[n];
        deck[n] = deck[k];
        deck[k] = t;
    }
}
int main(int argc, char **argv) {
    int deck[DECK_SIZE], i;
    for (i = 0; i < DECK_SIZE; ++i)
        deck[i] = i + 1;
    shuffle(deck);
    for (i = 0; i < DECK_SIZE; ++i)
        printf("%i\n", deck[i]);
    return 0;
}

Run it, it works perfectly fine. That means there is something else going on. Try printing the value of all the cards in your deck before you call shuffle to see if it segfaults there too, I suspect it would.

However, there IS an error in your code. Your function does not shuffle correctly. The correct way to shuffle is not to swap each card with a card selected from the entire deck, but to swap each card at position N with an card selected from the range 0..N. If you swapped each card with a random card you get N^N possible outcomes, some of which overlap if you swap a card back to its original place. With a 3 card deck it's apparent that this is wrong because you will end up with 27 different shuffles, some of which are the same, even though there are 3!=6 permutations of 3 cards. The problem is that since 6 is not a factor of 27, some permutations are more likely than others. To avoid this, try doing it this way:

void shuffle_correctly(int deck[]) {
    int i, t, k;
    for (i = 2; i < DECK_SIZE; ++i) {
        k = lrand48() % i;
        t = deck[i-1];
        deck[i-1] = deck[k];
        deck[k] = t;
    }
}
Dietrich Epp
Dietrick, I believe your routine and his are equivalent, but start at opposite ends of the array. Note that your "i" monotonically increases from 2 to DECK_SIZE. And his "n" monotonically decreases from DECK_SIZE down to 2. Both are Fisher-Yates.
Adam Luter
Oh my mistake, he should call lrand48() % n as you correctly point out. However, this does mean both of your routines are quite different. I feel certain that his (with the adjustment above) is Fisher-Yates (http://en.wikipedia.org/wiki/Fisher-Yates_shuffle). However, I believe yours is not. You shuffle the current place with a random card *up to* the current place. Which means a card may move more than once. In Fisher-Yates it may only move once.
Adam Luter
+4  A: 

Just use std::random_shuffle found in <algorithm>, like this:

std::random_shuffle(deck, deck + deckSize);

and your deck with be shuffled.

+1  A: 

You could also use

std::random_shuffle(deck, deck + deckSize)

which does Fisher-Yates for you.

However, there probably aren't enough bits in the standard random libraries to genuinely choose from all possible random permutations of cards.

kibibu