Javascript: forEach() loop to populate an array - closure issue - javascript

Let's say we have an array of objects like:
var fruits = [ {name:"banana", weight:150},{name:"apple", weight:95},{name:"orange", weight:160},{name:"kiwi", weight:80} ];
I want to populate a "heavy_fruits" array with items from the "fruits" array above which weight is > 100. Here is my code:
var heavy_fruits = [];
myfruit = {};
fruits.forEach(function(item,index) {
if ( item.weight > 100 ) {
myfruit ["name"] = item.name;
myfruit ["weight"] = item.weight;
}
heavy_fruits.push(myfruit);
});
However it shows:
name:"orange", weight:160
name:"orange", weight:160
name:"orange", weight:160
name:"orange", weight:160
I know this is an issue with mixing closures and loops... but I read an article (http://zsoltfabok.com/blog/2012/08/javascript-foreach/) explaining that I would avoid this kind of issue using a forEach loop instead of the classic for loop.
I know I can use array methods like filter(), etc. but I'm asking that on purpose since I'm actually having troubles with a much bigger function that I cannot expose here... So I tried to summarize and simplify my issue description with "fruits".

var heavy_fruits = [];
myfruit = {}; // here's your object
fruits.forEach(function(item,index) {
if ( item.weight > 100 ) {
myfruit ["name"] = item.name;
myfruit ["weight"] = item.weight; // you modify it's properties
}
heavy_fruits.push(myfruit); // you push it to the array
});
You end up with an array [myfruit, myfruit, myfruit, myfruit].
Now if you modify myfruit anywhere in the code, the change will be visible in every single occurence of myfruit. Why?
Because you are modifying the referenece to the object.
In this example, your array stores just copies of your object. And if you change one of it, every single one will change, because they are all references.
To fix this with each iteration you should be creating a new object and then doing some stuff on it.
BTW, as a matter of fact, your if could just be like this:
if ( item.weight > 100 ) {
heavy_fruits.push(item); // if `item` only has `name` and `weight` properties
}

fruits.forEach(function (item, index) {
if (item.weight > 100) {
myfruit = {};
myfruit["name"] = item.name;
myfruit["weight"] = item.weight;
heavy_fruits.push(myfruit);
}
});

The shorter would use filter
var heavy_fruits = fruits.filter(x => x.weight > 100);
But if you realy want to use forEach do this way
var heavy_fruits = [];
fruits.forEach(x => {if(x.weight > 100) heavy_fruits.push(x)} );

Related

Adding a property to an array of objects from another array

I have an array like this with names and address:
BTDevices = [
{name:"n1", address:"add1"},
{name:"n2", address:"add2"},
{name:"n3", address:"add3"}]
And another array with alias and address:
EqAlias = [
{btAlias:"a1", address:"add0"},
{btAlias:"a2", address:"add2"},
{btAlias:"a3", address:"add9"}]
I want to add btAlias property to all objects in BTDevices and set the value only if the address are the same, for example in this case I want the following result:
BTDevices:
name:"n1", address:"add1", btAlias:""
name:"n2", address:"add2", btAlias:"a2"
name:"n3", address:"add3", btAlias:""
My first solution was adding btAlias property using forEach and then using two for loops:
// Add Alias
this.BTDevices.forEach(function(obj) { obj.btAlias = "" });
// Set Alias
for (let m = 0; m < this.EqAlias.length; m ++)
{
for (let n = 0; n < this.BTDevices.length; n++)
{
if (this.BTDevices[n].address == this.EqAlias[m].address)
this.BTDevices[n].btAlias = this.EqAlias[m].btAlias;
}
}
Is there a better way to do the same? I guess using forEach
Using forEach instead of for will just replace the two for loop with forEach. We could argue on which is the best between for and forEach but i don't think there's a good answer. In modern javascript you can also use the for of loop.
Your algorithm is the simpliest and it will work.
But, if you want to address some performances issues, you should want to know that your algorithm is also the slowest (O(n²) complexity)
Another way to do that is to store items of BTDevices in a map to find them faster. Example:
let map = new Map();
BTDevices.forEach(e => map.set(e.address, e));
EqAlias.forEach(e => {
let device = map.get(e.address);
if (device) {
device.btAlias = e.btAlias;
}
});
The only advantage of this code is that looking for an item in a Map is faster (between O(1) and O(n), it depends of Map implementation). But you won't see any differences unless you try to manipulate some very big arrays.
You can use map and find
Use map to loop the array and create a new array.
Use find to check if a string is in an array.
let BTDevices = [{name:"n1", address:"add1"},{name:"n2", address:"add2"},{name:"n3", address:"add3"}];
let EqAlias = [{btAlias:"a1", address:"add0"},{btAlias:"a2", address:"add2"},{btAlias:"a3", address:"add9"}];
let result = BTDevices.map( v => {
v.btAlias = ( EqAlias.find( e => e.address == v.address ) || { btAlias:"" } ).btAlias;
return v;
});
console.log( result );
Please check doc: .map, .find
You could also do something like this.
var BTDevices = [{name:"n1", address:"add1"},{name:"n2", address:"add2"},{name:"n3", address:"add3"}];
var EqAlias = [{btAlias:"a1", address:"add0"},{btAlias:"a2", address:"add2"},{btAlias:"a3", address:"add9"}];
var EqAliasAdd = EqAlias.map((e)=>e.address);
var BTDevicesAdd = BTDevices.map((e)=>e.address);
BTDevicesAdd.map(function(i,k) {
BTDevices[k].btAlias = "";
if(EqAliasAdd.indexOf(i) >= 0)
BTDevices[k].btAlias = EqAlias[k].btAlias;
});
console.log(BTDevices);

remove items from array with the same id one by one

the problem is that I have multiple objects with the same id. As you can see this works when it comes to removing all the items with the same id. How I can remove the objects one by one no matter if they are the same ID...thanks
individualObjects:[],
actions:{
increment:function(){
var obj = this.get('object');
this.get('individualObjects').pushObject(obj);
},
decrement:function(){
var obj = this.get('object');
var filter = this.get('individualObjects').findBy('obj_id', obj.get('obj_id'));
this.get('individualObjects').removeObject(filter);
}
}
Well to filter array you would need to use Array.filter to find out the items that do not belong in the "individualObjects" and later simply remove them by using "removeObjects"
decrement:function(){
var objects = this.get('individualObjects')
var notWanted = objects.filterBy('obj_id', this.get('object.obj_id'));
this.get('individualObjects').removeObjects(notWanted);
}
and solution 2
decrement:function(){
var removeObj = this.get('object');
var objects = this.get('individualObjects')
// As the condition is true given object is returned
var notWanted = objects.filter(obj => { return obj.get('obj_id') === removeObj.get('obj_id')  });
this.get('individualObjects').removeObjects(notWanted);
}
Ok so you want to remove items one by one. Weird but can be accomplished
first get the length for
var notWantedCount = objects.filterBy('obj_id', this.get('object.obj_id')).length;
Now
for(var i=0; i <= notWantedCount; i++) {
var toRemove = individualObjects.findBy('obj_id', obj.get('obj_id'));
individualObjects.removeObject(toRemove);
// Make some custom actions one by one.
}
I don't know ember, but you'll want to do a foreach on the array, and then test for id on each one. It should be something like this:
decrement:function(){
var obj = this.get('object');
self = this;
this.get('individualObjects').each(function(individualObject) {
if (individualObject.get('obj_id') == obj.get('obj_id'))
... you want to do something here? ...
self.get('individualObjects').removeObject(individualObject);
}
}
That way you can remove each object individually. Running any necessary code before or after it's removed. If you want to sort it first, you can do that before running the each function.

FInd object in array by value and update entire object

I have a list of objects and sometimes I receive an update from the API for one of those objects and what I need to do is to find the object with the id of the one to update and update the entire object...
I was trying to avoid a for loop because the list could be very very long.
So what I was trying to use is $.grep but it doesn't seem to work as expected.
Here is what I tried so far:
// item is the response data from the API
var item = res.item;
var index = $.grep(arrayOfItems, function (e, i) {
if (e.id === item.id) {
return i;
}
});
arrayOfItems[index] = item;
the item is not updated unfortunately...
If it's speed you're after, especially with a long list, you may consider indexing your list by id when you first retrieve it, making updates later quicker than having to loop the entire array to find an index.
To demonstrate, assume you have retrieved an array of objects
var data = [
{id:1,data:'hello'},
{id:2,data:'world'},
{id:3,data:'foo'},
{id:4,data:'bar'}];
now create an object which represents your data where the property is the Id (object properties cannot start with a number, so if id is numeric, prefix it) and the value is the index back into the original array. So, the above data would be transformed to
var dataIndex = {
id1:0,
id2:1,
id3:2,
id4:3
};
This can be done trivially with a function
function indexDataById(data)
{
var index = {};
$.each(data, function(e,i){
index['id' + e.id] = i;
});
return index;
}
var dataIndex = indexDataById(data);
Now, when it comes to your update, you can find the index instantly using the id
var updateId = 2;
var elementIdx = dataIndex ['id' + updateId];
data[elementIdx] = myNewData;
The one complication is that you need to go back and update the index if the id of the new data has changed:
var updateId = 2;
var elementIdx = dataIndex [`id` + updateId];
data[elementIdx] = myNewData;
delete dataIndex[elementIdx]
dataIndex['id' + myNewData.id] = elementIdx;
This should be easy enough to handle atomically with your update.
$.map and $.grep return both an array so you will never get the index.
Inside $.map or $.grep function you need to return true or false based
on your filter logic. They re not useful in your case.
if your structure is not ordered you can only loop trough it and stop the loop when you find your element... like that:
var item = res.item;
var index = "";
$.each(arrayOfItems, function(i,v){
if(item.id == v.id){
index = i;
return true;
}
});
arrayOfItems[index] = item;
if you wanna order your structure before loop use this:
arrayOfItems.sort(function(a, b) {
return a.id > b.id;
});
i ve made a fiddle with an example https://jsfiddle.net/L08rk0u3/
try this way using $.grep
var arrList = [
{name :11,id :11},{name :12,id :12},{name :111,id :111},
{name :13,id :13},{name :15,id :15},{name :11,id :11},
{name :41,id :41},{name :31,id :31},{name :81,id :81},
{name :91,id :91},{name :13,id :13},{name :17,id :17},
{name :1111,id :1111}
]
console.log(arrList);
var respItem ={name :1111000,id:1111};
var intSearchedIndex;
$.grep(arrList,function(oneItem,index){
if(respItem.id==oneItem.id){
return intSearchedIndex = index;
}
})
arrList[intSearchedIndex] =respItem;
console.log(intSearchedIndex,arrList);
Try with map method like this.
Code snippets:
// item is the response data from the API
var item = res.item;
var index = $.map(arrayOfItems, function (e, i) {
if (e.id === item.id) {
return i;
}
});
if(index.length)
arrayOfItems[index[0]] = item;
Update:
arrayOfItems[index] = item;
This will work if index array has an single element. See fiddle
But,
arrayOfItems[index[0]] = item;
This is the appropriate way since it is an array.

Getting index from 2D array quickly without iteration jquery

I have this 2D array as follows:
var data = [[1349245800000, 11407.273], [1349247600000, 12651.324],
[1349249400000, 11995.017], [1349251200000, 11567.533],
[1349253000000, 11126.858], [1349254800000, 9856.455],
[1349256600000, 8901.779], [1349258400000, 8270.123],
[1349260200000, 8081.841], [1349262000000, 7976.148],
[1349263800000, 7279.652], [1349265600000, 6983.956],
[1349267400000, 7823.309], [1349269200000, 6256.398],
[1349271000000, 5487.86], [1349272800000, 5094.47],
[1349274600000, 4872.403], [1349276400000, 4168.556],
[1349278200000, 4501.939], [1349280000000, 4150.769],
[1349281800000, 4061.599], [1349283600000, 3773.741],
[1349285400000, 3876.534], [1349287200000, 3221.753],
[1349289000000, 3330.14], [1349290800000, 3147.335],
[1349292600000, 2767.582], [1349294400000, 2638.549],
[1349296200000, 2477.312], [1349298000000, 2270.975],
[1349299800000, 2207.568], [1349301600000, 1972.667],
[1349303400000, 1788.853], [1349305200000, 1723.891],
[1349307000000, 1629.002], [1349308800000, 1660.084],
[1349310600000, 1710.227], [1349312400000, 1708.039],
[1349314200000, 1683.354], [1349316000000, 2236.317],
[1349317800000, 2228.405], [1349319600000, 2756.069],
[1349321400000, 4289.437], [1349323200000, 4548.436],
[1349325000000, 5225.245], [1349326800000, 6261.156],
[1349328600000, 8103.636], [1349330400000, 10713.788]]
How do I get the index of value 1349247600000 in the array? I have tried $.inArray(1349247600000, data) but as expected this fails. Is there any other way or do I have to iterate over each? I am reluctant to add another loop to my process
This is a typical performance versus memory issue. The only way (that I know of) to avoid looping through the array, would be to maintain a second data structure mapping the timestamps to the index of the array (or whatever data might needed).
So you would have
var data = [
[1349245800000, 11407.273],
[1349247600000, 12651.324],
// ...
[1349330400000, 10713.788]
];
// the timestamps pointing at their respective indices
var map = {
'1349245800000': 0, // 0
'1349247600000': 1, // 1
// ...
'1349330400000': 42, // n - 1 (the length of the data array minus one)
}
This way, you use more memory, but have a constant lookup time when needing the index of the item in the array that a given timestamp belongs to.
To get the index of a given timestamp do:
map['1349247600000']; // resulting in 1 (e.g.)
If the data structure is dynamically changed, you would of course need to maintain the map data structure, but depending on the context in which you need the lookup, the constant time lookup can potentially be a real time saver compared to a linear time lookup.
I think you need a different data structure.
Try using a standard javascript object ({ key: value } - sometimes called a map or dictionary) to express your data. Looking up keys in an object is highly optimized (using something called hash tables).
If the index in your array has any meaning, store it as a property (typically named _id).
Ideally you should be using an object for this:
var data = {
'1349247600000': 12651.324
}
which you can access like:
data['1349247600000'];
However, this might be a nice solution (IE9 and above) in the meantime:
var search = 1349247600000;
function findIndex(data, search) {
var filter = data.filter(function (el, i) {
el.unshift(i);
return el[1] === search;
});
return filter[0][0];
}
console.log(findIndex(data, search));
fiddle : http://jsfiddle.net/CLa56/
var searchElement = 1349251200000;
var strdata = data.toString();
var newdata = eval("[" + strdata + "]");
var indexsearch = newdata.indexOf(searchElement);
var index = indexsearch/2; // 2 because array.length = 2
var params = {id: 1349251200000, index: -1};
data.some(function (e, i) {
if (e[0] === this.id) {
this.index = i;
return true;
}
}, params);
console.log(params.index);
jsfiddle
MDN|some Array Method
Note that this solution stops iterating after found, not necessarily over the entire array, so could be much faster for large arrays.
What about a custom cross browser solution ?
function findIndexBy(a, fn) {
var i = 0, l = a.length;
for (; i < l; i++) {
if (fn(a[i], i)) {
return i;
}
}
return -1;
}
Usage :
var list = [[1],[2],[3]], idx;
// idx === 1
idx = findIndexBy(list, function (item, i) {
return item[0] === 2;
});
// idx === -1
idx = findIndexBy(list, function (item, i) {
return item[0] === 4;
});

access javascript array element by JSON object key

I have an array that looks like this
var Zips = [{Zip: 92880, Count:1}, {Zip:91710, Count:3}, {Zip:92672, Count:0}]
I would like to be able to access the Count property of a particular object via the Zip property so that I can increment the count when I get another zip that matches. I was hoping something like this but it's not quite right (This would be in a loop)
Zips[rows[i].Zipcode].Count
I know that's not right and am hoping that there is a solution without looping through the result set every time?
Thanks
I know that's not right and am hoping that there is a solution without
looping through the result set every time?
No, you're gonna have to loop and find the appropriate value which meets your criteria. Alternatively you could use the filter method:
var filteredZips = Zips.filter(function(element) {
return element.Zip == 92880;
});
if (filteredZips.length > 0) {
// we have found a corresponding element
var count = filteredZips[0].count;
}
If you had designed your object in a different manner:
var zips = {"92880": 1, "91710": 3, "92672": 0 };
then you could have directly accessed the Count:
var count = zips["92880"];
In the current form, you can not access an element by its ZIP-code without a loop.
You could transform your array to an object of this form:
var Zips = { 92880: 1, 91710: 3 }; // etc.
Then you can access it by
Zips[rows[i].Zipcode]
To transform from array to object you could use this
var ZipsObj = {};
for( var i=Zips.length; i--; ) {
ZipsObj[ Zips[i].Zip ] = Zips[i].Count;
}
Couple of mistakes in your code.
Your array is collection of objects
You can access objects with their property name and not property value i.e Zips[0]['Zip'] is correct, or by object notation Zips[0].Zip.
If you want to find the value you have to loop
If you want to keep the format of the array Zips and its elements
var Zips = [{Zip: 92880, Count:1}, {Zip:91710, Count:3}, {Zip:92672, Count:0}];
var MappedZips = {}; // first of all build hash by Zip
for (var i = 0; i < Zips.length; i++) {
MappedZips[Zips[i].Zip] = Zips[i];
}
MappedZips is {"92880": {Zip: 92880, Count:1}, "91710": {Zip:91710, Count:3}, "92672": {Zip:92672, Count:0}}
// then you can get Count by O(1)
alert(MappedZips[92880].Count);
// or can change data by O(1)
MappedZips[92880].Count++;
alert(MappedZips[92880].Count);
jsFiddle example
function getZip(zips, zipNumber) {
var answer = null;
zips.forEach(function(zip){
if (zip.Zip === zipNumber) answer = zip;
});
return answer;
}
This function returns the zip object with the Zip property equal to zipNumber, or null if none exists.
did you try this?
Zips[i].Zip.Count

Categories

Resources