Does anyone know of an efficient way to select M numbers from a list of 1 to N randomly?

JenniferMurphy

Well-known Member
Joined
Jul 23, 2011
Messages
2,525
Office Version
  1. 365
Platform
  1. Windows
I need to write a UDF that will select M numbers from 1 to N randomly without replacement. That is, no duplicates.

I know I can write a loop that will select one random number, check if it has already been selected, repeat if it has. Something like this:

VBA Code:
Function RandSelect(pM As Double, pN As Double) As Variant
Dim List() As Variant
ReDim List(1 To pN) As Variant
Dim i As Integer
Dim j As Integer

i = 0
RandSelect = ""
Do
  j = Int(Rnd() * pN) + 1
  If List(j) = 0 Then
    List(j) = 1
    RandSelect = RandSelect & " " & j
    i = i + 1
    If i >= pM Then Exit Do
  End If
Loop

End Function

This works, but it could take a while if N is large and M is close to N.

I don't see that either Excel or VBA have a function that will randomly reorder a list. If they do, I could do that and then just take the first M elements.

Thanks
 

Excel Facts

Format cells as currency
Select range and press Ctrl+Shift+4 to format cells as currency. (Shift 4 is the $ sign).
How about:

VBA Code:
Function RandSelect(pM As Double, pN As Double) As Variant
  Dim i As Long, x As Long, y As Long
  Dim arr As Variant
  
  arr = Evaluate("=row(1:" & pN & ")")
  Randomize
  For i = 1 To UBound(arr)
    x = Int(UBound(arr) * Rnd + 1)
    y = arr(x, 1)
    arr(x, 1) = arr(i, 1)
    arr(i, 1) = y
  Next
  
  For i = 1 To pM
    RandSelect = RandSelect & " " & arr(i, 1)
  Next
End Function
 
Upvote 0
Solution
How about:

Brilliant! ??

Now I am kicking myself for not thinking about that approach. I was pondering moving items from one list to another as they are selected, but this is much better.

I have an enhancement to offer. In my application, the lists on the order of 1,000 items. I will need anywhere from 0 to maybe 20 random items at most. I see that your algorithm reorders the entire list regardless of the number requested. Here's my version which only loops pM times.

VBA Code:
' My variation that stops after M iterations
Function RandSelect3(pM As Double, pN As Double) As Variant
 
  Dim iTo As Long     'Index of element to be swapped to
  Dim iFrom As Long   'Index of element to be swapped from
  Dim iToOld As Long  'Place to hold one element during swap
  Dim iLen As Long    'Remaining length of list
  Dim arr As Variant  'The list to be randomly reordered
 
  arr = Evaluate("row(1:" & pN & ")") 'Is "=" needed???
  Randomize
  iLen = pN           'Starting length
  For iTo = 1 To pM
    iFrom = Int(Rnd * iLen + iTo)   'Random number on [iTo,pN]
    iToOld = arr(iTo, 1)            'Save the current To item
    arr(iTo, 1) = arr(iFrom, 1)     'Move the From item to the To spot
    arr(iFrom, 1) = iToOld          'Move the old To item to the From spot
    RandSelect3 = RandSelect3 & " " & arr(iTo, 1)
    iLen = iLen - 1                 'Reduce the length for next loop
  Next iTo
 
End Function

Is there anything about this that is less "random" then your version? I tested it a bit and it seems random. ??
 
Upvote 0
Hmmm... It just occurred to me that my algorithm may have a randomness problem.

In the first loop, I generate a random number on [1,N]. Item #1 has an equal chance to take on any value.

In loop #2, I generate a random number on [2,N]. Item 2 has an equal chance for any of the numbers except the one in item 1. Does that affect the randomness? I;m not sure, but it's easy to change that to be [1,N] for all loops. I going to dinner now, but I'll do that when I return.
 
Upvote 0
@DanteAmor, your pN was limited as 1.048.576 (maximum rows limit), though it does not effect to OP' requirement.
My solution is, using dictionary to test dupplication, and While...Loop to repeat the random numbers, if its dupplicate.
Like this:
VBA Code:
Function RandSelect3(pM As Double, pN As Double)
Dim dic As Object
Dim rNum&, key
Set dic = CreateObject("scripting.dictionary")
If pM > pN Then
    MsgBox "Invalid Number!"
    Exit Function
End If
Randomize
rNum = Int(pN * Rnd + 1)
dic.Add rNum, "" ' first random number added
    For i = 2 To pM ' 2nd to last random numbers added
        Do While dic.exists(rNum) ' that mean if random number repeat, continue to generate  other random number
            rNum = Int(pN * Rnd + 1)
        Loop
        dic.Add rNum, ""
    Next
    For Each key In dic.keys
        RandSelect3 = RandSelect3 & " " & key
    Next
End Function
 
Upvote 0

Forum statistics

Threads
1,214,652
Messages
6,120,746
Members
448,989
Latest member
mariah3

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top