// underscore.js helpers var _ = require('underscore'); // State aliases var OCCUPIED = true; var VACANT = false; // Sample a current state var knock = function(state) { var correct = state.sign === state.bathroom; if (correct && state.sign === OCCUPIED) { return _({}).extend(state, { truePositive: state.truePositive + 1 }); } if (correct && state.sign === VACANT) { return _({}).extend(state, { trueNegative: state.trueNegative + 1 }); } if (state.sign && !state.bathroom) { return _({}).extend(state, { falsePositive: state.falsePositive + 1 }); } return _({}).extend(state, { falseNegative: state.falseNegative + 1 }); }; // Define behaviors as modifying the current state of the bathroom. Can be invoked as entering or exiting. // Don't change the state of the sign var unobservant = function(state, exit) { return _({}).extend(state, { sign: state.sign, bathroom: !exit }); }; // Change the sign when entering, but not on leaving var forgetful = function(state, exit) { return _({}).extend(state, { sign: OCCUPIED, bathroom: !exit }); }; // Make sure that the sign reflects the actual state of the bathroom var conscientious = function(state, exit) { return _({}).extend(state, { sign: !exit, bathroom: !exit }); }; // Each behavior is as likely to appear var chooseBehavior = _(_.sample).partial([unobservant, forgetful, conscientious]); // Create a person, randomly choosing a behavior var Person = function() { var behavior = chooseBehavior(); this.enterBehavior = behavior; // Behavior functionality is based on the exit flag, so we can reuse and partially apply that flag to the exit behavior this.exitBehavior = _(behavior).partial(_, true); }; // Enter bathroom Person.prototype.enterBathroom = function(state) { return this.enterBehavior(state); }; // Exit bathroom Person.prototype.exitBathroom = function(state) { return this.exitBehavior(state); }; Person.prototype.useBathroom = function(state) { // Enter the bathroom and sample the result var enterState = knock(this.enterBathroom(state)); // Exit the bathroom and sample the result return knock(this.exitBathroom(enterState)); }; // Set number of runs for the model var numberOfRuns = 500000; // Sample twice per run var samples = numberOfRuns * 2; // Initial starting state var initialState = { sign: VACANT, bathroom: VACANT, truePositive: 0, trueNegative: 0, falsePositive: 0, falseNegative: 0 }; // Get the results var results = _(numberOfRuns).chain() // Get a list with length numberOfRuns .range() // Generate as many people as runs .map(function() { return new Person(); }) // Work through the runs, returning a final state .reduce(function(state, person) { return person.useBathroom(state); }, initialState) .value(); var tp = results.truePositive / samples; var tn = results.trueNegative / samples; var fp = results.falsePositive / samples; var fn = results.falseNegative / samples; // Reporting console.log('Results after ' + numberOfRuns + ' runs:'); console.log('Probability of the bathroom being occupied when the sign reads occupied: ' + (tp / (tp + fp)).toPrecision(4)); console.log('Probability of the bathroom being vacant when the sign reads vacant: ' + (tn / (tn + fn)).toPrecision(4));