A coding conundrum - weighted randoms?

BlitzMax Forums/BlitzMax Programming/A coding conundrum - weighted randoms?

GfK(Posted 2009) [#1]
I'm having a brain fart.

What I want to do, is have a call to Rand() that will return a result, either 0, 1, or 2. Let's call them Bronze, Silver, and Gold for the sake of coherence.

I have a 'bias' parameter, which can be any value from 1 to 10. 1 will be mostly bronze, 10 will be mostly gold. A bias of 5 would be a fairly even mix of all three.

What's the best way of generating a random result while taking the bias value into account?


matibee(Posted 2009) [#2]
I was googling this yesterday but didn't find a suitable solution that suited my purpose.

Normal weighted randoms have a table representing probabilty, where;
prob[] = [0,0,0,0,0,1,1,1,2,2]
result = prob[ rand( 0, 9 ) ]

You could do it with a set of tables;
weight1[] = [0,0,0,0,0,1,1,1,2,2]
weight2[] = [0,0,0,1,1,1,1,2,2,2]

weight = rand( 0, 1 )
if ( weight = 1 )
 return weight1[ rand(0,9) ]
else 
 return weight2[ rand(0,9) ]
end if


It's a quick and easy way, simple to test run. I don't know enough about probability math to describe a better solution :)

Cheers
Matt

( first post! w00t!)


GfK(Posted 2009) [#3]
I've spent unhealthy amounts of time on google over this too, and that's the best solution I could come up with as well. But I can't help feeling there has to be a better way?

Oh, and welcome. :D


matibee(Posted 2009) [#4]
Thanks for the welcome :)

Food for thought;

The weight tables can also be parabolic curves. Think of the X axis going left to right, Y is the height (or value);

[0,0,0,1,1,1,1,2,2,3,2,2,1,1,1,1,0,0,0]

The form of the curve can give more weight to higher or lower values so there's no need to precalculate the probabilities.


markcw(Posted 2009) [#5]
Surely it's as simple as this?

maxbias=10
bias=rand(1,maxbias)
value=rand(0,2)
result=((value*maxbias)+bias)/maxbias)-1


Warpy(Posted 2009) [#6]
didn't we have a biiiig discussion about this a few months ago?

My method is to have an array of probabilities, and I think that would be clearer than matibee's method if you want the weighting to depend on a parameter.

Strict


Function weightedrand(probs#[])
	Local p#=Rnd(0,1)	'generate a uniform random variable P, which we will transform into a weighted variable X
	Local i=0
	Local tot#=probs[0]
	While p>tot
		i:+1
		tot:+probs[i]
	Wend
	Return i
End Function


'test - draw a bar graph of 1000 runs
Graphics 600,600,0
SeedRnd MilliSecs()

Local probs#[]
Local results[3]

Local ox=-1,mx
Local i,c
Local bias#

While Not (KeyHit(KEY_ESCAPE) Or AppTerminate())
	mx=MouseX()
	If mx<>ox
		bias#=.1+.8*(mx/600.0)	'bias is 0.1 when mouse is on left of screen, 0.9 when mouse is on right of screen
		
		probs=[bias*2/3,1.0/3,(1-bias)*2/3]	'remember, probabilities have to add up to 1
									'I've made the probability of getting 1 to be halfway between that of 0 and 2
									'which effectively means the probability of getting 1 is always 1/3
		
		For i=0 To 2
			results[i]=0
		Next
		
		For c=1 To 1000
			i=weightedrand(probs)
			results[i]:+1
		Next
		
		ox=mx
	EndIf	
	
	DrawText "Bias: "+bias,250,0
	
	Local width=600/Len(results)
	For i=0 To 2
		Local x#=i*width
		Local y#=600-results[i]*600/1000.0
		DrawRect x,y,width,600-y
		DrawText results[i]+"/1000   (P = 0."+Int(probs[i]*100)+")",x,y-13
	Next
	Flip
	Cls
Wend



markcw - your code can generate a result of -1 when bias=1 and value=0.


matibee(Posted 2009) [#7]
probs=[bias*2/3,1.0/3,(1-bias)*2/3]


That's it, right there in a nutshell :)


TommyH(Posted 2009) [#8]
There is a nice article about never-ending shuffled sequences (when random is not random enough) over here.

Although the source code is in Java the introduction explains it all.


ImaginaryHuman(Posted 2009) [#9]
Someone asked about this a while ago I think.

What springs to mind is simple to make an array which represents the frequency of occurance of each value, so maybe you'll have 3 x 1's, 10 x 2's and 25 x 3's. Then just pick a random index.

I think this is an integer version of the above formula ;-D


Warpy(Posted 2009) [#10]
Here's what I understood TommyH's article to mean:

Function shuffler:tshuffler(numbers[])
	s:tshuffler=New tshuffler
	s.numbers=numbers
	Return s
End Function

Type tshuffler
	Field i
	Field numbers[]
	
	Method nxt()
		c=Rand(i,Len(numbers)-1)
		b=numbers[c]
		numbers[c]=numbers[i]
		numbers[i]=b
		i:+1
		If i=Len(numbers) i=0
		Return b
	End Method
End Type

s:tshuffler=shuffler([1,2,3,4,5])
For c=1 To 25
	Print s.nxt()
Next


It's like a knuth shuffle that loops back round when it's done.


ImaginaryHuman - your method is what matibee suggested in the second post. GreyAlien made the good point in the old thread that it involves only one array lookup, but I don't like it just from an aesthetic standpoint. This all just depends on how comfortable you are with the mathematics of probability, though. Maybe that's an idea for my next warpy code maths blog?


Floyd(Posted 2009) [#11]
We have the usual difficulty here. The problem would be easy if it were well defined. But we don't know what bias really means. For example, bias = 1 means "mostly bronze". What number is that?

For a given bias we must determine the probability of bronze. Call it pB. Then we generate a random float in the range 0 to 1 with Rnd(0,1). If this number is less than pB then we have chosen Bronze. Otherwise we must choose Silver or Gold, according to some weighting.

To do this we need probabilities for Silver and Gold, call them pS and pG. Once we know these we generate Rnd( 0, pS+pG). If the value is less than pS then we choose Silver, otherwise we choose gold.

The only remaining task to define bias. i.e. for a given bias just what are pB, pS, and pG. They must be non-negative and add up to 1.


Warpy(Posted 2009) [#12]
Actually, following what you say, the probability of getting a silver is (1-pB)*pS, not just pS, since you need to not get a bronze first.


ImaginaryHuman(Posted 2009) [#13]
You could also just use RndFloat() and then if the value is <0.1 choose bronze, if it's >0.6 choose gold, otherwise choose silver ??


Zeke(Posted 2009) [#14]
http://www.blitzmax.com/Community/posts.php?topic=76412#854464


Floyd(Posted 2009) [#15]
Actually, following what you say, the probability of getting a silver is (1-pB)*pS...


The probability of Silver is (1-pB) * ( pS / (pS+pG) ).

pS+pG equals 1-pB, so this simplifies to pS as desired.

This is all standard technique when dealing with conditional probability.


GfK(Posted 2009) [#16]
I went with matibee's original suggestion. It suits my needs, doesn't need to be especially fast (not that its slow) as I'm not doing it in realtime, and was easy to implement. All the other suggestions are a bit overkill for my requirements. Feel free to discuss further though, in case somebody is asking the same question later.


Floyd(Posted 2009) [#17]
That's fine as long as it represents what you want. His scheme is:

bias must be in the range 0 to 1.
Silver alway has probability 1/3.
Bronze and Gold split the remaining 2/3, based on bias, e.g.
bias = 0.4 means bronze gets 40% of the non-silver leftovers.

Using these probabilities the method I outlined becomes:

If Rnd( 0, 1 ) <= 1.0/3.0 then result is Silver.
Else if Rnd( 0, 1 ) <= bias result is Bronze.
Else result is Gold


matibee(Posted 2009) [#18]
I had issues with that method when it came to my own needs. The article TommyH pointed out would have suited me more, but I didn't find that method and ending up rolling my own variation for when 'random is too random'.

Rand averages out nicely over very large samples and it's too easy to type 'for t = 0 to 10000000' in test code and get a false impression that this will suit your purpose.

In smaller test runs (say, 20 or 50) it's easy to see that your 'weights' can often have very little bearing.

I had 8 powerups with the probability array [0.2,0.2,0.15,0.05,0.05,0.2,0.1,0.05] but wanted to make sure they all came out over small runs. Again the shuffle method would have been perfect, but here's what I came up with...

Keep a dynamic probability array to choose from
When an item is chosen, half it's current probability (or experiment with other ratios)
Spread the other half of it's probability over the other 7 items according to their initial probabilites

It worked out well enough to live with but next time I'll test out the never ending shuffle method.