Best way to randomly pull from an array? - javascript

I have a website that will give you a random fact from Wikipedia when you click a big red button. I have heard from a few people that they are getting certain facts repeatedly, even though there are over 200.
The big red button has onclick="giveafact()" which triggers this function:
function giveafact(){ //instead of relisting all array items here, add all the other arrays to this one
var factsList= foodFactsList.concat(musicFactsList,historyFactsList,popFactsList,sportsFactsList,technologyFactsList,televisionFactsList,miscFactsList);
randomFact = Math.floor(Math.random()*factsList.length);
document.getElementById("total").innerHTML=factsList.length;
document.getElementById("fact").innerHTML=factsList[randomFact];
updateShareLinks();
return false;
}
Basically, I have 8 different arrays of facts as you can see in var factsList above. This is so the user can filter by fact. By default, there is no filter so all lists are concatenated.
If it helps, the full .js file is here: http://thewikifix.com/scripts/script.js. These random "give a fact" functions start at about line 442, with the arrays above them. (Pardon the messy code, I know I mix jQuery and Javascript a lot.)
The site is http://thewikifix.com, if it helps anyone to look at all the code.
I'm just trying to see if there's a way to better randomize the facts than I currently have, maybe by adding a function that won't allow a fact to show up twice in a row, or something similar.
Any suggestions would be great!
Edit - Additional thoughts: Thanks for the answers so far. Is there a way to remove an item from an array once it has been picked via the Math.random function so it just wouldn't show up again at all (unless the page was refreshed)? If so, once all items were removed from the array is there a way to reset the array to its original state without the user having to refresh? Thanks.

"Random" and "varied" are, to some extent, conflicting. With 200 facts, and one selected randomly each time, it becomes more likely than not that you'll see a repeat after getting only a couple dozen or so (this is known as the "Birthday Problem").
A simple approach is to store a seed and an index on the client. The seed is set once, and the index is incremented after each fact access. To get a "random" fact, seed a PRNG with the seed, use the PRNG to shuffle the list of facts, then get the fact at the given index in the shuffled list. When you run out, pick a new seed and reset the index.

Here's what I would do:
n = 0 - Find a random fact
add that fact to a new array (arr)
display fact
n + 1 - Find a random fact
check arr using lodash for that facts existence
display or find a new fact, based on result of above
--OR--
you could take the arr and use lodash's _.shuffle() to mix them up and display them in order.
https://lodash.com/docs
I love using lodash for collection and array operations.

Related

Realm-JS: Performant way to find the index of an element in sorted results list

I am searching for a perfomant way to find the index of a given realm-object in a sorted results list.
I am aware of this similar question, which was answered with using indexOf, so my current solution looks like this:
const sortedRecords = realm.objects('mySchema').sorted('time', true) // 'time' property is a timestamp
// grab element of interest by id (e.g. 123)
const item = realm.objectForPrimaryKey('mySchema','123')
// find index of that object in my sorted results list
const index = sortedRecords.indexOf(item)
My basic concern here is performance for lager datasets. Is the indexOf implementation of a realm-list improved for this in any way, or is it the same as from a JavaScript array? I know there is the possibility to create indexed properties, would indexing the time property improve the performance in this case?
Note:
In the realm-js api documentation, the indexOf section does not reference to Array.prototype.indexOf, as other sections do. This made me optimistic it's an own implementation, but it's not stated clearly.
Realm query methods return a Results object which is quite different from an Array object, the main difference is that the first one can change over time even without calling methods on it: adding and/or deleting record to the source schema can result in a change to Results object.
The only common thing between Results.indexOf and Array.indexOf is the name of the method.
Once said that is easy to also say that it makes no sense to compare the efficiency of the two methods.
In general, a problem common to all indexOf implementations is that they need a sequential scan and in the worst case (i.e. the not found case) a full scan is required. The wort implemented indexOf executed against 10 elements has no impact on program performances while the best implemented indexOf executed against 1M elements can have a severe impact on program performances. When possible it's always a good idea avoiding to use indexOf on large amounts of data.
Hope this helps.

Javascript: Efficiently move items in and out of a fixed-size array

If I have an array that I want to be of fixed size N for the purpose of caching the most recent of N items, then once limit N is reached, I'll have to get rid of the oldest item while adding the newest item.
Note: I don't care if the newest item is at the beginning or end of the array, just as long as the items get removed in the order that they are added.
The obvious ways are either:
push() and shift() (so that cache[0] contains the oldest item), or
unshift() and pop() (so that cache[0] contains the newest item)
Basic idea:
var cache = [], limit = 10000;
function cacheItem( item ) {
// In case we want to do anything with the oldest item
// before it's gone forever.
var oldest = [];
cache.push( item );
// Use WHILE and >= instead of just IF in case the cache
// was altered by more than one item at some point.
while ( cache.length >= limit ) {
oldest.push( cache.shift() );
}
return oldest;
}
However, I've read about memory issues with shift and unshift since they alter the beginning of the array and move everything else around, but unfortunately, one of those methods has to be used to do it this way!
Qs:
Are there other ways to do this that would be better performance-wise?
If the two ways I already mentioned are the best, are there specific advantages/disadvantages I need to be aware of?
Conclusion
After doing some more research into data structures (I've never programmed in other languages, so if it's not native to Javascript, I likely haven't heard of it!) and doing a bunch of benchmarking in multiple browsers with both small and large arrays as well as small and large numbers of reads / writes, here's what I found:
The 'circular buffer' method proposed by Bergi is hands-down THE best as far performance (for reasons explained in the answer and comments), and hence it has been accepted as the answer. However, it's not as intuitive, and makes it difficult to write your own 'extra' functions (since you always have to take offset into account). If you're going to use this method, I recommend an already-created one like this circular buffer on GitHub.
The 'pop/unpush' method is much more intuitive, and performs fairly well, accept at the most extreme numbers.
The 'copyWithin' method is, sadly, terrible for performance (tested in multiple browsers), quickly creating unacceptable latency. It also has no IE support. It's such a simple method! I wish it worked better.
The 'linked list' method, proposed in the comments by Felix Kling, is actually a really good option. I initially disregarded it because it seemed like a lot of extra stuff I didn't need, but to my surprise....
What I actually needed was a Least Recently Used (LRU) Map (which employs a doubly-linked list). Now, since I didn't specify my additional requirements in my original question, I'm still marking Bergi's answer as the best answer to that specific question. However, since I needed to know if a value already existed in my cache, and if so, mark it as the newest item in the cache, the additional logic I had to add to my circular buffer's add() method (primarily indexOf()) made it not much more efficient than the 'pop/unpush' method. HOWEVER, the performance of the LRUMap in these situations blew both of the other two out of the water!
So to summarize:
Linked List -- most options while still maintaining great performance
Circular Buffer -- best performance for just adding and getting
Pop / Unpush -- most intuitive and simplest
copyWithin -- terrible performance currently, no reason to use
If I have an array that caches the most recent of N items, once limit N is reached, I'll have to get rid of the oldest while adding the newest.
You are not looking to copy stuff around within the array, which would take O(n) steps every time.
Instead, this is the perfect use case for a ring buffer. Just keep an offset to the "start" and "end" of the list, then access your buffer with that offset and modulo its length.
var cache = new Array(10000);
cache.offset = 0;
function cacheItem(item) {
cache[cache.offset++] = item;
cache.offset %= cache.length;
}
function cacheGet(i) { // backwards, 0 is most recent
return cache[(cache.offset - 1 - i + cache.length) % cache.length];
}
You could use Array#copyWithin.
The copyWithin() method shallow copies part of an array to another location in the same array and returns it, without modifying its size.
Description
The copyWithin works like C and C++'s memmove, and is a high-performance method to shift the data of an Array. This especially applies to the TypedArray method of the same name. The sequence is copied and pasted as one operation; pasted sequence will have the copied values even when the copy and paste region overlap.
The copyWithin function is intentionally generic, it does not require that its this value be an Array object.
The copyWithin method is a mutable method. It does not alter the length of this, but will change its content and create new properties if necessary.
var array = [0, 1, 2, 3, 4, 5];
array.copyWithin(0, 1);
console.log(array);
You need to splice the existing item and put it in the front using unshift (as the newest item). If the item doesn't already exist in your cache, then you can unshift and pop.
function cacheItem( item )
{
var index = cache.indexOf( item );
index != -1 ? cache.splice( index, 1 ) : cache.pop();
cache.unshift( item );
}
item needs to be a String or Number, or otherwise you'll need to write your own implementation of indexOf using findIndex to locate and object (if item is an object).

JQuery: Number of Array value exceed 100

This question is a bit hard to explain so please tell me if this is not clear.
Firstly, i have a .map() function to handle some data, and since it is a for loop, it might generate a big amount of data.
And when i console.log() it , I realize that the Array has been slice in to [0-99],[100-200] like the image below.
And when i pass the array thing via node.js to the backend it turns out like below:
it passes all the number to backend as well and cause exception, how may i get rid of these numbers? It's working fine if the array didn't exceed 100
it not suppose to have something like "0":["1","2"]
[]3
Ok let's make everything clear. When the array "NOT" exceeding 100 it would pass normal array to the backend like below:
The problem is when it exceed 100 it will pass something like below instead and which our backed system cannot read it due to the array has some wield contains.
I am using .slice() to limit the max lenght of the array, so users need to upload data 100 per time but i want to see if there's anyway to fix it, may i could use .slice(), turn huge array into tiny pieces and .join() it back together?
UPDATE:
var array = $(".AdvKeyowrdAND").map(function() {
return [$(this).html().replace(/\+/g,',').split(",")];
}).get();
The above is the actualy Javascript code which handle all the elements

Record answer randomization in Qualtrics using javascript

I am making a survey in Qualtrics. This survey has a repeating question with six answer choices. The six choices are randomized (in the standard way, no javascript). The question is being repeated using loop&merge, which works great because it's the same question structure over and over (36 times), but I can use the field function to adjust the question and answers for every iteration.
However, one problem I am running into is that Qualtrics does not (as standard) support the recording of the randomization data in the results - i.e. how it has randomized the six answer choices in each iteration. When I use the 'Export Randomized Viewing Order data' function when downloading results, it only shows the answer order of the last time it asked the question. So it seems that this is a value that gets overwritten after each iteration.
So now I'm looking to record the answer order for each iteration through javascript. However, I haven't found a function that gives the order answer (after randomization). I have consulted the Qualtrics javascript API and found some functions that seem promising, such as getChoices (), but when I try this all I get back is the order of answers without randomization (i.e. just 1,2,3,4,5,6).
Does anyone know a way to record the randomized choice order for each iteration, using javascript or otherwise?
I found a different way to record the loop and merge randomization order.
Create an embedded data field in survey flow. Here we will record the randomization order. I will call the field rand_order.
Add a loop and merge field with a unique number to identify each loop (e.g. 1, 2, 3, 4, 5, ..., n).
Then add the next javascript to any page of the looped block.
//*Place Your Javascript Below This Line*/
var questionText = "${lm://Field/1}"; // "${lm://Field/1}" will actually evaluate to
//whatever is Field 1 in the current Loop & Merge loop.
// You can do this with embedded data too, as seen in the next line
var order = "${e://Field/rand_order}" + "|" + questionText; // gets the value of the embedded data
// field "rand_order", concatenates the current loop's identifier to it,
//and stores that as a variable
Qualtrics.SurveyEngine.setEmbeddedData('rand_order', order); // updates the
//embeddeddata field "rand_order" to be our order variable, which has the current loop's
//identifier attached, effectively constructing a string of numbers representing the order
You will get a column with the name "rand_order" filled with "1|5|23|2...|n". You can change the separator to make more compatible with whatever script you are using to manipulate data.
Qualtrics already records this information for you. It's just a matter of explicitly asking for it when you download your data. Number 5 on this page has more info, but I'll recount the important bits:
In the “Data & Analysis” tab, click “Export & Import” and then “Export Data”.
In the “Download Data Table” window click “More Options”.
Check the box for “Export viewing order data for randomized surveys”.
I think the thing here is to look at the order of choices in the DOM. Qualtrics provides the getChoiceContainer() method to get the div containing the choices. Here's a snippet I wrote and minimally tested:
//get the div containing the choices, then get all input child elements of that div
var choices = this.getChoiceContainer().getElementsByTagName("input");
//initialize an array for the IDs of the choices
var choiceIDs = []
//add the ID of each choice to the array
for (var i=0; i < choices.length; i++) {
choiceIDs.push(choices[i].id);
}
//get the current choice order from embedded data and add this loop to it.
//Add a | to distinguish between loops.
var choiceOrder = "${e://field/choiceorder}" + choiceIDs.toString() + "|";
//set the embedded data with the new value
Qualtrics.SurveyEngine.setEmbeddedData("choiceorder", choiceOrder);
A couple of notes/caveats:
I only tested this on a basic multiple choice question with radio buttons. It may need to be adjusted for different question types.
I also just got the IDs of the question choices. You could probably modify it pretty easily to get other information, like the label of the choice, or the numeric value it corresponds to.

Java Script Randomize Array Function

Trying to plan out a function and I wanted to get some input. I am trying to find an efficient way to:
Double the frequency of numbers in an array
Randomize the location of the values in the array.
For example: Lets say I have an array. [0,1,2,3]
first I want to duplicate each number once in a new array. So now we would have something like this.
[0,0,1,1,2,2,3,3].
Lastly, I want to randomize these values so: [0,4,2,3,0,2,3,4]
Eventually, the algorithm I write will need to handle an initial array of 18 digits (so the final, randomized array will be of size 36)
My initial thoughts are to just have a simple while loop that:
Randomly selects a spot in the new array
Checks to see if it is full
-If it is full, then it will select a new spot and check again.
If it is not full, then it will place the value in the new array, and go to the next value.
I am leaving out some details, etc. but I want this algorithm to be fairly quick so that the user doesn't notice anything.
My concern is that when there is only one digit left to be placed, the algorithm will take forever to place it because there will be a 1/36 chance that it will select the empty space.
In general terms, how can I make a smarter and faster algorithm to accomplish what I want to do?
Many thanks!
first I want to duplicate each number once in a new array. So now we would have something like this. [0,0,1,1,2,2,3,3].
That would be rather complicated to accomplish. Since the positions are not relevant anyway, just build [0,1,2,3,0,1,2,3] by
var newArray = arr.concat(arr);
Lastly, I want to randomize these values so: [0,4,2,3,0,2,3,4]
Just use one of the recognized shuffle algorithms - see How to randomize (shuffle) a JavaScript array?. There are rather simple ones that run in linear time and do not suffer from the problem you described, because they don't need to randomly try.
Here is an alternative to the known methods with two arrays, I came up with. It gives relatively good randomization.
var array1=[]
var array=[0,1,2,3,0,1,2,3]
a=array.length;
b=a;
c=b;
function rand(){for (i=0;i<c;i++){
n=Math.floor(Math.random()*b);
array1[i]=array[n]; array.splice(n,1);b--;}
for (i1=0;i1<c;i1++){array[i1]=array1[i1]}

Categories

Resources