Javascript Array Arbitrary Index Memory Allocation - javascript

is declaring an array and initializing some arbitrary indexes allocate all the array elements in the memory even the undefined ones?
Example:
var users = [];
function addUser(userID, name, address) {
if (typeof (users[userID]) === 'undefined')
users[userID] = new User(userID, name, address)
}
function User (userID, name, address) {
this.userID = userID;
this.name = name;
this.address = address;
}
$(function () {
addUser(63, 'John', 'VA');
addUser(5, 'Kate', 'NY');
addUser(895, 'Yaz', 'DC');
});
So in the above example, will the browser allocate 896 instances of User in the memory (only 3 are defined) or only 3?
Thanks,

Nope
JavaScript doesn't care what you put in the array, and it's not going to auto-populate it with values you didn't give it.
If you add 3 users to the array, you will only have 3 users in memory.
The indices in the gaps will just be undefined
var x = [];
// undefined
x[0] = "user1";
// 'user1'
x[3] = "user2";
// 'user2'
x[10] = "user3";
// 'user3'
x;
// ['user1',,,'user2',,,,,,,'user3']
All of that said, you might be better off using an Object ({})
var users = {};
function addUser(userID, name, address) {
if (!(userID in users)) {
users[userID] = new User(userID, name, address)
}
}
You will have an object that looks like this
{"63": [object User], "5": [object User], "895": [object User]}

Related

Google spreadsheet error TypeError: Cannot read property 'filter' of undefined

I am taking data from a sheet and then converting it to json, so far all good. But when I am trying to take out specific data from arrays but it is making undefined if I get data from array, but when I use raw json data everything works fine
function doGet(e) {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Users');
var userData = getUser(ss);
var array = userData.user.filter(function b(e) { return e.id == 11281 });
console.log(array)
}
function getUser(ss){
var usr = {};
var usrArray = [];
var data = ss.getRange(2,1,ss.getLastRow(), ss.getLastColumn()).getValues();
for (var i=0, l=data.length; i<l; i++){
var dataRow = data[i];
var record = {};
record['id'] = dataRow[1];
record['name'] = dataRow[0];
record['profession'] = dataRow[2];
usrArray.push(record);
}
usr.user = usrArray;
var result = JSON.stringify(usr);
return result;
}
ERROR: TypeError: Cannot read property 'filter' of undefined
Error picture
Sheet data picture
If I console log the result directly the it is working fine like this: Output
JSON.stringify returns a string — the getUser function returns a string. And you're trying to read the user property from it which returns undefined.
You could do a JSON.parse in doGet but it's simpler to just return the usr object from getUser.
function getUser(ss) {
const usr = {}
const usrArray = []
// populate usrArray
usr.user = usrArray
return usr
}
And there's another issue. You're setting record['id'] to dataRow[1] but it should be dataRow[0] since id is the first column (This is why the shared screenshot has the name in the id property). You could also refactor the code.
function getUser(ss) {
const data = ss
.getRange(2, 1, ss.getLastRow(), ss.getLastColumn())
.getValues()
const users = data.map((row) => ({
id: row[0],
name: row[1],
profession: row[2],
}))
return {
user: users,
}
}
I would also recommend renaming the user property to users for clarity.

Local storage set Item replaces instead of update

I tried to save my data in local Storage with setItem but when I refresh the chrome tab and add data to my array, the data in localStorage delete the old data and set new data instead of updating that old data.
Here is my code:
let capacity = 200;
let reservedRooms = 0;
let users = [];
let rsBox = document.getElementById('reservebox');
class Reserver {
constructor(name , lastName , nCode , rooms){
this.name = name ;
this.lastName = lastName ;
this.nCode = nCode ;
this.rooms = rooms ;
}
saveUser(){
if(this.rooms > capacity){
console.log('more than capacity');
}else{
users.push({
name : this.name ,
lastName : this.lastName ,
id : this.nCode ,
rooms : this.rooms
});
capacity -= this.rooms ;
reservedRooms += this.rooms ;
}
}
saveData(){
localStorage.setItem('list',JSON.stringify(users));
}
}
rsBox.addEventListener('submit',function(e){
let rsName = document.getElementById('name').value;
let rsLastName = document.getElementById('lastname').value;
let rsNationalCode = Number(document.getElementById('nationalcode').value);
let rooms = Number(document.getElementById('rooms').value);
//Save the user data
let sign = new Reserver(rsName , rsLastName , rsNationalCode , rooms);
sign.saveUser();
sign.saveData();
e.preventDefault();
});
You are pushing an empty users array each time you reload the page, to resolve this you need to populate the users array from the items you have in storage.
e.g.
let users = [];
needs to be something like
let users = JSON.parse(localStorage.getItem('list')) || [];
The key point being that you need to load your existing users to be able to add to them, if you don't then you are essentially creating the users array fresh each time the page loads and you put data into it.
You may want to create something like a "loadData" function that checks if the array is initialized, loads it if it is and creates it if it isn't. You can make this generic so that you can use it to access any key and provide a default value if the key isn't present e.g.
function loadData(key, def) {
var data = localStorage.getItem(key);
return null == data ? def : JSON.parse(data)
}
then
// load "list" - set to an empty array if the key isn't present
let users = loadData('list', []);
Another option would be to change the saveData method so you won't have to load the localStorage when the app is loading:
saveData(){
let newList = JSON.parse(localStorage.getItem('list') || '[]')
if(users.length) newList.push(users[users.length - 1]);
localStorage.setItem('list',JSON.stringify(newList));
newList=null;
}
Note: Be careful with localStorage because it doesn't have the same capacity on all browsers, you can check this post for more information

pg-promise create custom filters for select query

The function that I am working on is getting an input object that has 7 different key-values and each of them could be undefined or not. I want to filter my database based on those key-values that exists in the input. For example if only input.userID exists I want to run this query:
db.query("...WHERE userID = ${userID}", {userID: input.userID});
else if both input.userID and input.startTime exist, I want to do this:
db.query("...WHERE userID = ${userID} and startTime= ${startTime}", {userID: input.userID, startTime: input.startTime});
What I have done is I created a params and keys object like this:
if(input.userID) {
keys.push('userID');
params.push(input.userID);
query = addFilterToTheQuery(query, 'userID', input.userID, filteredItemsCount);
filteredItemsCount = filteredItemsCount +1;
}
addFilterToTheQuery is a simple function I implemented myself. I basically make 7 if cases. Then I have to use those keys and param values to pass to the query function in a way that might need another huge switch case code.
Is this the only way to do this? Is there a better way to get rid of the redundancies in this code?
Custom Type Formatting is the most suitable here.
For example, if we want to convert an object with properties - filter values, we could do it like this:
var pgp = require('pg-promise')(/* initialization options */);
function FilterSet(filters) {
if (!filters || typeof filters !== 'object') {
throw new TypeError('Parameter \'filters\' must be an object.');
}
this._rawDBType = true; // property renamed later - see UPDATE below
this.formatDBType = function () {
var keys = Object.keys(filters);
var s = keys.map(function (k) {
return pgp.as.name(k) + ' = ${' + k + '}';
}).join(' AND ');
return pgp.as.format(s, filters);
};
}
TEST
var filter = new FilterSet({
first: 1,
second: 'two'
});
var test = pgp.as.format('WHERE $1', filter);
console.log(test);
This outputs:
WHERE "first" = 1 AND "second" = 'two'
If your filters are to be used as %value% with LIKE or ILIKE, then you would need to change your custom type accordingly.
See related questions:
42, 49, 89, 90,
UPDATE
Below is the same example re-written for the latest pg-promise (version 8.x or newer):
const pgp = require('pg-promise')(/* initialization options */);
class FilterSet {
constructor(filters) {
if (!filters || typeof filters !== 'object') {
throw new TypeError('Parameter \'filters\' must be an object.');
}
this.filters = filters;
this.rawType = true; // do not escape the result from toPostgres()
}
toPostgres(/*self*/) {
// self = this
const keys = Object.keys(this.filters);
const s = keys.map(k => pgp.as.name(k) + ' = ${' + k + '}').join(' AND ');
return pgp.as.format(s, this.filters);
}
}
See Custom Type Formatting.

Should I bother cleaning array in node.js?

In one of my script, I make extensive use of array to temporary store data. The problem I m facing is that I have a lot of code handling the array just so I make economic use of the space.
Should I even bother since Node.js array are associative array?
My current solution is:
//Get the minimum empty id in array
function get_id(callback) {
var i = 0;
while(array[i] != null) {
i = i + 1;
}
array[i] = 0;
callback(i);
}
get_id(function (i) {
array[i] = {large object};
//...
array[i] = null;
});
But I feel it is wrong and bug prone.
Can I just do:
array[i] = {large object};
i = i + 1;
//...
array[i] = null;
Or would it lead to large consumption of memory?
array is a global variable of the module using it.
Cut down code (I ve removed all computing not linked to the array player.active_mission):
var player = {},
missions = [{time: 1000}];
function end_mission(mission, squad, mission_log, callback) {
//Make all the computing of the mission to know if the player won...
callback(mission_log);
}
function get_ami(callback) {
var i = 0;
while(player.active_mission[i] != null) {
i = i + 1;
}
player.active_mission[i] = 0;
callback(i);
}
function wait_mission(mission, squad, mission_log, i, time, callback) {
setTimeout(function () {
console.log('End of mission');
player.active_mission[i] = null;
end_mission(mission, squad, mission_log, callback);
}, time);
}
function start_mission(mission, squad, callback) {
var mission_log = {mission: mission, time_start: new Date(), completed: false, read: false};
//Verify if the player can start the mission...
console.log('start_mission');
get_ami(function (i) {
player.active_mission[i] = {mission: mission, squad: squad, mission_log: mission_log}
wait_mission(mission, squad, mission_log, i, missions[mission].time, callback);
});
}
player.active_mission = [];
//This part is inside get request, after sanitizing all input
start_mission(0, [0, 1], function (r) {
//r.id = req.session.player_id;
if(r.error) {
console.log('start: error: ' + r.error);
} else {
console.log('start: Success: ' + r.result);
}
});
player.active_mission hold all uncompleted request of the player, and need to be saved if the player quit before completion. My problem is just if I should try to keep it with small id, or just go on with .push() and get the id with .length()?
In short: If a array have nothing but null for the 1000 first id, and start having data only at array[1000]`, am I wasting memory?
Can I just do:
i = i + 1;
array[i] = null;
Or would it lead to large consumption of memory?
Yes, considering that array is a global variable and won't get garbage-collected itself, filling it constantly with values (even if only null ones) will eventually let you run out of memory.
Your get_id approach that recycles unused ids does work, but is horribly inperformant - it requires linear time to find a new id. So it'll work for few users with few concurrent missions, but it won't scale.
You'll rather want to use an object and delete keys from it, then you don't get into problems when just counting up:
var count = 0;
var missions = {};
function somethingThatNeedsTheStore() {
var id = count++;
missions[id] = …;
// later
delete missions[id];
}
// repeatedly call somethingThatNeedsTheStore()
Or actually, on recent node versions, you should consider using a Map instead:
var count = 0;
var missions = new Map;
function somethingThatNeedsTheStore() {
var id = count++;
missions.set(id, …);
// later
missions.delete(id);
}
// repeatedly call somethingThatNeedsTheStore()
NodeJS has a garbage collector to destroy unreachable object/array/variable.
So when you do array[i] = {large object};, the large object will be in the memory and it will stay here. When you do array[i] = null;, the garbage collector will erase the large object (only if there's no other reference to this object of course).
So yes, it is always good to remove references to useless objects to let the garbage collector clean it.
The impact on the memory of an array of 1000 null (or undefined) will not be very big.
If you want to preserve your memory, you should use an object instead of an array. You can use it with this syntax :
var obj = {};
obj[id] = {large object};
// Free the id
delete obj[id];

Add Relational Query Data to main JSON result

in my Angular app I'm trying to display a set of results that come from three Classes. Data is stored on Parse.com.
I can do just fine with the Pointer relation type for one-to-one (associated scores are being returned).
Problem starts when I try to include data from the Location class into my final returned JSON.
Hopefully this is just a basic notation thing I'm missing.
Thanks a lot!
Doctors
String: firstname
Pointer: score
Relation: location
Scores
Number: scoreOne
Number: scoreTwo
Locations
String: name
String: address
.controller('BrowseCtrl', function($scope) {
// Get "Doctors" and associated "Scores"
// via Pointer in Doctors Class named "score"
var query = new Parse.Query("Doctors");
query.include("score");
query.find()
.then(function(result){
var doctorsArray = new Array();
for(i in result){
var obj = result[i];
var doctorIds = obj.id;
var docFirstname = obj.get("firstname");
var mainScore = obj.get("score").get("mainScore");
doctorsArray.push({
Scores:{
DocMainScore: mainScore
},
firstname: docFirstname,
});
}
// Get Locations.
// -can be more than one per Doctor
// Class Doctors has a Relation column "location" pointing to Locations
var locationsArray = new Array();
var locationRelation = obj.relation('location');
var locationQuery = locationRelation.query();
locationQuery.find({
success: function(locations) {
for(j in locations){
var locObj = locations[j];
var locName = locObj.get("name");
console.log(locName);
}
}
})
// send data to the view
$scope.myData = doctorsArray;
console.log(doctorsArray);
});
})
What I am trying to do is get the data from Locations into the doctorsArray.
First of all, you are using obj outside the for where it's assigned. This way it'll only get location for the last doc, I'm pretty sure it's not what you wanted.
What you want must be something like this:
// inside your Doctors query result
Parse.Promise.when(
// get locations for all doctors
result.map(function(doc) {
return doc.relation('location').query().find();
})
).then(function() {
var docsLocations = arguments;
// then map each doctor
$scope.myData = result.map(function(doc, i) {
// and return an object with all properties you want
return {
Scores: {
DocMainScore: doc.get('score').get('mainScore')
},
firstname: doc.get('firstname'),
locations: docsLocations[i]
};
});
});

Categories

Resources