M[UMPS] Functions - $R[ANDOM]

Introduced in the 1977 ANSI M[UMPS] language standard.

This function returns a random (integer) value.

(The notation [0..n) means these examples that the result is an integer value between 0 (inclusive) and n (exclusive).)

Reference   Value
$RANDOM(N)   (0, N)
$RANDOM(2)   1 or 0
$RANDOM(2.3)   1 or 0
$RANDOM(1)   0
$RANDOM(0)   Error (M3)
$RANDOM(-1)   Error (M3)

A common mistake is to write code like

 Kill Selected
 For i=1:1:max Do
 . Set ok=0 For  Do  Quit:ok
 . . Set r=$Random(max)+1
 . . Set:'$Data(Selected(r)) ok=r
 . . Quit
 . Set result(i)=ok
 . Set Selected(ok)=1
 . Quit

This code will repeat a random selection until it finds an element that hasn't been selected before, and typically, one doesn't even notice that it may take multiple attempts to find an "not-yet-selected" element until about 90% of the elements have been selected. After that, the time that is spent in the multiple iterations to find a "not-yet-selected" element typically tend to consume noticeable amounts of time.
In short, it does pay to avoid multiple attempts to find a "not-yet-selected" element.

In true keeping with the spirit of a Standards Development Committee, the amount of attention paid to a topic should be in reverse proportion to the importance of the topic.

So... since many people feel that $Random is the least important function in the standardized suite, here goes:

To shuffle a deck of cards:

 Set DeckOne=""
 For i=1:1:52 Set DeckOne=DeckOne_$Char(i)
 Set DeckTwo=""
 For i=52:-1:1 Do
 . Set card=$Random(i)+1
 . Set DeckTwo=DeckTwo_$ASCII(DeckOne,card)
 . Set $Extract(DeckOne,card)=""
 . Quit
 Set suites="Clubs Hearts Clovers Diamonds"
 Set cards="Ace 2 3 4 5 6 7 8 9 10 Jack Queen King"
 For i=1:1:52 Do
 . Set code=$ASCII(DeckTwo,i)-1
 . Set suite=code\13+1,card=code#13+1
 . Write !,$Piece(cards," ",card),
 . Write " of ",$Piece(suites," ",suite)
 . Quit

When displaying the shuffled deck, internal code numbers 1 through 13 correspond to the suite of Clubs, 14 through 26 the suite of Hearts, 27 through 39 the Clovers, and 40 through 52 the Diamonds.
Within each suite, the first element corresponds to the Ace, and the thirteenth to the King.

Note that one (random) card is removed from DeckOne at each step of the loop, and is added to DeckTwo. Thus, the random selection can never select the same card twice.

Of course, the shuffle can also be accomplished by using one single variable that holds both the original deck of cards, and the cards that still need to be randomized.
The trick is to interchange the first card with a randomly selected one, then the second one, etcetera.
Note that the loop only goes to 51 in this case, because once 51 cards have been selected, the remaining one doesn't need the random generator to be able to find it anymore... (nor does it need to be interchanged with any other card).

 Set Deck=""
 For i=1:1:52 Set Deck=Deck_$Char(i)
 Set n=52 For i=1:1:51 Do
 . Set r=$Random(n)
 . Set n=n-1
 . Set temp=$Extract(Deck,i+r)
 . Set $Extract(Deck,i+r)=$Extract(Deck,i)
 . Set $Extract(Deck,i)=temp
 . Quit

The above approach works fine as long as the collection of elements has a predictably small size (particularly: less than the number of characters in the character-set).
So, with the 7-bit ASCII set, this will work uip to 127 elements, with the 8-bit ASCII, it will work up to 255 elements, with with Unicode, it will work up to 65,535 elements, and with ISO-10646 it would work up to 2**32-1 elements (4,294,967,295), but there currently aren't any known complete implementations of that character-set.

So, for large collections, it will be necessary to rely on MUMPS's strong point: the sparse global variable.

One approach could be to do it like this:

 Set max=verymany
 For i=1:1:max Set ^GloVar(i)=0
 For i=max:-1:1 Do
 . Set done=0,t="",target=$Random(i)+1
 . Set count=0 For  Do  Quit:done
 . . Set t=$Order(^GloVar(t))
 . . Set:t'="" count=count+1
 . . If target=count Set done=1 Kill ^GloVar(t)
 . . Quit
 . Write !,"Element number "_(max-i+1)," = ",t
 . Quit

Or, making it a tad faster:

 Set max=verymany
 For n=1:1:max Set ^GloVar(n)=""
 For i=max:-1:1 Do
  . Set r=$Random(i)+1
  . Set n="" For k=1:1:r Set n=$Order(^GloVar(n))
  . Write !,"Element number "_(max-i+1)," = ",n
  . Kill ^GloVar(n)
  . Quit

And, making it a lot faster by removing the need for the $Order altogether:

 Set max=verymany
 For n=1:1:max Set ^GloVar(n)=""
 Set n=max For i=1:1:max-1 Do
 . Set r=$Random(n)
 . Set n=n-1
 . Set temp=^GloVar(i+r)
 . Set ^GloVar(i+r)=^GloVar(i)
 . Set ^GloVar(i)=temp
 . Quit

Examples with naked references:

SET ^ABC(1,2)="reset naked indicator"
; Naked indicator is now ^ABC(1,
SET ^(3,4)=$RANDOM(^(5,6))

; 1. fetch ^(5,6) = ^ABC(1,5,6)
; 2. store ^(3,4) = ^ABC(1,5,3,4)
; Naked indicator is now: ^ABC(1,5,3,

This document is © Ed de Moel, 1995-2005.
It is part of a book by Ed de Moel that is published under the title "M[UMPS] by Example" (ISBN 0-918118-42-5).
Printed copies of the book are no longer available.