Reassigning Lists in For Loops (JavaScript) - javascript

Apologies if this has been asked before - I couldn't find what I was looking for after a search, but I'm a beginner, so I might have missed something.
I am trying to implement Lloyd's algorithm in JavaScript (very crudely) to get some practice.
var k_means = function (array,number_clusters,max_loops) {
var initial_centers = underscore.sample(shelter_lat_lon,number_clusters);
var current_centers = initial_centers;
var current_associations = {};
for (p = 0; p < current_centers.length; p++) {
current_associations[current_centers[p]] = []
}
for (loops = 0; loops < max_loops; loops++) {
for (i = 0; i < array.length; i++) {
var current_loc = array[i]
temp_array = new Array();
for (j = 0; j < current_centers.length; j++) {
var distance_from_center = distance(current_centers[j],current_loc)
temp_array.push(distance_from_center)
}
var closest_center_lat_lon = current_centers[smallest_index(temp_array)]
current_associations[closest_center_lat_lon].push(current_loc)
}
new_clusters_temp = []
for (var key in current_associations) {
lat = []
lon = []
for (i = 0; i < current_associations[key].length; i++){
lat.push(current_associations[key][i][0])
lon.push(current_associations[key][i][1])
}
mean_lat = math_module.mean(lat)
mean_lon = math_module.mean(lon)
new_clusters_temp.push([mean_lat,mean_lon])
}
current_centers = new_clusters_temp;
}
}
Sorry for the ugly code. There are 2 module requirements - underscore and mathjs (mathjs is called math_module). Additionally, the function distance returns the Euclidean distance (I'm using 2 dimensional data), and smallest_index returns the index of the smallest element in an array.
The only problem I'm having is coming at the line
current_centers = new_clusters_temp;
Node returns the error "Cannot read property 'push' of undefined." After debugging a bit, it essentially thinks the array "current_centers" is empty. In Python, this is how I would reassign a list outside of a for loop. Is this different in JavaScript?
Cheers!
Alex

Related

Why is this comparison not working, using google aps scripts?

The code below is not giving me the expected result.
It's to compare rows from two ranges and, although the second range's last row equals the one from the first range, it gives me false as the result.
var entryValuesCom = sheet.getRange(7, 1, LastRowSource, 9).getValues();
var dbDataCom = dbSheet.getRange(2, 1, dbSheet.getLastRow(), 9).getValues();
var entryVlArray = new Array();
var dbArray = new Array();
for (var r = 0; r < entryValuesCom.length; r++) {
if (entryValuesCom[r][0] != '' && entryValuesCom[r][5] != 'Daily Ledger Bal') {
entryVlArray.push(entryValuesCom[r]);
}
}
for (var a = 0; a < dbDataCom.length; a++) {
if (dbDataCom[a][1] != '' && dbDataCom[a][8] == bank) {
dbArray.push(dbDataCom[a]);
}
}
var duplicate = false;
loop1:
for (var x = 0; x < entryVlArray.length; x++) {
loop2:
for (var j = 0; j < dbArray.length; j++) {
if (JSON.stringify(entryVlArray) == JSON.stringify(dbArray)) {
duplicate = true;
break loop1;
}
}
}
Here's a snapshot of how the array is coming:
I've tried it using .join(), but still...
This is for thousands of rows, so is this going to do well performance wise?
I believe your goal as follows.
You want to compare the arrays of entryVlArray and dbArray using Google Apps Script.
When the duplicated rows are existing between entryVlArray and dbArray, you want to output duplicate = true.
Modification points:
When your script is modified, at if (JSON.stringify(entryVlArray) == JSON.stringify(dbArray)) {, all 2 dimensional arrays are compared. I think that this might be the reason of your issue. From your script, I think that it is required to compare each element in the 2 dimensional array.
When above points are reflected to your script, it becomes as follows.
Modified script:
From:
var duplicate = false;
loop1:
for (var x = 0; x < entryVlArray.length; x++) {
loop2:
for (var j = 0; j < dbArray.length; j++) {
if (JSON.stringify(entryVlArray) == JSON.stringify(dbArray)) {
duplicate = true;
break loop1;
}
}
}
To:
var duplicate = false;
for (var x = 0; x < entryVlArray.length; x++) {
for (var j = 0; j < dbArray.length; j++) {
if (JSON.stringify(entryVlArray[x]) == JSON.stringify(dbArray[j])) {
duplicate = true;
break;
}
}
}
console.log(duplicate)
By this modification, when each element (1 dimensional array) in the 2 dimensional array is the same, duplicate becomes true.
Note:
As other method, when an object for searching each row value is prepared, I think that the process cost might be able to be reduced a little. In this case, the script is as follows. Please modify as follows.
From:
var duplicate = false;
loop1:
for (var x = 0; x < entryVlArray.length; x++) {
loop2:
for (var j = 0; j < dbArray.length; j++) {
if (JSON.stringify(entryVlArray) == JSON.stringify(dbArray)) {
duplicate = true;
break loop1;
}
}
}
To:
var obj = entryVlArray.reduce((o, e) => Object.assign(o, {[JSON.stringify(e)]: true}), {});
var duplicate = dbArray.some(e => obj[JSON.stringify(e)]);
References:
reduce()
some()
Added:
About your following 2nd question,
AMAZING!!!! Would there be a way of capturing these duplicates in a pop up, using reduce() and some()?
When you want to retrieve the duplicated rows, how about the following script? In this case, I thought that filter() is useful instead of some().
Modified script:
var obj = entryVlArray.reduce((o, e) => Object.assign(o, {[JSON.stringify(e)]: true}), {});
// var duplicate = dbArray.some(e => obj[JSON.stringify(e)]);
var duplicatedRows = dbArray.filter(e => obj[JSON.stringify(e)]);
console.log(duplicatedRows)
In this modified script, you can see the duplicated rows at the log.
About a pop up you expected, if you want to open a dialog including the duplicated rows, how about adding the following script after the line of var duplicatedRows = dbArray.filter(e => obj[JSON.stringify(e)]);?
Browser.msgBox(JSON.stringify(duplicatedRows));

Push same information to two arrays using a For Loop

Bit of a theoretical question, if I had a JavaScript application where I have multiple Players and for each player there will be 100 computer generated maths questions.
In single player mode it's easy, just generating the questions for the one player:
var player1Qs = [];
for (i = 0; i < maxQustions; i++) {
// Generate Question Object
var question = {};
...
// Add to Array
player1Qs.push(question);
}
That works with no issue. However, when I add a second player into the mix using the same sort of idea as above is where I get a bit puzzled. I'm tryinng to do it without using a multi-dimensional array becasue I'm trying to keep it as simple as possible, but it might be unavoidable.
So player 2 would look something similar to this:
var player1Qs = [];
var player2Qs = [];
for (i = 0; i < playerCount; i++) {
for (j = 0; j < maxQustions; j++) {
// Generate Question Object
var question = {};
...
// Add to Array
???
}
}
Would there be a way of me adding to those two arrays dynamically using a for loop? Or would I need a containing array of players and inside that an array for the questions?
Something like this should do the trick:
// Make sure all players exist.
var players = [];
for (var i = 0; i < playerCount; i++) {
players.push({ name: "Player " + i, questions: [] });
}
// Create questions
for (var i = 0; i < maxQuestions; i++) {
// Generate Question Object
var question = {};
// Do stuff with this question
// Assign the current question to all players.
for (var j = 0; j < playerCount; j++) {
players[j].questions.push(question);
}
}
I'm tryinng to do it without using a multi-dimensional array becasue I'm trying to keep it as simple as possible
I would argue that having a two-dimentional array is the simplest use-case, as you suggest in your question:
Or would I need a containing array of players and inside that an array
for the questions?
The answer is yes (at least if you want to keep it simple). The players array will keep all the players, and each player can then have 100 questions each.
var maxQustions = 100;
var players= []
var player1Qs = [];
var player2Qs = [];
players.push(player1Qs);
players.push(player2Qs);
for (i = 0; i < players.length; i++) {
for (j = 0; j < maxQustions; j++) {
var question = {};
players[i].push(question);
}
}
follow this approach, I assume you have dynamic players and its array:-
var data = {};
var player = [1,2]
var c = [1,2,3,4,5]
for(j=0;j<player.length;j++)
{
data['players'] = player;
data['questions'] = c
}
console.log(data)

Google App Script arrays

I'm working on Google Script and I'm testing different ways to create two dimensions arrays.
I have created an array like this:
var codes = new Array(6);
for (var i = 0; i < 6; i++) {
codes[i] = new Array(4);
}
codes[0][0]="x";
codes[0][1]="x";
codes[0][2]="x";
codes[0][3]="x";
codes[1][0]="x";
codes[1][1]="x";
codes[1][2]="x";
codes[1][3]="x";
codes[2][0]="x";
codes[2][1]="x";
codes[2][2]="x";
codes[2][3]="x";
codes[3][0]="x";
codes[3][1]="x";
codes[3][2]="x";
codes[3][3]="x";
codes[4][0]="x";
codes[4][1]="x";
codes[4][2]="x";
codes[4][3]="x";
codes[5][0]="x";
codes[5][1]="x";
codes[5][2]="x";
codes[5][3]="x";
And it is working fine.
I read following links here, here and here.
But when I do it like this:
var codes = new Array(6);
for (var i = 0; i < 6; i++) {
codes[i] = new Array(4);
}
codes[0]=["x","x","x","x"];
codes[1]=["x","x","x","x"];
codes[2]=["x","x","x","x"];
codes[3]=["x","x","x","x"];
codes[4]=["x","x","x","x"];
codes[5]=["x","x","x","x"];
It didn't work, so I tried like this:
var codes = new Array([["x","x","x","x"],["x","x","x","x"],["x","x","x","x"],["x","x","x","x"],["x","x","x","x"],["x","x","x","x"]]);
it didn't work either.
When the code don't work, I get no error, just no display of the values.
What am I doing wrong? It looks to be the same code and the two not working ways are recommended in many documentations.
W3schools says that there is no need to use new Array().
For simplicity, readability and execution speed, use literal method ex:
var animals = ["cat", "rabbit"];
Reason why your code was not working is that you're equaling codes inside the loop and after end of loop scope 'codes' is getting only the last set array. Instead you should push those arrays to codes.
var codes = [];
for (var i = 0; i < 6; i++) {
codes.push([i]);
}
console.log(codes)
codes[0]=["x","x","x","x"];
codes[1]=["x","x","x","x"];
codes[2]=["x","x","x","x"];
codes[3]=["x","x","x","x"];
codes[4]=["x","x","x","x"];
codes[5]=["x","x","x","x"];
Better yet, two for loops to create the double array:
var codes = [], // Initiate as array, in Javascript this is actually fastre than using new (I don't know any cases you should use new)
rows = 6,
columns = 6;
for (var i = 0; i < rows; i++){
codes.push([]); // Initiate
for (var j = 0; j < columns; j++){
codes[i][j] = 'x';
}
}
Other idea, pre-initiate an array with the correct columns then copy:
var arrTemp = [],
codes = [],
rows = 6,
columns = 6;
for (var j = 0; j < columns; j++)
arrTemp[i] = 'x';
for (var i = 0; i < rows; i++)
codes.push( arrTemp.slice(0) ); // If you just push the array without slice it will make a reference to it, not copy
Other way to pre-initiate the array with 'x's:
arrTemp = Array.apply(null, Array(columns)).map(function () {return 'x'});

Setting vars using for loops

I have the following javascript
information0 = xmlDoc.getElementsByTagName("info")[0].textContent;
information1 = xmlDoc.getElementsByTagName("info")[1].textContent;
information2 = xmlDoc.getElementsByTagName("info")[2].textContent;
information3 = xmlDoc.getElementsByTagName("info")[3].textContent;
information4 = xmlDoc.getElementsByTagName("info")[4].textContent;
information5 = xmlDoc.getElementsByTagName("info")[5].textContent;
information6 = xmlDoc.getElementsByTagName("info")[6].textContent;
I want to create a new var for each index number. There are 600 in total. How can I do this using a for loop?
Thanks in advance
The best thing here is to use an array, not a bunch of individual variables.
var information = [];
var index;
var info = xmlDoc.getElementsByTagName("info");
for (index = 0; index < info.length; ++index) {
information[index] = info[index].textContent;
}
Um... use an array? Also, don't call getElementsByTagName repeatedly, it's expensive!
var tags = xmlDoc.getElementsByTagName('info'), l = tags.length, i, information = [];
for( i=0; i<l; i++) information[i] = tags[i].textContent;
If you're in a reasonably up-to-date browser:
var information = [].map.call(xmlDoc.getElementsByTagName('info'),function(a) {return a.textContent;});
Like this:
var information = [],
i,
elements = xmlDoc.getElementsByTagName("info"),
n = elements.length;
for (i = 0; i < n; ++i) {
information[i] = elements[i].textContent;
}
You need to use an array.
var infoTags = xmlDoc.getElementsByTagName("info"),
i = 0,
len = infoTags.length,
values = []; //array literal syntax, you could also use new Array()
for (; i < len; i++) {
values.push(infoTags[i].textContent); //push the textContent into the array
}
Things that you should note:
I cached the result of getElementsByTagName instead of performing the query multiple times.
I cached the length property of infoTags. That avoids multiple property lookups to access infoTags.length on every iterations. Property lookups are expensive in JavaScript.
To know how you can work with arrays, have a look at the Array object.
-

Using .set on a filtered array of IDs in backbone

I used Underscore.js's _.filter to get an array of object ids like so:
var downstreamMeters = _.filter(that.collection.models, function(item) { return item.get("isdownstreammeter"); });
Now I want to set a certain attribute of each model in the array. I thought it would make sense to do this:
for (var i = 0; i < downstreamMeters.length; i++) {
var sum = 0;
inputMeters = downstreamMeters[i].get("inputmeters");
for (var i = 0; i < inputMeters.length; i++) {
var flow = parseFloat(that.collection.get(inputMeters[i]).get("adjustedflow"));
sum += flow;
}
downstreamMeters[i].set({incrementalflow: sum});
}
However, I get the error:
Uncaught TypeError: Cannot call method 'set' of undefined
I checked the downstreamMeters array and it has the right objects in it. What do I need to do to set the attribute for each model in the array?
Saying for(var i = 0; ...) is somewhat misleading. JavaScript hoists all var declarations up to the top of the closest scope and for loop doesn't create its own scope. The result is that this:
for (var i = 0; i < downstreamMeters.length; i++) {
var sum = 0;
inputMeters = downstreamMeters[i].get("inputmeters");
for (var i = 0; i < inputMeters.length; i++) {
var flow = parseFloat(that.collection.get(inputMeters[i]).get("adjustedflow"));
sum += flow;
}
downstreamMeters[i].set({incrementalflow: sum});
}
is the same as this:
var i, sum, flow;
for (i = 0; i < downstreamMeters.length; i++) {
sum = 0;
inputMeters = downstreamMeters[i].get("inputmeters");
for (i = 0; i < inputMeters.length; i++) {
flow = parseFloat(that.collection.get(inputMeters[i]).get("adjustedflow"));
sum += flow;
}
downstreamMeters[i].set({incrementalflow: sum});
}
Now you can see that you are using exactly the same i in the outer and inner loops. On the first run through the loop, i will be inputMeters.length when you say downstreamMeters[i].set(...). Apparently, inputMeters.length > downstreamMeters.length so you end up running off the end of downstreamMeters; if you try to access an element of an array that is past the array's end, you get undefined and there's your
Cannot call method 'set' of undefined.
error.
Nesting loops is fine but you should be using different variables:
var i, j, sum, inputMeters;
for (i = 0; i < downstreamMeters.length; i++) {
sum = 0;
inputMeters = downstreamMeters[i].get("inputmeters");
for (j = 0; j < inputMeters.length; j++)
sum += parseFloat(that.collection.get(inputMeters[j]).get("adjustedflow"));
downstreamMeters[i].set({incrementalflow: sum});
}

Categories

Resources