I have a list of ID numbers 1101-1137 in cells A1-A37. I would like to click a button to randomly select 20 of these, with no repetitions, and display them in a message box.
Just shuffle the indices:
Sub MAIN()
Dim ary(1 To 37) As Variant
Dim i As Long, j As Long
For i = 1 To 37
ary(i) = i
Next i
Call Shuffle(ary)
msg = ""
For i = 1 To 20
j = ary(i)
msg = msg & vbCrLf & Cells(j, 1).Value
Next i
MsgBox msg
End Sub
Public Sub Shuffle(InOut() As Variant)
Dim i As Long, j As Long
Dim tempF As Double, Temp As Variant
Hi = UBound(InOut)
Low = LBound(InOut)
ReDim Helper(Low To Hi) As Double
Randomize
For i = Low To Hi
Helper(i) = Rnd
Next i
j = (Hi - Low + 1) \ 2
Do While j > 0
For i = Low To Hi - j
If Helper(i) > Helper(i + j) Then
tempF = Helper(i)
Helper(i) = Helper(i + j)
Helper(i + j) = tempF
Temp = InOut(i)
InOut(i) = InOut(i + j)
InOut(i + j) = Temp
End If
Next i
For i = Hi - j To Low Step -1
If Helper(i) > Helper(i + j) Then
tempF = Helper(i)
Helper(i) = Helper(i + j)
Helper(i + j) = tempF
Temp = InOut(i)
InOut(i) = InOut(i + j)
InOut(i + j) = Temp
End If
Next i
j = j \ 2
Loop
End Sub
I've added a little to one line in your code... the line is now:
strString = strString & vbCrLf & Cells(idx(i), 1).Value
the full code is:
Private Sub CommandButton1_Click()
Const nItemsToPick As Long = 20
Const nItemsTotal As Long = 37
Dim rngList As Range
Dim idx() As Long
Dim varRandomItems() As Variant
Dim i As Long
Dim j As Long
Dim booIndexIsUnique As Boolean
Set rngList = Range("A1").Resize(nItemsTotal, 1)
ReDim idx(1 To nItemsToPick)
ReDim varRandomItems(1 To nItemsToPick)
For i = 1 To nItemsToPick
Do
booIndexIsUnique = True ' Innocent until proven guilty
idx(i) = Int(nItemsTotal * Rnd + 1)
For j = 1 To i - 1
If idx(i) = idx(j) Then
' It's already there.
booIndexIsUnique = False
Exit For
End If
Next j
If booIndexIsUnique = True Then
strString = strString & vbCrLf & Cells(idx(i), 1).Value
Exit Do
End If
Loop
varRandomItems(i) = rngList.Cells(idx(i), 1)
Next i
Msg = strString
MsgBox Msg
' varRandomItems now contains nItemsToPick unique random
' items from range rngList.
End Sub
So rather than returning the number, it uses the number returned to look at the value on the row that it relates to.
If you construct a string containing the IDs already found through randomization, you can check for repeats.
Dim i As Long, msg As String, id As String
msg = Chr(9)
For i = 1 To 20
id = 1100 + Int((37 - 1 + 1) * Rnd + 1)
Do Until Not CBool(InStr(1, msg, Chr(9) & id & Chr(9)))
Debug.Print id & msg
id = 1100 + Int((37 - 1 + 1) * Rnd + 1)
Loop
msg = msg & id & Chr(9)
Next i
msg = Mid(Left(msg, Len(msg) - 1), 2)
MsgBox msg
another one approach:
Sub test()
Dim Dic As Object, i%
Set Dic = CreateObject("Scripting.Dictionary")
Dic.comparemode = vbTextCompare
While Dic.Count <> 20
i = WorksheetFunction.RandBetween(1, 37)
If Not Dic.exists(i) Then Dic.Add i, Cells(i, "A")
Wend
MsgBox Join(Dic.Items, Chr(13))
End Sub
test: