I'm working on modernizing Rosetta Code's infrastructure. Starting with communications. Please accept this time-limited open invite to RC's Slack.. --Michael Mol (talk) 20:59, 30 May 2020 (UTC)

Two bullet roulette

From Rosetta Code
Two bullet roulette is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

The following is supposedly a question given to mathematics graduates seeking jobs on Wall Street:

   A revolver handgun has a revolving cylinder with six chambers for bullets.
   
   It is loaded with the following procedure:
       1. Check the first chamber to the right of the trigger for a bullet. If a bullet
          is seen, the cylinder is rotated one chamber clockwise and the next chamber
          checked until an empty chamber is found.
       2. A cartridge containing a bullet is placed in the empty chamber.
       3. The cylinder is then rotated one chamber clockwise.
   
   To randomize the cylinder's position, the cylinder is spun, which causes the cylinder
   to take a random position from 1 to 6 chamber rotations clockwise from its starting position.
   
   When the trigger is pulled the gun will fire if there is a bullet in position 0, which is just
   counterclockwise from the loading position.
   
   The gun is unloaded by removing all cartridges from the cylinder.
   
   According to the legend, a suicidal Russian imperial military officer plays a game of 
   Russian roulette by putting two bullets in a six-chamber cylinder and pulls the trigger 
   twice. If the gun fires with a trigger pull, this is considered a successful suicide.
   
   The cylinder is always spun before the first shot, but it may or may not be spun
   after putting in the first bullet and may or may not be spun after taking the first shot.
   
   Which of the following situations produces the highest probability of suicide?
   
   A. Spinning the cylinder after loading the first bullet, and spinning again
      after the first shot.
   B. Spinning the cylinder after loading the first bullet only.
   C. Spinning the cylinder after firing the first shot only.
   D. Not spinning the cylinder either after loading the first bullet or after
      the first shot.
   E. The probability is the same for all cases.
Task
  1. Run a repeated simulation of each of the above scenario, calculating the percentage of suicide with a randomization of the four spinning, loading and firing order scenarios.
  2. Show the results as a percentage of deaths for each type of scenario.
  3. The hand calculated probabilities are 5/9, 7/12, 5/9, and 1/2. A correct program should produce results close enough to those to allow a correct response to the interview question.
Reference

Youtube video on the Russian 1895 Nagant revolver [[1]]

Factor[edit]

Translation of: Julia
Translation of: Python
USING: accessors assocs circular formatting fry kernel literals
math random sequences ;
IN: rosetta-code.roulette
 
CONSTANT: cyl $[ { f f f f f f } <circular> ]
 
: cylinder ( -- seq ) cyl [ drop f ] map! ;
 
: load ( seq -- seq' )
0 over nth [ dup rotate-circular ] when
t 0 rot [ set-nth ] [ rotate-circular ] [ ] tri ;
 
: spin ( seq -- seq' ) [ 6 random 1 + + ] change-start ;
 
: fire ( seq -- ? seq' )
[ 0 swap nth ] [ rotate-circular ] [ ] tri ;
 
: LSLSFSF ( -- ? ) cylinder load spin load spin fire spin fire drop or ;
: LSLSFF ( -- ? ) cylinder load spin load spin fire fire drop or ;
: LLSFSF ( -- ? ) cylinder load load spin fire spin fire drop or ;
: LLSFF ( -- ? ) cylinder load load spin fire fire drop or ;
 
: percent ( ... n quot: ( ... -- ... ? ) -- ... x )
0 -rot '[ _ call( -- ? ) 1 0 ? + ] [ times ] keepd /f 100 * ; inline
 
: run-test ( description quot -- )
100,000 swap percent
"Method <%s> produces %.3f%% deaths.\n" printf ;
 
: main ( -- )
{
{ "load, spin, load, spin, fire, spin, fire" [ LSLSFSF ] }
{ "load, spin, load, spin, fire, fire" [ LSLSFF ] }
{ "load, load, spin, fire, spin, fire" [ LLSFSF ] }
{ "load, load, spin, fire, fire" [ LLSFF ] }
} [ run-test ] assoc-each ;
 
MAIN: main
Output:
"rosetta-code.roulette" run
Method <load, spin, load, spin, fire, spin, fire> produces 55.598% deaths.
Method <load, spin, load, spin, fire, fire> produces 58.390% deaths.
Method <load, load, spin, fire, spin, fire> produces 55.500% deaths.
Method <load, load, spin, fire, fire> produces 49.841% deaths.

Go[edit]

Translation of: Wren

Though procedural rather than OO.

package main
 
import (
"fmt"
"math/rand"
"strings"
"time"
)
 
var cylinder = [6]bool{}
 
func rshift() {
t := cylinder[5]
for i := 4; i >= 0; i-- {
cylinder[i+1] = cylinder[i]
}
cylinder[0] = t
}
 
func unload() {
for i := 0; i < 6; i++ {
cylinder[i] = false
}
}
 
func load() {
for cylinder[0] {
rshift()
}
cylinder[0] = true
rshift()
}
 
func spin() {
var lim = 1 + rand.Intn(6)
for i := 1; i < lim; i++ {
rshift()
}
}
 
func fire() bool {
shot := cylinder[0]
rshift()
return shot
}
 
func method(s string) int {
unload()
for _, c := range s {
switch c {
case 'L':
load()
case 'S':
spin()
case 'F':
if fire() {
return 1
}
}
}
return 0
}
 
func mstring(s string) string {
var l []string
for _, c := range s {
switch c {
case 'L':
l = append(l, "load")
case 'S':
l = append(l, "spin")
case 'F':
l = append(l, "fire")
}
}
return strings.Join(l, ", ")
}
 
func main() {
rand.Seed(time.Now().UnixNano())
tests := 100000
for _, m := range []string{"LSLSFSF", "LSLSFF", "LLSFSF", "LLSFF"} {
sum := 0
for t := 1; t <= tests; t++ {
sum += method(m)
}
pc := float64(sum) * 100 / float64(tests)
fmt.Printf("%-40s produces %6.3f%% deaths.\n", mstring(m), pc)
}
}
Output:

Sample run:

load, spin, load, spin, fire, spin, fire produces 55.267% deaths.
load, spin, load, spin, fire, fire       produces 58.110% deaths.
load, load, spin, fire, spin, fire       produces 55.405% deaths.
load, load, spin, fire, fire             produces 49.889% deaths.

Julia[edit]

Translation of: Python
const cyl = zeros(Bool, 6)
 
function load()
while cyl[1]
cyl .= circshift(cyl, 1)
end
cyl[1] = true
cyl .= circshift(cyl, 1)
end
 
spin() = (cyl .= circshift(cyl, rand(1:6)))
 
fire() = (shot = cyl[1]; cyl .= circshift(cyl, 1); shot)
 
function LSLSFSF()
cyl .= 0
load(); spin(); load(); spin()
fire() && return true
spin(); return fire()
end
 
function LSLSFF()
cyl .= 0
load(); spin(); load(); spin()
fire() && return true
return fire()
end
 
function LLSFSF()
cyl .= 0
load(); load(); spin()
fire() && return true
spin(); return fire()
end
 
function LLSFF()
cyl .= 0
load(); load(); spin()
fire() && return true
return fire()
end
 
function testmethods(N = 10000000)
for (name, method) in [("load, spin, load, spin, fire, spin, fire", LSLSFSF),
("load, spin, load, spin, fire, fire", LSLSFF),
("load, load, spin, fire, spin, fire", LLSFSF),
("load, load, spin, fire, fire", LLSFF)]
percentage = 100 * sum([method() for _ in 1:N]) / N
println("Method $name produces $percentage per cent deaths.")
end
end
 
testmethods()
 
Output:
Method load, spin, load, spin, fire, spin, fire produces 55.54253 per cent deaths.
Method load, spin, load, spin, fire, fire produces 58.32598 per cent deaths.
Method load, load, spin, fire, spin, fire produces 55.54244 per cent deaths.
Method load, load, spin, fire, fire produces 50.02247 per cent deaths.

Perl[edit]

Translation of: Raku
use strict;
use warnings;
use feature 'say';
 
my @cyl;
my $shots = 6;
 
sub load {
push @cyl, shift @cyl while $cyl[1];
$cyl[1] = 1;
push @cyl, shift @cyl
}
 
sub spin { push @cyl, shift @cyl for 0 .. int rand @cyl }
sub fire { push @cyl, shift @cyl; $cyl[0] }
 
sub LSLSFSF {
@cyl = (0) x $shots;
load, spin, load, spin;
return 1 if fire;
spin;
fire
}
 
sub LSLSFF {
@cyl = (0) x $shots;
load, spin, load, spin;
fire or fire
}
 
sub LLSFSF {
@cyl = (0) x $shots;
load, load, spin;
return 1 if fire;
spin;
fire
}
 
sub LLSFF {
@cyl = (0) x $shots;
load, load, spin;
fire or fire
}
 
my $trials = 10000;
 
for my $ref (<LSLSFSF LSLSFF LLSFSF LLSFF>) {
no strict 'refs';
my $total = 0;
$total += &$ref for 1..$trials;
printf "%7s %.2f%%\n", $ref, $total / $trials * 100;
}
Output:
LSLSFSF 55.04%
 LSLSFF 58.77%
 LLSFSF 55.09%
  LLSFF 50.13%

Phix[edit]

function spin(sequence revolver, integer count)
while count do
revolver = revolver[$]&revolver[1..$-1]
count -= 1
end while
return revolver
end function
 
function load(sequence revolver)
while revolver[1] do
revolver = spin(revolver,1)
end while
revolver[1] = true
revolver = spin(revolver,1)
return revolver
end function
 
bool dead = false
function fire(sequence revolver)
if revolver[1] then dead = true end if
revolver = spin(revolver,1)
return revolver
end function
 
procedure test(string method)
integer deaths = 0,
limit = 100000
for n=1 to limit do
sequence revolver = repeat(false,6)
dead = false
for i=1 to length(method) do
integer ch = method[i]
switch ch
case 'L': revolver = load(revolver)
case 'S': revolver = spin(revolver,rand(6))
case 'F': revolver = fire(revolver)
end switch
end for
deaths += dead
end for
printf(1,"%s: %5.2f\n",{method,100*deaths/limit})
end procedure
 
printf(1,"Load/Spin/Fire method percentage fatalities:\n")
papply({"LSLSFSF","LSLSFF","LLSFSF","LLSFF"},test)
Output:
Load/Spin/Fire method percentage fatalities:
LSLSFSF: 55.66
LSLSFF: 58.55
LLSFSF: 55.76
LLSFF: 49.97

Python[edit]

""" Russian roulette problem """
import numpy as np
 
class Revolver:
""" simulates 6-shot revolving cylinger pistol """
 
def __init__(self):
""" start unloaded """
self.cylinder = np.array([False] * 6)
 
def unload(self):
""" empty all chambers of cylinder """
self.cylinder[:] = False
 
def load(self):
""" load a chamber (advance til empty if full already), then advance once """
while self.cylinder[1]:
self.cylinder[:] = np.roll(self.cylinder, 1)
self.cylinder[1] = True
 
def spin(self):
""" spin cylinder, randomizing position of chamber to be fired """
self.cylinder[:] = np.roll(self.cylinder, np.random.randint(1, high=7))
 
def fire(self):
""" pull trigger of revolver, return True if fired, False if did not fire """
shot = self.cylinder[0]
self.cylinder[:] = np.roll(self.cylinder, 1)
return shot
 
def LSLSFSF(self):
""" load, spin, load, spin, fire, spin, fire """
self.unload()
self.load()
self.spin()
self.load()
self.spin()
if self.fire():
return True
self.spin()
if self.fire():
return True
return False
 
def LSLSFF(self):
""" load, spin, load, spin, fire, fire """
self.unload()
self.load()
self.spin()
self.load()
self.spin()
if self.fire():
return True
if self.fire():
return True
return False
 
def LLSFSF(self):
""" load, load, spin, fire, spin, fire """
self.unload()
self.load()
self.load()
self.spin()
if self.fire():
return True
self.spin()
if self.fire():
return True
return False
 
def LLSFF(self):
""" load, load, spin, fire, fire """
self.unload()
self.load()
self.load()
self.spin()
if self.fire():
return True
if self.fire():
return True
return False
 
 
if __name__ == '__main__':
 
REV = Revolver()
TESTCOUNT = 100000
for (name, method) in [['load, spin, load, spin, fire, spin, fire', REV.LSLSFSF],
['load, spin, load, spin, fire, fire', REV.LSLSFF],
['load, load, spin, fire, spin, fire', REV.LLSFSF],
['load, load, spin, fire, fire', REV.LLSFF]]:
 
percentage = 100 * sum([method() for _ in range(TESTCOUNT)]) / TESTCOUNT
print("Method", name, "produces", percentage, "per cent deaths.")
 
Output:
Method load, spin, load, spin, fire, spin, fire produces 55.652 per cent deaths.
Method load, spin, load, spin, fire, fire produces 58.239 per cent deaths.
Method load, load, spin, fire, spin, fire produces 55.774 per cent deaths.
Method load, load, spin, fire, fire produces 50.071 per cent deaths.

Raku[edit]

unit sub MAIN ($shots = 6);
 
my @cyl;
 
sub load () {
@cyl.=rotate(-1) while @cyl[1];
@cyl[1] = 1;
@cyl.=rotate(-1);
}
 
sub spin () { @cyl.=rotate: (^@cyl).pick }
 
sub fire () { @cyl.=rotate; @cyl[0] }
 
sub LSLSFSF {
@cyl = 0 xx $shots;
load, spin, load, spin;
return 1 if fire;
spin;
fire
}
 
sub LSLSFF {
@cyl = 0 xx $shots;
load, spin, load, spin;
fire() || fire
}
 
sub LLSFSF {
@cyl = 0 xx $shots;
load, load, spin;
return 1 if fire;
spin;
fire
}
 
sub LLSFF {
@cyl = 0 xx $shots;
load, load, spin;
fire() || fire
}
 
my %revolver;
my $trials = 100000;
 
for ^$trials {
%revolver<LSLSFSF> += LSLSFSF;
%revolver<LSLSFF> += LSLSFF;
%revolver<LLSFSF> += LLSFSF;
%revolver<LLSFF> += LLSFF;
}
 
say "{.fmt('%7s')}: %{(%revolver{$_} / $trials × 100).fmt('%.2f')}"
for <LSLSFSF LSLSFF LLSFSF LLSFF>
Sample output (default; 6 shooter):
LSLSFSF: %55.37
 LSLSFF: %58.30
 LLSFSF: %55.42
  LLSFF: %50.29

Though if you go and look at the Wikipedia article for the 1895 Nagant revolver mentioned in the task reference section, you'll see it is actually a 7 shot revolver... so, run again with 7 chambers:

raku roulette.raku 7

Sample output (7 shooter):
LSLSFSF: %49.29
 LSLSFF: %51.14
 LLSFSF: %48.74
  LLSFF: %43.08

Or, how about a Ruger GP100 10 round revolver?

raku roulette.raku 10

Sample output (10 shooter):
LSLSFSF: %36.00
 LSLSFF: %37.00
 LLSFSF: %36.13
  LLSFF: %29.77

Doesn't change the answers, B (LSLSFF) is definitely the worst most likely choice in all cases.

Wren[edit]

Library: Wren-fmt
import "random" for Random
import "/fmt" for Fmt
 
var Rand = Random.new()
 
class Revolver {
construct new() {
_cylinder = List.filled(6, false)
}
 
rshift() {
var t = _cylinder[-1]
for (i in 4..0) _cylinder[i+1] = _cylinder[i]
_cylinder[0] = t
}
 
unload() {
for (i in 0..5) _cylinder[i] = false
}
 
load() {
while (_cylinder[0]) rshift()
_cylinder[0] = true
rshift()
}
 
spin() {
for (i in 1..Rand.int(1, 7)) rshift()
}
 
fire() {
var shot = _cylinder[0]
rshift()
return shot
}
 
method(s) {
unload()
for (c in s) {
if (c == "L") {
load()
} else if (c == "S") {
spin()
} else if (c == "F") {
if (fire()) return 1
}
}
return 0
}
 
static mstring(s) {
var l = []
for (c in s) {
if (c == "L") {
l.add("load")
} else if (c == "S") {
l.add("spin")
} else if (c == "F") {
l.add("fire")
}
}
return l.join(", ")
}
}
 
var rev = Revolver.new()
var tests = 100000
for (m in ["LSLSFSF", "LSLSFF", "LLSFSF", "LLSFF"]) {
var sum = 0
for (t in 1..tests) sum = sum + rev.method(m)
Fmt.print("$-40s produces $6.3f\% deaths.", Revolver.mstring(m), sum * 100 / tests)
}
Output:

Sample run:

load, spin, load, spin, fire, spin, fire produces 55.500% deaths.
load, spin, load, spin, fire, fire       produces 58.162% deaths.
load, load, spin, fire, spin, fire       produces 55.512% deaths.
load, load, spin, fire, fire             produces 50.013% deaths.