How to get random numbers with percent to show Rowel Virgo used Ask the Experts™
on
Hi i'm creating a program in vb that can generate random numbers. the thing is i want to lessen the chance to appear the highest number.

for Example i have numbers from 1-10 (in ramdom)

Number 10 the chance to appear is 10%
Number 9 the chance to appear is 20%
Number 8 the chance to appear is 30%
etc..

here is my sample code.
Dim R1 As New Random
Dim d1result1 As Integer = R1.Next(1, 10)
Label2.Text = d1result1.ToString

I want 1,2,3,4,5 appears more often
and 6,7,8,9,10 has less chance to appears.
Comment
Watch Question

Do more with EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Freelance programmer / Consultant

Commented:
So do you want random numbers or not?  Because weighting some numbers means they are not random.
Senior Developer

Commented:
Well, it is pretty simple..

Module Module1
Sub Main()
Dim Generator As New Random
Dim Weights() As Long = New Long(9) {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

Console.WriteLine(WeightedRandom(Weights, Generator))
Console.WriteLine(WeightedRandom(Weights, Generator))
Console.WriteLine(WeightedRandom(Weights, Generator))
Console.WriteLine(WeightedRandom(Weights, Generator))
Console.WriteLine(WeightedRandom(Weights, Generator))
Console.WriteLine(WeightedRandom(Weights, Generator))

Console.WriteLine("Done.")
End Sub

Function WeightedRandom(AWeights() As Long, AGenerator As Random) As Long
Dim MapCount As Long
Dim MapIndex As Long
Dim Range As Long = AWeights.Sum()
Dim WeightCount As Long
Dim Map(Range - 1) As Long

MapIndex = 0
For WeightCount = 0 To UBound(AWeights)
For MapCount = 1 To AWeights(WeightCount)
Map(MapIndex) = WeightCount
MapIndex = MapIndex + 1
Next
Next

WeightedRandom = Map(AGenerator.Next(0, Range - 1))
End Function
End Module
Consulting
Distinguished Expert 2017

Commented:
One way to achieve this is with an array.
The array elements will hold the chances of you number to get choosen.
The array index will be the desired number

Sample below:
Public Function weightedRandomNumber() As Integer
Const MIN As Integer = 1
Const MAX As Integer = 100
Dim numbers(1 To 10) As Integer
numbers(1) = 15
numbers(2) = 30
numbers(3) = 45
numbers(4) = 60
numbers(5) = 75
numbers(6) = 80
numbers(7) = 85
numbers(8) = 90
numbers(9) = 95
numbers(10) = 100

Randomize
Dim randomValue As Integer
randomValue = (MAX - MIN + 1) * Rnd + 1

Dim i As Integer
For i = 1 To 10
If (randomValue < numbers(i)) Then
weightedRandomNumber = i
Exit For
End If
Next
End Function
Ops, didn't notice it was .Net.
Well, hope you'll get the idea.
Senior Developer

Commented:
@Fabrice: But it requires careful calculation of your array..

Dim Weights() As Long = New Long() {3, 1, 2}     vs   Dim numbers() As Long = New Long() {50, 67, 100}

Module Module1
Sub Main()
Dim Generator As New Random
Dim Weights() As Long = New Long() {3, 1, 2}

Console.WriteLine(WeightedRandom(Weights, Generator))
Console.WriteLine(WeightedRandom(Weights, Generator))
Console.WriteLine(WeightedRandom(Weights, Generator))
Console.WriteLine(WeightedRandom(Weights, Generator))
Console.WriteLine(WeightedRandom(Weights, Generator))
Console.WriteLine(WeightedRandom(Weights, Generator))
Console.WriteLine(WeightedRandom(Weights, Generator))
Console.WriteLine(WeightedRandom(Weights, Generator))
Console.WriteLine(WeightedRandom(Weights, Generator))
Console.WriteLine(WeightedRandom(Weights, Generator))

Console.WriteLine("--")

Dim numbers() As Long = New Long() {50, 67, 100}
Console.WriteLine(WeightedRandom2(numbers, Generator))
Console.WriteLine(WeightedRandom2(numbers, Generator))
Console.WriteLine(WeightedRandom2(numbers, Generator))
Console.WriteLine(WeightedRandom2(numbers, Generator))
Console.WriteLine(WeightedRandom2(numbers, Generator))
Console.WriteLine(WeightedRandom2(numbers, Generator))
Console.WriteLine(WeightedRandom2(numbers, Generator))
Console.WriteLine(WeightedRandom2(numbers, Generator))
Console.WriteLine(WeightedRandom2(numbers, Generator))
Console.WriteLine(WeightedRandom2(numbers, Generator))

Console.WriteLine("Done.")
End Sub

Function WeightedRandom(AWeights() As Long, AGenerator As Random) As Long
Dim MapCount As Long
Dim MapIndex As Long
Dim Range As Long = AWeights.Sum()
Dim WeightCount As Long
Dim Map(Range - 1) As Long

MapIndex = 0
For WeightCount = 0 To UBound(AWeights)
For MapCount = 1 To AWeights(WeightCount)
Map(MapIndex) = WeightCount
MapIndex = MapIndex + 1
Next
Next

WeightedRandom = Map(AGenerator.Next(0, Range - 1)) + 1
End Function

Function WeightedRandom2(AWeights() As Long, AGenerator As Random) As Long
Dim index As Long
Dim randomValue As Long = AGenerator.Next(1, 100)

WeightedRandom2 = -1
For index = 0 To UBound(AWeights)
If (randomValue < AWeights(index)) Then
WeightedRandom2 = index + 1
Exit For
End If
Next
End Function
End Module

p.s. it must be

WeightedRandom = Map(AGenerator.Next(0, Range - 1)) + 1
in my function, otherwise it is off by one.
Consulting
Distinguished Expert 2017

Commented:
@ste5an:
@Fabrice: But it requires careful calculation of your array..
Yeah, ehence why it is "one way to achieve it".
Senior Developer

Commented:
omg, still of by one, should have rtfm first, the upper boundary of Next() is exclusive:

Function WeightedRandom(AWeights() As Long, AGenerator As Random) As Long
Dim MapCount As Long
Dim MapIndex As Long
Dim Range As Long = AWeights.Sum()
Dim WeightCount As Long
Dim Map(Range - 1) As Long

MapIndex = 0
For WeightCount = 0 To UBound(AWeights)
For MapCount = 1 To AWeights(WeightCount)
Map(MapIndex) = WeightCount
MapIndex = MapIndex + 1
Next
Next

WeightedRandom = Map(AGenerator.Next(0, Range)) + 1
End Function

Function WeightedRandom2(AWeights() As Long, AGenerator As Random) As Long
Dim index As Long
Dim randomValue As Long = AGenerator.Next(1, 101)

WeightedRandom2 = -1
For index = 0 To UBound(AWeights)
If (randomValue < AWeights(index)) Then
WeightedRandom2 = index + 1
Exit For
End If
Next
End Function
Top Expert 2014

Commented:
I expanded your specification list and see that you might want to explain how often you see a 1
10	10%
9	20%
8	30%
7	40%
6	50%
5	60%
4	70%
3	80%
2	90%
1	100%
Senior Developer

Commented:
In a 100 of 550 cases.. or so ;)
Commented:
Just to visualize solution :)
Private rnd = New Random
Private Function GetWeightedRandom() As Integer
Dim weight_sum As Integer = 550
Dim w = rnd.Next(weight_sum)
Select Case w
Case 0 To 10 '10 times
Return 10
Case 11 To 30 '20 times
Return 9
Case 31 To 60 '30 times
Return 8
Case 61 To 100 '40
Return 7
Case 101 To 150 '50
Return 6
Case 151 To 210 '60
Return 5
Case 211 To 280 '70
Return 4
Case 281 To 360 '80
Return 3
Case 361 To 450 '90
Return 2
Case 451 To 550 '100
Return 1
End Select
Return 0
End Function

Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim counters(10) As Integer
For i = 1 To 100000
counters(GetWeightedRandom()) += 1
Next
For i = 0 To 10
Debug.Print(i & "=>" & counters(i))
Next
End Sub
Senior Developer
Commented:
To visualize the logic I would separate map creation from random number creation. This should be also done for performance issues:

Module Module1
Sub Main()
Dim Generator As New Random
Dim Map() As Long
Dim RandomNumber As Long
Dim Weights() As Long = New Long(5) {1, 2, 2, 2, 3, 5}         'Fake dice.

Map = CreateMap(Weights)
RandomNumber = WeightedRandom(Map, Generator)
RandomNumber = WeightedRandom(Map, Generator)
RandomNumber = WeightedRandom(Map, Generator)
RandomNumber = WeightedRandom(Map, Generator)
RandomNumber = WeightedRandom(Map, Generator)
RandomNumber = WeightedRandom(Map, Generator)

Console.WriteLine("Done.")
End Sub

Private Function CreateMap(AWeights() As Long) As Long()
Dim MapCount As Long
Dim MapIndex As Long
Dim Range As Long = AWeights.Sum()
Dim WeightCount As Long
Dim Map(Range - 1) As Long

MapIndex = 0
For WeightCount = 0 To UBound(AWeights)
For MapCount = 1 To AWeights(WeightCount)
Map(MapIndex) = WeightCount
MapIndex = MapIndex + 1
Next
Next

Console.WriteLine("Map from weights: {0}", String.Join("", Map))
CreateMap = Map
End Function

Function WeightedRandom(AMap() As Long, AGenerator As Random) As Long
Dim Range As Long = UBound(AMap)
Dim RandomNumber As Long = AGenerator.Next(0, Range)

WeightedRandom = AMap(RandomNumber) + 1
Console.WriteLine("Random number {0} maps to {1}.", RandomNumber, WeightedRandom)
End Function
End Module

or cleaner

Module Module1
Sub Main()
Dim Weights() As Long = New Long(5) {1, 2, 2, 2, 3, 5}         'Fake dice.
Dim weigthedRandoms As New WeightedRandom(Weights, True)

Console.WriteLine("Instanciated..")
weigthedRandoms.NextNumber()
weigthedRandoms.NextNumber()
weigthedRandoms.NextNumber()
weigthedRandoms.NextNumber()
weigthedRandoms.NextNumber()
weigthedRandoms.NextNumber()

Console.WriteLine("Shared..")
WeightedRandom.NextNumber(New Long(3) {1, 2, 3, 4}, True)

Console.WriteLine("Done.")
End Sub

End Module

Class WeightedRandom
Private generator As New Random
Private lastNumber As Long
Private map() As Long
Private range As Long
Private showDebugInformation As Boolean

Public Sub New(AWeights() As Long, Optional AShowDebugInformation As Boolean = False)
Me.showDebugInformation = AShowDebugInformation
Me.CreateMap(AWeights)
End Sub

Public Function CurrentNumber() As Long
lastNumber = Me.lastNumber
End Function

Public Function NextNumber() As Long
Me.GenerateNextNumber()
NextNumber = Me.lastNumber
End Function

Public Shared Function NextNumber(AWeights() As Long, Optional AShowDebugInformation As Boolean = False) As Long
Dim weigthedRandoms As New WeightedRandom(AWeights, AShowDebugInformation)
NextNumber = weigthedRandoms.NextNumber
weigthedRandoms = Nothing
End Function

Private Sub CreateMap(AWeights() As Long)
Dim MapCount As Long
Dim MapIndex As Long
Dim WeightCount As Long

Me.range = AWeights.Sum()
ReDim Me.map(range - 1)
MapIndex = 0
For WeightCount = 0 To UBound(AWeights)
For MapCount = 1 To AWeights(WeightCount)
Me.map(MapIndex) = WeightCount
MapIndex = MapIndex + 1
Next
Next

If showDebugInformation Then
Console.WriteLine("Map from weights: {0}", String.Join("", Map))
End If
End Sub

Private Sub GenerateNextNumber()
Dim RandomNumber As Long = Me.generator.Next(0, Me.range)

Me.lastNumber = Me.map(RandomNumber) + 1
If showDebugInformation Then
Console.WriteLine("Random number {0} maps to {1}.", RandomNumber, Me.lastNumber)
End If
End Sub
End Class

and maybe we can use an singleton here..
Freelance programmer / Consultant

Commented:
Just don't forget any numbers generated like this are not random.  For a number to be random then it must have an equal chance with any other number in the desired range.

Commented:
No comment has been added to this question in more than 21 days, so it is now classified as abandoned.

I have recommended this question be closed as follows:

Split:
-- ste5an (https:#a42539675)
-- Ark (https:#a42539448)

If you feel this question should be closed differently, post an objection and the moderators will review all objections and close it as they feel fit. If no one objects, this question will be closed automatically the way described above. 