When to compare several Arrays in a 'for' loop - javascript

I'm still quite new to Javascript and Google Apps Script, and I'm attempting to create a script that takes your friends steam IDs, loops over their owned games, lists them to a spreadsheet, and displays if someone owns a game or not.
I've achieved the first part, looping over all of the owned games for each ID and adding them to an array if they don't already exist in the array works perfectly using:
var steamId = ['SomeSteamIDs'];
var gameIds = [];
var games = [];
function getGames(){
for (var i = 0; i < steamId.length; i++){
var response = UrlFetchApp.fetch("http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=**YourSteamKey**&steamid=" + steamId[i] + "&format=json&include_appinfo=1");//Steam URL.
Logger.log('Response Code: ' + response.getResponseCode());//Checks the request actually connected.
var data = JSON.parse(response.getContentText());//Gets the plaintext JSON response and converts it to an object.
for (var n = 0; n < data.response.games.length; n++){//For the length of the games list
var code = data.response.games[n].appid;
var name = data.response.games[n].name;
if (gameIds.indexOf(code) === -1){//if the AppID doesn't appear in the 'appId' array and sub-arrays
gameIds[gameIds.length] = code;//then put it in the appId array for comparison
games[games.length] = [code,name];// and add the AppId and Game to the games array for writting.
};
};
}
var range = sheet.getRange("A2:B" + (gameIds.length + 1));//+1 here to compensate for starting on line 2, instead of 1.
range.setValues(games);//Perform one write action
}
This works perfectly in compiling a master list of games that are owned across all SteamIDs, but I'm having difficulty in finding a way of checking off what games are owned by each Individual ID, and what is not.
Initially I was experimenting with adding a 'Yes/No' string to the 'games' array when running the 'getGames' function, but any solution I come up with looses data. If I compare the values too early, the 'gameIds' array doesn't contain all of the data, so the first SteamID misses out on comparing against any games that the last SteamID owns.
If I do it too late, the 'data' variable only contains the response data from the last SteamID it checked, so I miss out on checking what games the first SteamID owns.
I've read the answer at How to compare arrays in JavaScript? several times now, but I'm still trying to figure out if I can use it to solve my issue.
Is there a way for me to achieve what I'm looking for, and what would be the most efficient way?

I would approach this a bit differently. I would keep an object of gameList with game object keys by id that have a name property and then userList property that is an array of users attached to each game. This will do a few things for you. One, you can lookup the game in constant time now instead of looping to find it in the array (which indexOf does). Two, you now have a unique list of games (all properties of a games object) with an array of user ids (who owns them) for easy lookup. here's the code of what I'm describing
var steamIds = [],
gameList = {};
function getGames(){
steamIds.forEach(function(steamId) {
var response = UrlFetchApp.fetch("http://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=**YourSteamKey**&steamid=" + steamId[i] + "&format=json&include_appinfo=1");//Steam URL.
Logger.log('Response Code: ' + response.getResponseCode());//Checks the request actually connected.
var data = JSON.parse(response.getContentText());//Gets the plaintext JSON response and converts it to an object.
data.response.games.forEach(function(game) {//For the length of the games list
var code = game.appid;
var name = game.name;
if (!gameList.hasOwnProperty(code)) {
gameList[code] = {name: name, userList: [steamId]};
} else {
gameList[code].userList.push(steamId);
}
});
});
}
Here's and example of the end result of what the gameList will look like when it's done
gameList : {
123: {
name: 'Some Game',
userList: [80, 90, 52]
},
567: {
name: 'Another Game',
userList: [68]
}
}
Writing to the cells will change a bit but your information is now associated in a way that makes it easy to get information about a particular game or users that own it.

Related

Is this O(N) approach the only way of avoiding a while loop when walking this linked list in Javascript?

I have a data structure that is essentially a linked list stored in state. It represents a stream of changes (patches) to a base object. It is linked by key, rather than by object reference, to allow me to trivially serialise and deserialise the state.
It looks like this:
const latest = 'id4' // They're actually UUIDs, so I can't sort on them (text here for clarity)
const changes = {
id4: {patch: {}, previous: 'id3'},
id3: {patch: {}, previous: 'id2'},
id2: {patch: {}, previous: 'id1'},
id1: {patch: {}, previous: undefined},
}
At some times, a user chooses to run an expensive calculation and results get returned into state. We do not have results corresponding to every change but only some. So results might look like:
const results = {
id3: {performance: 83.6},
id1: {performance: 49.6},
}
Given the changes array, I need to get the results closest to the tip of the changes list, in this case results.id3.
I've written a while loop to do this, and it's perfectly robust at present:
let id = latest
let referenceId = undefined
while (!!id) {
if (!!results[id]) {
referenceId = id
id = undefined
} else {
id = changes[id].previous
}
}
The approach is O(N) but that's the pathological case: I expect a long changelist but with fairly frequent results updates, such that you'd only have to walk back a few steps to find a matching result.
While loops can be vulnerable
Following the great work of Gene Krantz (read his book "Failure is not an option" to understand why NASA never use recursion!) I try to avoid using while loops in code bases: They tend to be susceptible to inadvertent mistakes.
For example, all that would be required to make an infinite loop here is to do delete changes.id1.
So, I'd like to avoid that vulnerability and instead fail to retrieve any result, because not returning a performance value can be handled; but the user's app hanging is REALLY bad!
Other approaches I tried
Sorted array O(N)
To avoid the while loop, I thought about sorting the changes object into an array ordered per the linked list, then simply looping through it.
The problem is that I have to traverse the whole changes list first to get the array in a sorted order, because I don't store an ordering key (it would violate the point of a linked list, because you could no longer do O(1) insert).
It's not a heavy operation, to push an id onto an array, but is still O(N).
The question
Is there a way of traversing this linked list without using a while loop, and without an O(N) approach to convert the linked list into a normal array?
Since you only need to append at the end and possibly remove from the end, the required structure is a stack. In JavaScript the best data structure to implement a stack is an array -- using its push and pop features.
So then you could do things like this:
const changes = [];
function addChange(id, patch) {
changes.push({id, patch});
}
function findRecentMatch(changes, constraints) {
for (let i = changes.length - 1; i >= 0; i--) {
const {id} = changes[i];
if (constraints[id]) return id;
}
}
// Demo
addChange("id1", { data: 10 });
addChange("id2", { data: 20 });
addChange("id3", { data: 30 });
addChange("id4", { data: 40 });
const results = {
id3: {performance: 83.6},
id1: {performance: 49.6},
}
const referenceId = findRecentMatch(changes, results);
console.log(referenceId); // id3
Depending on what you want to do with that referenceId you might want findRecentMatch to return the index in changes instead of the change-id itself. This gives you the possibility to still retrieve the id, but also to clip the changes list to end at that "version" (i.e. as if you popped all the entries up to that point, but then in one operation).
While writing out the question, I realised that rather than avoiding a while-loop entirely, I can add an execution count and an escape hatch which should be sufficient for the purpose.
This solution uses Object.keys() which is strictly O(N) so not technically a correct answer to the question - but it is very fast.
If I needed it faster, I could restructure changes as a map instead of a general object and access changes.size as per this answer
let id = latest
let referenceId = undefined
const maxLoops = Object.keys(changes).length
let loop = 0
while (!!id && loop < maxLoops) {
loop++
if (!!results[id]) {
referenceId = id
id = undefined
} else {
id = changes[id].previous
}
}

How can I return the step number [i] when a certain key-value pair is found in an array?

Super NOOB to javascript, so I need some help trying to parse this JSON object and return a specific value.
So I have a JSON payload from an API Call that I've turned into an object (sorry, I still don't know how to line break this so it shows easily here on Stack Overflow):
var myObj = JSON.parse("{'ClassID':'1ghjk2345','ProcessName':'Workflow','Steps':[{'Id':'1234ghjhg102','Name':'Start','ActivityInstanceID':'12435r3ffe2'},{'Id':'134gbvgg102','Name':'attendance','ActivityInstanceID':'7865fdghd'},{'Id':'1jhgy102','Name':'lesson','ActivityInstanceID':'12gs5ghdse2'},{'Id':'1ghkb102','Name':'quiz','ActivityInstanceID':'1sgdtgt'},{'Id':'12fkfh02','Name':'evaluation','ActivityInstanceID':'1243sdfgssdfg2'},{'Id':'1khfhf02','Name':'dismissal','ActivityInstanceID':'124sdfgdfgrrrfe2'}],'TimeTaken':54838,'_id':'PK21342ffh','status':'Complete'}");
I would like to return the Step number in the Array that contains this key-value pair:
'Name':'evaluation'
I need this because the order of the steps changes for each payload - sometimes it's step 4, sometimes 2 depending on when the teacher initiates that step in the application.
I would then like to retrieve the 'ActivityInstanceID' of that step.
You need to change your data do fit json requirements.
Use variables names in lower case.
var myData = JSON.parse('{"ClassID":"1ghjk2345","ProcessName":"Workflow","Steps":[{"Id":"1234ghjhg102","Name":"Start","ActivityInstanceID":"12435r3ffe2"},{"Id":"134gbvgg102","Name":"attendance","ActivityInstanceID":"7865fdghd"},{"Id":"1jhgy102","Name":"lesson","ActivityInstanceID":"12gs5ghdse2"},{"Id":"1ghkb102","Name":"quiz","ActivityInstanceID":"1sgdtgt"},{"Id":"12fkfh02","Name":"evaluation","ActivityInstanceID":"1243sdfgssdfg2"},{"Id":"1khfhf02","Name":"dismissal","ActivityInstanceID":"124sdfgdfgrrrfe2"}],"TimeTaken":54838,"_id":"PK21342ffh","status":"Complete"}');
for (let i = 0; i < myData.Steps.length; i++) {
if(myData.Steps[i].Name === "evaluation") console.log("the step is: ", i);
}

Javascript object/array manipulation

Struggling with some javascript array manipulation/updating. Hope someone could help.
I have an array:
array('saved_designs'=array());
Javascript JSON version:
{"saved_design":{}}
I will be adding a label, and associated array data:
array("saved_designs"=array('label'=array('class'='somecssclass',styles=array(ill add more assoc elements here),'hover'=array(ill add more assoc elements here))))
Javascript version:
{"saved_designs":{"label":{"class":"someclass","style":[],"hover":[]}}}
I want to be able to append/modify this array. If 'label' already defined...then cycle through the sub data for that element...and update. If 'label' doesnt exist..then append a new data set to the 'saved_designs' array element.
So, if label is not defined, add the following to the 'saved_designs' element:
array('label2' = array('class'=>'someclass2',styles=array(),'hover=>array()')
Things arent quite working out as i expect. Im unsure of the javascript notation of [], and {} and the differences.
Probably going to need to discuss this as answers are provided....but heres some code i have at the moment to achive this:
//saveLabel = label the user chose for this "design"
if(isUnique == 0){//update
//ask user if want to overwrite design styles for the specified html element
if (confirm("Their is already a design with that label ("+saveLabel+"). Overwrite this designs data for the given element/styles?")) {
currentDesigns["saved_designs"][saveLabel]["class"] = saveClass;
//edit other subdata here...
}
}else{//create new
var newDesign = [];
newDesign[saveLabel] = [];
newDesign[saveLabel]["class"] = saveClass;
newDesign[saveLabel]["style"] = [];
newDesign[saveLabel]["hover"] = [];
currentDesigns["saved_designs"].push(newDesign);//gives error..push is not defined
}
jQuery("#'.$elementId.'").val(JSON.stringify(currentDesigns));
thanks in advance. Hope this is clear. Ill update accordingly based on questions and comments.
Shaun
It can be a bit confusing. JavaScript objects look a lot like a map or a dictionary from other languages. You can iterate over them and access their properties with object['property_name'].
Thus the difference between a property and a string index doesn't really exist. That looks like php you are creating. It's called an array there, but the fact that you are identifying values by a string means it is going to be serialized into an object in javascript.
var thing = {"saved_designs":{"label":{"class":"someclass","style":[],"hover":[]}}}
thing.saved_designs.label is the same thing as thing["saved_designs"]["label"].
In javascript an array is a list that can only be accessed by integer indices. Arrays don't have explicit keys and can be defined:
var stuff = ['label', 24, anObject]
So you see the error you are getting about 'push not defined' is because you aren't working on an array as far as javascript is concerned.
currentDesigns["saved_designs"] == currentDesigns.saved_designs
When you have an object, and you want a new key/value pair (i.e. property) you don't need a special function to add. Just define the key and the value:
**currentDesigns.saved_designs['key'] = newDesign;**
If you have a different label for every design (which is what it looks like) key is that label (a string).
Also when you were defining the new design this is what javascript interprets:
var newDesign = [];
newDesign is an array. It has n number of elements accessed by integers indices.
newDesign[saveLabel] = [];
Since newDesign is a an array saveLabel should be an numerical index. The value for that index is another array.
newDesign[saveLabel]["class"] = saveClass;
newDesign[saveLabel]["style"] = [];
newDesign[saveLabel]["hover"] = [];
Here explicitly you show that you are trying to use an array as objects. Arrays do not support ['string_key']
This might very well 'work' but only because in javascript arrays are objects and there is no rule that says you can't add properties to objects at will. However all these [] are not helping you at all.
var newDesign = {label: "the label", class: saveClass};
is probably what you are looking for.

Convert One field in an Object Array data with angularjs and Javascript to another value

In an angularjs controller I have this code:
var ysshControllers = angular.module('theControllers', []);
ysshControllers.controller('CommonController',
function($scope, $http, $route) {
$scope.dbKey = $route.current.customInput;
$scope.urlToDb = 'https://' + $scope.dbKey + '/.json';
$http.get($scope.urlToDb).success(function(data) {
var values = [];
for (var name in data) {
values.push(data[name]);
}
$scope.Items = values;
});
// Initially order by date and time
$scope.orderProp = 'ac';
}
);
It creates an object array with the name Items. The key values are just labed aa, ab, ac etc. When the user inputs data from a drop down menu, I want to save only values like: 1,2,3,4,5 and then when the data is imported back into the website, convert the values back. Like 0 = Bad; 5 = Very Good. So, for every record with the key name ae I want to convert the values from 1,2,3,4,5 to Bad, Okay, Fair, Good, Very Good.
I can figure out the program flow, what I need to know is how to reference the values using object oriented JavaScript I guess.
The data is structured like this:
C5_200630_Option 1
aa:"Option 1"
ab:"Toyota"
ac:6499
ad:"Truck"
ae:"Tacoma, Silver, 1/2 ton 4wd"
af:4
I ran this like of code:
alert(Object.keys($scope.UsedItems));
And it gives values of 0,1,2,3,4 etc. So I guess the key values in $scope.UsedItems are just numbers. I don't know how to access the key and value data specifically. What is a simple way I can just display in an alert what the content of the array is?
I used this line:
alert(data[name].ad);
And that will reference the data in every record with the name ad. So that gives me a way to identify a specific item in the record.
Okay, I figured out a solution:
if (data[name].af === "3") {
data[name].af = "Awesome!";
}
Even though I figured out the solution to my problem, I still have almost no idea what I'm doing. So if there is a better way, let me know.
You can define an array like this
var example = ["Bad", "Not Bad", "Fine", "Good", "Very Good"];
and instead of checking value of data[name].af every time you can simply set it like this
data[name].af = example[data[name].af];
which gives you the result you want as you want data[name].af=3 should be fine and example[3] is what you want...

How do I match this text faster?

I'm building an autosuggest for names. When the user types in the textbox, it hits the server and runs this:
var names = [ list of 1000 names ]; //I have a list of 1000 names, this is static.
var query = 'alex';
var matched_names = [];
//This is when it gets slow....
names.forEach(function(name){
if(name.indexOf(query) >= 0){
matched_names.push(name);
}
});
return matched_names;
How can I make this faster? I'm using Node.js
If the names are static then move this code to the client and run it there. The only reason to run code like this on the server is if the data source is dynamic in some way.
Doing this logic client-side will dramatically improve performance.
You should probably use filter instead, for one thing, because it's native:
var names = [ /* list of 1000 names */ ];
var query = 'alex';
var matched_names = names.filter(function(name) {
return name.indexOf(query) > -1;
});
return matched_names;
If you store the names in sorted order, then you can use binary search to find the region of names within the sorted order that start with the fragment of name that the user has typed so far, instead of checking all the names one by one.
On a system with a rather odd programming language, where I wanted to find all matches containing what the user had typed so far in any position, I got a satisfactory result for not much implementation effort by reviving http://en.wikipedia.org/wiki/Key_Word_in_Context. (Once at university I searched through a physical KWIC index, printed out from an IBM lineprinter, and then bound as a document for just this purpose.
I would suggest you to do this stuff on the client-side and prefer (for now) a while loop instead of a filter/forEach approach:
var names = [ /* list of 1000 names */ ]
, query = 'alex'
, i = names.length
, matched_names = [];
while(i--){
if(names[i].indexOf(query) > -1){
matched_names.push(names[i]);
}
}
return matched_names;
This will be much faster (even if filter/forEach are natively supported). See this benchmark: http://jsperf.com/function-loops/4

Categories

Resources