I have an applet that allows the user to define values for 10 variables, all of which could be arrays. I then want the program to iterate over all possible combinations and then save the calculations to a variable.
Previously I hard-coded it to initialize the result array, SIGMA, by looping over every variable, regardless of if it is a vector or a single value, IE:
SIGMA = new Array(A.length);
for (i1=0; i1<A.length; i1++) {
SIGMA[i1] = new Array(B.length);
for (i2=0; i2<B.length; i2++) {
SIGMA[i1][i2] = new Array(C.length);
for (i3=0; i3<C.length; i3++) {
...
}
}
}
This results in a 10-dimensional SIGMA array, which makes processing really slow if a few or more variables are arrays.
What I'd like to do is to have it initialize SIGMA only for those variables that are an array and not a singular value. Therefore, if all variables are a single number except for two, say X and Y, then I'd want to have:
SIGMA = new Array(X.length);
for (i1=0; i1<X.length; i1++) {
SIGMA[i1] = new Array(Y.length);
}
However, I'm not quite sure how the best way to do this would be, as the number of for loops would depend on the number of variables that are arrays. I'm thinking I either need to use a recursive function or somehow incorporate a while loop.
Anybody have any good ideas on how this can be done? Thanks!
I was able to solve this problem by making a recursive function that included the for loops:
sigma = new Array(eval(var_idx[0]).length);
sigma_forloop('sigma', 0)
function sigma_forloop(X, idx) {
for (var i=0; i<eval(var_idx[idx]).length; i++) {
eval(X + '[' + i + ']' + ' = new Array(eval(var_idx[idx+1]).length);')
if (idx+2 < var_idx.length) {
var Y = X + '[' + i + ']';
sigma_forloop(Y, idx+1);
}
}
}
The variable 'var_idx' in an array of strings containing the variables that have more than one value, therefore, it's those variables that I want to loop over.
I'm sure there's an easier way of doing it, but this works for now.
Related
I have this game which i am designing where i store 1 or 0 values as correct or incorrect answers in the local storage in variables q1 - q6.
Since local storage doesnt store numbers as integers, before my main page loads i parseInt all of my variables to get the integers. Although some of them havent been declared as anything yet so they seem to be NaN or null, so when i try to compute the addition of all of the variables, it gives me NaN unless i use this mess of a code.
if (isNaN(totalpoints)) totalpoints = 0;
if (localStorage.getItem("points") === null){
localStorage.setItem("points", "0");
}
var q0 = parseInt(localStorage.q0);
var q1 = parseInt(localStorage.q1);
var q2 = parseInt(localStorage.q2);
var q3 = parseInt(localStorage.q3);
var q4 = parseInt(localStorage.q4);
var q5 = parseInt(localStorage.q5);
var q6 = parseInt(localStorage.q6);
if (isNaN(q0))q0=0;
if (isNaN(q1))q1=0;
if (isNaN(q2))q2=0;
if (isNaN(q3))q3=0;
if (isNaN(q4))q4=0;
if (isNaN(q5))q5=0;
if (isNaN(q6))q6=0;
var totalpoints = totalpoints + q0 + q1 + q2 + q3 + q4 + q5 + q6;
localStorage.setItem("points", totalpoints);
I am trying to think of a way where i can simplify this code into maybe a few lines using a loop for the variables possibly or something?
Can anyone give me any sense of direction on how i can approach this or help me possibly?
Im just not sure as it seems like i got to parseInt the localstorage variables all individually?
Thanks
Rather than having multiple separate numeric-indexed properties, consider using a single array instead:
const qArr = localStorage.qArr
? JSON.parse(localStorage.qArr)
: new Array(7).fill(0);
Then, instead of assigning to the q variables, assign to an index of the qArr, and to save it, use JSON.stringify:
qArr[3] = 555;
localStorage.qArr = JSON.stringify(qArr);
To add up all the items in the array, you can use reduce:
totalPoints = qArr.reduce((a, b) => a + b, totalPoints);
Since it sounds like this is all in one self-contained script, I'd highly recommend consolidating all the relevant items that need to be stored into a single object too, if at all possible, so as not to pollute localStorage with lots of separate properties that really deserve to be in a single place, rather than spread out, eg:
const dataObj = localStorage.pointsData
? JSON.parse(pointsData)
: {
totalPoints: 0,
qArr: new Array(7).fill(0)
};
Then, do stuff with dataObj.totalPoints and dataObj.qArr, and when you need to save it, do something like:
localStorage.pointsData = JSON.stringify(dataObj);
I would suggest an object.
You can store object as answers in localStorage and update the values.
So suppose answers object is something like this
answers = {
q0: 10,
q2: 20
}
Then it won't matter whether key is present or not, as you can loop through object to get total sum
let total = 0;
for (const key in answers) {
total += answers[key];
}
Of course you have to store that object in localStorage like this
localStorage.setItem('answers', JSON.stringify(answers));
And while getting you have to parse it
const answers = JSON.parse(localStorage.getItem('answers'));
I suggest to replace multiple values with one single array.
So, your saving function could be something like this:
function savePoint(point) {
var points = JSON.parse(localStorage.points || "[]");
points.push(point);
localStorage.points = JSON.stringify(points);
}
And you will calculate the total like this:
function calculateTotal() {
var points = JSON.parse(localStorage.points || "[]");
return points.reduce(function(total, point) {
return total + point;
}, 0);
}
Anyway, I suggest to think about a data model to store the points and using JSON.stringify + JSON.parse to store it in localStorage.
Since you have points q0 - q7 plus the points, there are 8 variables. Now, if we carefully go through your code, things that are common
You fetch a variable from a local storage
You convert it to integer
You check whether it is NaN. If NaN, you set to 0.
Finally, you use the variable to calculate the sum
Okay. So we will try to write a function that does steps 1-3 with
function oneForAll(variableName){
if (localStorage.getItem(variableName) === null){
localStorage.setItem(variableName, "0");
}
var variable = parseInt(localStorage.getItem(variableName));
if (isNaN(variable)) {
variable = 0;
}
return variable
}
I basically used most of your lines so that you can relate to it.
Now, for step 4, we need to use our function to get the total sum. Also, we need a loop.
var listOfVariables = ["points", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7" ]
var totalPoints = 0;
// loop over the list of variables
listOfVariables.forEach(variableName => {
totalPoints += oneForAll(variableName);
});
// finally you set the new calculated total points
localStorage.setItem("points", totalpoints);
Hope this is helpful.
I am trying to make two arrays. the unique array can get the elements (no repeats) from the text array, and the counter one can count the frequency of each elements. but something is wrong with the counter one.
var unique_array=new Array();
var counter_array=new Array();
var unique=true;
for (i=0;i<text_array.length;i++){
if (unique_array.length==0){
unique_array.push(text_array[0]);
counter_array.push(1);
}
else if(unique_array.length>0&&unique_array.length<=text_array.length){
for (j=0; j<unique_array.length;j++){
if (text_array[i]==unique_array[j]){
counter_array[j]=counter_array[j]+1;// something wrong with the
alert(counter_array[j]);
var unique=false;
}
}
if (unique==true){
unique_array.push(text_array[i]);
counter_array.push[1];
}
unique=true;
}
You could also simplify the code down using a hashmap and some ES5 higher-order functions:
var text_array = ["a1","a1","a2","a3","a2","a4","a1","a5"];
var counts = {};
text_array.forEach(function(el) {
counts[el] = counts.hasOwnProperty(el) ? counts[el]+1 : 1;
});
var unique_array = Object.keys(counts);
var counter_array=unique_array.map(function(key) { return counts[key]; })
You can do this much more simply using an object. Let the values be the keys of an object, then just increment the count of each property as you go. At the end, you can get an array of the unique keys and their values:
var text_array = ['foo','bar','foo','fum','fum','foo'];
var i = text_array.length;
var obj = {};
while (i--) {
if (obj.hasOwnProperty(text_array[i])) {
obj[text_array[i]]++;
} else {
obj[text_array[i]] = 1;
}
}
console.log('Unique values: ' + Object.keys(obj)); // Unique values: foo,fum,bar
console.log('Value counts: ' + Object.keys(obj).map(function(v){return obj[v]})); // Value counts: 3,2,1
Note that the sorting of counts in the output is purely coincidental.
As Jasvir posted, you can make it pretty concise:
var obj = {};
text_array.forEach(function(v) {
obj.hasOwnProperty(v)? ++obj[v] : obj[v] = 1;
});
But the first example is a bit easier to digest.
I think the approach is what's making it difficult. A hash table / associative array would be much easier to work with.
With a hash table (an object {} in JS), you can store each word in a key and increment the value of the key when you encounter the word again. Then, at the end, just go through the hash table and gather up all the keys which have small values. Those are your unique words.
function get_unique_words(text_array) {
var hash_table, i, unique_words, keys;
hash_table = {};
for(i = 0; i < text_array.length; i++) {
if(hash_table[text_array[i]] === undefined) {
hash_table[text_array[i]] = 1;
} else {
hash_table[text_array[i]]++;
}
}
// go through the hash table and get all the unique words
unique_words = [];
keys = Object.keys(hash_table);
for(i = 0; i < keys.length; i++) {
if(hash_table[keys[i]] === 1) {
unique_words.push(keys[i]);
}
}
return unique_words.sort();
}
console.log(get_unique_words(
['blah', 'blah', 'blah', 'goose', 'duck',
'mountain', 'rock', 'paper', 'rock', 'scissors']
));
Some issues and suggestions :
Don't use var twice for the same variable.
Browsers deal with it ok, but for clarity you should only be declaring your variables once.
Always localize your loop counters - forgetting a var before your i and j will cause them to become global variables.
This is relevant when you have a page with lots of code - all global variables will show up in the debugger's watch list at all times, making it harder to debug your code.)
Use the array literal notation [] instead of the function form Array.
The function form is longer and it's easier to forget the new. It's also easier to read (IMO).
Use more whitespace (it won't bite), such as before and after an equals sign:
var x = 1;
// vs.
var x=1;
It makes the code easier to read and most people don't overdo it.
Indent your code when it's inside a block (e.g. function, if, else, while, for, etc.).
This makes it easier to read the control flow of the code and will help prevent bugs.
Use three equals signs (===) unless you are using loose equality on purpose.
This will help someone looking at your code later (probably yourself) understand better what the test is supposed to be testing.
I have a big question.
I have many Strings in my Programm and want to check these Strings on there values.
I wrote a Loop for it, but insted of the Definition of an String he is creating a new value. It's basicly really difficult to discribe, also because i am basicly German.
But i can give you my current code, so maybee you will see what I mean:
{
var Loch1G = $('#m1-Rundenanalyse-Datum').val(); //In the strings just the number is changing
var Loch2G = $('#m1-Rundenanalyse-Turnier').val();
x=1
while (x <= 2) {
if ("Loch" + x + "G" == ""){ //Next String is genrated (x=x+1)
alert("Eingabe war leer");
}
x=x+1
}
}
How can I solve this?
I'd suggest using an array to store the values you want to check:
var lochs = [];
lochs.push($('#m1-Rundenanalyse-Datum').val());
lochs.push($('#m1-Rundenanalyse-Turnier').val());
for (var i = 0, len = lochs.length; i < len; i++){
if (lochs[i] == ''){
alert("Eingabe war leer");
}
}
JS Fiddle demos: passes (no alert), fails (alert)
This suggestion is based on my presumption that you're trying to create the names of the vars you want to check, which won't work, whereas this approach lets you store all values (however many) in the same array and then iterate over that array to find any values that are equal to an empty string.
If you really want to stick with your current approach, you could do the following:
{
window.Loch1G = $('#m1-Rundenanalyse-Datum').val(); //In the strings just the number is changing
window.Loch2G = $('#m1-Rundenanalyse-Turnier').val();
var x=1;
while (x <= 2) {
if (window["Loch" + x + "G"] == ""){ //Next String is genrated (x=x+1)
alert("Eingabe war leer");
}
x=x+1;
}
}
But I can't think why you'd want to; plus the use of global variables is poor practice as it explicitly makes those variables available to every closure within the document, which allows them to be easily, and accidentally, overwritten.
In a reasonably up-to-date browser, that implements Array.prototype.every, you could dispense with the explicit iteration:
var lochs = [];
lochs.push($('#m1-Rundenanalyse-Datum').val());
lochs.push($('#m1-Rundenanalyse-Turnier').val());
if (!lochs.every(function(a){ return a !== ''; })) {
alert("Eingabe war leer");
}
JS Fiddle demos: passes (no alert), fails (alerts).
OK, so I am attempting to write some code that will take x number of classrooms and x number of students per classroom and then pair two students together randomly from separate classrooms.
Sounds Simple, right!
I have already been able to create x number of input elements dynamically on the page and all works up until I try to create the array for the x number of elements.
Is there a different approach I should Try?
I got my code working up til this point:
for (i = 0; i < classSize; i++) {
var arrField = container2.appendChild(document.createElement('fieldset'));
var arrDiv = arrField.appendChild(document.createElement('div'));
arrDiv.id = "div" + (i + 1);
var studentArr = "studentArr" + (i + 1);
alert(studentArr); //returns "studentArr1" "studentArr2", etc.
for (n = 0; n < arrSize; n++) {
studentArr = new student();
studentArr[(n + 1)] = new student(document.getElementById("name" + (n + 1)).value, document.getElementById("email" + (n + 1)).value, document.getElementById("phone" + (n + 1)).value);
alert(studentArr[(n + 1)].name);
alert(studentArr[(n + 1)].email);
alert(studentArr[(n + 1)].phone);
}
}
FYI- I am very new to JavaScript and programming in general!- Sorry
Have you defined the student object constructor elsewhere? As it stands, student() looks like it's standing in for both an array and an object with attributes like name, email, etc. You should use something along the lines of
studentArray = []
studentArray.push(new student(name,email,etc.))
in order to prevent overloading the student class.
Also, on a separate note, you shouldn't use alerts unless you literally would say, "Alert!" They are problematic for at least three reasons:
1) They're really annoying
2) alert(object) will display something like [Object Object] for anything more complicated than a simple data structure like a string
3) They prevent anything else from running on the page.
You're better off using console.log, which writes information to the JavaScript Console. Different browsers implement the console in different places, but in Chrome, the console is in View -> Developer -> JavaScript Console. console.log is less obtrusive and allows you to inspect objects much more thoroughly than alert does.
Use :
array.push();
Example :
// initialize array
var arr = [
"Hi",
"Hello",
"Bonjour"
];
// append new value to the array
arr.push("Hola");
// display all values
for (var i = 0; i < arr.length; i++) {
alert(arr[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/