Shg and my "cut a string 3 places randomly" approach works as randomly as the million monkey approach. The only adjustment would be rounding to get to integers. But, in both my VBA approach and Shg's spreadsheet approach, "adjust the last number up" occurs as often as "adjust the last number down" so it doesn't really matter.
Your way doesn't qualify Mike because it generates numbers between -1 and +21.
Mr shg has a similar problem with his solution. It returns numbers from 0 to 21 (with diminishing probability).
I assume that the numbers generated should be between 1 and 9. If zeros were allowed (which, although I don't know for sure I very much doubt), then it would have to allow a leading zero or have more jiggery pokery to manage the first digit as a special case.
My argument as stated above is that the algorithm of simply trying all cases (random numbers between 1 and 9 inclusive) and discarding the sets that don't add up to 21 is correct
prima faci. By that I mean "on the face of it" or "on the surface". In other words without having to offer a rigorous proof to support its correctness.
Any additions to your algorithms to bring them back to the allowable space will take it further from the
prima faci correct mega-monkey algorithm and this would require more work to prove that they are correct.
Your solution and Mr shg's, while very clever and creative, are not only not correct prima faci, they are clearly incorrect.
Randomness is surprisingly delicate and random systems have to be treated very carefully and conservatively in order not to leave finger prints. Thats is my basic point. And anyway, I did a little test and my 1.36 million monkeys took 25 seconds to generate and document 100,000 acceptible solutions, an average of 250 micro seconds and with a 99.99% chance of doing it in under 22 milliseconds, so there is no perceptible delay with the monkey approach and therefore no reason not to use it.
Here is the code to do it
Code:
Sub Rand4()
'Select as many output rows as you want in a column and the results will be output to the right.
'For a single solution, just select one cell.
Const limit As Long = 21
Dim r(1 To 4) As Long, rSum As Long, i As Long, c As Range, attemptsCount As Long
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
For Each c In Selection
attemptsCount = 0
Do
rSum = 0
For i = 1 To 4
r(i) = Round(Rnd() * 9 + 0.5, 0)
rSum = rSum + r(i)
Next i
attemptsCount = attemptsCount + 1
Loop Until rSum = limit
c.Value2 = attemptsCount
For i = 1 To 4
c.Offset(0, i).Value2 = r(i)
Next i
Next c
With Application
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
End With
End Sub