Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions projects/003-probability-calculator/js/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const probCalculator = require( './probCalculator.js' ); // For CommonJS environment

function init(){
const hat = new probCalculator.Hat({black:6}, {red:4}, {green:3});
const probability = probCalculator.experiment(hat, {red:2,green:1}, 5, 2000);

console.log(probability);
}

init();
64 changes: 64 additions & 0 deletions projects/003-probability-calculator/js/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const probCalculator = require( './probCalculator.js' ); // For CommonJS environment

test('Expected creation of hat object to add correct contents.', () => {
const hat = new probCalculator.Hat({red: 3},{blue:2});
const actual = hat.contents;
const expected = ["red","red","red","blue","blue"];
expect(actual).toEqual(expected);
});

test('Expected error on creation of hat object with empty arguments.', () => {
const expected = new Error('Can\'t create the Hat, ball/s not found');
expect(() => { new probCalculator.Hat() }).toThrow(expected);
});

test('Expected getContents method of hat object returns correct contents.', () => {
const hat = new probCalculator.Hat({red: 3},{blue:2});
const actual = hat.getContents();
const expected = ["red","red","red","blue","blue"];
expect(actual).toEqual(expected);
});

test('Expected getBalls method of hat object returns correct object.', () => {
const hat = new probCalculator.Hat({red: 3},{blue:2});
const actual = hat.getBalls();
const expected = {red: 3, blue:2};
expect(actual).toEqual(expected);
});

describe('Expected hat draw to ', () => {
let actual, expected;
const hat = new probCalculator.Hat({red: 5},{blue:2});

test('return two random items from hat contents.', () => {
actual = hat.draw(2);
expected = ['blue', 'red'];
expect(actual).toEqual(expected);
});

test('reduce number of items in contents.', () => {
actual = hat.contents.length;
expected = 5;
expect(actual).toEqual(expected);
});
});

describe('Expected experiment method to return a different probability. ', () => {
let actual, expected, probability;

test('First Hat.', () => {
const hat = new probCalculator.Hat({blue:3},{red:2},{green:6});
probability = probCalculator.experiment(hat, {blue:2,green:1}, 4, 1000);
actual = probability;
expected = 0.272;
expect(actual >= (expected - 0.01) && actual <= (expected + 0.01)).toBeTruthy();
});

test('Second Hat.', () => {
const hat = new probCalculator.Hat({yellow:5},{red:1},{green:3},{blue:9},{test:1});
probability = probCalculator.experiment(hat, {yellow:2,blue:3,test:1}, 20, 100);
actual = probability;
expected = 1.0;
expect(actual >= (expected - 0.01) && actual <= (expected + 0.01)).toBeTruthy();
});
});
160 changes: 160 additions & 0 deletions projects/003-probability-calculator/js/probCalculator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/** Class representing a Hat. */
class Hat {
/**
* Creates a hat containing balls whose color and number is determined by the list that is passed during creation, a hat without balls cannot be created
* @param {...object} balls - The list of balls.
*/
constructor(...balls){ // Python's kwargs operator doesn't exist In Javascript, then it was chosen to pass a series of objects
if(balls.length === 0){
throw new Error('Can\'t create the Hat, ball/s not found');
}

this.balls = balls.reduce( (totalBalls, ball) => ({...totalBalls, ...ball}));
this.contents = Hat.quantityBallsToOccurence(this.balls);
}
/**
* Returns the list of balls as a list that groups the colors and their quantities as pairs: {colorBall1: quantity, colorBall2: quantity, etc.}
* @returns {object} The list of balls
*/
getBalls(){
return this.balls;
}
/**
* Returns the list of balls as a list in which each ball is represented by color alone: ["singleBallColor1", "singleBallColor2", etc.]
* @returns {Array} The list of balls
*/
getContents(){
return this.contents;
}
/**
* Returns a list of balls randomly drawn from the hat
* @param {number} ballsToDraw - Number of balls to draw from the hat
* @returns {Array} The list of balls randomly drawn from the hat
*/
draw(ballsToDraw){
let remainingBalls = this.contents;
let drawnBalls = [];

if(ballsToDraw > remainingBalls.length){
return remainingBalls;
}

for (let i = 0; i < ballsToDraw; i++) {
const randomNumber = Math.floor(Math.random() * remainingBalls.length);
drawnBalls.push(remainingBalls.splice(randomNumber, 1)[0]);
}

return drawnBalls;
}
/**
* A static method that returns the list of balls represented as pairs of color and quantity converted into a list in which each ball is represented by color alone
* @param {object} listToConvert - The list of balls to convert
* @returns {Array} The converted list of balls
*/
static quantityBallsToOccurence(listToConvert){
let listConverted = [];

for (const ballColor in listToConvert) {
for (let i = 0; i < listToConvert[ballColor]; i++) {
listConverted.push(ballColor);
}
}

return listConverted;
}

}
/**
* Returns a deep copy of an object including its properties and methods separate from the original object
* @param {object} objectToCopy - The original object to copy
* @returns {object} The resulting copied object
*/
function deepCopyObject(objectToCopy) {
const copiedObject = structuredClone(objectToCopy);
Object.setPrototypeOf(copiedObject, Object.getPrototypeOf(objectToCopy));

return copiedObject;
}

// Approach suggested in the exercise
/**
* Returns the probability that occurs when trying to find how many times a list of balls matches the one given by a draw from a hat
* @param {Hat} hat - The hat object containing balls that should be copied inside the function
* @param {object} expctedBalls - The list of balls to attempt to draw from the hat for the experiment
* @param {number} numBallsDrawn - Quantity of balls to extract from the hat
* @param {number} numExperiments - Quantity of experiments to perform
* @returns {number} The probability
*/
function experiment(hat, expctedBalls, numBallsDrawn, numExperiments){
let manyTimes = 0;

for (let i = 0; i < numExperiments; i++) {
const hatCopy = deepCopyObject(hat);
const drawnBalls = hatCopy.draw(numBallsDrawn);

let success = true;

for (const key in expctedBalls) {
const drawnBallsFounded = drawnBalls.filter(value => {
if(value === key){
return value
}
});

if (drawnBallsFounded.length < expctedBalls[key]) {
success = false;
break;
}
}

if(success === true){
manyTimes += 1;
}
}

return manyTimes/numExperiments;
}

// Custom approach
/**
* Tests whether a given list of balls is found during a random hat draw
* @param {Array} expctedBalls - The list of balls to attempt to draw from the hat
* @param {Array} drawnBalls - The list of balls drawn
* @returns {boolean} `false` if the given list of balls is not found during the random hat draw, otherwise `true`
*/
/*
function checkDrawResult(expctedBalls, drawnBalls){
for (let i = 0; i < expctedBalls.length; i++) {
if(drawnBalls.includes(expctedBalls[i])){
drawnBalls.splice(drawnBalls.indexOf(expctedBalls[i]), 1);
} else {
return false;
}
}

return true;
}

function experiment(hat, expctedBalls, numBallsDrawn, numExperiments){
let manyTimes = 0;

expctedBalls = Hat.quantityBallsToOccurence(expctedBalls);

for (let i = 0; i < numExperiments; i++) {
const hatCopy = deepCopyObject(hat);
const drawnBalls = hatCopy.draw(numBallsDrawn);

if(checkDrawResult(expctedBalls, drawnBalls)){
manyTimes += 1;
}
}

return manyTimes/numExperiments;
}
*/

module.exports = { // For CommonJS environment
// export { // For ES module environment. In addition for Visual Studio Code two package.json files must be created, one in this file folder, the other one in the application file folder, they must contain the following code { "type": "module" }
Hat,
experiment
};