JavaScript array cloning - javascript

I have this way to make an array
var playerList = [];
exports.player = function(socket, name)
{
this.id = socket.id;
this.name = name;
this.x = 20;
this.y = 40;
return this
}
exports.addPlayer = function(data)
{
playerList.push(data)
}
And I'm adding items to playerList array like this
var client = new player(socket, data);
exports.addPlayer(client);
But I also got a function that makes the following
exports.getSafeList = function(id)
{
var player_array = playerList.slice();
for(var i = 0; i < player_array.length; i++)
{
if(player_array[i].id != id)
{
player_array[i].id = 'unknown';
}
}
return player_array;
}
And now I do the following
exports.getPlayerList = function()
{
return playerList;
}
console.log(players.getPlayerList())
console.log(players.getSafeList(id))
So far the code is working fine but when I log the 2 functions it seems that getPlayerList variable merges with player_list one, this is the output
When theres just ONE player on the array
[ { id: 'tjvh8XdMtX-o6QYDAAAB', name: 'Raggaer', x: 20, y: 40 } ]
[ { id: 'tjvh8XdMtX-o6QYDAAAB', name: 'Raggaer', x: 20, y: 40 } ]
But when there are more:
[ { id: 'unknown', name: 'Raggaer', x: 20, y: 40 },
{ id: '2-K5At07wLV4BDiAAAAC', name: 'Alvaro', x: 20, y: 40 } ]
[ { id: 'unknown', name: 'Alvaro', x: 20, y: 40 },
{ id: '2-K5At07wLV4BDiAAAAC', name: 'Alvaro', x: 20, y: 40 } ]
As you can see on both arrays id appears as "unknown" when it shouldn't, since I'm not modyfing the playerList array...

The problem is that while Array.prototype.slice() will create a separate copy of the original array, its items will still be references to the same object instances. So modifying an item in one array ends up modifying the corresponding item in the cloned array.
If your items are simple data objects (no functions), this workaround might do the trick for you:
// instead of "var player_array = playerList.slice();"
var player_array = JSON.parse(JSON.stringify(playerList));

Related

How to invert the structure of nested array of objects in Javascript?

I currently have an array that has the following structure:
data = [
{
time: 100,
info: [{
name: "thing1",
count: 3
}, {
name: "thing2",
count: 2
}, {
}]
},
{
time: 1000,
info: [{
name: "thing1",
count: 7
}, {
name: "thing2",
count: 0
}, {
}]
}
];
But I would like to restructure the array to get something like this:
data = [
{
name: "thing1",
info: [{
time: 100,
count: 3
}, {
time: 1000,
count: 7
}, {
}]
},
{
name: "thing2",
info: [{
time: 100,
count: 2
}, {
time: 1000,
count: 0
}, {
}]
}
];
So basically the key would have to be switched from time to name, but the question is how. From other posts I have gathered that using the map function might work, but since other posts had examples to and from different structures I am still not sure how to use this.
There are a number of ways to achieve this however, the key idea will be to perform a nested looping of both data items and their (nested) info items. Doing that allows your algorithm to "visit" and "map" each piece of input data, to a corresponding value in the resulting array.
One way to express that would be to use nested calls to Array#reduce() to first obtaining a mapping of:
name -> {time,count}
That resulting mapping would then be passed to a call to Object.values() to transform the values of that mapping to the required array.
The inner workings of this mapping process are summarized in the documentation below:
const data=[{time:100,info:[{name:"thing1",count:3},{name:"thing2",count:2},{}]},{time:1e3,info:[{name:"thing1",count:7},{name:"thing2",count:0},{}]}];
const result =
/* Obtain array of values from outerMap reduce result */
Object.values(
/* Iterate array of data items by reduce to obtain mapping of
info.name to { time, count} value type */
data.reduce((outerMap, item) =>
/* Iterate inner info array of current item to compound
mapping of info.name to { time, count} value types */
item.info.reduce((innerMap, infoItem) => {
if(!infoItem.name) {
return innerMap
}
/* Fetch or insert new { name, info } value for result
array */
const nameInfo = innerMap[ infoItem.name ] || {
name : infoItem.name, info : []
};
/* Add { time, count } value to info array of current
{ name, info } item */
nameInfo.info.push({ count : infoItem.count, time : item.time })
/* Compound updated nameInfo into outer mapping */
return { ...innerMap, [ infoItem.name] : nameInfo }
}, outerMap),
{})
)
console.log(result)
Hope that helps!
The approach I would take would be to use an intermediate mapping object and then create the new array from that.
const data = [{time: 100, info: [{name: "thing1", count: 3}, {name: "thing2", count: 2}, {}]}, {time: 1e3, info: [{name: "thing1", count: 7}, {name: "thing2", count: 0}, {}]} ];
const infoByName = {};
// first loop through and add entries based on the name
// in the info list of each data entry. If any info entry
// is empty ignore it
data.forEach(entry => {
if (entry.info) {
entry.info.forEach(info => {
if (info.name !== undefined) {
if (!infoByName[info.name]) {
infoByName[info.name] = [];
}
infoByName[info.name].push({
time: entry.time,
count: info.count
});
}
});
}
});
// Now build the resulting list, where name is entry
// identifier
const keys = Object.keys(infoByName);
const newData = keys.map(key => {
return {
name: key,
info: infoByName[key]
};
})
// newData is the resulting list
console.log(newData);
Well, the other guy posted a much more elegant solution, but I ground this one out, so I figured may as well post it. :)
var data = [
{
time: 100,
info: [{
name: "thing1",
count: 3
}, {
name: "thing2",
count: 2
}, {
}]
},
{
time: 1000,
info: [{
name: "thing1",
count: 7
}, {
name: "thing2",
count: 0
}, {
}]
}
];
var newArr = [];
const objInArray = (o, a) => {
for (var i=0; i < a.length; i += 1) {
if (a[i].name === o)
return true;
}
return false;
}
const getIndex = (o, a) => {
for (var i=0; i < a.length; i += 1) {
if (a[i].name === o) {
return i;
}
}
return false;
}
const getInfoObj = (t, c) => {
let tmpObj = {};
tmpObj.count = c;
tmpObj.time = t;
return tmpObj;
}
for (var i=0; i < data.length; i += 1) {
let t = data[i].time;
for (var p in data[i].info) {
if ("name" in data[i].info[p]) {
if (objInArray(data[i].info[p].name, newArr)) {
let idx = getIndex(data[i].info[p].name, newArr);
let newInfoObj = getInfoObj(t, data[i].info[p].count);
newArr[idx].info.push(newInfoObj);
} else {
let newObj = {};
newObj.name = data[i].info[p].name;
let newInfo = [];
let newInfoObj = getInfoObj(t, data[i].info[p].count);
newInfo.push(newInfoObj);
newObj.info = newInfo;
newArr.push(newObj);
}}
}
}
console.log(newArr);
try to use Object.keys() to get the key

How to convert an unorganized array into an grouped array by id

I'm trying to create an array that contains objects with an id and amount, grouped by id. The ids needs to be unique. So if there is 2 objects with same id, the amount will be added.
I can do it with nested for-loops, but I find this solution inelegant and huge. Is there a more efficient or cleaner way of doing it?
var bigArray = [];
// big Array has is the source, it has all the objects
// let's give it 4 sample objects
var object1 = {
id: 1,
amount: 50
}
var object2 = {
id: 2,
amount: 50
}
var object3 = {
id: 1,
amount: 150
}
var object4 = {
id: 2,
amount:100
}
bigArray.push(object1,object2,object3,object4);
// organizedArray is the array that has unique ids with added sum. this is what I'm trying to get
var organizedArray = [];
organizedArray.push(object1);
for(var i = 1; i < bigArray.length; i++ ) {
// a boolean to keep track whether the object was added
var added = false;
for (var j = 0; j < organizedArray.length; j++){
if (organizedArray[j].id === bigArray[i].id) {
organizedArray[j].amount += bigArray[i].amount;
added = true;
}
}
if (!added){
// it has object with new id, push it to the array
organizedArray.push(bigArray[i]);
}
}
console.log(organizedArray);
You can definitly make it cleaner and shorter by using reduce, not sure about efficiency though, i would say a traditional for loop is more efficient :
var bigArray = [];
var object1 = {id: 1, amount: 50}
var object2 = {id: 2, amount: 50}
var object3 = {id: 1, amount: 150}
var object4 = {id: 2, amount: 100}
bigArray.push(object1, object2, object3, object4);
var organizedArray = bigArray.reduce((acc, curr) => {
// check if the object is in the accumulator
const ndx = acc.findIndex(e => e.id === curr.id);
if(ndx > -1) // add the amount if it exists
acc[ndx].amount += curr.amount;
else // push the object to the array if doesn't
acc.push(curr);
return acc;
}, []);
console.log(organizedArray)
Rather than an organized array, how about a single object whose keys are the ids and values are the sums.
var bigArray = [
{ id: 1, amount: 50 },
{ id: 2, amount: 50 },
{ id: 1, amount: 150 },
{ id: 2, amount: 100 }
];
let total = {}
bigArray.forEach(obj => {
total[obj.id] = (total[obj.id] || 0) + obj.amount;
});
console.log(total);
If you really need to convert this to an array of objects then you can map the keys to objects of your choosing like this:
var bigArray = [
{ id: 1, amount: 50 },
{ id: 2, amount: 50 },
{ id: 1, amount: 150 },
{ id: 2, amount: 100 }
];
let total = {}
bigArray.forEach(obj => {
total[obj.id] = (total[obj.id] || 0) + obj.amount;
});
console.log(total);
// If you need the organized array:
let organizedArray = Object.keys(total).map(key => ({ id: key, amount: total[key] }));
console.log(organizedArray);
function getUniqueSums(array) {
const uniqueElements = [];
const arrayLength = array.length;
for(let index = 0; index < arrayLength; index++) {
const element = array[index];
const id = element.id;
const uniqueElement = findElementByPropertyValue(uniqueElements, 'id', id);
if (uniqueElement !== null) {
uniqueElement.amount += element.amount;
continue;
}
uniqueElements.push(element);
}
return uniqueElements;
}
function findElementByPropertyValue(array, property, expectedValue) {
const arrayLength = array.length;
for(let index = 0; index < arrayLength; index++) {
const element = array[index];
const value = element[property];
if (value !== expectedValue) {
continue;
}
return element;
}
return null;
}
This is an untested code. You will be able to understand the logic. Logic is almost same yours. But, perhaps a more readable code.

Return a random Object containing a specific parameter from an object array

I'm currently storing data as objects inside a array in the following way:
let data = [];
module.exports.init = function() {
database.pool.query("SELECT * FROM data", (error, rows) => {
if (error) {
logUtil.log.error(`Loading failed: ${ error.message }`);
}
else {
rows.forEach((row) => data.push({dimension: row.dimension, x: row.x, y: row.y, z: row.z}));
logUtil.log.info(data);
}
});
};
data will hold the following now: [{ dimension: 2, x: -973.097, y: -133.411, z: 38.2531 }, { dimension: 3, x: -116.746, y: -48.414, z: 17.226 }, { dimension: 2, x: -946.746, y: -128.411, z: 37.786 }, { dimension: 2, x: -814.093, y: -106.724, z: 37.589 }]
Now I'm trying to receive a random object from this array storing a specific dimension parameter.
For example I want to return a random object storing the dimension: 2
I've tried to filter the array using something like:
let result = jsObjects.filter(data => {
return data.dimension === 2
})
then return a random object from the result.
Question: How could I receive this random object in the best way?
You can do it in two steps.
Get all record which satisfy criteria like dimension === 2
let resultArr = jsObjects.filter(data => {
return data.dimension === 2
})
Get random object from result.
var randomElement = resultArr[Math.floor(Math.random() * resultArr.length)];
var arr = [{ dimension: 2, x: -973.097, y: -133.411, z: 38.2531 }, { dimension: 3, x: -116.746, y: -48.414, z: 17.226 }, { dimension: 2, x: -946.746, y: -128.411, z: 37.786 }, { dimension: 2, x: -814.093, y: -106.724, z: 37.589 }]
//Filter out with specific criteria
let resultArr = arr.filter(data => {
return data.dimension === 2
})
//Get random element
var randomElement = resultArr[Math.floor(Math.random() * resultArr.length)];
console.log(randomElement)
You could use Math.random() and in the range of 0 to length of array.
let result = jsObjects.filter(data => {
return data.dimension === 2
})
let randomObj = result[Math.floor(Math.random() * result.length)]

Fill in missing properties in an array of objects

What is the best way to fill in missing properties in an array of objects, such as this example:
[
{
name: 'Tom',
number: '01234 567 890',
website: 'http://www.tom.com'
},
{
name: 'Richard',
number '07777 666 555'
},
{
name: 'Harry',
website: 'http://www.harry.com'
}
]
I need to add the missing properties with a null value, so that when I pass this array on to be rendered in something such as a HTML table or CSV file, everything lines up correctly. I was thinking of passing over the array twice, once to get all the possible properties, and a second time to add those missing properties with a null value to each object where it doesn't exist. Is there a better way to do this?
EDIT: I won't know what the keys are until I have the data, it's coming from an API and the keys are not always requested explicitly.
My final solution
Thanks all, it seems the two pass approach is indeed the best approach. After I started to write this using the examples provided, I realised that the order of the properties wasn't being maintained. This is how I achieved filling in the missing props, and maintaining the correct order. Any suggestions for potential improvements are welcome.
var fillMissingProps = function(arr) {
// build a list of keys in the correct order
var keys = [];
arr.forEach(function(obj) {
var lastIndex = -1;
Object.keys(obj).forEach(function(key, i) {
if (keys.includes(key)) {
// record the position of the existing key
lastIndex = keys.lastIndexOf(key);
if (lastIndex < i) {
// this key is in the wrong position so move it
keys.splice(i, 0, keys.splice(lastIndex, 1)[0]);
lastIndex = i;
}
} else {
// add the new key in the correct position
// after the previous existing key
lastIndex++;
keys.splice(lastIndex, 0, key);
}
});
});
// build a template object with all props set to null
// and in the correct position
var defaults = {};
keys.forEach(function(key) {
defaults[key] = null;
});
// and update the array by overwriting each element with a
// new object that's built from the template and the original object
arr.forEach(function(obj, i, arr) {
arr[i] = Object.assign({}, defaults, obj);
});
return arr;
};
/** TEST **/
var currentArray = [
{
website: 'http://www.unknown.com'
},
{
name: 'Tom',
number: '01234 567 890',
website: 'http://www.tom.com'
},
{
title: 'Mr',
name: 'Richard',
gender: 'Male',
number: '04321 666 555'
},
{
id: '003ABCDEFGHIJKL',
name: 'Harry',
website: 'http://www.harry.com',
mobile: '07890 123 456',
city: 'Brentwood',
county: 'Essex'
}
];
var newArray = fillMissingProps(currentArray);
for (var i = 0; i < newArray.length; i++) {
for (var prop in newArray[i]) {
console.log(prop + ": " + newArray[i][prop]);
}
console.log('---------');
}
Given that you don't know apriori which keys are supposed to exist, you have no choice but to iterate over the array twice:
// build a map of unique keys (with null values)
var keys = {}
array.forEach(el => Object.keys(el).forEach(k => keys[k] = null));
// and update the array by overwriting each element with a
// new object that's built from the null map and the original object
array.forEach((el, ix, a) => a[ix] = Object.assign({}, keys, el));
Use Array.prototype.map():
const arr = [
{
name: 'Tom',
number: '01234 567 890',
website: 'http://www.tom.com',
},
{
name: 'Richard',
number: '07777 666 555',
},
{
name: 'Harry',
website: 'http://www.harry.com',
},
];
const newArr = arr.map(x => (
arr.map(x => Object.keys(x))
.reduce((a, b) =>
(b.forEach(z => a.includes(z) || a.push(z)), a)
)
.forEach(
y => (x[y] = x.hasOwnProperty(y) ? x[y] : null)
), x)
);
console.log(newArr);
Here is a more interesting answer, its a tad fun one but it will build up your objects on the fly as new properties appear:
var currentArray = [
{
name: 'Tom',
number: '01234 567 890',
website: 'http://www.tom.com'
},
{
name: 'Richard',
number: '07777 666 555'
},
{
name: 'Harry',
website: 'http://www.harry.com'
}
]
var newArray = []
function NewObject() {
}
for(var i = 0; i < currentArray.length; i++){
var nObj = new NewObject();
for(var prop in currentArray[i]){
if(!NewObject.hasOwnProperty(prop))
NewObject.prototype[prop] = null;
nObj[prop]=currentArray[i][prop];
}
newArray.push(nObj);
}
for(var i = 0; i < newArray.length; i++){
for(var prop in newArray[i]){
console.log(prop+ ": "+newArray[i][prop]);
}
console.log('---------');
}
It builds new objects from the ones you provide and adds new properties to the objects if they don't exist already.
This idea was more for curiosities sake tho so any comments would be interesting :)
You can get all keys and set all keys using for..of loop, .map() to iterate all Object.keys(), redefine original array
var arr = [{
name: 'Harry',
website: 'http://www.harry.com'
},{
name: 'Tom',
number: '01234 567 890',
website: 'http://www.tom.com'
}, {
name: 'Richard',
number: '07777 666 555'
}];
for (var obj of arr) {
for (var key of Object.keys(obj)) {
arr = arr.map(o => (o[key] = o[key] || null, o))
}
};
console.log(arr);
Something like this could work:
for (var i = 0; i < arrayLength; i++) {
yourArray[i].name = yourArray[i].name || null;
yourArray[i].number = yourArray[i].number || null;
yourArray[i].website= yourArray[i].website|| null;
}

Why is my JS Loop overwriting previous entries in my object?

So I have an object, that I'm using in nodejs. It looks as such:
for(var i = 0; i < x.length; i++) {
var sUser = x[i];
mUsers[sUser.userid] = CreateUser(sUser);
++mUsers.length;
}
So I'm pulling information from an external source, and it breaks down as an array full of instances of this:
[{ name: 'Michael Lovesllamas Lankford',
created: 1338420951.11,
laptop: 'pc',
laptop_version: null,
userid: '4fc6aed7eb35c14ad6000057',
acl: 0,
fans: 1,
points: 5,
avatarid: 34 }]
and so forth.
so that information is passed as x in the above function.
global.mUsers = {length:0}
global.UserBase = {
userid: -1,
name: "noidea",
isSuperUser: false,
isDJ: false,
laptop: "pc" };
process.on("registered", OnRegistered);
global.OnRegistered = function(a) {
//misc code here
RegisterUsers(a.users);
//misc code here
}
global.CreateUser = function(a) {
var b = UserBase;
b.userid = a.userid;
b.name = a.name;
b.laptop = a.laptop;
if (a.acl > 0) b.isSuperUser = true;
return b;
};
global.RegisterUsers = function(x) {
for(var i = 0; i < x.length; i++) {
var sUser = x[i];
mUsers[sUser.userid] = sUser;
++mUsers.length;
}
}
Now, I've logged it in the loop, and mUsers[sUser.userid] does indeed = sUser.
but when I console.log(mUsers) immediately after the loop, I get this:
{
userid1: { userid: userid3, name: name3, item: item3 },
userid2: { userid: userid3, name: name3, item: item3 },
userid3: { userid: userid3, name: name3, item: item3 }
}
And I don't know why it's overwriting. Any ideas?
The main problem is that you where continuously referencing the same object when you where calling CreateUser, as such it was simply updating and returning a reference which was being kept through out all the calls, this is why when you where printing it, it just printed the last update.
You need to create a copy of the object.
global.CreateUser = function(a) {
var b = Object.create(UserBase); // this will create a copy of it.
b.userid = a.userid;
b.name = a.name;
b.laptop = a.laptop;
if (a.acl > 0) b.isSuperUser = true;
return b;
};
now CreateUser is actually creating a copy, when you go through the properties the default ones may not appear right away, but theres still there, they've being simply moved to __proto__ you can still call them.
Try the below it is working for me
var obj = {
userid1: { userid: "userid1", name: "name3", item: "item3" },
userid2: { userid: "userid2", name: "name3", item: "item3" },
userid3: { userid: "userid3", name: "name3", item: "item3" }
};
var muser = {};
for (var key in obj) {
muser[key] = obj[key];
}

Categories