Out of memory issue on memoization in recursion - javascript

I am trying to solve the LeetCode problem 746. Min Cost Climbing Stairs:
You are given an integer array cost where cost[i] is the cost of ith step on a staircase. Once you pay the cost, you can either climb one or two steps.
You can either start from the step with index 0, or the step with index 1.
Return the minimum cost to reach the top of the floor.
Example 1:
Input: cost = [10,15,20]
Output: 15
Explanation: You will start at index 1.
Pay 15 and climb two steps to reach the top.
The total cost is 15.
Example 2:
Input: cost = [1,100,1,1,1,100,1,1,100,1]
Output: 6
Explanation: You will start at index 0.
Pay 1 and climb two steps to reach index 2.
Pay 1 and climb two steps to reach index 4.
Pay 1 and climb two steps to reach index 6.
Pay 1 and climb one step to reach index 7.
Pay 1 and climb two steps to reach index 9.
Pay 1 and climb one step to reach the top.
The total cost is 6.
Constraints
2 <= cost.length <= 1000
0 <= cost[i] <= 999
My code:
/**
* #param {number[]} cost
* #return {number}
*/
var minCostClimbingStairs = function(A) {
A.unshift(0)
let maxSum = A.reduce(
(accumulator, currentValue) => accumulator + currentValue,
0
);
let cache = []
for(let i=0; i<A.length+2; i++){
maxSum = Math.max(maxSum, 1) + 1
let arr1 = new Array(maxSum)
arr1.fill("10000")
cache.push(arr1)
}
function findMin(A, step=0, sum =0){
if(step+1 == A.length || step+2 == A.length){
let result1 = sum + A[step]
return "" + result1
}
if(step> A.length-1){
return "99999";
}
let result1, result2
if(cache[step+1][sum+A[step]] != "10000"){
result1 = cache[step+1][sum+A[step]]
}
else{
result1 = findMin(A, step+1, sum+A[step])
cache[step+1][sum+A[step]] = "" + result1
}
if(cache[step+2][sum+A[step]] != "10000"){
result2 = cache[step+2][sum+A[step]]
}
else{
result2 = findMin(A, step+2, sum+A[step])
cache[step+2][sum+A[step]] = "" + result2
}
let result = Math.min(+result1, +result2)
cache[step][sum] = ""+ result
return cache[step][sum]
}
return findMin(A)
};
I had this code on recursion as well but I got the error of maximum time out on larger-scale data.
So I used memoization, and now with memoization, I get this error:
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
on this data:
cost = [841,462,566,398,243,248,238,650,989,576,361,126,334,729,446,897,953,38,195,679,65,707,196,705,569,275,259,872,630,965,978,109,56,523,851,887,91,544,598,963,305,481,959,560,454,883,50,216,732,572,511,156,177,831,122,667,548,978,771,880,922,777,990,498,525,317,469,151,874,202,519,139,670,341,514,469,858,913,94,849,839,813,664,163,3,802,21,634,944,901,446,186,843,742,330,610,932,614,625,169,833,4,81,55,124,294,71,24,929,534,621,543,417,534,427,327,179,90,341,949,368,692,646,290,488,145,273,617,596,82,538,751,80,616,763,826,932,184,630,478,163,925,259,237,839,602,60,786,603,413,816,278,4,35,243,64,631,405,23,638,618,829,481,877,756,482,999,973,718,157,262,752,931,882,741,40,77,535,542,879,607,879,321,46,210,116,244,830,591,285,382,925,48,497,913,203,239,696,162,623,291,525,950,27,546,293,108,577,672,354,256,3,671,998,22,989,557,424,251,923,542,243,46,488,80,374,372,334,190,817,150,742,362,196,75,193,162,645,859,758,433,903,199,289,175,303,475,818,213,576,181,668,243,297,572,549,840,161,292,719,226,338,981,345,203,655,210,65,111,746,76,935,406,646,976,567,32,726,638,674,727,861,426,297,349,464,973,341,452,826,223,805,940,458,468,967,107,345,987,553,407,916,103,324,367,864,74,946,712,596,105,194,79,634,855,703,70,170,543,208,739,632,663,880,857,824,258,743,488,659,647,470,958,492,211,927,356,488,744,570,143,674,502,589,270,80,6,463,506,556,495,713,407,229,689,280,162,454,757,565,267,575,417,948,607,269,852,938,560,24,222,580,604,800,628,487,485,615,796,384,555,226,412,445,503,810,949,966,28,768,83,213,883,963,831,390,951,378,497,440,780,209,734,290,96,398,146,56,445,880,910,858,671,164,552,686,748,738,837,556,710,787,343,137,298,685,909,828,499,816,538,604,652,7,272,729,529,343,443,593,992,434,588,936,261,873,64,177,827,172,712,628,609,328,672,376,628,441,9,92,525,222,654,699,134,506,934,178,270,770,994,158,653,199,833,802,553,399,366,818,523,447,420,957,669,267,118,535,971,180,469,768,184,321,712,167,867,12,660,283,813,498,192,740,696,421,504,795,894,724,562,234,110,88,100,408,104,864,473,59,474,922,759,720,69,490,540,962,461,324,453,91,173,870,470,292,394,771,161,777,287,560,532,339,301,90,411,387,59,67,828,775,882,677,9,393,128,910,630,396,77,321,642,568,817,222,902,680,596,359,639,189,436,648,825,46,699,967,202,954,680,251,455,420,599,20,894,224,47,266,644,943,808,653,563,351,709,116,849,38,870,852,333,829,306,881,203,660,266,540,510,748,840,821,199,250,253,279,672,472,707,921,582,713,900,137,70,912,51,250,188,967,14,608,30,541,424,813,343,297,346,27,774,549,931,141,81,120,342,288,332,967,768,178,230,378,800,408,272,596,560,942,612,910,743,461,425,878,254,929,780,641,657,279,160,184,585,651,204,353,454,536,185,550,428,125,889,436,906,99,942,355,666,746,964,936,661,515,978,492,836,468,867,422,879,92,438,802,276,805,832,649,572,638,43,971,974,804,66,100,792,878,469,585,254,630,309,172,361,906,628,219,534,617,95,190,541,93,477,933,328,984,117,678,746,296,232,240,532,643,901,982,342,918,884,62,68,835,173,493,252,382,862,672,803,803,873,24,431,580,257,457,519,388,218,970,691,287,486,274,942,184,817,405,575,369,591,713,158,264,826,870,561,450,419,606,925,710,758,151,533,405,946,285,86,346,685,153,834,625,745,925,281,805,99,891,122,102,874,491,64,277,277,840,657,443,492,880,925,65,880,393,504,736,340,64,330,318,703,949,950,887,956,39,595,764,176,371,215,601,435,249,86,761,793,201,54,189,451,179,849,760,689,539,453,450,404,852,709,313,529,666,545,399,808,290,848,129,352,846,2,266,777,286,22,898,81,299,786,949,435,434,695,298,402,532,177,399,458,528,672,882,90,547,690,935,424,516,390,346,702,781,644,794,420,116,24,919,467,543,58,938,217,502,169,457,723,122,158,188,109,868,311,708,8,893,853,376,359,223,654,895,877,709,940,195,323,64,51,807,510,170,508,155,724,784,603,67,316,217,148,972,19,658,5,762,618,744,534,956,703,434,302,541,997,214,429,961,648,774,244,684,218,49,729,990,521,948,317,847,76,566,415,874,399,613,816,613,467,191]
I thought storing strings in the cache array might have an advantage over using integers, but still I am not able to get rid of this error.
This code is working fine for smaller data like in the two examples given in the above code challenge description.
In another atttempt, I changed that cache from array to object so that I don't have to occupy wasted space in the cache array:
var minCostClimbingStairs = function(A) {
A.unshift(0)
let cache = {}
// for(let i=0; i<A.length+2; i++){
// maxSum = Math.max(maxSum, 1) + 1
// let arr1 = new Array(maxSum)
// arr1.fill("10000")
// cache.push(arr1)
// }
function findMin(A, step=0, sum =0){
if(step+1 == A.length || step+2 == A.length){
let result1 = BigInt(sum + A[step])
return result1
}
if(step> A.length-1){
return "99999";
}
let result1, result2
let newSum = BigInt(sum+A[step])
let step1 = step+1
let step2 = step+2
let key1 = step1 + "-" + newSum
let key2 = step2 + "-" + newSum
let key = step + "-" + sum
if(cache[key] != undefined){
return cache[key]
}
if(cache[key1] != undefined){
result1 = cache[key1]
}
else{
result1 = findMin(A, step+1, sum+A[step])
cache[key1] = result1
}
if(cache[key2] != undefined){
result2 = cache[key2]
}
else{
result2 = findMin(A, step+2, sum+A[step])
cache[key2] = result2
}
let result = result2>result1 ? result1 : result2
cache[key] = result
return cache[key]
}
return findMin(A)
};
but still the same error persists!
I changed the cache to have only one key which is step and i am not getting correct answer
var minCostClimbingStairs = function(A) {
A.unshift(0)
let cache = {}
// for(let i=0; i<A.length+2; i++){
// maxSum = Math.max(maxSum, 1) + 1
// let arr1 = new Array(maxSum)
// arr1.fill("10000")
// cache.push(arr1)
// }
function findMin(A, step=0, sum =0){
if(step+1 == A.length || step+2 == A.length){
let result1 = BigInt(sum) + BigInt(A[step])
return result1
}
if(step> A.length-1){
return "99999";
}
let result1, result2
let newSum = BigInt(sum)+BigInt(A[step])
let step1 = step+1
let step2 = step+2
let key1 = step1
let key2 = step2
let key = step
if(cache[key] != undefined){
return cache[key]
}
if(cache[key1] != undefined){
result1 = cache[key1]
}
else{
result1 = findMin(A, step+1, newSum)
cache[key1] = result1
}
if(cache[key2] != undefined){
result2 = cache[key2]
}
else{
result2 = findMin(A, step+2, newSum)
cache[key2] = result2
}
let result = result2>result1 ? result1 : result2
cache[key] = result
return cache[key]
}
return findMin(A)
};
for test case of
TestCase 1:
[0,1,0,0]
Expected: 0
Returned: 1
TestCase 2:
[1,100,1,1,1,100,1,1,100,1]
Expected: 6
Returned: 207

The memory your code needs for cache can be 1000x1000 strings, and there is the memory needed for recursion.
Your cache is not keyed correctly. What you want to cache is the best result that can be achieved from a given step onward. So the key of your cache should not be a combination of step and sum, but of step only.
Here is how your top-down approach could be changed to use a cache keyed by step only:
var minCostClimbingStairs = function(cost) {
const cache = Array(cost.length).fill(1e10);
cache.push(0, 0);
function recur(i) {
if (cache[i] === 1e10) cache[i] = cost[i] + Math.min(recur(i+1), recur(i+2));
return cache[i];
}
return Math.min(recur(0), recur(1));
};
However, you can do this with constant space complexity only. Instead of performing a top-down algorithm, do a bottom-up one: start at the end of the input array and walk back. At each step you only need to know two results: the one for the step ahead, and the one for 2 steps ahead. It is easy to keep track of these two results as you walk backwards along the input array.
Here is how that looks (spoiler):
var minCostClimbingStairs = function(cost) {
let dp = [0, 0]; // Results for the next two positions
for (let i = cost.length - 1; i >= 0; i--) { // Walk backward
dp = [cost[i] + Math.min(...dp), dp[0]];
}
return Math.min(...dp);
};

Related

How to prevent Math.random from repeating a return? [duplicate]

I have the following function
function randomNum(max, used){
newNum = Math.floor(Math.random() * max + 1);
if($.inArray(newNum, used) === -1){
console.log(newNum + " is not in array");
return newNum;
}else{
return randomNum(max,used);
}
}
Basically I am creating a random number between 1 - 10 and checking to see if that number has already been created, by adding it to an array and checking the new created number against it. I call it by adding it to a variable..
UPDATED:
for(var i=0;i < 10;i++){
randNum = randomNum(10, usedNums);
usedNums.push(randNum);
//do something with ranNum
}
This works, but in Chrome I get the following error:
Uncaught RangeError: Maximum call stack size exceeded
Which I guess it's because I am calling the function inside itself too many times. Which means my code is no good.
Can someone help me with the logic? what's a best way to make sure my numbers are not repeating?
If I understand right then you're just looking for a permutation (i.e. the numbers randomised with no repeats) of the numbers 1-10?
Maybe try generating a randomised list of those numbers, once, at the start, and then just working your way through those?
This will calculate a random permutation of the numbers in nums:
var nums = [1,2,3,4,5,6,7,8,9,10],
ranNums = [],
i = nums.length,
j = 0;
while (i--) {
j = Math.floor(Math.random() * (i+1));
ranNums.push(nums[j]);
nums.splice(j,1);
}
So, for example, if you were looking for random numbers between 1 - 20 that were also even, then you could use:
nums = [2,4,6,8,10,12,14,16,18,20];
Then just read through ranNums in order to recall the random numbers.
This runs no risk of it taking increasingly longer to find unused numbers, as you were finding in your approach.
EDIT: After reading this and running a test on jsperf, it seems like a much better way of doing this is a Fisher–Yates Shuffle:
function shuffle(array) {
var i = array.length,
j = 0,
temp;
while (i--) {
j = Math.floor(Math.random() * (i+1));
// swap randomly chosen element with current element
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
var ranNums = shuffle([1,2,3,4,5,6,7,8,9,10]);
Basically, it's more efficient by avoiding the use of 'expensive' array operations.
BONUS EDIT: Another possibility is using generators (assuming you have support):
function* shuffle(array) {
var i = array.length;
while (i--) {
yield array.splice(Math.floor(Math.random() * (i+1)), 1)[0];
}
}
Then to use:
var ranNums = shuffle([1,2,3,4,5,6,7,8,9,10]);
ranNums.next().value; // first random number from array
ranNums.next().value; // second random number from array
ranNums.next().value; // etc.
where ranNums.next().value will eventually evaluate to undefined once you've run through all the elements in the shuffled array.
Overall this won't be as efficient as the Fisher–Yates Shuffle because you're still splice-ing an array. But the difference is that you're now doing that work only when you need it rather than doing it all upfront, so depending upon your use case, this might be better.
//random number without repetition in JavaScript, Just in one line;
//it can be used as _id;
//it not need to store or check;
const myRnId = () => parseInt(Date.now() * Math.random());
console.log(myRnId()); // any random number included timeStamp;
function Myrand(max,min){
arr=[];
for (i = 0; i < max; i++) {
x = Math.floor( Math.random() * max) + min;
if(arr.includes(x) == true){
i=i-1;
}else{
if(x>max==false){
arr.push(x);
}
}
}
return arr;
}
console.log(Myrand(5,1));
Try this:
var numbers = []; // new empty array
var min, max, r, n, p;
min = 1;
max = 50;
r = 5; // how many numbers you want to extract
for (let i = 0; i < r; i++) {
do {
n = Math.floor(Math.random() * (max - min + 1)) + min;
p = numbers.includes(n);
if(!p){
numbers.push(n);
}
}
while(p);
}
console.log(numbers.join(" - "));
let arr = [];
do {
let num = Math.floor(Math.random() * 10 + 1);
arr.push(num);
arr = arr.filter((item, index) => {
return arr.indexOf(item) === index
});
} while (arr.length < 10);
console.log(arr);
HTML
<p id="array_number" style="font-size: 25px; text-align: center;"></p>
JS
var min = 1;
var max = 90;
var stop = 6; //Number of numbers to extract
var numbers = [];
for (let i = 0; i < stop; i++) {
var n = Math.floor(Math.random() * max) + min;
var check = numbers.includes(n);
if(check === false) {
numbers.push(n);
} else {
while(check === true){
n = Math.floor(Math.random() * max) + min;
check = numbers.includes(n);
if(check === false){
numbers.push(n);
}
}
}
}
sort();
//Sort the array in ascending order
function sort() {
numbers.sort(function(a, b){return a-b});
document.getElementById("array_number").innerHTML = numbers.join(" - ");
}
DEMO
The issue is that as you approach saturation you begin to take longer and longer to generate a unique number "randomly". For instance, in the example you provided above the max is 10. Once the used number array contains 8 numbers it can potentially take a long time for the 9th and 10th to be found. This is probably where the maximum call stack error is being generated.
jsFiddle Demo showing iteration count being maxed
By iterating inside of your recursion, you can see that a large amount of execution occurs when the array is completely saturated, but the function is called. In this scenario, the function should exit.
jsFiddle Demo with early break
if( used.length >= max ) return undefined;
And one last way to accomplish both the iteration checks and the infinite recursion would be like this jsFiddle Demo:
function randomNum(max, used, calls){
if( calls == void 0 ) calls = 0;
if( calls++ > 10000 ) return undefined;
if( used.length >= max ) return undefined;
var newNum = Math.floor(Math.random() * max + 1);
if($.inArray(newNum, used) === -1){
return newNum;
}else{
return randomNum(max,used,calls);
}
}
<!DOCTYPE html>
<html>
<body>
<h2>JavaScript Math.random()</h2>
<p>Math.random() returns a random number between 0 (included) and 1 (excluded):</p>
<p id="demo"></p>
<script>
var storeArray = []
function callRamdom(){
var randomNumber = Math.floor(Math.random() * 5);
return randomNumber;
}
function randomStore(){
var localValue = callRamdom()
var status = false;
for(i=0;i<5; i++){
var aa = storeArray[i];
if(aa!=localValue){
console.log(storeArray[i]+"hhhhh"+ localValue);
if(i==4){
status=true;
}
}
else
break;
}
if(status==true){
storeArray.push(localValue);
}
if(storeArray.length!=5){
randomStore();
}
return storeArray;
}
document.getElementById("demo").innerHTML = randomStore();
</script>
</body>
</html>
while(randArr.length < SIZEOFARRAY){
val = Math.floor((Math.random() * RANGEOFVALUES));
if(randArr.indexOf(val) < 0){
randArr.push(val);
}
}
You can change SIZEOFARRAY to the size of the array you wish to use
and also change RANGEOFVALUES to the range of values you wish to randomize
const GenerateRandomNumbers = (max) => {
let orderNumbers = new Set();
for (let i = 1; ;i++){
let random = Math.floor(Math.random() * max + 1) ;
orderNumbers.add(random);
if (orderNumbers.size == max){
break;
}
}
return orderNumbers;}
function randomNumbers(max) {
function range(upTo) {
var result = [];
for(var i = 0; i < upTo; i++) result.push(i);
return result;
}
function shuffle(o){
for(var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
return o;
}
var myArr = shuffle(range(max));
return function() {
return myArr.shift();
};
}
Built a little test, try this on jsfiddle:
var randoms = randomNumbers(10),
rand = randoms(),
result = [];
while(rand != null) {
result.push(rand);
rand = randoms();
}
console.log(result);
Shuffle function courtesy of dzone.com.
This is how I achieve it using underscore.js
To get n integers from min to max values. Where n is the size argument.
var randomNonRepeatingIntFromInterval = function(min, max, size) {
var values = [];
while (values.length < size) {
values.push(Math.floor(Math.random() * ( max - min + 1) + min));
values = _.uniq(values);
}
return values;
}
Sorry this is a new answer to an old question, but this can be done more efficiently with a map. What you're after is random selection rather than non-repeating random. Non-repeating random is nonsensical.
Where _a is the collection, and r is not part of the collection, we lambda the random value r:
function aRandom(f){
var r = Math.random();
aRandom._a[r] ? aRandom(f) : f(r,aRandom._a[r] = 1);
}
aRandom._a = {};
//usage:
aRandom(function(r){ console.log(r) });
Redefine aRandom._a when the browser gets sluggish. To avoid eventual sluggishness, one should really use an UUID generation algo with sufficient entropy so that chances of repeat are effectively zero, rather than brute forcing differentiability. I chose the function name aRandom because latin prefix A- means "away from." Since the more it's used, the further away from random the output. The function produces one million unique values in 2100 ms on a Macbook.
Advantage of the above solution is no need to limit the set. As well, multiple callers can use it at the same time and assume their values are different from all other callers. This is handy for such things as noise jittering distributions with insured no overlaps.
However, it can be modified to return integers as well, so as to restrict ram use to the length supplied:
function aRandom(f,c){
var r = Math.floor(Math.random()*c);
aRandom._a[r] ? aRandom(f,c) : f(r,aRandom._a[r] = 1);
}
aRandom._a = {};
//usage:
var len = 10;
var resultset = [];
for(var i =0; i< len; i++){
aRandom(function(r){ resultset.push(r); }, len);
}
console.log(resultset);
randojs.com makes this a simple one-liner:
randoSequence(1, 10)
This will return an array of numbers from 1 through 10 in random order.
You just need to add the following to the head of your html document, and you can do pretty much whatever you want with randomness easily. Random values from arrays, random jquery elements, random properties from objects, and even preventing repetitions as I've shown here.
<script src="https://randojs.com/1.0.0.js"></script>
Just one solution for reference
const fiveNums = () => {
const ranNum = () => Math.floor(Math.random() * (10 + 1));
let current;
let arr = [];
while(arr.length < 5) {
if(arr.indexOf(current = ranNum()) === -1) {
arr.push(current);
}
}
return arr;
};
fiveNums();
In case no permutation is wanted and/or length shall be variable, here is a solution for non repeating randomized lists/arrays without if-statements:
Shuffle function:
Input:
Array or object(list) of arbitrary length
optional: last key to be filtered (Array: index number, List: String of key)
Output:
random Key
to get your random item use myArrayOrList[key]
// no repeat if old_key is provided
function myShuffle(arr_or_list, old_key = false) {
var keys = Array.from(Object.keys(arr_or_list)); //extracts keys
if (old_key != false) {
keys.splice(keys.indexOf(old_key), 1); // removes old_key from keys
};
var randomKey = keys[Math.floor(Math.random() * keys.length)]; // get random key
return randomKey;
}
//test:
const a = [10, 20, 30, 40, 50, 60];
const b = {
"a": 10,
"bla-bla bla": 20,
"d": 30,
"c": 40
};
var oldKeys_a = [];
var oldKeys_b = [];
oldKeys_a[0] = myShuffle(a);
oldKeys_b[0] = myShuffle(b);
var i;
for (i = 1; i < 10; i++) {
oldKeys_a[i] = myShuffle(a, oldKeys_a[i - 1]);
oldKeys_b[i] = myShuffle(b, oldKeys_b[i - 1]);
}
alert('oldKeys_a: ' + oldKeys_a + '; oldKeys_b: ' + oldKeys_b)
//random...
//>>> oldKeys_a: 1,3,0,0,5,0,4,5,2,3; oldKeys_b: d,a,d,bla-bla bla,a,c,d,bla-bla bla,a,d <<<
Non-repeating range random number generation with the recursive patterns.
const getRandom = (max, memo) => {
if (max != memo.length) {
const pos = Math.floor(Math.random() * max);
if (memo.includes(pos)) {
return getRandom(max, memo);
} else {
return pos;
}
}
}
const random = [];
const range = 6;
for (let index = 0; index < range; index++) {
random.push(getRandom(range, random))
}
console.log('random', random) // random (6) [5, 3, 0, 2, 1, 4]
function getRandomNumberNoRepeat(length){
let numberPick = [0,1,2,3,4,5,6,7,8,9]
return numberPick.sort(() => Math.random() -0.5).slice(0, length)}
console.log(getRandomNumberNoRepeat(3));
let display = document.getElementById("container");
let myArray = [];
let randomiser = (min, max, vals) => {
while (myArray.length < vals) {
let randNum = Math.floor(Math.random() * (max - min + 1) + min);
if (!myArray.includes(randNum)) {
myArray.push(randNum);
}
}
return (display.textContent = myArray.join(" - "));
};
randomiser(1, 35, 7);
Here is one where you can specify how many number you need.
I decided to write this code and make a chrome extension for it to use when playing the lotto. haha. Just a learning experience for me and thanks to all who contributed. I read all posts and I'm better of today than I was yesterday in understanding shuffle and random non repeating numbers.
You don't really want a lost of random numbers. Truly random numbers must be able to repeat.
Truly random number are like throwing dice. Any number can come up next.
Shuffled numbers are like drawing playing cards. Each number can come up only once.
What you are really asking for is to shuffle a list of numbers and then use the first so many numbers from the shuffled list.
Think of making a list of numbers in order, and then using the random number generator to randomly select a number from a copy of that list. Each time, put the selected number at the end of your new list and remove it from the copy of the old list, shortening that list. When you are done, the new list will contain the shuffled numbers and the copy of the old list will be empty.
Alternately, you can take the number selected and use it immediately, shortening the copy of the list by removing the used number. Because you have removed the number from the list, it can't come up again.

Get Random unique variable num non-repeating previous or next In javascript [duplicate]

I have the following function
function randomNum(max, used){
newNum = Math.floor(Math.random() * max + 1);
if($.inArray(newNum, used) === -1){
console.log(newNum + " is not in array");
return newNum;
}else{
return randomNum(max,used);
}
}
Basically I am creating a random number between 1 - 10 and checking to see if that number has already been created, by adding it to an array and checking the new created number against it. I call it by adding it to a variable..
UPDATED:
for(var i=0;i < 10;i++){
randNum = randomNum(10, usedNums);
usedNums.push(randNum);
//do something with ranNum
}
This works, but in Chrome I get the following error:
Uncaught RangeError: Maximum call stack size exceeded
Which I guess it's because I am calling the function inside itself too many times. Which means my code is no good.
Can someone help me with the logic? what's a best way to make sure my numbers are not repeating?
If I understand right then you're just looking for a permutation (i.e. the numbers randomised with no repeats) of the numbers 1-10?
Maybe try generating a randomised list of those numbers, once, at the start, and then just working your way through those?
This will calculate a random permutation of the numbers in nums:
var nums = [1,2,3,4,5,6,7,8,9,10],
ranNums = [],
i = nums.length,
j = 0;
while (i--) {
j = Math.floor(Math.random() * (i+1));
ranNums.push(nums[j]);
nums.splice(j,1);
}
So, for example, if you were looking for random numbers between 1 - 20 that were also even, then you could use:
nums = [2,4,6,8,10,12,14,16,18,20];
Then just read through ranNums in order to recall the random numbers.
This runs no risk of it taking increasingly longer to find unused numbers, as you were finding in your approach.
EDIT: After reading this and running a test on jsperf, it seems like a much better way of doing this is a Fisher–Yates Shuffle:
function shuffle(array) {
var i = array.length,
j = 0,
temp;
while (i--) {
j = Math.floor(Math.random() * (i+1));
// swap randomly chosen element with current element
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
var ranNums = shuffle([1,2,3,4,5,6,7,8,9,10]);
Basically, it's more efficient by avoiding the use of 'expensive' array operations.
BONUS EDIT: Another possibility is using generators (assuming you have support):
function* shuffle(array) {
var i = array.length;
while (i--) {
yield array.splice(Math.floor(Math.random() * (i+1)), 1)[0];
}
}
Then to use:
var ranNums = shuffle([1,2,3,4,5,6,7,8,9,10]);
ranNums.next().value; // first random number from array
ranNums.next().value; // second random number from array
ranNums.next().value; // etc.
where ranNums.next().value will eventually evaluate to undefined once you've run through all the elements in the shuffled array.
Overall this won't be as efficient as the Fisher–Yates Shuffle because you're still splice-ing an array. But the difference is that you're now doing that work only when you need it rather than doing it all upfront, so depending upon your use case, this might be better.
//random number without repetition in JavaScript, Just in one line;
//it can be used as _id;
//it not need to store or check;
const myRnId = () => parseInt(Date.now() * Math.random());
console.log(myRnId()); // any random number included timeStamp;
function Myrand(max,min){
arr=[];
for (i = 0; i < max; i++) {
x = Math.floor( Math.random() * max) + min;
if(arr.includes(x) == true){
i=i-1;
}else{
if(x>max==false){
arr.push(x);
}
}
}
return arr;
}
console.log(Myrand(5,1));
Try this:
var numbers = []; // new empty array
var min, max, r, n, p;
min = 1;
max = 50;
r = 5; // how many numbers you want to extract
for (let i = 0; i < r; i++) {
do {
n = Math.floor(Math.random() * (max - min + 1)) + min;
p = numbers.includes(n);
if(!p){
numbers.push(n);
}
}
while(p);
}
console.log(numbers.join(" - "));
let arr = [];
do {
let num = Math.floor(Math.random() * 10 + 1);
arr.push(num);
arr = arr.filter((item, index) => {
return arr.indexOf(item) === index
});
} while (arr.length < 10);
console.log(arr);
HTML
<p id="array_number" style="font-size: 25px; text-align: center;"></p>
JS
var min = 1;
var max = 90;
var stop = 6; //Number of numbers to extract
var numbers = [];
for (let i = 0; i < stop; i++) {
var n = Math.floor(Math.random() * max) + min;
var check = numbers.includes(n);
if(check === false) {
numbers.push(n);
} else {
while(check === true){
n = Math.floor(Math.random() * max) + min;
check = numbers.includes(n);
if(check === false){
numbers.push(n);
}
}
}
}
sort();
//Sort the array in ascending order
function sort() {
numbers.sort(function(a, b){return a-b});
document.getElementById("array_number").innerHTML = numbers.join(" - ");
}
DEMO
The issue is that as you approach saturation you begin to take longer and longer to generate a unique number "randomly". For instance, in the example you provided above the max is 10. Once the used number array contains 8 numbers it can potentially take a long time for the 9th and 10th to be found. This is probably where the maximum call stack error is being generated.
jsFiddle Demo showing iteration count being maxed
By iterating inside of your recursion, you can see that a large amount of execution occurs when the array is completely saturated, but the function is called. In this scenario, the function should exit.
jsFiddle Demo with early break
if( used.length >= max ) return undefined;
And one last way to accomplish both the iteration checks and the infinite recursion would be like this jsFiddle Demo:
function randomNum(max, used, calls){
if( calls == void 0 ) calls = 0;
if( calls++ > 10000 ) return undefined;
if( used.length >= max ) return undefined;
var newNum = Math.floor(Math.random() * max + 1);
if($.inArray(newNum, used) === -1){
return newNum;
}else{
return randomNum(max,used,calls);
}
}
<!DOCTYPE html>
<html>
<body>
<h2>JavaScript Math.random()</h2>
<p>Math.random() returns a random number between 0 (included) and 1 (excluded):</p>
<p id="demo"></p>
<script>
var storeArray = []
function callRamdom(){
var randomNumber = Math.floor(Math.random() * 5);
return randomNumber;
}
function randomStore(){
var localValue = callRamdom()
var status = false;
for(i=0;i<5; i++){
var aa = storeArray[i];
if(aa!=localValue){
console.log(storeArray[i]+"hhhhh"+ localValue);
if(i==4){
status=true;
}
}
else
break;
}
if(status==true){
storeArray.push(localValue);
}
if(storeArray.length!=5){
randomStore();
}
return storeArray;
}
document.getElementById("demo").innerHTML = randomStore();
</script>
</body>
</html>
while(randArr.length < SIZEOFARRAY){
val = Math.floor((Math.random() * RANGEOFVALUES));
if(randArr.indexOf(val) < 0){
randArr.push(val);
}
}
You can change SIZEOFARRAY to the size of the array you wish to use
and also change RANGEOFVALUES to the range of values you wish to randomize
const GenerateRandomNumbers = (max) => {
let orderNumbers = new Set();
for (let i = 1; ;i++){
let random = Math.floor(Math.random() * max + 1) ;
orderNumbers.add(random);
if (orderNumbers.size == max){
break;
}
}
return orderNumbers;}
function randomNumbers(max) {
function range(upTo) {
var result = [];
for(var i = 0; i < upTo; i++) result.push(i);
return result;
}
function shuffle(o){
for(var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
return o;
}
var myArr = shuffle(range(max));
return function() {
return myArr.shift();
};
}
Built a little test, try this on jsfiddle:
var randoms = randomNumbers(10),
rand = randoms(),
result = [];
while(rand != null) {
result.push(rand);
rand = randoms();
}
console.log(result);
Shuffle function courtesy of dzone.com.
This is how I achieve it using underscore.js
To get n integers from min to max values. Where n is the size argument.
var randomNonRepeatingIntFromInterval = function(min, max, size) {
var values = [];
while (values.length < size) {
values.push(Math.floor(Math.random() * ( max - min + 1) + min));
values = _.uniq(values);
}
return values;
}
Sorry this is a new answer to an old question, but this can be done more efficiently with a map. What you're after is random selection rather than non-repeating random. Non-repeating random is nonsensical.
Where _a is the collection, and r is not part of the collection, we lambda the random value r:
function aRandom(f){
var r = Math.random();
aRandom._a[r] ? aRandom(f) : f(r,aRandom._a[r] = 1);
}
aRandom._a = {};
//usage:
aRandom(function(r){ console.log(r) });
Redefine aRandom._a when the browser gets sluggish. To avoid eventual sluggishness, one should really use an UUID generation algo with sufficient entropy so that chances of repeat are effectively zero, rather than brute forcing differentiability. I chose the function name aRandom because latin prefix A- means "away from." Since the more it's used, the further away from random the output. The function produces one million unique values in 2100 ms on a Macbook.
Advantage of the above solution is no need to limit the set. As well, multiple callers can use it at the same time and assume their values are different from all other callers. This is handy for such things as noise jittering distributions with insured no overlaps.
However, it can be modified to return integers as well, so as to restrict ram use to the length supplied:
function aRandom(f,c){
var r = Math.floor(Math.random()*c);
aRandom._a[r] ? aRandom(f,c) : f(r,aRandom._a[r] = 1);
}
aRandom._a = {};
//usage:
var len = 10;
var resultset = [];
for(var i =0; i< len; i++){
aRandom(function(r){ resultset.push(r); }, len);
}
console.log(resultset);
randojs.com makes this a simple one-liner:
randoSequence(1, 10)
This will return an array of numbers from 1 through 10 in random order.
You just need to add the following to the head of your html document, and you can do pretty much whatever you want with randomness easily. Random values from arrays, random jquery elements, random properties from objects, and even preventing repetitions as I've shown here.
<script src="https://randojs.com/1.0.0.js"></script>
Just one solution for reference
const fiveNums = () => {
const ranNum = () => Math.floor(Math.random() * (10 + 1));
let current;
let arr = [];
while(arr.length < 5) {
if(arr.indexOf(current = ranNum()) === -1) {
arr.push(current);
}
}
return arr;
};
fiveNums();
In case no permutation is wanted and/or length shall be variable, here is a solution for non repeating randomized lists/arrays without if-statements:
Shuffle function:
Input:
Array or object(list) of arbitrary length
optional: last key to be filtered (Array: index number, List: String of key)
Output:
random Key
to get your random item use myArrayOrList[key]
// no repeat if old_key is provided
function myShuffle(arr_or_list, old_key = false) {
var keys = Array.from(Object.keys(arr_or_list)); //extracts keys
if (old_key != false) {
keys.splice(keys.indexOf(old_key), 1); // removes old_key from keys
};
var randomKey = keys[Math.floor(Math.random() * keys.length)]; // get random key
return randomKey;
}
//test:
const a = [10, 20, 30, 40, 50, 60];
const b = {
"a": 10,
"bla-bla bla": 20,
"d": 30,
"c": 40
};
var oldKeys_a = [];
var oldKeys_b = [];
oldKeys_a[0] = myShuffle(a);
oldKeys_b[0] = myShuffle(b);
var i;
for (i = 1; i < 10; i++) {
oldKeys_a[i] = myShuffle(a, oldKeys_a[i - 1]);
oldKeys_b[i] = myShuffle(b, oldKeys_b[i - 1]);
}
alert('oldKeys_a: ' + oldKeys_a + '; oldKeys_b: ' + oldKeys_b)
//random...
//>>> oldKeys_a: 1,3,0,0,5,0,4,5,2,3; oldKeys_b: d,a,d,bla-bla bla,a,c,d,bla-bla bla,a,d <<<
Non-repeating range random number generation with the recursive patterns.
const getRandom = (max, memo) => {
if (max != memo.length) {
const pos = Math.floor(Math.random() * max);
if (memo.includes(pos)) {
return getRandom(max, memo);
} else {
return pos;
}
}
}
const random = [];
const range = 6;
for (let index = 0; index < range; index++) {
random.push(getRandom(range, random))
}
console.log('random', random) // random (6) [5, 3, 0, 2, 1, 4]
function getRandomNumberNoRepeat(length){
let numberPick = [0,1,2,3,4,5,6,7,8,9]
return numberPick.sort(() => Math.random() -0.5).slice(0, length)}
console.log(getRandomNumberNoRepeat(3));
let display = document.getElementById("container");
let myArray = [];
let randomiser = (min, max, vals) => {
while (myArray.length < vals) {
let randNum = Math.floor(Math.random() * (max - min + 1) + min);
if (!myArray.includes(randNum)) {
myArray.push(randNum);
}
}
return (display.textContent = myArray.join(" - "));
};
randomiser(1, 35, 7);
Here is one where you can specify how many number you need.
I decided to write this code and make a chrome extension for it to use when playing the lotto. haha. Just a learning experience for me and thanks to all who contributed. I read all posts and I'm better of today than I was yesterday in understanding shuffle and random non repeating numbers.
You don't really want a lost of random numbers. Truly random numbers must be able to repeat.
Truly random number are like throwing dice. Any number can come up next.
Shuffled numbers are like drawing playing cards. Each number can come up only once.
What you are really asking for is to shuffle a list of numbers and then use the first so many numbers from the shuffled list.
Think of making a list of numbers in order, and then using the random number generator to randomly select a number from a copy of that list. Each time, put the selected number at the end of your new list and remove it from the copy of the old list, shortening that list. When you are done, the new list will contain the shuffled numbers and the copy of the old list will be empty.
Alternately, you can take the number selected and use it immediately, shortening the copy of the list by removing the used number. Because you have removed the number from the list, it can't come up again.

How do read the last character per string?

My code generates a string, as shown in the image. At the end of each line a number in euros is shown, let call these euros1 and euros2. The idea is that the amount ✓ icons is multiplied with euros1 and euros2 shown per line. So in the case of my example: (10,45 + 5,50) x 2 = 31,90 for the first line, and for the second line (18,24 + 9,60) x 3 = 83,52. These numbers are supposed to be combined to a total of 115,42.
However my current code produces a total of 218,95. So it takes the sum of all the euros1 and euros2 and multiplies it by the total amount of ✓ icons.
How can I calculate the sum of the euros1 + euros2 x the amount ✓ icons, per line?
I think a for loop could help me, however I am new to Javascript and I am not sure how to proceed.
var temp = g_form.getValue('mwadm_onkosten_list_refList_changes');
var count = (temp.match(/✓/g) || []).length;
var lastChar = temp[temp.length -1];
if (count != temp);
{
total = total * count;
}
I was thinking of a split solution just like #dganenco mentioned. I created a TEMP variable to reproduce your result string. And execute a function on it foreach row.
And i intentionally kept it really simple. Hope this helps.
var temp = ["✓|| €10,45 | €1,50 ", "✓|| €10,45 | €2,50 ", "✓|| €10,45 | €3,50 "];
var totalTimes = (String(temp).match(/✓/g) || []).length;
//perform function for each row
temp.forEach(CalculateRow);
//Splits the row, Gets euro1 as decimal, euro2 as decimal, calculates the amount a character is found, calculates total, prints it to console.
function CalculateRow(item, index) {
var arr = item.split('|');
var euro1 = GetValueFromArray(arr,1);
var euro2 = GetValueFromArray(arr, 2);
var times = (String(arr).match(/✓/g) || []).length;
var _total = (euro1 + euro2) * times;
console.log(_total);
}
//Takes the index value, and casts it to decimal value
function GetValueFromArray(arr, index){
var getindex = arr.length -index;
var result = arr[getindex];
result = result.replace('€', '');
return parseFloat(result, 10);
}
I've got a solution for you in case if is it possible to only split strings by | symbol, then get last 2 values and do all needed stuff using them.
const strArr = ['asd|addf|$56.60|$10.40', 'asd|addf|$5.60|$1.40'];
let sum = 0;
strArr.forEach(str => {
const splitted = str.split('|');
debugger;
const x = getNumber(splitted[splitted.length - 2]);
const y = getNumber(splitted[splitted.length - 1]);
sum += (x+y)*2;
});
function getNumber(str){
return +str.substring(1);
}
console.log(sum)
Assuming temp is the multiline string in the form of
asd|√|√| €12.34 | €15.25
zxc|√|| €18.34 | €19.25
This code should give you a sum of last two numbers multiplied by the amount of √
var temp = "asd|√|√| €12.34 | €15.25\nzxc|√|| €18.34 | €19.25"
let lines = temp.split('\n')
let linesSummaries = []
for(let line of lines){
var count = line.match(/√/g).length
var vars = line.split('|')
var x = parseFloat(vars[vars.length-1].match(/\d+\.?\d*/))
var y = parseFloat(vars[vars.length-2].match(/\d+\.?\d*/))
var lineSum = (x + y ) * count
linesSummaries.push(lineSum)
}
console.log(linesSummaries)

Unique words counter using javascript

I want to make a function uniqueWordCount which can count all unique words in a string (words.txt). I should be able to get the answer after returning it, and I have done something like this now:
let fs = require('fs');
function run() {
let text = fs.readFileSync('./words.txt', 'utf8');
return uniqueWordCount(text);
}
function uniqueWordCount(str) {
let count = 0;
let set = new Set();
let words = str.split (' ');
for(let i = 1; 1 < words.length; i++){
set.add(str[i]);
count = set.size;
}
return uniqueWordCount;
}
module.exports.run = run;
split the string on spaces using split() and make it a set. Set will remove the duplicates. Return the size of the set using size()
function uniqueWordCount(str) {
let set = new Set(str.split(' '));
return set.size;
}
console.log(uniqueWordCount('as as de we re'))
An ES5 & ES6 compliant solution could be:
"aabc adsd adsd hasd"
.split(/\b(?:\s+)?/)
.reduce(function(ac,d,i){!~ac.indexOf(d) && ac.push(d);return ac;},[]).length //return 3
Here are 4 changes you have to make to get your uniqueWordCount function to work:
Start your for-loop at let i = 0, not let i = 1
1 < words.length will ALWAYS be true, creating an infinite loop. Replace 1 with i -> i < words.length
In your for-loop access words[i], not str[i] since words is your array of words, str is just your original string.
You're returning a variable uniqueWordCount which is a function, but you need to return count which is storing the size of your set.
Here is how your function should now look:
function uniqueWordCount(str) {
let count = 0;
let set = new Set();
let words = str.split (' ');
// 1. replace 'let i = 1;' with 'let i = 0;'
// 2. replace the second condition 1 < words.length with i < words.length
for(let i = 1; i < words.length; i++){
// 3. replace 'str[i]' with 'words[i]'
set.add(words[i]);
count = set.size;
}
// 4. change 'uniqueWordCount' to 'count'
return count;
}
Note: You can and probably should get rid of your count variable and just return set.size at the end of your function. Then you will be able to avoid updating count on every iteration of your for-loop as well. This change is not required but would make your function nicer. Here is how it would look:
function uniqueWordCount(str) {
let set = new Set();
let words = str.split (' ');
for(let i = 1; i < words.length; i++){
set.add(words[i]);
}
return set.size;
}
One final optimization would be similar to what #ellipsis mentioned - creating a Set immediately while splitting your str string. Like this:
function uniqueWordCount(str) {
// automatically populate your 'Set'
let set = new Set(str.split (' '));
// immediately return your set's size
return set.size;
}
Note: If you want to count unique words regardless of case, you can immediately do str.toLowerCase() in your uniqueWordCount function.
function uniqueWordCount(str) {
var i = [];
return str.split(' ')
.map( e => (i[e] === undefined) ? i[e] = 1 : 0)
.reduce((accumulator, currentValue) => accumulator + currentValue);
}
console.log(uniqueWordCount("ws ws ws we we wf h"));

for loop not executing properly Javascript

i m trying to calculate weight of a string using the following function
function weight(w)
{
Cap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
small = 'abcdefghijklmnopqrstuvwxyz'
spcl = "~!##$%^&*()_+[]\{}|;':,./<>?"
num = '0123456789'
var p = []
for(i=0;i<w.length;i++)
{
if(Cap.contains(w[i])==true)
p[i] = Cap.indexOf(w[i]) + 2
else if(small.contains(w[i])==true)
p[i] = small.indexOf(w[i]) + 1
else if(num.contains(w[i]))
p[i] = num.indexOf(w[i])
else if(spcl.contains(w[i]))
p[i] = 1
}
return _.reduce(p,function(memo, num){ return memo + num; }, 0);
}
where w is a string. this properly calculates weight of the string.
But whn i try to to calculate weight of strings given in a an array, it jst calculates the weight of the first element, ie. it does not run the full for loop. can anyone explain to me why is that so??
the for loop is as given below
function weightList(l)
{
weigh = []
for(i=0;i<l.length;i++)
weigh.push(weight(l[i]));
return weigh;
}
input and output:
>>> q = ['abad','rewfd']
["abad", "rewfd"]
>>> weightList(q)
[8]
whereas the output array should have had 2 entries.
[8,56]
i do not want to use Jquery. i want to use Vanilla only.
Because i is a global variable. So when it goes into the function weight it sets the value of i greater than the lenght of l. Use var, it is not optional.
for(var i=0;i<l.length;i++)
and
for(var i=0;i<w.length;i++)
You should be using var with the other variables in the function and you should be using semicolons.
I think your issue is just malformed JavaScript. Keep in mind that JavaScript sucks, and is not as forgiving as some other languages are.
Just by adding a few "var" and semicolons, I was able to get it to work with what you had.
http://jsfiddle.net/3D5Br/
function weight(w) {
var Cap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
small = 'abcdefghijklmnopqrstuvwxyz',
spcl = "~!##$%^&*()_+[]\{}|;':,./<>?",
num = '0123456789',
p = [];
for(var i=0;i<w.length;i++){
if(Cap.contains(w[i])==true)
p[i] = Cap.indexOf(w[i]) + 2
else if(small.contains(w[i])==true)
p[i] = small.indexOf(w[i]) + 1
else if(num.contains(w[i]))
p[i] = num.indexOf(w[i])
else if(spcl.contains(w[i]))
p[i] = 1
}
return _.reduce(p,function(memo, num){ return memo + num; }, 0);
}
function weightList(l) {
var weigh = [];
for(var i=0;i<l.length;i++)
weigh.push(weight(l[i]));
return weigh;
}
q = ['abad','rewfd'];
results = weightList(q);
Hope that helps

Categories

Resources