473,417 Members | 1,396 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes and contribute your articles to a community of 473,417 developers and data experts.

Dealing Cards

NeoPa
32,556 Expert Mod 16PB
Introduction:

How do you randomise a set of numbers without having any attempts clash with earlier ones? For instance, if using a random number generator (Rnd([SeedCode]) in VBA.) and you want to emulate randomising a pack of cards without any card being selected more than once, as would be the case if simply using a random index into the original set (or pack) over and over again.

Solution:

You start with the simple numbers 0 to 51 representing the various different cards and these are stored originally in memory locations with a base and offsets between 0 and 51. Note it is not necessary that any of these cards start in any logical or predetermined order. As long as each is there, which implies none can be duplicated.

This is an iterative process which terminates when all cards have been handled - or just those cards considered to be 'dealt' if dealing/selecting fewer than the full set. Today we will look at randomising the full set though. Consider the dealing phase to come afterwards and this illustrates the fullest set of the logic - some of which could be dispensed with if only dealing straight off the 'deck' and not requiring the full set to be randomised in place.

Key:
X = Random Number.
Y = Number of Items Left.
Z = Temp Variable (Used for swapping).

The first step of each iteration is to capture a new random number. In this explanation I will ignore seeding and always use X = Rnd(). A number may be passed as a parameter to control seeding but we're not interested in that here. Using the default means we'll get the next in the current sequence regardless of what that sequence is and how far through it we are already. All values returned are 0 <= X < 1. One (1) can never be returned but zero (0) can.

Multiply that random number by Y and chop any decimal places.
X = Fix(X * Y)
Y starts at 52 but is decremented in each iteration. This gives an even distribution of the numbers available in the range (1st: 0 to 51; 2nd: 0 to 50; 3rd 0 to 49; etc). This is the offset you need - now in X.

Decrement Y now to simplify next step.
Y = Y - 1
Swap the current value found at offset X with that found in offset Y (after decrementing).
Z = Base(X): Base(X) = Base(Y): Base(Y) = Z

Rinse & repeat as long as Y is still greater than one (1).

Illustration:

For simplicity of illustration I will use a restricted set of just 8 items - 0 to 7. The logic works for any number but the illustrations get bigger and more complicated as that number increases.
Expand|Select|Wrap|Line Numbers
  1.  Apply Update  Offset: 0  1  2  3  4  5  6  7
  2.                Values: 0  1  2  3  4  5  6  7
  3. 0.7055475==>5  Values: 0  1  2  3  4  7  6  5
  4. 0.533424 ==>3  Values: 0  1  2  6  4  7  3  5
  5. 0.5795186==>3  Values: 0  1  2  7  4  6  3  5
  6. 0.2895625==>1  Values: 0  4  2  7  1  6  3  5
  7. 0.301948 ==>1  Values: 0  7  2  4  1  6  3  5
  8. 0.7747401==>2  Values: 0  7  2  4  1  6  3  5
  9. 0.01401764=>0  Values: 7  0  2  4  1  6  3  5
The second column reflects the calculated offset to switch. Notice it's an integer by that point. Also notice that the second to last update switches column #2 with itself. Clearly this is unnecessary and has no effect. In actual code I would usually compare X & Y then only switch if they don't match.
Attached Files
File Type: zip Cards.Zip (17.0 KB, 60 views)
Dec 21 '22 #1
9 19966
ADezii
8,834 Expert 8TB
Hello NeoPa, good to see that an old friend is still around and active. I actually use a Dictionary Object (Microsoft Scripting Runtime) and one of it's critical Properties (Exists) to generate X unique, random numbers between a Low and High Range. Using your example above, the following Code will generate 52 random numbers between 0 and 51. Just a different approach that you may/may not agree with. For the sake of brevity and simplicity, I have removed any validations and error checking.
Expand|Select|Wrap|Line Numbers
  1. Dim varRet As Variant
  2. Dim intCtr As Integer
  3.  
  4. '****************** USER DEFINED SDECTION ******************
  5. Const conNUM_OF_RANDOMS = 52
  6. Const conLOWER = 0
  7. Const conUPPER = 51
  8. '***********************************************************
  9.  
  10. varRet = fGenerateUniqueNumbersList(conNUM_OF_RANDOMS, conLOWER, conUPPER)
  11.  
  12. For intCtr = LBound(varRet) To UBound(varRet)
  13.   Debug.Print varRet(intCtr)
  14. Next
Expand|Select|Wrap|Line Numbers
  1. Public Function fGenerateUniqueNumbersList(intListLength As Integer, intLowerBound As Integer, intUpperBound As Integer) As Variant
  2. 'Set a Reference to the Microsoft Scripting Runtime
  3. Dim dict As Scripting.Dictionary
  4. Dim intVal As Integer
  5. Dim var As Variant
  6.  
  7. Set dict = New Scripting.Dictionary
  8.  
  9. With dict
  10.   Do While .Count < intListLength
  11.     intVal = Int((intUpperBound - intLowerBound + 1) * Rnd + intLowerBound)
  12.       If Not .Exists(intVal) Then .Add intVal, ""
  13.   Loop
  14.  
  15.   fGenerateUniqueNumbersList = .Keys
  16. End With
  17. End Function
'Partial' Output:
Expand|Select|Wrap|Line Numbers
  1.  14 
  2.  36 
  3.  21 
  4.  42 
  5.  38 
  6.  22 
  7.  4 
  8.  17 
  9.  16 
  10.  41 
  11.  7 
  12.  30 
  13.  49 
  14.  12 
  15.  48 
  16.  5 
  17.  51 
  18.  32 
  19.  31 
  20.  46 
  21.  29 
  22.  44 
  23.  3 
  24.  39 
Mar 2 '23 #2
NeoPa
32,556 Expert Mod 16PB
Hi ADezii.

Always good to catch up old friend :-)

I would comment on your code, that it illustrates exactly why I developed (Independently - I suspect others use similar techniques.) the way I show to assign a correct value for each iteration through the loop. It means the loop is executed exactly 52 times rather than as many times as it takes to come across 52 separate values (On average 236 as it happens). When you think about the last iteration then each attempt has a 1/52 chance of arriving at the one valid number left. It starts a lot more reliably though, of course.

Using computers you're very unlikely to notice the inefficiency of course, but I have a natural aversion to such things. They make me feel uncomfortable. It's why I was so pleased to come up with the alternative when I did.

Another point worth noting, for the code efficiency coefficient, is that while .Exists() shows as one simple command in your code, you must realise that under the hood it is checking through each value already added to the dictionary. Relatively speaking (IE. at the processor level where everything is super-fast anyway.) that is quite extremely slow and expensive.

Apologies for critiquing your code so harshly, but it does have the value of illustrating that not all approaches are the same. Additionally it does help to explain, for others who may not grasp the importance of such things as I know you & I do, how the technique I illustrated can get to the heart of the matter rather than a trial-and-error loop where most tries have to be discarded as unfit for purpose. They both work in the end of course.
Mar 3 '23 #3
ADezii
8,834 Expert 8TB
Apologies for critiquing your code so harshly, but it does have the value of illustrating that not all approaches are the same.
Don't ever be concerned about that, your criticisms and recommendations are always welcome and appreciated. Oftentimes, I'll replace my approach with yours since it will usually be more efficient and faster, I am definitely receptive to new and better ideas.

What I would like to do, strictly out of curiosity?, and of course with your explicit approval, is to run Benchmark Tests against the two approaches but with much larger Datasets, something similar to returning 1,500 Random Values between 20,000 and 30,000. All Parameters would be less than the Maximum INTEGER Value of 32,767. In order to accomplish this, I would obviously need the Code that you would like to use for these Tests. Don't feel bad if you are not willing to provide it, I will understand.

Again, always a pleasure to hear from you, and don't ever lose that fine edge that you have.
Mar 3 '23 #4
NeoPa
32,556 Expert Mod 16PB
Now you have me laughing out loud.

I was preparing a reply of the sort - "Well, what else do you want than the code I already included in the article?", when I noticed I hadn't actually included the code at all!!!!! I'll rectify that.

Of course I'm very happy for you to run tests on the different approaches. I'll get on to - probably rewriting - the original code now.

Feeling sheepish - but at the same time can't stop laughing at my mistake.
Mar 3 '23 #5
NeoPa
32,556 Expert Mod 16PB
It looks like I didn't save the file at all :-(

Never mind. I'll post the code and attach the XLSM file I used for showing the results. It's currently signed as NeoPa so if anyone wants to play around with it then they will lose the signature, but otherwise no harm done. Unsigned code can also run fine as long as the MotW (Mark of the Web) is removed and the folder it's in is marked as trusted on your system.

The following code, in a standard module I've called "modMain" but whose name doesn't really matter, the actual randomising is done. I've deliberately kept the displaying of the data in the Excel worksheet as separate code as it relies heavily on the Excel interface whereas the randomising code is pure VBA.
Expand|Select|Wrap|Line Numbers
  1. Option Explicit
  2.  
  3. 'DealCards() Randomises a set of cards.
  4. Public Sub DealCards(lngTotal As Long)
  5.     Dim lngX As Long, lngY As Long, lngZ As Long, lngMax As Long
  6.     Dim lngArray() As Long
  7.  
  8.     ' ** Initialise **
  9.     lngMax = lngTotal - 1
  10.     ReDim lngArray(0 To lngMax)
  11.     For lngX = 0 To lngMax
  12.         lngArray(lngX) = lngX
  13.     Next lngX
  14.     ' ** Randomise **
  15.     ' No special work to redo seed here.  Just using what comes for now.
  16.     For lngX = lngMax To 1 Step -1
  17.         lngY = CLng(CSng(lngX + 1) * Rnd())
  18.         If lngY < lngX Then
  19.             lngZ = lngArray(lngX)
  20.             lngArray(lngX) = lngArray(lngY)
  21.             lngArray(lngY) = lngZ
  22.         End If
  23.     Next lngX
  24.     Call ShowCards(lngArray())
  25. End Sub
I'll discuss the ShowCards() procedure separately. It's helpful to allow people to see the results but otherwise it's fundamentally irrelevant to the discussion.

NB. The working version has now been attached to the original post of this article.
Mar 3 '23 #6
NeoPa
32,556 Expert Mod 16PB
To fill in the gaps where the operator may specify how many items they want randomised and how to show the data (If the value 52 is entered it assigns all the cards to four separate players and each value is marked with the card code (A, 2-10, J, Q & K) as well as a suit code (S=Spades, H=Hearts, D=Diamonds & C=Clubs.) whereas any other value just lists the numerical values in Column B in the worksheet). Cell A1 is where the total number is entered and whenever this is changed a new set of data will appear depending on the logic just described.

Handling the number in A1 being updated is done within the module for the Worksheet. it's pretty basic to be fair :
Expand|Select|Wrap|Line Numbers
  1. Option Explicit
  2.  
  3. Private Sub Worksheet_Change(ByVal Target As Range)
  4.     With Target
  5.         If .Address() <> "$A$1" Then Exit Sub
  6.         If .Value < 1 Then Exit Sub
  7.         Call DealCards(.Value)
  8.     End With
  9. End Sub
The last bit of code still missing is the ShowCards() procedure referenced at the end of DealCards() from my earlier post :
Expand|Select|Wrap|Line Numbers
  1. 'ShowCards() converts the resulting numbers to actual cards in their suits etc.
  2. Private Sub ShowCards(lngArray() As Long)
  3.     Dim lngX As Long, lngY As Long, lngZ As Long
  4.     Dim strRange As String, strCard As String
  5.     Dim blnCards As Boolean
  6.  
  7.     Call Columns("B:E").Delete
  8.     blnCards = (UBound(lngArray) = 51)
  9.     If blnCards Then
  10.         Range("B1") = "Player 1"
  11.         Range("C1") = "Player 2"
  12.         Range("D1") = "Player 3"
  13.         Range("E1") = "Player 4"
  14.     End If
  15.     For lngX = 0 To UBound(lngArray)
  16.         If blnCards Then
  17.             Select Case lngX
  18.             Case Is < 13
  19.                 strRange = "B" & lngX + 2
  20.             Case Is < 26
  21.                 strRange = "C" & lngX - 11
  22.             Case Is < 39
  23.                 strRange = "D" & lngX - 24
  24.             Case Else
  25.                 strRange = "E" & lngX - 37
  26.             End Select
  27.             lngY = lngArray(lngX)
  28.             Select Case lngY Mod 13
  29.             Case 0
  30.                 strCard = "A "
  31.             Case Is < 10
  32.                 strCard = (lngY Mod 13) + 1 & " "
  33.             Case 10
  34.                 strCard = "J "
  35.             Case 11
  36.                 strCard = "Q "
  37.             Case 12
  38.                 strCard = "K "
  39.             End Select
  40.             strCard = strCard & Mid("SHDC", (lngY \ 13) + 1, 1)
  41.             Range(strRange) = strCard
  42.         Else
  43.             Range("B" & lngX + 1) = lngArray(lngX) + 1
  44.         End If
  45.     Next lngX
  46. End Sub
Much more verbiage in that one as it has to handle the differences between simple numbers and a standard deck of cards. Relatively basic at its core though.
Mar 3 '23 #7
ADezii
8,834 Expert 8TB
Back again, NeoPa. I had some free time, so I ran some Benchmarks Tests comparing both your approach (arbitrarily called the Single Pass Approach) against the Collection Approach. The results weren't exactly what I expected, but here they are:
  1. With each approach, I generated 10,000 Unique, Random Numbers.
  2. Each approach consisted of 20 Trial Runs.
  3. For a simple timing mechanism, I used the timeGetTime() API Functrion which returns the number of milliseconds that Windows has been running.
  4. The Tests were performed on an Intel(R) Core(TM) i7-6700 CPU @ 3.40 GHz, RAM 8.00 GB, Windows 10 Enterprise.
  5. Believe it or not, the Collection Approach was significantly faster which I would not expect. Interested in hearing your comments on this.

Attached Images
File Type: jpg NeoPa.JPG (81.6 KB, 481 views)
Mar 8 '23 #8
NeoPa
32,556 Expert Mod 16PB
Hi ADezii.

Indeed that does seem perplexing. I assume you recognise and understand why the Single Pass Approach would logically be quicker - quicker by a far more significant margin even than the results seem to show in the reverse direction.

Ultimately, I'd need your whole test setup (Not computer, I just mean the software/code.) in order to be able to investigate what might be leading to such seriously anomalous results.

I've sent you a PM with my e-mail address (in case you don't have it already - which you may well have of course).
Mar 8 '23 #9
NeoPa
32,556 Expert Mod 16PB
I have managed to work with ADezii's test system and, with a couple of very minor adjustments (that had unfortunately a great deal of adverse effect) it now accurately (and as one might expect understanding the logic) reflects the improvement of performance of this approach when compared with the general, more hit-&-hope approach as you can find in many places.

One of the things it demonstrated very strongly is that the performance of the single-pass approach as I've illustrated does not suffer as the number of items to sort increases. That is to say that it grows, but only in a linear fashion. However, the hit-&-hope approach degrades quite appreciably (exponentially) as the number increases.

I have to admit that the whole topic is a little esoteric as, when the number of items is something of practical use like a pack of cards (52) then if we can see that 10,000 items takes about a 50th of a second even for the slow approach, then when would it ever matter? Nevertheless, just understanding how & why one is relatively so much faster than the other - and for those circumstances where millions are required of course - it's pretty handy to know.

I've included, with thanks to ADezii who set up the test system this was taken from, the overall results for both sets of data.



PS. I cheated a little as the delays were so long so I took the first ten results for the "Collection" data and just copied them into the slots for 11 to 20. Nevertheless you'll see the minimal variation within these results indicates clearly that the other ten would have been very similar. I've attached the database so anyone can test for themselves should they wish to ;-)
Attached Images
File Type: jpg CardsTenK.Jpg (86.8 KB, 267 views)
File Type: jpg CardsOneMil.Jpg (98.5 KB, 265 views)
Attached Files
File Type: zip Cards_Revised.Zip (40.6 KB, 51 views)
Mar 21 '23 #10

Sign in to post your reply or Sign up for a free account.

Similar topics

2
by: kelvSYC | last post by:
I'm trying to program something along the lines of a "trading card" idea: I have a single copy of the "card" in the program, yet there may be multiple "instances" of the "card", with differing...
10
by: Arun Nair | last post by:
Can any one help me with this im not getting it even after reading books because there is not much of discussion anywhere a> Implement a calss that represents a playing card. The class should...
2
by: RobcPettit | last post by:
Hi, could somebody in the right direction to write a program to deal cards. What I want to do is: With an 8 deck of cards. I know the player has an ace, the dealer has a 2, is deal every possible...
0
by: =?Utf-8?B?TmljayBIb2x3YXk=?= | last post by:
I'm trying to follow a tutorial (1) on getting and setting a sound card's volume. It uses a function in winmm.dll public static extern int waveOutGetVolume(IntPtr hwo, out uint dwVolume); ...
8
by: garyrowell | last post by:
I have been at this programme for hours trying to work out what is wrong. Any help would be very much appricated. Here is the breif I received. The program This week you are going to write three...
8
by: l1nuxxx | last post by:
I have a file well call file.pl. It's a card sorting program. I need to create a lib fuction with part of the original file that shuffles the deck of cards. After it shuffles the first deck and deals...
0
by: ToBe | last post by:
Ok, the cards game I'm developing is pretty similar to Scopa if someone knows it. The deck contains 40 cards divided into 4 different suits of 10 cards each (ace => value 1, two => value 2, three =...
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
0
BarryA
by: BarryA | last post by:
What are the essential steps and strategies outlined in the Data Structures and Algorithms (DSA) roadmap for aspiring data scientists? How can individuals effectively utilize this roadmap to progress...
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...
0
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...
0
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven...
0
by: Hystou | last post by:
Overview: Windows 11 and 10 have less user interface control over operating system update behaviour than previous versions of Windows. In Windows 11 and 10, there is no way to turn off the Windows...
0
agi2029
by: agi2029 | last post by:
Let's talk about the concept of autonomous AI software engineers and no-code agents. These AIs are designed to manage the entire lifecycle of a software development project—planning, coding, testing,...
0
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 1 May 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome a new...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.