Unique Random Numbers using VBA - excel

I am trying to create a series of unique (non-duplicating) random numbers within a user defined range. I have managed to create the random numbers, but I am getting duplicate values. How can I ensure that the random numbers will never be a duplicate?
Sub GenerateCodesUser()
Application.ScreenUpdating = False
Worksheets("Users").Activate
Dim MINNUMBER As Long
Dim MAXNUMBER As Long
MINNUMBER = 1000
MAXNUMBER = 9999999
Dim Row As Integer
Dim Number As Long
Dim high As Double
Dim Low As Double
Dim i As Integer
If (CustomCodes.CardNumberMin.Value = "") Then
MsgBox ("Fill Card Number Field!")
Exit Sub
ElseIf (CustomCodes.CardNumberMin.Value < MINNUMBER) Then
MsgBox ("Card Number Value must be equal or higher then" & MINNUMBER)
Exit Sub
End If
If (CustomCodes.CardNumberMax.Value = "") Then
MsgBox ("Fill Card Number Field!")
Exit Sub
ElseIf (CustomCodes.CardNumberMax.Value > MAXNUMBER) Then
MsgBox ("Card Number Value must be equal or higher then " & MAXNUMBER)
Exit Sub
End If
Low = CustomCodes.CardNumberMin.Value
high = CustomCodes.CardNumberMax.Value '<<< CHANGE AS DESIRED
If (Low < 1000) Then
'break
End If
For i = 1 To Cells(1, 1).End(xlToRight).Column
If InStr(Cells(1, i), "CardNumber") Then
Row = 2
While Cells(Row, 1) <> 0
Do
Number = ((high - Low + 1) * Rnd() + Low)
Loop Until Number > Low
Cells(Row, i) = Number
Row = Row + 1
Wend
End If
Next
Application.ScreenUpdating = True
End Sub

Here's a method of guaranteeing unique integer random numbers. Inline comments describe the method.
Function UniuqeRandom(Mn As Long, Mx As Long, Sample As Long) As Long()
Dim dat() As Long
Dim i As Long, j As Long
Dim tmp As Long
' Input validation checks here
If Mn > Mx Or Sample > (Mx - Mn + 1) Then
' declare error to suit your needs
Exit Function
End If
' size array to hold all possible values
ReDim dat(0 To Mx - Mn)
' Fill the array
For i = 0 To UBound(dat)
dat(i) = Mn + i
Next
' Shuffle array, unbiased
For i = UBound(dat) To 1 Step -1
tmp = dat(i)
j = Int((i + 1) * Rnd)
dat(i) = dat(j)
dat(j) = tmp
Next
'original biased shuffle
'For i = 0 To UBound(dat)
' tmp = dat(i)
' j = Int((Mx - Mn) * Rnd)
' dat(i) = dat(j)
' dat(j) = tmp
'Next
' Return sample
ReDim Preserve dat(0 To Sample - 1)
UniuqeRandom = dat
End Function
use it like this
Dim low As Long, high As Long
Dim rng As Range
Dim dat() As Long
Set rng = Range(Cells(1, 1), Cells(1, 1).End(xlToRight))
dat = UniuqeRandom(low, high, rng.Columns.Count)
rng.Offset(1, 0) = dat
Note: see this Wikipedia article regarding shuffle bias
The edit fixed one source of bias. The inherent limitations of Rnd (based on a 32 bit seed) and Modulo bias remain.

I see you have an accepted answer, but for whatever it's worth here is my stab at this question. This one uses a boolean function instead of numerical arrays. It's very simple yet fast. The advantage of it, which I'm not saying is perfect, is an effective solution for numbers in a long range because you only ever check the numbers you have already picked and saved and don't need a potentially large array to hold the values you have rejected so it won't cause memory problems because of the size of the array.
Sub UniqueRandomGenerator()
Dim N As Long, MaxNum As Long, MinNum As Long, Rand As Long, i As Long
MinNum = 1 'Put the input of minimum number here
MaxNum = 100 'Put the input of maximum number here
N = MaxNum - MinNum + 1
ReDim Unique(1 To N, 1 To 1)
For i = 1 To N
Randomize 'I put this inside the loop to make sure of generating "good" random numbers
Do
Rand = Int(MinNum + N * Rnd)
If IsUnique(Rand, Unique) Then Unique(i, 1) = Rand: Exit Do
Loop
Next
Sheet1.[A1].Resize(N) = Unique
End Sub
Function IsUnique(Num As Long, Data As Variant) As Boolean
Dim iFind As Long
On Error GoTo Unique
iFind = Application.WorksheetFunction.Match(Num, Data, 0)
If iFind > 0 Then IsUnique = False: Exit Function
Unique:
IsUnique = True
End Function

It Works perfectly:
Option Base 1
Public Function u(a As Variant, b As Variant) As Variant
Application.Volatile
Dim k%, p As Double, flag As Boolean, x() As Variant
k = 1
flag = False
ReDim x(1)
x(1) = Application.RandBetween(a, b)
Do Until k = b - a + 1
Do While flag = False
Randomize
p = Application.RandBetween(a, b)
'Debug.Assert p = 2
resultado = Application.Match(p, x, False)
If IsError(resultado) Then
k = k + 1
ReDim Preserve x(k)
x(k) = p
flag = True
Else
flag = False
End If
Loop
flag = False
Loop
u = x
End Function

Related

Trying to Sorting the Loaded Column in Listbox from A to Z

I have been trying to sort the Column values from A to Z which are populated in the List Box.
I have tried with the following but it does not adjust it. Any help will be appreciated.
Dim ws As Worksheet
Dim rng As Range
Dim myArray
Set ws = Sheets("Sheet2")
Set rng = ws.Range("A2:A" & ws.Range("A" & ws.Rows.Count).End(xlUp).Row), Order1:=xlAscending
With Me.ListBox1
.Clear
.ColumnHeads = False
.ColumnCount = rng.Columns.Count
MyArray = rng
.List = SortArray(myArray)
End With
I want to use the Arrays for Sorting Function which will be populated to Listbox.
Sub SortArray(myListBox As MSForms.ListBox, Optional resetMacro As String)
Dim j As Long
Dim i As Long
Dim temp As Variant
If resetMacro <> "" Then
Run resetMacro, myListBox
End If
With myListBox
For j = 0 To .ListCount - 2
For i = 0 To .ListCount - 2
If LCase(.List(i)) > LCase(.List(i + 1)) Then
temp = .List(i)
.List(i) = .List(i + 1)
.List(i + 1) = temp
End If
Next i
Next j
End With
End Sub
Method 1: Sort Data in Cells
You need to sort the range using the Range.Sort method
Set rng = ws.Range("A2:A" & ws.Range("A" & ws.Rows.Count).End(xlUp).Row)
rng.Sort key1:=ws.Range("A2"), order1:=xlAscending, Header:=xlNo
Also see VBA Excel sort range by specific column.
Method 2: Sort Data in Array
Or load the data into an array and sort the array. See VBA array sort function?
Note: The QuickSort algorithm was retrieved from the link above.
Option Explicit
Private Sub LoadButton_Click()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet2")
Dim DataRange As Range
Set DataRange = ws.Range("A2:A" & ws.Range("A" & ws.Rows.Count).End(xlUp).Row)
' 2-dimensional array of the data
Dim DataArray() As Variant
DataArray = DataRange.Value
' Sort data in 2-dimensional array DataArray
QuickSortArray SortArray:=DataArray, SortColumn:=1
' Load sorted data into ListBox
SortedListForm.SortedListBox.List = DataArray
End Sub
' QickSort algorithm that takes a 2-dimensional array
Public Sub QuickSortArray(ByRef SortArray As Variant, Optional ByVal Min As Long = -1, Optional ByVal Max As Long = -1, Optional ByVal SortColumn As Long = 0)
On Error Resume Next
'Sort a 2-Dimensional array
' SampleUsage: sort arrData by the contents of column 3
'
' QuickSortArray arrData, , , 3
'
'Posted by Jim Rech 10/20/98 Excel.Programming
'Modifications, Nigel Heffernan:
' ' Escape failed comparison with empty variant
' ' Defensive coding: check inputs
Dim i As Long
Dim j As Long
Dim RowTemp As Variant
Dim ColTempIdx As Long
If IsEmpty(SortArray) Then
Exit Sub
End If
If InStr(TypeName(SortArray), "()") < 1 Then 'IsArray() is somewhat broken: Look for brackets in the type name
Exit Sub
End If
If Min = -1 Then
Min = LBound(SortArray, 1)
End If
If Max = -1 Then
Max = UBound(SortArray, 1)
End If
If Min >= Max Then ' no sorting required
Exit Sub
End If
i = Min
j = Max
Dim SortItem As Variant
SortItem = Empty
SortItem = SortArray((Min + Max) \ 2, SortColumn)
' We send 'Empty' and invalid data items to the end of the list:
If IsObject(SortItem) Then ' note that we don't check isObject(SortArray(n)) - SortItem *might* pick up a valid default member or property
i = Max
j = Min
ElseIf IsEmpty(SortItem) Then
i = Max
j = Min
ElseIf IsNull(SortItem) Then
i = Max
j = Min
ElseIf SortItem = "" Then
i = Max
j = Min
ElseIf VarType(SortItem) = vbError Then
i = Max
j = Min
ElseIf VarType(SortItem) > 17 Then
i = Max
j = Min
End If
Do While i <= j
Do While SortArray(i, SortColumn) < SortItem And i < Max
i = i + 1
Loop
Do While SortItem < SortArray(j, SortColumn) And j > Min
j = j - 1
Loop
If i <= j Then
' Swap the rows
ReDim RowTemp(LBound(SortArray, 2) To UBound(SortArray, 2))
For ColTempIdx = LBound(SortArray, 2) To UBound(SortArray, 2)
RowTemp(ColTempIdx) = SortArray(i, ColTempIdx)
SortArray(i, ColTempIdx) = SortArray(j, ColTempIdx)
SortArray(j, ColTempIdx) = RowTemp(ColTempIdx)
Next ColTempIdx
Erase RowTemp
i = i + 1
j = j - 1
End If
Loop
If (Min < j) Then
QuickSortArray SortArray, Min, j, SortColumn
End If
If (i < Max) Then
QuickSortArray SortArray, i, Max, SortColumn
End If
End Sub

Goal: randomization without doubling up two names Problem: comparing and writing (to worksheet) collection and/or arrays

I am trying to write a simple randomizing program that reads from a column of names and randomly writes them to three columns of four. I have something that kind of works, but it is duplicating my names and I can figure out how to fix it with arrays or collections as those wont let me compare values. Thank you in advance.
Goal: randomization without doubling up two names
Problem: comparing and writing (to worksheet) collection and/or arrays
Option Explicit
Private Sub Randomize_Click()
Dim HowMany As Integer
Dim NoOfNames As Long
Dim RandomNumber As Integer
Dim Names(), dub() As String 'Array to store randomly selected names
Dim i, j, r, a, p As Byte
Dim CellsOut As Long 'Variable to be used when entering names onto worksheet
Dim ArI As Byte 'Variable to increment through array indexes
Application.ScreenUpdating = False
HowMany = 4 ' use with a third loops?
CellsOut = 4
For a = 1 To 6
For r = 1 To 3
For j = 2 To 5
'CellsOut = i 'turn this into loops
ReDim Names(1 To 4) 'Set the array size to how many names required
NoOfNames = Application.CountA(Worksheets("Employees").Range("A:A")) - 1 ' Find how many
names in the list
i = 1
Do While i <= HowMany
RandomNo:
RandomNumber = Application.RandBetween(2, NoOfNames + 1)
dub = RandomNumber
'dub.Add Unit.Value
If Names(i) = Cells(RandomNumber, 1).Value Then
'If Names(i) = dub(Unit) Then
GoTo RandomNo
End If
Names(i) = Worksheets("Employees").Cells(RandomNumber, 1).Value ' Assign random
name to the array
i = i + 1 '
Loop
'Loop through the array and enter names onto the worksheet
For ArI = LBound(Names) To UBound(Names)
Cells(CellsOut, j) = Names(ArI)
CellsOut = CellsOut + 1
Next ArI
Application.ScreenUpdating = True
CellsOut = 4
Next j
Next r
Next a
End Sub
Display
Names
Random Names
Current Setup
This solution uses the dictionary to randomize numbers which I was exploring earlier today.
The complete code goes into a standard module.
Adjust the three constants at the beginning of randomizeNames.
You only run randomizeNames, e.g. via a command button:
Private Sub Randomize_Click()
randomizeNames
End Sub
The Code
Option Explicit
Sub randomizeNames()
' Constants
Const srcFirst As String = "A2"
Const NoC As Long = 3
Const tgtFirst As String = "C2"
' Define Source First Cell Range ('cel').
Dim cel As Range
Set cel = Range(srcFirst)
' Define Source Last Cell Range ('rng').
Dim rng As Range
Set rng = Cells(Rows.Count, cel.Column).End(xlUp)
' Define Source Column Range ('rng').
Set rng = Range(cel, rng)
' Define Number of Elements (names) ('NoE').
Dim NoE As Long
NoE = rng.Rows.Count
' Write values from Source Column Range to Source Array ('Source').
Dim Source As Variant
If NoE > 1 Then
Source = rng.Value
Else
ReDim Source(1 To 1, 1 To 1)
Source(1, 1) = rng.Value
End If
' Define Random Numbers Array ('RNA').
Dim RNA As Variant
' This line uses both functions.
RNA = getDictionary(Dictionary:=getRandomDictionary(1, NoE), _
FirstOnly:=True)
' Instead of numbers, write elements from Source Array
' to Random Number Array (Random Names Array).
Dim i As Long
For i = 1 To NoE
RNA(i, 1) = Source(RNA(i, 1), 1)
Next i
' Define Number of Rows in Target Array ('NoR') and the Remainder
' of elements ('Remainder').
Dim NoR As Long
NoR = Int(NoE / NoC)
Dim Remainder As Long
Remainder = NoE Mod NoC
If Remainder > 0 Then
NoR = NoR + 1
Else
Remainder = NoC
End If
' Define Target Array ('Target').
Dim Target As Variant
ReDim Target(1 To NoR, 1 To NoC)
' Declare additional variables.
Dim j As Long ' Target Array Columns Counter
Dim k As Long ' Random Names Array Rows Counter
' Write values from Random Names Array to Target Array.
For i = 1 To NoR - 1
For j = 1 To NoC
k = k + 1
Target(i, j) = RNA(k, 1)
Next j
Next i
For j = 1 To Remainder
k = k + 1
Target(i, j) = RNA(k, 1)
Next j
' Define Target First Cell Range ('cel').
Set cel = Range(tgtFirst)
' Clear contents from Target First Cell Range to bottom-most cell
' of last column of Target Range.
cel.Resize(Rows.Count - cel.Row + 1, NoC).ClearContents
' Write values from Target Array to Target Range.
Range(tgtFirst).Resize(NoR, NoC).Value = Target
End Sub
Function getRandomDictionary(ByVal LowOrHigh As Long, _
ByVal HighOrLow As Long) _
As Object
' Define Numbers Dictionary ('dict').
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
' Define the lower number ('Minimum') and the Number of Elements ('NoE').
Dim NoE As Long
Dim Minimum As Long
If LowOrHigh < HighOrLow Then
Minimum = LowOrHigh
NoE = HighOrLow - LowOrHigh + 1
Else
Minimum = HighOrLow
NoE = LowOrHigh - HighOrLow + 1
End If
' Write random list of numbers to Numbers Dictionary.
Dim Current As Long
Do
' Randomize ' Takes considerably longer.
Current = Int(Minimum + NoE * Rnd)
dict(Current) = Empty
Loop Until dict.Count = NoE
' Write result.
Set getRandomDictionary = dict
End Function
Function getDictionary(Dictionary As Object, _
Optional ByVal Horizontal As Boolean = False, _
Optional ByVal FirstOnly As Boolean = False, _
Optional ByVal Flip As Boolean = False) _
As Variant
' Validate Dictionary.
If Dictionary Is Nothing Then
GoTo ProcExit
End If
Dim NoE As Long
NoE = Dictionary.Count
If NoE = 0 Then
GoTo ProcExit
End If
' Write values from Dictionary to Data Array ('Data').
Dim Data As Variant
Dim Key As Variant
Dim i As Long
If Not Horizontal Then
If Not FirstOnly Then
ReDim Data(1 To NoE, 1 To 2)
If Not Flip Then
For Each Key In Dictionary.Keys
i = i + 1
Data(i, 1) = Key
Data(i, 2) = Dictionary(Key)
Next Key
Else
For Each Key In Dictionary.Keys
i = i + 1
Data(i, 1) = Dictionary(Key)
Data(i, 2) = Key
Next Key
End If
Else
ReDim Data(1 To NoE, 1 To 1)
If Not Flip Then
For Each Key In Dictionary.Keys
i = i + 1
Data(i, 1) = Key
Next Key
Else
For Each Key In Dictionary.Keys
i = i + 1
Data(i, 1) = Dictionary(Key)
Next Key
End If
End If
Else
If Not FirstOnly Then
ReDim Data(1 To 2, 1 To NoE)
If Not Flip Then
For Each Key In Dictionary.Keys
i = i + 1
Data(1, i) = Key
Data(2, i) = Dictionary(Key)
Next Key
Else
For Each Key In Dictionary.Keys
i = i + 1
Data(1, i) = Dictionary(Key)
Data(2, i) = Key
Next Key
End If
Else
ReDim Data(1 To 1, 1 To NoE)
If Not Flip Then
For Each Key In Dictionary.Keys
i = i + 1
Data(1, i) = Key
Next Key
Else
For Each Key In Dictionary.Keys
i = i + 1
Data(1, i) = Dictionary(Key)
Next Key
End If
End If
End If
' Write result.
getDictionary = Data
ProcExit:
End Function
List of US Top 30 Names
James
John
Robert
Michael
William
Mary
David
Joseph
Richard
Charles
Thomas
Christopher
Daniel
Elizabeth
Matthew
Patricia
George
Jennifer
Linda
Anthony
Barbara
Donald
Paul
Mark
Andrew
Edward
Steven
Kenneth
Margaret
Joshua

Code to output arrays to Excel spreadsheet is affecting prior iterations

I'm attempting to print small arrays to an Excel spreadsheet. The bulk of the code to loops n times based on the user's discretion.
The output Sub functions print correctly on the first iteration, but when the array changes on the next iteration and the sub functions move to the next line to output, they also modify the first array values in the spreadsheet from the first iteration.
Example: If I go through five iterations, and they all produce different values in their respective arrays, by the 5th iteration All five columns that have been printed are modified to be the same as the last iteration.
I'm trying to prevent the code from replacing previous values.
I've attempted the Erase function for the array inside of the big for loop, which broke the code.
For loop for iterations
Dim Iter As Integer
For Iter = 1 To number_of_iterations
Randomize
reward_present = Int((1 - 0 + 1) * Rnd + 0)
reward_string = reward_present
reward_present = 1
'Randomize whether there is a reward present or not
If reward_present = 1 Then
Dim door_probabilities() As Variant
ReDim door_probabilities(1 To number_of_doors)
Dim remainder As Double
Dim reward_door As Integer
Dim reward_door_string As String
remainder = 1
For i = 1 To (number_of_doors - 1)
door_probabilities(i) = RndDbl(0, remainder)
remainder = remainder - door_probabilities(i)
Next i
door_probabilities(number_of_doors) = remainder
'randomizing probabilities of each door
Dim max As Variant
max = door_probabilities(1)
reward_door = 0
For i = 1 To number_of_doors
If max <= door_probabilities(i) Then
max = door_probabilities(i)
reward_door = i
End If
Next i
reward_door_string = reward_door
'choosing the reward door based on probability
If number_of_doors = 3 Then
random_player_choice = Int((number_of_doors - 1 + 1) * Rnd + 1)
game_doors(random_player_choice) = 1
ArrayFillPlayer1 game_doors, Iter
'choose first player door randomly
'output here
For i = LBound(game_doors) To UBound(game_doors)
msg = msg & game_doors(i) & vbNewLine
Next i
MsgBox "Game doors player choice 1: " + msg
random_host_choice = Int((number_of_doors - 1 + 1) * Rnd + 1)
Do While random_host_choice = random_player_choice
random_host_choice = Int((number_of_doors - 1 + 1) * Rnd + 1)
Loop
If random_host_choice = reward_door Then
Do While random_host_choice = reward_door
random_host_choice = Int((number_of_doors - 1 + 1) * Rnd + 1)
Loop
End If
game_doors(random_host_choice) = 1
ArrayFillHost game_doors, Iter
'choose host door randomly
'output here
For i = LBound(game_doors) To UBound(game_doors)
msg = msg & game_doors(i) & vbNewLine
Next i
MsgBox "Game doors host choice: " + msg
random_player_choice2 = Int((number_of_doors - 1 + 1) * Rnd + 1)
Do While random_player_choice2 = random_host_choice
random_player_choice2 = Int((number_of_doors - 1 + 1) * Rnd + 1)
Loop
game_doors(random_player_choice2) = 1
'choose second player door
ArrayFillPlayer2 game_doors, Iter
For i = LBound(game_doors) To UBound(game_doors)
msg = msg & game_doors(i) & vbNewLine
Next i
ReDim game_doors(1 To number_of_doors)
End If
Sub ArrayFillPlayer1(TempArray As Variant, RowToWrite As Integer)
'Fill a range by transferring an array
Dim CellsDown As Long, CellsAcross As Long
Dim TheRange As Range
CellsDown = 1
CellsAcross = 3
'Cells.Clear
'Set worksheet range
Set TheRange = Range(Cells(RowToWrite, 1), Cells(CellsDown, CellsAcross))
Transfer temporary array to worksheet
TheRange.Value = TempArray
End Sub
Sub ArrayFillHost(TempArray As Variant, RowToWrite As Integer)
'Fill a range by transferring an array
Dim CellsDown As Long, CellsAcross As Long
Dim TheRange As Range
CellsDown = 1
CellsAcross = 6
'Cells.Clear
'Set worksheet range
Set TheRange = Range(Cells(RowToWrite, 4), Cells(CellsDown, CellsAcross))
Transfer temporary array to worksheet
TheRange.Value = TempArray
End Sub
Sub ArrayFillPlayer2(TempArray As Variant, RowToWrite As Integer)
'Fill a range by transferring an array
Dim CellsDown As Long, CellsAcross As Long
Dim TheRange As Range
CellsDown = 1
CellsAcross = 9
'Cells.Clear
'Set worksheet range
Set TheRange = Range(Cells(RowToWrite, 7), Cells(CellsDown, CellsAcross))
Transfer temporary array to worksheet
TheRange.Value = TempArray
End Sub
I expect the output of each consecutive row to be different, but they are all modified retroactively.
Kind of looks like you mean to use Resize() in your subs which fill the array to the sheet?
For example this:
Sub ArrayFillPlayer1(TempArray As Variant, RowToWrite As Integer)
'Fill a range by transferring an array
Dim CellsDown As Long, CellsAcross As Long
Dim TheRange As Range
CellsDown = 1
CellsAcross = 3
'Cells.Clear
'Set worksheet range
Set TheRange = Range(Cells(RowToWrite, 1), Cells(CellsDown, CellsAcross))
Transfer temporary array to worksheet
TheRange.Value = TempArray
End Sub
Here the line:
Set TheRange = Range(Cells(RowToWrite, 1), Cells(CellsDown, CellsAcross))
is the same as:
Set TheRange = Range( Cells(RowToWrite, 1), Range("C1") )
and that "C1" remains constant as RowToWrite increases, so each time you're filling a larger range with TempArray.
This is closer to what you want:
Sub ArrayFillPlayer1(TempArray As Variant, RowToWrite As Integer)
Range(Cells(RowToWrite, 1).Resize(1, 3).Value = TempArray
End Sub

Developing proper algorithm for numeric combination

Hello to all experts in Excel formula programming and mathematicians.
I am trying to develop a formula applicable in Excel that generates possible combinations of 7 numbers within number span from 1 to 50.
Interesting here is that I can`t find a proper way how to fully integrate all needed variables in same formula for expected result.
Following variables I am trying to integrate are these:
numbers can be formed from 1 and 2 digits
number span to form combination of numbers is from 1 - 50
each combination contains 7 different numbers (without repeating inside same combination)
numbers should be lined in order from smallest to highest value if possible
to simplify (reduce) from all possible combinations, it is crucial that combinations can be generated from "manually inserted" chosen numbers (they are within this same numeric span of 1 - 50). This is what I think is most challenging how to create.
alternatively, is it possible to integrate in this kind of formula a loop that checks or blocks that same combination of set of 7 numbers are repeated within all combinations? This is to prevent repeating of same set of combination of numbers within ultimate possible of combinations following these all previous conditions.
Thank you for support,
DucyD
This is not a complete solution.
But it will list the first 1,000,000 combinations (the total number would be around 99,884,401):
Sub kombo()
Z = 1
For a = 1 To 50 - 6
For b = a + 1 To 50 - 5
For c = b + 1 To 50 - 4
For d = c + 1 To 50 - 3
For e = d + 1 To 50 - 2
For f = e + 1 To 50 - 1
For g = f + 1 To 50
Cells(Z, 1) = a & "," & b & "," & c & "," & d & "," & e & "," & f & "," & g
Z = Z + 1
If Z = 1000000 Then Exit Sub
Next g
Next f
Next e
Next d
Next c
Next b
Next a
End Sub
Near the top:
At the bottom:
User defined function that accepts a comma an array of comma delimited numbers and generates a combination is within the first five parameters described in OP. The last parameter greatly complicates the problem, do you expect to get collisions? Are you planning on generating a million sets of combinations, fifty, a hundred thousand? You can pass the range that you want to check to CombinationGenerator and check. See bottom of post for an idea as to handle this parameter.
Option Explicit
Public Function CombinationGenerator(Optional ByRef valueString As String) As String
Dim rndNum As Long
Dim indexI As Long
Dim indexII As Long
Dim doubleValuePass As Boolean
Dim tempStr As String
Dim position As Long
Dim values() As String
Dim shuffled(7) As String
values = Split(valueString, ",")
ReDim Preserve values(7)
For indexI = 0 To UBound(values)
If values(indexI) = "" Then
Randomize
rndNum = Int(Rnd * 50) + 1
values(indexI) = CStr(rndNum)
Do Until doubleValuePass = True
doubleValuePass = True
For indexII = 0 To UBound(values)
If (values(indexI) = values(indexII)) And (indexI <> indexII) Then
doubleValuePass = False
Randomize
rndNum = Int(Rnd * 50) + 1
values(indexI) = CStr(rndNum)
End If
Next indexII
Loop
doubleValuePass = False
End If
Next indexI
For indexI = 0 To UBound(values)
position = 0
For indexII = 0 To UBound(values)
If CInt(values(indexI)) > CInt(values(indexII)) Then position = position + 1
Next indexII
shuffled(position) = values(indexI)
Next indexI
For indexI = 0 To UBound(shuffled)
tempStr = tempStr + "," + shuffled(indexI)
Next indexI
tempStr = Right(tempStr, Len(tempStr) - 1)
CombinationGenerator = tempStr
End Function
This breaks the functions out. The last function, to check that your generated string does not exist in a range that you have passed to the first function, really depends on expected use.
Option Explicit
Public Function CombinationGenerator(Optional ByRef valueString As String, Optional ByRef rng As Range) As String
Dim tempStr As String
Dim position As Long
Dim inputValues() As String
Dim combination() As String
inputValues = Split(valueString, ",")
ReDim Preserve inputValues(7)
combination = CombinationGenerate(inputValues)
combination = CombinationShuffle(combination)
tempStr = CombinationToString(combination)
tempStr = CombinationNotInRange(tempStr)
CombinationGenerator = tempStr
End Function
Private Function CombinationGenerate(ByRef combination() As String) As String()
Dim indexI As Long
Dim indexII As Long
Dim rndNum As Long
Dim doubleValuePass As Boolean
For indexI = 0 To UBound(combination)
If combination(indexI) = "" Then
Randomize
rndNum = Int(Rnd * 50) + 1
combination(indexI) = CStr(rndNum)
Do Until doubleValuePass = True
doubleValuePass = True
For indexII = 0 To UBound(combination)
If (combination(indexI) = combination(indexII)) And (indexI <> indexII) Then
doubleValuePass = False
Randomize
rndNum = Int(Rnd * 50) + 1
combination(indexI) = CStr(rndNum)
End If
Next indexII
Loop
doubleValuePass = False
End If
Next indexI
CombinationGenerate = combination
End Function
Private Function CombinationShuffle(ByRef combination() As String) As String()
Dim indexI As Long
Dim indexII As Long
Dim position As Long
Dim shuffled(7) As String
For indexI = 0 To UBound(combination)
position = 0
For indexII = 0 To UBound(combination)
If CInt(combination(indexI)) > CInt(combination(indexII)) Then position = position + 1
Next indexII
shuffled(position) = combination(indexI)
Next indexI
CombinationShuffle = shuffled
End Function
Private Function CombinationToString(ByRef shuffledCombination() As String) As String
Dim indexI As Long
Dim tempStr As String
For indexI = 0 To UBound(shuffledCombination)
tempStr = tempStr + "," + shuffledCombination(indexI)
Next indexI
tempStr = Right(tempStr, Len(tempStr) - 1)
CombinationToString = tempStr
End Function
Private Function CombinationNotInRange(ByRef combination As String, Optional ByRef rngToCheck As Range) As String
'Depends
CombinationNotInRange = combination
End Function

FOR Cycle inside a UDF in Excel not working

I'm currently programming an Excel Function which should return the average of the last 5 non-empty positions of an array. To do that I want to go through the array while inside the function as follows:
Function AVERAGE_LAST_5(rng As Range) As Long
Dim x As Integer, i As Integer, j As Integer, sum As Integer
Dim myArr() As Variant
myArr() = Application.Transpose(Application.Transpose(rng))
x = rng.Count
i = 0:: j = 0:: sum = 0
For i = x To 1 Step -1
If myArr(x).Value <> 0 Then
sum = sum + myArr(x)
j = j + 1
Else
End If
If j = 5 Then Stop
x = x - 1
Next
AVERAGE_LAST_5 = sum / 5
End Function
Problem: the for loop doesn't work, when reaching the first if the program aborts.
Does anyone has had the same problem?
Can anyone help me with it?
myarr will be a two-dimensional array, and not a range. You will need to provide both dimensions:
If isarray(myarr) then
for i = ubound(myarr,1) to lbound(myarr,1) step -1
for j = ubound(myarr,2) to lbound (myarr,2) step -1
if myarr(i,j) <> 0 then
K=k+1
Mysum = mysum + myarr(I,j)
Endif
Next j
Next i
Else ‘ single value
mysum =myarr(I,j)
Endif
Arrays Are Faster
Final Version (Hopefully)
This version additionally has the NumberOfLastValues argument (Required) so you can choose how many values will be summed up and it is shortened with the GoSub...Return statement since the If statement is the same for by rows and by columns.
For some other details look in the First Version below.
Usage
In VBA:
Sub LastAverage()
Debug.Print AvgLast(Range("B4:G14"), 5)
End Sub
In Excel:
=AvgLast(B4:G14,5)
Function AvgLast(SearchRange As Range, ByVal NumberOfLastValues As Long, _
Optional ByVal Row_0_Column_1 As Integer = 0) As Double
Dim vntRange As Variant ' Range Array
Dim i As Long ' Range Array Rows Counter
Dim j As Integer ' Range Array Columns Counter
Dim k As Long ' Values Counter
Dim dblSum As Double ' Values Accumulator
If SearchRange Is Nothing Then Exit Function
vntRange = SearchRange.Value
If Row_0_Column_1 = 0 Then
' By Row
For i = UBound(vntRange) To 1 Step -1
For j = UBound(vntRange, 2) To 1 Step -1
GoSub Calc
Next
Next
Else
' By Column
For j = UBound(vntRange, 2) To 1 Step -1
For i = UBound(vntRange) To 1 Step -1
GoSub Calc
Next
Next
End If
TiDa:
If k > 0 Then
AvgLast = dblSum / k
End If
Exit Function
Calc:
If vntRange(i, j) <> "" And IsNumeric(vntRange(i, j)) Then
k = k + 1
dblSum = dblSum + vntRange(i, j)
If k = NumberOfLastValues Then GoTo TiDa
End If
Return
End Function
First Version
It will return the average if there is at least 1 value and at most 5 values, otherwise it will return 0.
The Row_0_Column_1 arguments parameter is by default 0 and means that the search is done by row (first loop). If it is 1, then the search is done by column (second loop).
The basics are that the range is pasted (depsited) into an array and then the array is searched for existing 'numeric' values and not "" values that are summed up and when reaching the fifth value it 'jumps' out of the loop and divides the sum by 5.
Function AvgLast5(SearchRange As Range, Optional Row_0_Column_1 As Integer = 0) _
As Double
Dim vntRange As Variant ' Range Array
Dim i As Long ' Range Array Rows Counter
Dim j As Integer ' Range Array Columns Counter
Dim k As Long ' Values Counter
Dim dblSum As Double ' Values Accumulator
If SearchRange Is Nothing Then Exit Function
vntRange = SearchRange.Value
If Row_0_Column_1 = 0 Then
' By Row
For i = UBound(vntRange) To 1 Step -1
For j = UBound(vntRange, 2) To 1 Step -1
If vntRange(i, j) <> "" And IsNumeric(vntRange(i, j)) Then
k = k + 1
dblSum = dblSum + vntRange(i, j)
If k = 5 Then GoTo TiDa
End If
Next
Next
Else
' By Column
For j = UBound(vntRange, 2) To 1 Step -1
For i = UBound(vntRange) To 1 Step -1
If vntRange(i, j) <> "" And IsNumeric(vntRange(i, j)) Then
k = k + 1
dblSum = dblSum + vntRange(i, j)
If k = 5 Then GoTo TiDa
End If
Next
Next
End If
TiDa:
If k > 0 Then
AvgLast5 = dblSum / k
End If
End Function
after a couple of tough days at work I've finally got some time to improve my function taking your tips on board.
I've made some changes to enable the function to deal either with 1-Row or 1-Column Ranges. Basic Error handling was also added and a function discripton is as well available (under the FX Excel button).
Feel free to comment and/or use the code.
Here the result:
Function AVERAGE_LAST_N(rng As Range, N As Integer)
Dim NrN As Integer, NrR As Integer, NrC As Integer
Dim i As Integer, j As Integer
Dim sum As Double
Dim myArr As Variant
NrN = rng.Count 'Number of array positions
NrR = rng.Rows.Count 'Number of Rows in the array
NrC = rng.Columns.Count 'Number of Rows in the array
i = 0:: j = 0:: sum = 0 'Counters
'####################################################'
'## Transpose Range into array if row or if column ##'
'####################################################'
If rng.Rows.Count > 1 And rng.Columns.Count = 1 Then 'Transpose a Column Range into an Array
myArr = Application.Transpose(rng)
ElseIf rng.Rows.Count = 1 And rng.Columns.Count > 1 Then 'Transpose a Row Range into an Array
myArr = Application.Transpose(Application.Transpose(rng))
ElseIf rng.Rows.Count > 1 And rng.Columns.Count > 1 Then 'Retunrs an Error if Range is a Matrix *ERR_002*
AVERAGE_LAST_N = "ERR_002"
Exit Function
End If
'####################################################'
'## Transpose Range into array if row or if column ##'
'####################################################'
'################'
'## Start Main ##'
'################'
For i = NrN To 1 Step -1
If IsNumeric(myArr(NrN)) Then
sum = sum + myArr(NrN)
j = j + 1
End If
If j = N Then Exit For
NrN = NrN - 1
Next
AVERAGE_LAST_N = sum / N
'##############'
'## End Main ##'
'##############'
'####################'
'## Error Debuging ##'
'####################'
If j < N Then
AVERAGE_LAST_N = "ERR_001"
Exit Function
End If
'####################'
'## Error Debuging ##'
'####################'
End Function
Sub DescribeFunction()
Dim FuncName As String
Dim FuncDesc As String
Dim Category As String
Dim ArgDesc(1) As String
FuncName = "AVERAGE_LAST_N"
FuncDesc = "Returns the average of the last N non-empty values in the selected Range"
Category = 14 'Text category
ArgDesc(0) = "Range that contains the values" & Chr(10) & _
"ERR_001 - There are not enought non-empty or null values in the range" & Chr(10) & _
"ERR_002 - Selected range is a matrix and not a row or column range"
ArgDesc(1) = "Dimention of the sample" & Chr(10) & _
"ERR_001 - There are not enought non-empty or null values in the range" & Chr(10) & _
"ERR_002 - Selected range is a matrix and not a row or column range"
Application.MacroOptions _
Macro:=FuncName, _
Description:=FuncDesc, _
Category:=Category, _
ArgumentDescriptions:=ArgDesc
End Sub
'#######################################################################################
' ###############################################
' ############# Error DB ##############
' ###############################################
'
'
' ERR_001 - There are not enought non-empty values in the range
' ERR_002 - Selected range is a matrix and not a row or column range
'
Rafa

Resources