Look up tables and integer ranges - javascript - javascript

So I am looking to create look up tables. However I am running into a problem with integer ranges instead of just 1, 2, 3, etc. Here is what I have:
var ancient = 1;
var legendary = 19;
var epic = 251;
var rare = 1000;
var uncommon = 25000;
var common = 74629;
var poolTotal = ancient + legendary + epic + rare + uncommon + common;
var pool = general.rand(1, poolTotal);
var lootPool = {
1: function () {
return console.log("Ancient");
},
2-19: function () {
}
};
Of course I know 2-19 isn't going to work, but I've tried other things like [2-19] etc etc.
Okay, so more information:
When I call: lootPool[pool](); It will select a integer between 1 and poolTotal Depending on if it is 1 it will log it in the console as ancient. If it hits in the range of 2 through 19 it would be legendary. So on and so forth following my numbers.
EDIT: I am well aware I can easily do this with a switch, but I would like to try it this way.

Rather than making a huge lookup table (which is quite possible, but very inelegant), I'd suggest making a (small) object, choosing a random number, and then finding the first entry in the object whose value is greater than the random number:
// baseLootWeight: weights are proportional to each other
const baseLootWeight = {
ancient: 1,
legendary: 19,
epic: 251,
rare: 1000,
uncommon: 25000,
common: 74629,
};
let totalWeightSoFar = 0;
// lootWeight: weights are proportional to the total weight
const lootWeight = Object.entries(baseLootWeight).map(([rarity, weight]) => {
totalWeightSoFar += weight;
return { rarity, weight: totalWeightSoFar };
});
console.log(lootWeight);
const randomType = () => {
const rand = Math.floor(Math.random() * totalWeightSoFar);
return lootWeight
.find(({ rarity, weight }) => weight >= rand)
.rarity;
};
for (let i = 0; i < 10; i++) console.log(randomType());

Its not a lookup, but this might help you.
let loots = {
"Ancient": 1,
"Epic": 251,
"Legendary": 19
};
//We need loots sorted by value of lootType
function prepareSteps(loots) {
let steps = Object.entries(loots).map((val) => {return {"lootType": val[0], "lootVal": val[1]}});
steps.sort((a, b) => a.lootVal > b.lootVal);
return steps;
}
function getMyLoot(steps, val) {
let myLootRange;
for (var i = 0; i < steps.length; i++) {
if((i === 0 && val < steps[0].lootVal) || val === steps[i].lootVal) {
myLootRange = steps[i];
break;
}
else if( i + 1 < steps.length && val > steps[i].lootVal && val < steps[i + 1].lootVal) {
myLootRange = steps[i + 1];
break;
}
}
myLootRange && myLootRange['lootType'] ? console.log(myLootRange['lootType']) : console.log('Off Upper Limit!');
}
let steps = prepareSteps(loots);
let pool = 0;
getMyLoot(steps, pool);

Related

Clean Solution of Car Fueling problem with JavaScript

Car Fueling Problem is :
You are going to travel to another city that is located 𝑑 miles away from your home city. Your car can travel at most 𝑚 miles on a full tank and you start with a full tank. Along your way, there are gas stations at distances stop1 stop2 . . . ,stopN from your home city. What is the minimum number of refills needed?
Sample 1
Input:
950
400
4
200 375 550 750
Output:
2
The distance between the cities is 950, the car can travel at most 400
miles on a full tank. It suffices to make two refills: at points 375
and 750. This is the minimum number of refills as with a single refill
one would only be able to travel at most 800 miles.
Sample 2
Input:
10
3
4
1 2 5 9
Output:
-1
One cannot reach the gas station at point 9 as the previous gas station is too far away.
I have solved that problem with the below codes but I didn't satisfied with my solution. I think my solution can be more clear than now. Especially, input part which taking inputs from the terminal and sending those inputs to findMinFuelStops function.
May I know how can I use fewer codes to reach the same results and how can I use that approach for my other algorithms?
const readline = require("readline");
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false,
});
process.stdin.setEncoding("utf8");
let inputLines = [];
let fuelArr = [];
rl.on("line", readLine);
function readLine(line) {
let distanceDestination = 0;
let carCap = 0;
let availableStops = 0;
if (inputLines[2] == undefined) {
inputLines.push(line.toString().split(" ").map(Number));
} else {
availableStops = inputLines[2][0];
fuelArr.push(line.toString().split(" ").map(Number));
if (fuelArr[0].length > 1) {
fuelArr = fuelArr[0];
distanceDestination = inputLines[0][0];
carCap = inputLines[1][0];
console.log(
findMinFuelStops(distanceDestination, carCap, availableStops, fuelArr)
);
process.exit();
} else {
if (fuelArr.length == availableStops) {
fuelArr = fuelArr.join(" ").toString().split(" ").map(Number);
distanceDestination = inputLines[0][0];
carCap = inputLines[1][0];
console.log(
findMinFuelStops(distanceDestination, carCap, availableStops, fuelArr)
);
process.exit();
}
}
}
}
function findMinFuelStops(
distanceDestination,
carCap,
availableStops,
fuelArr
) {
let countStops = 0;
let tookDistance = 0;
const tempCarCap = carCap;
if (carCap < distanceDestination) {
for (let i = 0; i < availableStops; i++) {
if (carCap >= fuelArr[i] - tookDistance) {
carCap -= fuelArr[i] - tookDistance;
tookDistance = fuelArr[i];
} else {
carCap = tempCarCap;
if (carCap < fuelArr[i] - tookDistance) {
countStops = -1;
return countStops;
}
carCap -= fuelArr[i] - tookDistance;
tookDistance = fuelArr[i];
countStops++;
}
}
if (carCap >= distanceDestination - tookDistance) {
carCap = distanceDestination - tookDistance;
tookDistance = distanceDestination;
} else {
carCap = tempCarCap;
if (carCap < distanceDestination - tookDistance) {
countStops = -1;
return countStops;
}
tookDistance = distanceDestination;
countStops++;
carCap -= distanceDestination - tookDistance;
}
}
return countStops;
}
It seems like it should be relatively straightforward. Iterate over the possible stops. If the next stop is past the tank capacity and the current stop is not past the other city, add the current stop and reset the tank.
If there's a possibility that no path exists, take that into consideration too.
const fn = (targetDistance, tankDistance, stops) => {
let stopsNeeded = 0;
let lastStop = 0; // the mileage location of the last stop
for (let i = 0; i < stops.length; i++) {
if (lastStop + tankDistance >= targetDistance) {
return stopsNeeded;
}
const stop = stops[i];
// fail if this stop is out of reach
if (stop - lastStop > tankDistance) {
return -1;
}
// next stop may be the city
const nextStop = Math.min(stops[i + 1] ?? targetDistance, targetDistance);
// If the next stop is out of reach, and this stop is needed, stop here
if (nextStop - lastStop > tankDistance && stops[i] < targetDistance) {
stopsNeeded++;
lastStop = stop;
}
}
return targetDistance - lastStop <= tankDistance ? stopsNeeded : -1;
}
console.log(fn(950, 400, [200, 375, 550, 750]));
console.log(fn(10, 3, [1, 2, 5, 9]));
One of good approach code is on below
function minRefuelStops(target, startFuel, stations) {
let stopsNeeds = 0;
let maxR = startFuel; //The point of car maximum can reach.
let stationI = 0;
while (maxR < target && stationI < stations.length) {
while (maxR >= stations[stationI] && stationI < stations.length) {
stationI++;
}
maxR = stations[stationI - 1] + startFuel;
stopsNeeds++;
const nextStop= stations[stationI] ==undefined ? target - stations[stationI - 1] : stations[stationI] ;
if (maxR < nextStop) {
return -1;
}
}
return target <= maxR ? stopsNeeds : -1;
}
console.log(minRefuelStops(950, 400, [200, 375, 550, 750]));
console.log(minRefuelStops(10, 3, [1, 2, 5, 9]));

I generate 2 arrays of equal length (15), with a choice of 4 possible letters at each position - why do they come back 53% to 87% similar?

This is inspired by a Codecadamey project, where I'm learning JavaScript.
The problem is meant to simulate a simple DNA strand. It has 15 positions in the array, and those elements are either an A, T, C, or G to represent DNA bases.
There are no limits to the amount of times a single letter (base) can show up in the array.
I create 30 arrays that are made up of at least 60% C and/or G in any of the positions, these are meant to represent strong DNA strands.
I compare the strands to each other to see what % match they are. I consider a 'match' being true when there is the same base at the same position thisDNA[i] === comparisonDNA[i]
When I test a batch of 30 of these 'strong' samples to see the best and worst match levels, I find the results very tightly grouped (I ran it 3,000 times and the highest was 87%, lowest 53%), yet it is very easy for me to concieve of two samples that will survive that are a 0% match:
const sample1 = AGACCGCGCGTGGAG
const sample2 = TCTGGCGCGCACCTC
(obviously I've cheated by building these to be a 0% match and not randomly generating them)
Here's the full code: https://gist.github.com/AidaP1/0770307979e00d4e8d3c83decc0f7771
My question is as follows: Why is the grouping of matches so tight? Why do I not see anything below a 53% match after running the test thousands of times?
Full code:
// Returns a random DNA base
const returnRandBase = () => {
const dnaBases = ['A', 'T', 'C', 'G']
return dnaBases[Math.floor(Math.random() * 4)]
}
// Returns a random single stand of DNA containing 15 bases
const mockUpStrand = () => {
const newStrand = []
for (let i = 0; i < 15; i++) {
newStrand.push(returnRandBase())
}
return newStrand
}
const pAequorFactory = (num, arr) => { //factory function for new strand specimen
return {
specimenNum: num,
dna: arr,
mutate() {
//console.log(`old dna: ${this.dna}`) //checking log
let randomBaseIndex = Math.floor(Math.random() * this.dna.length) /// chooses a location to exchange the base
let newBase = returnRandBase()
while (newBase === this.dna[randomBaseIndex]) { // Rolls a new base until newBase !== current base at that position
newBase = returnRandBase()
}
this.dna[randomBaseIndex] = newBase;
//console.log(`New dna: ${this.dna}`) //checking log
return this.dna;
},
compareDNA(pAequor) { // compare two strands and output to the console
let thisDNA = this.dna;
let compDNA = pAequor.dna;
let matchCount = 0
for (i = 0; i < this.dna.length; i++) { //cycles through each array and log position + base matches on matchCount
if (thisDNA[i] === compDNA[i]) {
matchCount += 1;
};
};
let percMatch = Math.round(matchCount / this.dna.length * 100) //multiply by 100 to make 0.25 display as 25 in console log
console.log(`specimen #${this.specimenNum} and specimen #${pAequor.specimenNum} are a ${percMatch}% DNA match.`)
return percMatch;
},
compareDNAbulk(pAequor) { //as above, but does not log to console. Used for large arrays in findMostRelated()
let thisDNA = this.dna;
let compDNA = pAequor.dna;
let matchCount = 0
for (i = 0; i < this.dna.length; i++) {
if (thisDNA[i] === compDNA[i]) {
matchCount += 1;
};
};
let percMatch = Math.round(matchCount / this.dna.length * 100) //multiply by 100 to make 0.25 display as 25 in console log
return percMatch;
},
willLikelySurvive() { // looks for >= 60% of bases as either G or C
let countCG = 0;
this.dna.forEach(base => {
if (base === 'C' || base === 'G') {
countCG += 1;
}
})
//console.log(countCG) // testing
//console.log(this.dna) // testing
return countCG / this.dna.length >= 0.6
},
complementStrand() {
return this.dna.map(base => {
switch (base) {
case 'A':
return 'T';
case 'T':
return 'A';
case 'C':
return 'G';
case 'G':
return 'C';
}
})
} //close method
} // close object
} // close factory function
function generatepAequorArray(num) { // Generatess 'num' pAequor that .willLikelySurvive() = true
let pAequorArray = []; //result array
for (i = 0; pAequorArray.length < num; i++) {
let newpAequor = pAequorFactory(i, mockUpStrand()); // runs factory until there are 30 items in the result array
if (newpAequor.willLikelySurvive() === true) {
pAequorArray.push(newpAequor)
}
}
return pAequorArray;
}
function findMostRelated(array) { // champion/challenger function to find the most related specimens
let winningScore = 0;
let winner1;
let winner2;
for (let i = 0; i < array.length; i++) {
for (let j = i; j < array.length; j++) // j = i to halve the number of loops. i = 0, j = 5 is the same as i = 5, j = 0
if (i !== j) { // Do not check specimen against itself
let currentScore = array[i].compareDNAbulk(array[j]);
if (currentScore > winningScore) { // Challenger becomes the champion if they are a closer match
winningScore = currentScore;
winner1 = array[i].specimenNum;
winner2 = array[j].specimenNum;
}
}
}
let resultArray = [winner1, winner2, winningScore] // stored together for easy return
//console.log(`The most related specimens are specimen #${winner1} and specimen #${winner2}, with a ${winningScore}% match.`)
return resultArray
}
function multiArray(loops) { //test by running finding the closest match in 30 random 'will survive' samples, repaeated 1000 times. Returns the highest and lowest match across the 1000 runs
let highScore = 0;
let lowScore = 100
for (let i = 0; i < loops; i++) {
let pAequorArray = generatepAequorArray(30);
let currentArray = findMostRelated(pAequorArray);
highScore = Math.max(highScore, currentArray[2])
lowScore = Math.min(lowScore, currentArray[2])
}
return results = {
'high score': highScore,
'low score': lowScore
}
}
console.log(multiArray(10000))

Identify if a value is a Perfect Square and if so, then push it into an empty array

I'm trying to identify if a value is a Perfect Square and if that's the case, I want to push it into an array. I know that there is a built-in function that allows for it but I want to create an algorithm that does it. :)
Input: num = 16
Output: [4]
Example 2:
Input: num = 25
Output: [5]
Example 2:
Input: num = 14
Output: []
var isPerfectSquare = function(value) {
var perfectSquareVal = []
var highestValue = value;
var lowestValue = 0;
while (lowestValue < highestValue) {
var midpoint = 1 + Math.floor((highestValue + lowestValue)/2);
if (midpoint * midpoint === value) {
perfectSquareVal.push(midpoint);
} else if (midpoint * midpoint > value) {
highestValue = midpoint;
} else if (midpoint * midpoint < value) {
lowestValue = midpoint;
}
}
console.log(perfectSquareVal);
};
isPerfectSquare(16);
That seems really complicated to check if a number is a square, you could simply check if the square root is an Integer:
var isPerfectSquare = function(value) {
return Number.isInteger(Math.sqrt(value));
}
And if the function returns true, then push to array.
You could change the algorithm a bit by
taking the arthmetic mean with a flored value,
return if the product is found (why an array for the result?),
check only the greater oroduct because the smaller one is included in the check for equalness,
use decremented/incremented values, becaus the actual value is wrong,
keep a pure function, take ouput to the outside.
var isPerfectSquare = function (value) {
var highestValue = value,
lowestValue = 0;
while (lowestValue < highestValue) {
let midpoint = Math.floor((highestValue + lowestValue) / 2),
product = midpoint * midpoint;
if (product === value) return midpoint;
if (product > value) highestValue = midpoint - 1;
else lowestValue = midpoint + 1;
}
};
console.log(isPerfectSquare(25));
console.log(isPerfectSquare(250));

Change chances of picking a random string

I would like Anna to have 67% chance of being picked randomly, Bob to have a 30% change, and Tom to have a 3% chance. Is there a simpler way to do this?
This is what I have so far:
var nomes = ['Anna', 'Bob', 'Tom'];
var name = nomes[Math.ceil(Math.random() * (nomes.length - 1))];
console.log(name);
Based on this Stack Overflow I think the following code will work for you:
function randomChoice(p) {
let rnd = p.reduce((a, b) => a + b) * Math.random();
return p.findIndex(a => (rnd -= a) < 0);
}
function randomChoices(p, count) {
return Array.from(Array(count), randomChoice.bind(null, p));
}
const nomes = ['Anna', 'Bob', 'Tom'];
const selectedIndex = randomChoices([0.67, 0.3, 0.03], nomes);
console.log(nomes[selectedIndex]);
// Some testing to ensure that everything works as expected:
const odds = [0, 0, 0];
for (let i = 0; i < 100000; i++) {
const r = randomChoices([0.67, 0.3, 0.03], nomes);
odds[r] = odds[r] + 1;
}
console.log(odds.map(o => o / 1000));
Here's a short solution for you:
function pick(){
var rand = Math.random();
if(rand < .67) return "Anna";
if(rand < .97) return "Bob";
return "Tom";
}
console.log( pick() );

Javascript function to generate random integers with nonuniform probabilities

In javascript (or jquery) is there a simple function to have four integers with their probability values: 1|0.41, 2|0.29, 3|0.25, 4|0.05
how can I generate these four numbers taking into account their probabilities ?
This question is very similar to the one posted here: generate random integers with probabilities
HOWEVER the solution posted there:
function randomWithProbability() {
var notRandomNumbers = [1, 1, 1, 1, 2, 2, 2, 3, 3, 4];
var idx = Math.floor(Math.random() * notRandomNumbers.length);
return notRandomNumbers[idx];
}
states in the comment "create notRandomNumbers dynamically (given the numbers and their weight/probability)"
This is insufficient for my needs. That works well when the probabilities are say 10%,20%, 60%,10%.
In that case constructing notRandomNumbers with the required distribution is easy and the array size is small. But in the general case where probabilities can be something like 20.354%,30.254% etc , the array size would be huge to correctly model the situation.
Is there a clean solution to this more general problem?
EDIT: Thanks Georg, solution accepted, here is my final version, which may be useful for others. I have split the calculation of the cumulative into a separate function in order to avoid extra additions at each call to get a new random number.
function getRandomBinFromCumulative(cumulative) {
var r = Math.random();
for (var i = 0; i < cumulative.length; i++) {
if (r <= cumulative[i])
return i;
}
}
function getCummulativeDistribution(probs) {
var cumulative = [];
var sum = probs[0];
probs.forEach(function (p) {
cumulative.push(sum);
sum += p;
});
// the next 2 lines are optional
cumulative[cumulative.length - 1] = 1; //force to 1 (if input total was <>1)
cumulative.shift(); //remove the first 0
return cumulative;
}
function testRand() {
var probs = [0.1, 0.3, 0.3, 0.3];
var c = getCummulativeDistribution(probs);
console.log(c);
for (var i = 0; i < 100; i++) {
console.log(getRandomBinFromCumulative(c));
}
}
Just accumulate the probabilities and return an item for which current_sum >= random_number:
probs = [0.41, 0.29, 0.25, 0.05];
function item() {
var r = Math.random(), s = 0;
for(var i = 0; i < probs.length; i++) {
s += probs[i];
if(r <= s)
return i;
}
}
// generate 100000 randoms
a = [];
c = 0;
while(c++ < 100000) {
a.push(item());
}
// test actual distibution
c = {}
a.forEach(function(x) {
c[x] = (c[x] || 0) + 1;
});
probs.forEach(function(_, x) {
document.write(x + "=" + c[x] / a.length + "<br>")
});
Create a second parallel array with corresponding weights and use a "wheel" algorithm to get an index.
function randomWithProbability()
{
var notRandomNumbers = [1,2,3,4];
var w = [0.41, 0.29, 0.25, 0.05];
var placehldr = 0;
var maxProb = 0.41;
var index = Math.floor(Math.random() * w.length);
var i = 0;
placehldr = Math.random() * (maxProb * 2);
while(placehldr > index )
{
placehldr -= w[index];
index = (index + 1) % w.length
}
return (notRandomNumbers[index]);
}
This video has a good explanation as to why it works, it's easier to understand with the visual representation.
https://www.youtube.com/watch?v=wNQVo6uOgYA
There is an elegant solution only requiring a single comparison due to A. J. Walker (Electronics Letters 10, 8 (1974), 127-128; ACM Trans. Math Software 3 (1977), 253-256) and described in Knuth, TAOCP Vol. 2, 120-121.
You can also find a description here, generate random numbers within a range with different probabilities.

Categories

Resources