Javascript convert array of numbers with duplicates into unique sets of numbers - javascript
I have a javascript sorting algorithm problem which I am struggling to solve efficiently .
I have an array of numbers which I am calling cards e.g. [1,2,3,4,5,6,7,8,9,10]
Each day I have 6 teaching sets. In each set I want to display a maximum of 5 RANDOM & UNIQUE cards. Each card must be displayed exactly 3 times a day.
What I have done so far is to create 6 empty arrays (sets). I then iterate through my cards 3 times each attempting to add them to a random set if the card does not already exist in that array. Sometimes it works, but most of the time I get a problem where I have only one array with space left and that array already contains the card. my code:
Assume in my code that numberOfSetsPerCard = 3 & totalSets = 6. The only library available is JQuery.
shuffleIntoSets : function(cards, numberOfSetsPerCard, totalSets) {
var sets = [];
// initialize the sets as empty arrays
for (i = 0; i < totalSets; i++) {
sets[i] = [];
}
$.each(cards, function(index, card) {
for(x=0;x<numberOfSetsPerCard;) {
// attempt to place the card in a set which doesnt already contain the card
setNo = Math.floor((Math.random() * totalSets));
console.log("setNo: " + setNo);
if(jQuery.inArray(card,sets[setNo]) == -1 && sets[setNo].length<5) {
console.log(setNo + "does not contain: " + card);
sets[setNo].push(card);
console.log("Added the card, set now looks like :" + sets[setNo]);
x++;
}
}
});
return sets;
},
This is my solution to your problem, it is a bit long, but i think it a bit long, but it definitely creates randomness in the teach sets, and satisfy the conditions you have stated
codepen link: http://codepen.io/anon/pen/yyoaZy
html/include jquery:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
js:
//-----------------------------------------------
//For this question on StackOverflow:
//http://stackoverflow.com/questions/28192127/javascript-convert-array-of-numbers-with-duplicates-into-unique-sets-of-numbers
//-----------------------------------------------
var cards = [1,2,3,4,5,6,7,8,9,10];
var numOfTeachingSets = 6;
var numOfUniqueCardsPerTeachingSet = 5;
var numOfCardRepetitionPerDay = 3;
var shuffleCards = function(cards, numOfTeachingSets, numOfUniqueCardsPerTeachingSet, numOfRepetitionPerCard){
if(cards.length*numOfRepetitionPerCard != numOfTeachingSets*numOfUniqueCardsPerTeachingSet){
alert("invalid param");
return null;
}
//since each card is required to repeat numOfRepetitionPerCard times
//the available choices should be numOfRepetitionPerCard times of the original deck of cards
var availableCardChoices = cards.concat([]);
for (var i=0;i<numOfRepetitionPerCard-1;i++){
availableCardChoices = availableCardChoices.concat(cards);
}
//Record down items from [availableCardChoices] has been picked
var choosenList = [];
//Put 6 sets of unique cards into the result
var result = [];
for (var i=0;i<numOfTeachingSets;i++){
result.push( pickOutUniqueNumberSet(availableCardChoices,numOfUniqueCardsPerTeachingSet, choosenList) );
}
//return the result - an array of [numOfTeachingSets] arrays
return result;
}
//-----------------------------------------------------------
// Function:
// picks [cardsPerSet] number of unique item out of [availableChoices]
// each time an item is picked, this item is removed from [availableChoices]
//
// Important note: The number of card repetition is not really meaningful
// because if each round of picking produces unique sets,
// then the number of card repetition condition will be
// automatically satisfied.
//-----------------------------------------------------------
var pickOutUniqueNumberSet = function(availableChoices, cardsPerSet, disabledChoices){
if (cardsPerSet==0 || availableChoices.length==0){
return null;
}
var choosenSet = [];
var maxAttempts = 10000; //these 2 counter are used to prevent infinite looping
var attempts = 0;
for(var i=0;i<cardsPerSet;i++){
//items are choosen from [availableChoices] by their array index.
var randomIndex = Math.floor((Math.random() * availableChoices.length));
//repeatedly grab a random index from availableChoices till a unique one is selected
//unique item is an item that is not repeated in choosenSet, and its index is not in disabledChoices
while( (InArray(choosenSet, availableChoices[randomIndex]) || InArray(disabledChoices, randomIndex)) && attempts<maxAttempts){
randomIndex = Math.floor((Math.random() * availableChoices.length));
attempts++;
}
//Add this item to the chooseSet
choosenSet.push(availableChoices[randomIndex]);
//Add this item's index to disabledChoices
disabledChoices.push(randomIndex);
}
return choosenSet;
}
var InArray = function(array, itemToFind){
for(var i=0;i<array.length; i++){
if(array[i]==itemToFind){
return true;
}
}
return false;
}
//---------- Test --------
var teachingSets = shuffleCards(cards, numOfTeachingSets, numOfUniqueCardsPerTeachingSet, numOfCardRepetitionPerDay);
for(var i=0;i<teachingSets.length; i++){
document.write(teachingSets[i] + "<br>");
}
Related
How to iterate over an array of numbers to find the first number that occurs 3 times in the array
I have the following code let range = [1,2,3]; let multiples = [1,2,3,4,5,6,2,4,6,3,6]; I want to find the first number in the multiples array that occurs range.lenght times (3); I want to start with multiples[0] check how many times it occurs in multiples, if it occurs 3 times I want to return multiples[0], if it is less than 3 times, I want to check how many times multiples[1] occurs in the multiples array. If multiples[1] occurs 3 times I want to return multiples[1], else I move on to check multiples[2], etc. until I find a number that occurs 3 times. In the code above I should return 6. I've looked at How to count the number of certain element in an array? and Idiomatically find the number of occurrences a given value has in an array and get closest number out of array among other research but have not figured it out yet. I tried to simplify the question as much as possible. But if more info is needed it relates to this challenge on freeCodeCamp. Where I am at with my code is function smallestCommons(arr) { let sortArr = arr.sort((a, b) => a - b); console.log(sortArr); let range = []; for (let i = sortArr[0]; i <= sortArr[1]; i++) { range.push(i); } console.log("range = " + range); let maxNum = range.reduce( (a, b) => a * b); console.log("maxNum = " + maxNum); let multiples = []; for (let i = 0; i < maxNum; i++) { let j = 0; do { multiples.push(j + range[i]); j += range[i]; } while (j < maxNum); //j = 0; } for (let i = 0; i < multiples.length; i++) { let numberToFind = multiples[i]; /*stuck here hence my question, maybe I shouldn't even start with a for loop*/ //tried reduce, forEach, filter, while loop, do while loop } console.log("multiples = " + multiples); } console.log(smallestCommons([1,3])); The logs are 1,3 range = 1,2,3 maxNum = 6 multiples = 1,2,3,4,5,6,2,4,6,3,6,NaN,NaN,NaN
What you can do is, first split your string with , and then using below function loop for check. function countLength(arr, checkNumber) { var count = 0; for (var i = 0; i < arr.length; i++) { if (arr[i] === checkNumber) { count++; } } return count; } countLength(list, NUMBER YOU WANT TO CHECK); And if you want to check first number occur for 3 time then you need to make change in function and introduce .map or .filter in action to count number. Example const multiples = [1,2,3,4,5,6,2,4,6,3,6]; let occurance_arr=[]; const aCount = [...new Set(multiples)].map(x => { if(multiples.filter(y=> y==x).length == 3) { occurance_arr.push(x); } }); console.log(occurance_arr); Above code will give you 6 in console, if you have multiple value then 0th element is the answer you are looking for which is first three time occurrence of item.
You can loop through your list keeping an object that maps each number to the number of times you've seen it. You can check the counts object as you loop, so if you see a number and the count is one less than your target, you can return it. If you make it through the loop without returning you didn't find what you're looking for — return something sensible : let range = [1,2,3] let multiples = [1,2,3,4,5,6,2,4,6,3,6] function findFirstMult(arr, len){ let counts = {} // to keep track of how many times you've seen something for (let n of arr){ // loop throught the array if (!counts[n]) counts[n] = 0 // if it's then first time you've seen n, defined that key if (counts[n] == len - 1) return n // found it counts[n] +=1 // otherwise increase the count } return undefined } console.log(findFirstMult(multiples, range.length)) This will require only one loop through the array in the worse case and will return early if if finds something.
Manipulate more javascript array based on another array
I've a strange thing to do but I don't know how to start I start with this vars var base = [1,1,1,2,3,5,7,9,14,19,28,40,56,114,232,330]; var sky = [0,0,0,3,4,5,6,7,8,9,10,11,12,14,16,17]; var ite = [64,52,23,38,13,15,6,4,6,3,2,1,2,1,1,1]; So to start all the 3 array have the same length and the very first operation is to see if there is a duplicate value in sky array, in this case the 0 is duplicated and only in this case is at the end, but all of time the sky array is sorted. So I've to remove all the duplicate (in this case 0) from sky and remove the corresponding items from base and sum the corresponding items on ite. So if there's duplicate on position 4,5 I've to manipulate this conditions. But let see the new 3 array: var new_base = [1,2,3,5,7,9,14,19,28,40,56,114,232,330]; var new_sky = [0,3,4,5,6,7,8,9,10,11,12,14,16,17]; var new_ite = [139,38,13,15,6,4,6,3,2,1,2,1,1,1]; If you see the new_ite have 139 instead the 64,52,23, that is the sum of 64+52+23, because the first 3 items on sky are the same (0) so I remove two corresponding value from base and sky too and I sum the corresponding value into the new_ite array. There's a fast way to do that? I thought a for loops but I stuck at the very first for (i = 0; i < sky.length; i++) lol, cuz I've no idea on how to manipulate those 3 array in that way J
When removing elements from an array during a loop, the trick is to start at the end and move to the front. It makes many things easier. for( var i = sky.length-1; i>=0; i--) { if (sky[i] == prev) { // Remove previous index from base, sky // See http://stackoverflow.com/questions/5767325/how-to-remove-a-particular-element-from-an-array-in-javascript base.splice(i+1, 1); sky.splice(i+1, 1); // Do sum, then remove ite[i] += ite[i+1]; ite.splice(i+1, 1); } prev = sky[i]; } I won't speak to whether this is the "fastest", but it does work, and it's "fast" in terms of requiring little programmer time to write and understand. (Which is often the most important kind of fast.)
I would suggest this solution where j is used as index for the new arrays, and i for the original arrays: var base = [1,1,1,2,3,5,7,9,14,19,28,40,56,114,232,330]; var sky = [0,0,0,3,4,5,6,7,8,9,10,11,12,14,16,17]; var ite = [64,52,23,38,13,15,6,4,6,3,2,1,2,1,1,1]; var new_base = [], new_sky = [], new_ite = []; var j = -1; sky.forEach(function (sk, i) { if (!i || sk !== sky[i-1]) { new_ite[++j] = 0; new_base[j] = base[i]; new_sky[j] = sk; } new_ite[j] += ite[i]; }); console.log('new_base = ' + new_base); console.log('new_sky = ' + new_sky); console.log('new_ite = ' + new_ite);
You can use Array#reduce to create new arrays from the originals according to the rules: var base = [1,1,1,2,3,5,7,9,14,19,28,40,56,114,232,330]; var sky = [0,0,0,3,4,5,6,7,8,9,10,11,12,14,16,17]; var ite = [64,52,23,38,13,15,6,4,6,3,2,1,2,1,1,1]; var result = sky.reduce(function(r, n, i) { var last = r.sky.length - 1; if(n === r.sky[last]) { r.ite[last] += ite[i]; } else { r.base.push(base[i]); r.sky.push(n); r.ite.push(ite[i]); } return r; }, { base: [], sky: [], ite: [] }); console.log('new base:', result.base.join(',')); console.log('new sky:', result.sky.join(',')); console.log('new ite:', result.ite.join(','));
atltag's answer is fastest. Please see: https://repl.it/FBpo/5
Just with a single .reduce() in O(n) time you can do as follows; (I have used array destructuring at the assignment part. One might choose to use three .push()s though) var base = [1,1,1,2,3,5,7,9,14,19,28,40,56,114,232,330], sky = [0,0,0,3,4,5,6,7,8,9,10,11,12,14,16,17], ite = [64,52,23,38,13,15,6,4,6,3,2,1,2,1,1,1], results = sky.reduce((r,c,i) => c === r[1][r[1].length-1] ? (r[2][r[2].length-1] += ite[i],r) : ([r[0][r[0].length],r[1][r[1].length],r[2][r[2].length]] = [base[i],c,ite[i]],r),[[],[],[]]); console.log(JSON.stringify(results));
Exit function and call itself again without returning undefined
I have the following function that can pull out a random index from an array without repeating the index and keeps pulling them out until all have been used and then resets itself and starts re-using them. It also tries to make sure that the last one that was pulled out isn't the same as the next one pulled out on the reset so that you don't ever have the same index come out in a row. var listIndexes = []; var lastIndex; function getRandomIndex(indexes) { if (!listIndexes.length) { for (var i = 0; i < indexes; i++) { listIndexes.push(i); } } var randomIndex = Math.floor(Math.random() * listIndexes.length); var uniqueIndex = listIndexes[randomIndex]; listIndexes.splice(randomIndex, 1); if(lastIndex && uniqueIndex == lastIndex) { listIndexes = []; getRandomIndex(indexes); return; } lastIndex = uniqueIndex; return uniqueIndex; } var index = getRandomIndex(5); console.log(index); However when it hits the code: if(lastIndex && uniqueIndex == lastIndex) it causes it to return undefined for the index. So the way I'm trying to exit the function and re-call the function to try again isn't working as planned. How can I exit the current function call and re-call the function to get a new random index that isn't the same as the lastIndex. Note that the lastIndex is kept intact until the new index isn't the same regardless of how many times the function is called.
Try this: if(lastIndex && uniqueIndex == lastIndex) { listIndexes = []; return getRandomIndex(indexes); }
just change your empty return to run the function again: return getRandomIndex(indexes);
The approach of rethrowing the die when you don't get the result you want is not optimal and when the random number isn't so random (tends to repeat the same number), then you're cycling unnecessarily. Try this: function RandEleGenerator(list) { var lastChosen; var currentList = list.slice(); function randomIndex() { return Math.floor(Math.random() * currentList.length); } return function() { // Choose element var index = randomIndex(); var obj = currentList[index]; // Remove it from current list currentList.splice(index, 1); if(currentList.length == 0) { // If empty, restore list currentList = list.slice(); // But not without removing last chosen element index = currentList.indexOf(obj); currentList.splice(index, 1); } return obj; }; } Usage: var reg = new RandEleGenerator([1,2,3]); reg(); // 3 reg(); // 2 reg(); // 1 reg(); // 2 reg(); // 3 reg(); // 2 reg(); // 1 The chosen element is removed from the list so it cannot be rechosen. In order to guarantee that a value isn't repeated when the list ends, the list is recreated and the last element chosen is removed from the list immediately. The process then continues randomly choosing elements to remove from the list. Edit: In order to say, generate an array to pass to RandEleGenerator, the following code is sufficient: var arrToPass = []; for (var i = 0; i < 3; i++) { arrToPass.push(i); } var reg = new RandEleGenerator(arrToPass);
How do I grab the two largest integers from a Javascript Array and return the value to the DOM?
I've written a solution to take a list of integers entered through a form. It works. It gives you the sum of the two largest integers and posts it in the DOM. However, it's not very efficient for large arrays of say 1 million integers. How can I improve this solution to be more efficient. App.js // This function reverses the order of the array and places the biggest numbers first function sortNumber(a, b) { return b - a; } // this function is used to ensure the user didn't enter any letters function getArray() { var alphaExp = /^[a-zA-Z]+$/; // This function takes the array, orders it, adds the sum of the two largest numbers and returns the value function sumOf(x) { // Sort the ary with the sortNumber function array.sort(sortNumber); // Then we add the two biggest numbers of the array and save it to the result variable. var result = array[0] + array[1]; // Then we share the result with the user by updating the browser var myHeading = document.querySelector('h2'); myHeading.textContent = "The sum of your two biggest numbers is: " + result; // Like a good student, it's important to show your work var showYourWork = document.querySelector('h3'); showYourWork.textContent = array[0] + " + " + array[1] + " = " + result; } // This grabs the value of the input var arrayField = document.getElementById('arrayField').value; if (arrayField.match(alphaExp)) { // Fail if user enters letters var raiseError = document.querySelector('h5'); raiseError.textContent = 'No not letters! We want numbers!!'; } else { var array = JSON.parse("[" + arrayField + "]"); if (arrayField.length < 2) { // If the user enters only 1 number, tell them to enter more! var raiseError = document.querySelector('h5'); raiseError.textContent = 'Please enter atleast two numbers seperated by commas for us to add!' } else { // When the user enters a list of numbers, run the sumOf function. sumOf(arrayField); //Make the error go away var raiseError = document.querySelector('h5'); raiseError.textContent = ''; } } }; // use an eventlistener for the event (This is where the magic happens) var subButton = document.getElementById('subButton'); subButton.addEventListener('click', getArray, false);
You don't have to sort it, just search linearly for the two biggest ones: EDIT: the code below should work now and is asymptotically faster than the OP's code. The OP does sorting first which can be done in O(n log n), assuming a random list. My code does a linear search through the list in O(cn) with c = 2 (the two loops are not necessary but simple). The solution for ceil(n log n) = 2n with n a positive integer is 14, that is for every list longer than 14 entries the code below is faster. E.g.: for one million entries the relation is 13,815,511 to 2,000,000, more than six times faster. You can do the same thing in a single loop which halves the runtime (theoretically, but it is also a tiny bit faster because of the better locality). function maxtwo_wrong(a){ var b1 = -Infinity; var b2 = -Infinity; for (var i=0; i < a.length; i++) { if (a[i] > b1) { b1 = a[i]; } } for (var i=0; i < a.length; i++) { if (a[i] > b2 && a[i] < b1) { b2 = a[i]; } } return [b1,b2]; } EDIT-2: The code above maxtwo_wrong seems not to fit the requirements, so I wrote another one maxtwo_rightand put it below. Please, OP, tell me which one fulfills your requirements such that I can delete the wrong one. EDIT-3: made it simpler and correct. function maxtwo_right(a){ var b1 = -Infinity; var b2 = -Infinity; for (var i=0; i < a.length; i++) { // If the current entry is bigger than variable b1 // keep the old value in the variable b2 and set b1 to the // value of the current entry if (a[i] > b1) { b2 = b1; b1 = a[i]; } // if the current entry equals b1 set the variable b2 to // the value of the current entry else if(a[i] === b1){ b2 = a[i]; } } // return the sum of the two variables as requested return b1 + b2; }
I finally found some time to sit down and work this one out. I was looking at the problem all wrong. Here is my new solution // This function adds the sum of the two largest integers of an array and returns the value function topTwoInt(theArray) { var intArray = theArray; var highestInt = -Infinity; var secondHighestInt = -Infinity; var answer = 0; //Loop through the array for (var i=0; i < intArray.length; i++) { //grab the biggest int and assign it to the highestInt variable; if (intArray[i] > highestInt) { secondHighestInt = highestInt; highestInt = intArray[i]; } //If the next number is equal too highestInt or greater than secondHighestInt //Make that number become the new secondHighestInt else if(intArray[i] === highestInt || intArray[i] > secondHighestInt) { secondHighestInt = intArray[i]; } } answer = highestInt + secondHighestInt; return answer; }; This solution is largely inspired by #deamentiaemundi Thanks man.
Generate 7 unique random numbers in javascript
I have a project i'm working on. I am to basically recreate lotto on the client side using javascript to generate 6 random numbers plus a bonus ball. as we all know, lotto numbers cannot be the same. this is where my question comes in. Is it possible to remove a number that has been generated from being available in the next time round in the loop? This would make the function completely random. or do I need to still compare the number with the others in the array using indexOf? for example, is the following possible?, the first number that generates is 25, the function then removes that number from being able to come up again. so on... Here is my js code, function play(){ numbersArray = []; for (i=0; i<=6;){ n = Math.floor(Math.random()*40)+1; a = numbersArray.indexOf(n); if ( a == "-1"){ numbersArray[i] = n; i++; var ballId = "ball"+i; if( i != "7"){ document.getElementById(ballId).innerHTML = '<p>'+ n +'</p>'; } else { document.getElementById("bonus").innerHTML = '<p>'+ n +'</p>'; } } //end of if }//end of for loop }//end of play function
You need to create an object, in this case you could use an array, that holds all the possible numbers that can appear on the ball, we'll cal it n. Then you can use a while loop to keep picking numbers from that array, and splice/remove that specific number from the array on every iteration. function play(n) { var picks = []; // Store possibilities in the numbersArr array var numbersArr = []; // n is the max number you can choose to appear on a ball for ( var i = 0; i < n; i++ ) { numbersArr.push(i); } while (picks.length < 7){ var randomIndex = Math.floor(Math.random() * numbersArr.length); picks.push(numbersArr[randomIndex]); numbersArr.splice(randomIndex, 1); } return picks; }