Number variation, but with limits

I want to find a random number in a range that goes 1-100. I want the number to be the average of these two:

Code Block
var firstNumber = 20
var secondNumber = 30

but I want it to have some "variability"
Code Block
        var variability = Int.random(in:-12...12)


Code Block
result: Int.random(in: (firstNumber)...(secondNumber) + variability)


The problem is, the result cannot go beyond 1-100. If the first number is close to the end of the range, variability could make the result go beyond 1-100. I tried using an If statement, but this involves writing a lot of extra code for each factor I am adding variability to. Any suggestions?

Assume you have 20 for firstNumber, and 30 for secondNumber, and get 6 for variability,
do you want a random number in range 20...36? Doesn't variability affect the lower bound?

 I tried using an If statement,

Can you show an example code using if.
The variability should affect the lower bound if the range goes from -12...+12, so if the result is exactly in between 20 and 30 I may get 25, and then variability can change it to anywhere from 13-37.

By using an if statement I would have to do something like this;

Code Block
var result = (firstNumber+secondNumber)/2 +variability
if result > 100 { result = 100
}
if result < 0 { result = 0
}


I have a lot of numbers to handle and the code will be very long if I need an if statement for each factor, because then I need a variable outside of the code for a lot of the factors. It would be messy so I thought maybe there was a better way


You need to clarify your spec and constraints.

You want a number between first and second, randomly choose.
Then do you want it to range between first and second plus variability
Code Block
let result = Int.random(in: firstNumber...secondNumber + variability)

or do you want to add a random variability to the random between first and second ?
Code Block
let result = Int.random(in: firstNumber...secondNumber) + variability

And you want the final result between 1 and 100.

In the first option (the one you have coded, if second + variability < first, then you'll crash !

What do you want to do if the result is out of bounds ?
The simplest may be to consider the draw is invalid and start again ?

Then code would be
Code Block
let firstNumber = 20 // no need for var ?
let secondNumber = 30
let variabilityRange = 12
func rollADraw() -> Int? {
let variability = Int.random(in:-variabilityRange...variabilityRange)
let result = Int.random(in: firstNumber...secondNumber) + variability
if result >= 1 && result <= 100 { // use the result}
return result
} else {
return nil // Will call a new draw
}
}
var result: Int? = nil
while result == nil {
result = rollADraw()
}
print("Result", result!)

I figured if the result was out of bounds I could just "stop" it at 1 or 100 and use that as the result. So if my result is 95 and variability gives me 10, I get 105 and the code changes it to 100. Hope this isn't too confusing...

I figured if the result was out of bounds I could just "stop" it at 1 or 100 and use that as the result. So if my result is 95 and variability gives me 10, I get 105 and the code changes it to 100

No, that's is a bad idea.
That hurts the randomness, as you will have an excess of 100 values.

Another way would be to test :
firstNumber + variability >= 1
and
secondNumber + variability <= 100

if any is false, don't play.

I don't understand what you want to do here:
Code Block
var result = (firstNumber+secondNumber)/2 +variability

that's no more a random draw between first and second.

You probably need to clarify your spec.
I'll use a clearer example so it's easier to understand.
The numbers represent the value of some stats that are passed to offspring when two horses are bred together. The resultant foal will have an "average" of stats from the father and the mother, but there is some variability. The foal has a small chance to be better or worse than both parents, but in general it is pretty much somewhere between the stats of the parents. none can be below 1 or above 100, however
If you are interested to know what is the probability to get any number between 1 and 100 with such draw, here is code you can test in playground.

The logic is to compute Probability that the draw is n, with n between 1 and 100
This is the probability P(x = n), computed in
Code Block
func P(_ n: Int) -> Double

This probability is computed as:
Sum ( Probability(r = m) * Probability(v = n-m)
Where
  • Probability(r = m) is the probability that the first random between first and second gives m

This probability is zero out of [first...second] and 1 / (second - first + 1) inside
This is computed in
Code Block
func pR(_ r: Int) -> Double
  • Probability(v = n-m) is the probability that variability is n-m

This probability is zero out of [-variability...variability] and 1 / (2*variability+1) inside
This is computed in
Code Block
func pV(_ v: Int) -> Double

Sum ( Probability(r = m) * Probability(v = n-m) is computed in
Code Block
func P(_ n: Int) -> Double {
var r = 0.0
for m in firstNumber...secondNumber {
r += pR(m) * pV(n - m)
}
return r
}

Here is the full code for computation:
Code Block
let firstNumber = 20
let secondNumber = 30
let variabilityRange = 12
func pR(_ r: Int) -> Double {
if r < firstNumber r > secondNumber { return 0.0 }
return 1/Double(secondNumber - firstNumber + 1)
}
func pV(_ v: Int) -> Double {
if v < -variabilityRange v > variabilityRange { return 0.0 }
return 1/(Double(2 * variabilityRange + 1))
}
func P(_ n: Int) -> Double {
var r = 0.0
for m in firstNumber...secondNumber {
r += pR(m) * pV(n - m)
}
return r
}
var sigma = 0.0
for x in 1...100 {
print(x, " :", P(x))
sigma += P(x)
}
print("Sigma", sigma)

which yields
<= 7 : 0.0
8 : 0.0036363636363636364
9 : 0.007272727272727273
10 : 0.01090909090909091
11 : 0.014545454545454545
12 : 0.01818181818181818
13 : 0.021818181818181816
14 : 0.025454545454545452
15 : 0.029090909090909087
16 : 0.03272727272727272
17 : 0.03636363636363636
18 : 0.04
19 : 0.04
20 : 0.04
21 : 0.04
22 : 0.04
23 : 0.04
24 : 0.04
25 : 0.04
26 : 0.04
27 : 0.04
28 : 0.04
29 : 0.04
30 : 0.04
31 : 0.04
32 : 0.04
33 : 0.03636363636363636
34 : 0.03272727272727272
35 : 0.029090909090909087
36 : 0.025454545454545452
37 : 0.021818181818181816
38 : 0.01818181818181818
39 : 0.014545454545454545
40 : 0.01090909090909091
41 : 0.007272727272727273
42 : 0.0036363636363636364
>= 43 : 0.0
Sigma 1.0000000000000002

Note:
you see it grows linearity from 8 to 18
Then fix value of 0.04
and decrease linearily from 33 to 42.
That makes the computation absolutely immediate, without heavy computation.

With different initial data that may pass over 100:
Code Block
let firstNumber = 80
let secondNumber = 90
let variabilityRange = 15

You get
<=64 : 0.0
65 : 0.002932551319648094
66 : 0.005865102639296188
67 : 0.00879765395894428
68 : 0.011730205278592375
69 : 0.01466275659824047
70 : 0.017595307917888565
71 : 0.02052785923753666
72 : 0.023460410557184754
73 : 0.02639296187683285
74 : 0.029325513196480944
75 : 0.03225806451612904
76 : 0.03225806451612904
77 : 0.03225806451612904
78 : 0.03225806451612904
79 : 0.03225806451612904
80 : 0.03225806451612904
81 : 0.03225806451612904
82 : 0.03225806451612904
83 : 0.03225806451612904
84 : 0.03225806451612904
85 : 0.03225806451612904
86 : 0.03225806451612904
87 : 0.03225806451612904
88 : 0.03225806451612904
89 : 0.03225806451612904
90 : 0.03225806451612904
91 : 0.03225806451612904
92 : 0.03225806451612904
93 : 0.03225806451612904
94 : 0.03225806451612904
95 : 0.03225806451612904
96 : 0.029325513196480944
97 : 0.02639296187683285
98 : 0.023460410557184754
99 : 0.02052785923753666
100 : 0.017595307917888565
Sigma 0.9560117302052784

Same note applies here.

You see that if you trim values over 100 to 100, that gives a probability of 0.06158357771261014 to get 100
which is no more a random distribution

Redoing the draw if it is over 100 leads to multiply evenly each probability by 1/ 0.9560117302052784 which is around 1.046
Then Probability for 100 is 0,0184 (versus 0,0616)
What I want to do is to calculate the mean of two numbers, then add variability, but limit the value to a minimum of 1 and a maximum of 100.
Your initial post was completely misleading
Code Block
result: Int.random(in: (firstNumber)...(secondNumber) + variability)

So if you just want to compute the mean, it is effectively trivial and you don't have the double random.

but once again, don't trim the value to 0 or 100, that skews the result.
Just ignore the draw and do a new one.

That's what I explained in a previous answer.
Code Block
var result: Int? = nil
while result == nil {
result = rollADraw()
}
print("Result", result!)

You're right about the double random, I changed one to a mean. The problem is that it's not totally random. If two individuals have 100 the offspring will have a similar result. This is what I have so far with If statements. I thought there may be a shorter way of writing it, but for now it works

Code Block     @IBAction func breedHorsesButton(_ sender: Any) {
        let sire = myStallions[sireIndex]
        let dam = myMares[damIndex]
        let statVariability = Int.random(in:-12...12)
        let conformationVariability = Int.random(in:-1...1)
        //conformation
        var foalHead = ((dam.head + sire.head)/2)+conformationVariability
        if foalHead > 4 {foalHead = 4}
        if foalHead < 0 {foalHead = 0}
        
        var foalNeck = ((dam.neck + sire.neck)/2)+conformationVariability
        if foalNeck > 4 {foalNeck = 4}
        if foalNeck < 0 {foalNeck = 0}
      
        var foalBack = ((dam.back + sire.back)/2)+conformationVariability
        if foalBack > 4 {foalBack = 4}
        if foalBack < 0 {foalBack = 0}
        var foalLegs = ((dam.legs + sire.legs)/2)+conformationVariability
        if foalLegs > 4 {foalLegs = 4}
        if foalLegs < 0 {foalLegs = 0}
        var foalHindquarters = ((dam.hindquarters + sire.hindquarters)/2)+conformationVariability
        if foalHindquarters > 4 {foalHindquarters = 4}
        if foalHindquarters < 0 {foalHindquarters = 0}
        var foalSaddleSize = ((dam.saddleSize + sire.saddleSize)/2)+conformationVariability
        if foalSaddleSize > 4 {foalSaddleSize = 4}
        if foalSaddleSize < 0 {foalSaddleSize = 0}
        //stats
        var foalReactivity = ((sire.reactivity + dam.reactivity)/2)+statVariability
        if foalReactivity > 100 {foalReactivity = 100}
        if foalReactivity < 1 {foalReactivity = 1}
        
        if sire.health > 95 && dam.health > 95 {
            generateFoalGenotype()
            checkPhenotype()
            myHorses.append(Horse(
                //Base stats
                name: "New Foal",
                gender: ["Mare", "Stallion"][Int.random(in:0...1)],
                reactivity: foalReactivity,
               
//Conformation
                head: foalHead,
                neck: foalNeck,
                back: foalBack,
                legs: foalLegs,
                hindquarters: foalHindquarters,
                saddleSize: foalSaddleSize,


Thanks for the feedback.

I’m not sure to fully understand what you want to achieve, so I will just wish you good continuation.

Don’t forget to close the long thread now that you have a solution that suits you.

This is what I have so far with If statements. I thought there may be a shorter way of writing it

If nearly the same processing repeats in your code, why not defining your own function?

Define a function somewhere in your project:
Code Block
func clampedBiassedAverage(_ value1: Int, _ value2: Int, bias: Int, in range: ClosedRange<Int>) -> Int {
var result = ((value1 + value2)/2)+bias
if result > range.upperBound {result = range.upperBound}
if result < range.lowerBound {result = range.lowerBound}
return result
}

And use it like this:
Code Block
//conformation
var foalHead = clampedBiassedAverage(dam.head, sire.head, bias: conformationVariability, in: 0...4)
var foalNeck = clampedBiassedAverage(dam.neck, sire.neck, bias: conformationVariability, in: 0...4)
var foalBack = clampedBiassedAverage(dam.back, sire.back, bias: conformationVariability, in: 0...4)
var foalLegs = clampedBiassedAverage(dam.legs, sire.legs, bias: conformationVariability, in: 0...4)
var foalHindquarters = clampedBiassedAverage(dam.hindquarters, sire.hindquarters, bias: conformationVariability, in: 0...4)
var foalSaddleSize = clampedBiassedAverage(dam.saddleSize, sire.saddleSize, bias: conformationVariability, in: 0...4)
//stats
var foalReactivity = clampedBiassedAverage(dam.reactivity, sire.reactivity, bias: conformationVariability, in: 1...100)


You may need to update only one function clampedBiassedAverage when you want some better randomness.
Number variation, but with limits
 
 
Q