I have twelve squares and six of those are selected randomly and a style then applied. This is achieved by generating random values from an array of unique values. The problem is the random values being generated aren't unique e.g 5 could be chosen more than once. I've looked at so many posts on here and there seems to be a number of different ways of achieving this - shuffling etc. Which would be the most effective?
for (var i = 0; i < 6 ; i++)
(function (i) {
setTimeout(function () {
var rand = arr[Math.floor(Math.random() * arr.length)];
var square = document.getElementById('square' + rand);
square.style.background = black;
if (i == 6) Reset();
}, 1500 * i);
})(i);
};
Just for the sake of simplicity I will assume your elements have a class.
Example on jsFiddle
So, I would grab all elements:
var elements = document.getElementsByClassName("square");
Then I would create an array with the IDs
var ids = [];
for (var i = 0; i < elements.length; ++i)
{
ids.push(elements[i].getAttribute("id"));
}
And then generate random numbers on the length of the array
var random = roundingFunction(Math.random() * ids.length);
Then retrieve the element at the index and remove from the array
var elementID = ids.splice(random, 1);
And repeat.
There are bascially three different approaches:
Pick an item by random and repeat if it was used before.
Put all items in an array, pick by random and remove from the array.
Put all items in an array and shuffle.
Which one would be the most efficient can't be answered in the scope of your question. The difference between them is so small that it negligible considering how much the performance differs between browsers.
If you really need the most efficient, you have to try them all and test the performance in a wide variety of browser.
Otherwise just pick the one that seems easiest to implement. The are all good enough for any normal application.
Related
I am relatively new to programming and am having some issues with a project I am working on.
msg.newCG2 = [];
for(i=0;i<msg.newCG.length;i++){
for(j=0;j<msg.campaignGroup.length;i++){
if(msg.campaignGroup[j].col10 === msg.newCG[j]){
msg.groupTotals = msg.groupTotals + msg.campaignGroup[j].col11;
}
msg.newCG2.push(msg.newCG[i], msg.groupTotals)
}
}
Basically, for each one of the "IDs" (integers) in msg.newCG, I want to look for each ID in msg.campaignGroup and sum up the totals for all listings with the same ID, from msg.campaignGroup.col11 - then push the ID and the totals to a new array - msg.newCG2.
When I run the code, the first item sent through processes, but grinds to a halt because of memory. I assume this is because of an error in my code.
Where did this code go wrong? I am sure that there are better ways to do this as a whole, but I am curious where I went wrong.
There is a typo in your second for loop and the push needs to happen inside the outer loop.
msg.newCG2 = [];
for(i=0;i<msg.newCG.length;i++){
for(j=0;j<msg.campaignGroup.length;j++){
if(msg.campaignGroup[j].col10 === msg.newCG[i]){
msg.groupTotals = msg.groupTotals + msg.campaignGroup[j].col11;
}
}
msg.newCG2.push(msg.newCG[i], msg.groupTotals)
}
How about:
msg.newCG2 = [];
for (i=0; i < msg.newCG.length; i++) {
var groupTotal = 0;
for (j=0; j < msg.campaignGroup.length; j++) {
if (msg.campaignGroup[j].col10 === msg.newCG[i]){
groupTotal = groupTotal + msg.campaignGroup[j].col11
}
}
msg.newCG2.push(groupTotal)
}
Rather than looping 1.2M times, it would be more efficient to use a single-pass over the 4000 campaign groups, grouping by id to create an array of totals for all ids -- I like using the reduce() function for this:
var cgMap = msg.campaignGroups.reduce(function(arr, grp) {
var grpid = grp.col10;
var count = grp.col11;
var total = arr[grpid] || 0;
arr[grpid] = total + count;
},
[]);
I know, the reduce(...) function is not the easiest to grok, but it takes the second arg (the empty array) and passes it, along with each campaign group object in turn, to that inline function. The result should be a simple array of group totals (from col11), indexed by the group id (from col10).
Now, it's just a matter of returning the totals for those 300 ids found in msg.newCG -- and this map() function does that for us:
var cgOut = msg.newCG.map(function(gid) {
return cgMap[gid]; // lookup the total by group id
}
);
I've made some assumptions here, like the group ids are not terribly large integers, and are rather closely spaced (not too sparse). From the original code, I was not able to determine the format of the data you are wanting to return in msg.newCG2. The final push() function would append 2 integers onto the array -- the output group id and the total for that group. Having pairs of group ids and totals interleaved in a flat array is not a very useful data structure. Perhaps you meant to place the total value into an array, indexed by the group id? If so, you could re-write that line as:
msg.newCG2[msg.newCG[i]] = msg.groupTotals;
I have an array, and need to pick three random values from that array. These will the be put in a new array and I will be able to see the new array on my website. I also have to make sure that no value gets picked twice.
This is what I have so far:
var student = ["Hans","Ole","Nils","Olav","Per","Knut","Line","Pia"];
var velg = student[Math.floor(Math.random() * student.length)];
I'm thinking I should add an id to my HTML, so the new array will show on my website, but I'm not sure about the rest.
First sort it randomly and then get first three:
student
.sort(function(){
return Math.random() - 0.5;
})
.slice(0,3)
Since Math.random() returns random value between 0 and 1, while sort expects values to be positive or negative to determine order we, we need to subtract 0.5 to make those negatives possible.
You could try something like this in a loop
var students = ["Hans","Ole","Nils","Olav","Per","Knut","Line","Pia"];
var randomStudents = [];
for(var i = 0; i < 3; i++) {
var velg = student[Math.floor(Math.random() * students.length)];
randomStudents.push(velg);
}
Note that this can add duplicate students to the array. You should check if student is already in the array and try again.
Keyword for that would be recursion.
https://www.codecademy.com/courses/javascript-lesson-205/0/1
I modified the jQuery Autocomplete implementation to generate autocorrect suggestions too. I used Levenshtein distance as the metric to decide closest matches.
My code runs at each keypress after the jQuery autocomplete does not have any more suggestions. Here's the code that I wrote:
// Returns edit distance between two strings
edit_distance : function(s1, s2) {
// Auxiliary 2D array
var arr = new Array(s1.length+1);
for(var i=0 ; i<s1.length+1 ; i++)
arr[i] = new Array(s2.length+1);
// Algorithm
for(var i=0 ; i<=s1.length ; i++)
for(var j=0 ; j<=s2.length ; j++)
arr[i][j] = 0;
for(var i=0 ; i<=s1.length ; i++)
arr[i][0] = i;
for(var i=0 ; i<=s2.length ; i++)
arr[0][i] = i;
for(var i=1 ; i<=s1.length ; i++)
for(var j=1 ; j<=s2.length ; j++)
arr[i][j] = Math.min(arr[i-1][j-1] + (s1.charAt(i-1)==s2.charAt(j-1) ? 0 : 1), arr[i-1][j]+1, arr[i][j-1]+1);
// Final answer
return arr[s1.length][s2.length].toString(10);
},
// This is called at each keypress
auto_correct : function() {
// Make object array for sorting both names and IDs in one go
var objArray = new Array();
for(var i=0 ; i<idArray.length ; i++) {
objArray[i] = new Object();
objArray[i].id = idArray[i];
objArray[i].name = nameArray[i];
}
// Sort object array by edit distance
var out = this;
companyObjArray.sort (
function(a,b) {
var input = jQuery("#inputbox").val().toLowerCase();
var d1 = a.name.toLowerCase();
var d2 = b.name.toLowerCase();
return out.editDistance(input,d1) - out.editDistance(input,d2);
}
);
// Copy some closest matches in arrays that are shown by jQuery
this.suggestions = new Array();
this.data = new Array();
for(var i=0 ; i<5 ; i++) {
this.suggestions.push(companyObjArray[i].name);
this.data.push(companyObjArray[i].id);
}
}
All names have IDs associated with them, so before sorting I'm just creating an object array out of them, and sorting the array.
Since the list to search from is in thousands, its slow. I found a data structure called a BK-tree, which can speed it up, but I'm can't implement it right now. I'm looking for optimization suggestions to speed this up. Any suggestions are welcome. Thanks in advance.
EDIT. I decided to use Sift3 as my string distance metric instead of Levenshein distance, it gives more meaningful results and is faster.
There are quite a few things you can optimize here, but most of it boils down to this: Only do each of your 'heavier' calculations once. You are doing a lot of work on each keypress, in every sort comparison, etc., and caching those values will help a lot.
However, for a significant added performance boost, there is another, quite nifty optimization trick you can use. It is used by every major search engine, including on-site search engines on sites such as Amazon.com.
It takes advantage of the fact that you really don't need to sort the whole list, since all you're displaying are the top 10 or 12 items. Whether the rest of the thousands of list items are in the correct order doesn't matter in the least. So all you really have to keep track of while running through the list, are the top 10 or 12 items you've seen so far, which, as it turns out, is a lot faster than full sorting.
Here's the idea in pseudocode:
The user types a character, creating a new search term
We define an empty shortlist array of length 12 (or however many suggestions we want)
Loop through the full list of words (we will call it the dictionary):
Calculate the (Levenshtein) edit distance between the dictionary word and the search term
If the distance is lower (better) than the current threshold, we:
Add the word to the top of the shortlist
Let the bottom word in the shortlist 'overflow' out of the list
Set our threshold distance to match the word now at the bottom of the shortlist
When the loop has finished, we have a shortlist containing some of the best words, but they are unsorted, so we sort just those 12 words, which will be really fast.
One small caveat: Depending on the dataset and the number of elements in your shortlist, the shortlist may differ slightly from the actual top elements, but this can be alleviated by increasing the shortlist size, e.g. to 50, and just removing the bottom 38 once the final sort is done.
As for the actual code:
// Cache important elements and values
var $searchField = $('#searchField'),
$results = $('#results'),
numberOfSuggestions = 12,
shortlistWindowSize = 50,
shortlist,
thresholdDistance;
// Do as little as possible in the keyboard event handler
$searchField.on('keyup', function(){
shortlist = [];
thresholdDistance = 100;
// Run through the full dictionary just once,
// storing just the best N we've seen so far
for (var i=0; i<dictionarySize; i++) {
var dist = edit_distance(this.value, dictionary[i]);
if (dist < thresholdDistance) {
shortlist.unshift({
word: dictionary[i],
distance: dist
});
if (shortlist.length > shortlistWindowSize) {
shortlist.pop();
thresholdDistance = shortlist[shortlistWindowSize-1].distance;
}
}
}
// Do a final sorting of just the top words
shortlist.sort(function(a,b){
return a.distance - b.distance;
});
// Finally, format and show the suggestions to the user
$results.html('<p>' + $.map(shortlist, function(el){
return '<span>[dist=' + el.distance + ']</span> ' + el.word;
}).slice(0,numberOfSuggestions).join('</p><p>') + '</p>').show();
});
Try the method out in this 12.000 word demo on jsFiddle
I'm trying to change the contents of sequential divs with randomly-ordered contents. It's not working so well.
I have about 25 <div> tags that, because of other javascripts regulating touch behaviour, are unable to be put in a randomised order using the usual methods. So instead, I'm trying to have the contents of those <div>s randomised by setting different innerHTML values when the document is loaded.
I have a script that provides random integers as I need, but then I'm having trouble getting it to populate the ordered divs.
Here's what I'm using that isn't working:
ArrayList<Integer> list = new ArrayList<Integer>(25);
for(int i = 0; i < 25; i++)
{
list.add(i);
}
Collections.shuffle(list);
Object[] randomNumbers = (Object[])list.toArray();
function testit() {
for(int i = 0; i < 25; i++)
{
var a = document.getElementById('if'+i);
a.innerHTML = randomNumbers[i];
}
}
What I'd like to see is that this takes all my <div>s which have id's such as "if1" "if2" and so one and then set the content of that <div> with the randomised numbers.
I know document.getElementById('if'+i) is surely the wrong way to get ID "if1" then "if2", but I'm not really sure how else it can be done.
Basically I just need a way to populate 25 absolutely-positions <div>s with 25 chunks of HTML content but in a random order. I'd think that could be done by pairing sequential numbers to randomly ordered numbers and tying those into <div> id's. Normally I'd just do this with PHP and be done already but I need it to work as an offline mobile webapp so I'm trying to do it with JS and CSS. I'm definitely open to other approaches.
edit:
If the code above is Java and not Javascript, that's the problem. Then I guess I'm looking for alternative ways, in Javascript, to produce random, non-repeating integers and link those to sequential integers, all between 1 and 25
Here is a sample implementation using javascript:
function shuffle(arr){
//simple shuffle algorithm - you can also use other
//we will get simple base index as basis for swapping elements
//we will swap the array elements arr.length times
var index = Math.floor(Math.random() * arr.length);
for(var i=1;i<arr.length; i++) {
var t = arr[i];
arr[i] = arr[index];
arr[index] = t;
}
return arr;
}
//create an array
var numbers =[];
for(var i=0;i<10;i++)
numbers.push(i);
//call the shuffle function
//call this many times to reshuffle elements
numbers = shuffle(numbers);
//document.write(numbers.join(','));
//test
for(var i=0;i<numbers.length;i++)
{
var a = document.getElementById('if'+i);
a.innerHTML = numbers[i];
}
Situation
I'm currently writing a javascript widget that displays a random quote into a html element. the quotes are stored in a javascript array as well as how many times they've been displayed into the html element. A quote to be displayed cannot be the same quote as was previously displayed. Furthermore the chance for a quote to be selected is based on it's previous occurences in the html element. ( less occurrences should result in a higher chance compared to the other quotes to be selected for display.
Current solution
I've currently made it work ( with my severely lacking javascript knowledge ) by using a lot of looping through various arrays. while this currently works ( !! ) I find this solution rather expensive for what I want to achieve.
What I'm looking for
Alternative methods of removing an array element from an array, currently looping through the entire array to find the element I want removed and copy all other elements into a new array
Alternative method of calculating and selecting a element from an array based on it's occurence
Anything else you notice I should / could do different while still enforcing the stated business rules under Situation
The Code
var quoteElement = $("div#Quotes > q"),
quotes = [[" AAAAAAAAAAAA ", 1],
[" BBBBBBBBBBBB ", 1],
[" CCCCCCCCCCCC ", 1],
[" DDDDDDDDDDDD ", 1]],
fadeTimer = 600,
displayNewQuote = function () {
var currentQuote = quoteElement.text();
var eligibleQuotes = new Array();
var exclusionFound = false;
for (var i = 0; i < quotes.length; i++) {
var iteratedQuote = quotes[i];
if (exclusionFound === false) {
if (currentQuote == iteratedQuote[0].toString())
exclusionFound = true;
else
eligibleQuotes.push(iteratedQuote);
} else
eligibleQuotes.push(iteratedQuote);
}
eligibleQuotes.sort( function (current, next) {
return current[1] - next[1];
} );
var calculatePoint = eligibleQuotes[0][1];
var occurenceRelation = new Array();
var relationSum = 0;
for (var i = 0; i < eligibleQuotes.length; i++) {
if (i == 0)
occurenceRelation[i] = 1 / ((calculatePoint / calculatePoint) + (calculatePoint / eligibleQuotes[i+1][1]));
else
occurenceRelation[i] = occurenceRelation[0] * (calculatePoint / eligibleQuotes[i][1]);
relationSum = relationSum + (occurenceRelation[i] * 100);
}
var generatedNumber = Math.floor(relationSum * Math.random());
var newQuote;
for (var i = 0; i < occurenceRelation.length; i++) {
if (occurenceRelation[i] <= generatedNumber) {
newQuote = eligibleQuotes[i][0].toString();
i = occurenceRelation.length;
}
}
for (var i = 0; i < quotes.length; i++) {
var iteratedQuote = quotes[i][0].toString();
if (iteratedQuote == newQuote) {
quotes[i][1]++;
i = quotes.length;
}
}
quoteElement.stop(true, true)
.fadeOut(fadeTimer);
setTimeout( function () {
quoteElement.html(newQuote)
.fadeIn(fadeTimer);
}, fadeTimer);
}
if (quotes.length > 1)
setInterval(displayNewQuote, 10000);
Alternatives considered
Always chose the array element with the lowest occurence.
Decided against this as this would / could possibly reveal a too obvious pattern in the animation
combine several for loops to reduce the workload
Decided against this as this would make the code to esoteric, I'd probably wouldn't understand the code anymore next week
jsFiddle reference
http://jsfiddle.net/P5rk3/
Update
Rewrote my function with the techniques mentioned, while I fear that these techniques still loop through the entire array to find it's requirements, at least my code looks cleaner : )
References used after reading the answers here:
http://www.tutorialspoint.com/javascript/array_map.htm
http://www.tutorialspoint.com/javascript/array_filter.htm
http://api.jquery.com/jQuery.each/
I suggest array functions that are mostly supported (and easily added if not):
[].splice(index, howManyToDelete); // you can alternatively add extra parameters to slot into the place of deletion
[].indexOf(elementToSearchFor);
[].filter(function(){});
Other useful functions include forEach and map.
I agree that combining all the work into one giant loop is ugly (and not always possible), and you gain little by doing it, so readability is definitely the winner. Although you shouldn't need too many loops with these array functions.
The answer that you want:
Create an integer array that stores the number of uses of every quote. Also, a global variable Tot with the total number of quotes already used (i.e., the sum of that integer array). Find also Mean, as Tot / number of quotes.
Chose a random number between 0 and Tot - 1.
For each quote, add Mean * 2 - the number of uses(*1). When you get that that value has exceeded the random number generated, select that quote.
In case that quote is the one currently displayed, either select the next or the previous quote or just repeat the process.
The real answer:
Use a random quote, at the very maximum repeat if the quote is duplicated. The data usages are going to be lost when the user reloads/leaves the page. And, no matter how cleverly have you chosen them, most users do not care.
(*1) Check for limits, i.e. that the first or last quota will be eligible with this formula.
Alternative methods of removing an array element from an array
With ES5's Array.filter() method:
Array.prototype.without = function(v) {
return this.filter(function(x) {
return v !== x;
});
};
given an array a, a.without(v) will return a copy of a without the element v in it.
less occurrences should result in a higher chance compared to the other quotes to be selected for display
You shouldn't mess with chance - as my mathematician other-half says, "chance doesn't have a memory".
What you're suggesting is akin to the idea that numbers in the lottery that haven't come up yet must be "overdue" and therefore more likely to appear. It simply isn't true.
You can write functions that explicitly define what you're trying to do with the loop.
Your first loop is a filter.
Your second loop is a map + some side effect.
I don't know about the other loops, they're weird :P
A filter is something like:
function filter(array, condition) {
var i = 0, new_array = [];
for (; i < array.length; i += 1) {
if (condition(array[i], i)) {
new_array.push(array[i]);
}
}
return new_array;
}
var numbers = [1,2,3,4,5,6,7,8,9];
var even_numbers = filter(numbers, function (number, index) {
return number % 2 === 0;
});
alert(even_numbers); // [2,4,6,8]
You can't avoid the loop, but you can add more semantics to the code by making a function that explains what you're doing.
If, for some reason, you are not comfortable with splice or filter methods, there is a nice (outdated, but still working) method by John Resig: http://ejohn.org/blog/javascript-array-remove/