Dyota's blog

JavaScript: 100 prisoners

Again?

Yes again! I first did this in PowerShell, then in Power Query, and in C# to learn the basics of the language, but I had to go back and do this in JavaScript just to make sure I know how.

Things I learned

After all this time in amateur-level dabbling in JavaScript, there are still things that caught me off guard.

Reference types

There is a point in the script where I do something like this:

const hundred = sequence(1, 100) // this is an array
const prisonerNumbers = hundred // pay attention to this line

... // much later on...

prisonerNumbers.splice(randomNumber, 1) // I take out an element from the array prisonerNumbers

It turns out, that last line was also mutating the array hundred, and as a consequence, one of the loops I was running was behaving unexpectedly (instead of running 100 times as I expected, it was running only 50 times).

It turns out, arrays in JavaScript are reference types, which is why I had to do something like this:

const prisonerNumbers = Array.from(hundred) // copy the array to a new array

Sequence of numbers

Apparently JavaScript doesn't have a native way of easily making an array of numbers (e.g. you can go @(1..5) to make the numbers 1 to 5 in PowerShell).

So, I packaged up a one-lined I found on Stack Overflow into a function:

function sequence(start, end) {
    // e.g. given (1, 10), will return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    return Array.from({ length: (end - start + 1) }, (_, i) => i + start)
}

Code

function sequence(start, end) {
    // e.g. given (1, 10), will return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    return Array.from({ length: (end - start + 1) }, (_, i) => i + start)
}

function makeCabinet() {
    const hundred = sequence(1, 100)
    let cabinet = []

    // need to copy the array hundred, otherwise they will be treated as the same (reference type)
    const prisonerNumbers = Array.from(hundred)

    // set up a cabinet of 100 boxes with random prisoner number assignments

    hundred.forEach((_) => {

        const randomNumber = Math.floor(Math.random() * prisonerNumbers.length)

        const prisonerNumber = prisonerNumbers[randomNumber]

        const box = {
            number: _,
            prisonerNumber: prisonerNumber
        }

        prisonerNumbers.splice(randomNumber, 1)
        cabinet.push(box)
    })

    return cabinet
}

// look into a box and see which prisoner number is in it
function checkBoxContents(boxNumber) {
    return cabinet[boxNumber - 1].prisonerNumber
}

// get one prisoner to search through their allowed 50 attempts

function searchThroughBoxes(prisonerNumber) {
    let found = false
    let count = 0
    const startingNumber = prisonerNumber
    let nextNumber
    let out = ''

    while (!found && count < 50) {
        if (count == 0) {
            nextNumber = checkBoxContents(startingNumber)
        } else {
            nextNumber = checkBoxContents(nextNumber)
        }

        count++

        if (nextNumber == prisonerNumber) {
            found = true
            out = `Prisoner #${prisonerNumber} found in ${count} attempts!`
        }
    }

    if (!found) {
        out = `Prisoner #${prisonerNumber} search failed, everybody dies.`
    }

    return found
}

function trialGroupOfPrisoners() {
    let alive = true
    let found = false

    // start with prisoner #1
    let prisonerNumber = 1

    // get all the prisoners to go through and search
    while (alive && prisonerNumber < 100) {
        found = searchThroughBoxes(prisonerNumber)
        
        if (!found) {
            alive = false
            console.log('Search failed, everybody dies')
        } else {
            prisonerNumber++
        }
    }
    
    return alive
}

const lifetimes = 1000

let successes = 0

for (let i = 0; i < lifetimes; i++) {
    cabinet = makeCabinet()

    if (trialGroupOfPrisoners()) {
        console.log('Everybody lives and goes free!')
        successes++
    }
}

console.log({rate: successes/lifetimes})

#javascript #montecarlo