What is wrong with this use of "forEach"? - javascript

I expect the errorLogArrayList.length to be 3, but the result is 2.
Could anyone please tell me why this code doesn't work as I expect and how to fix it?
CODE:
var logArrayList = [
[1,2,3],
[1,2,"error"],
[1,2,"error"],
[1,2,"error"]
]
var errorLogArrayList = [];
console.log(logArrayList);
console.log("======================");
logArrayList.forEach(function(logArray, index, self) {
if(logArray[2] === "error") {
var errorArray = self.splice(index, 1)[0];
errorLogArrayList.push(errorArray);
}
});
// I expect this value to be 3, but the result is 2.
console.log("errorLogArrayList.length is " + errorLogArrayList.length)
console.log(errorLogArrayList);
console.log("======================");
// I expect this value to be 1, but the result is 2.
console.log("logArrayList.length is " + logArrayList.length);
console.log(logArrayList);
LOG:
[ [ 1, 2, 3 ],
[ 1, 2, 'error' ],
[ 1, 2, 'error' ],
[ 1, 2, 'error' ] ]
======================
// I expect this value to be 3, but the result is 2.
errorLogArrayList.length is 2
[ [ 1, 2, 'error' ], [ 1, 2, 'error' ] ]
======================
//I expect this value to be 1, but the result is 2.
logArrayList.length is 2
[ [ 1, 2, 3 ], [ 1, 2, 'error' ] ]

You can just do a filter operation:
var errorLogArrayList = logArrayList.filter(function(array) {
return array[2] === 'error';
});
logArrayList = logArrayList.filter(function(array) {
return array[2] !== 'error';
});
Unfortunately this requires some duplicate iterations. You can use _.partition from lodash if you want (looks a little cleaner):
var lists = _.partition(logArrayList, function(array) {
return array[2] === 'error';
});
var errorLogArrayList = lists[0];
logArrayList = lists[1];
The problem with your current approach is that you are trying to keep track of multiple states and modify two arrays at the same time, and this is leading to some data conflicts. filter is usually a little more declarative, and it doesn't mutate the original array, but instead returns a new one.
Edit
Wanted to add a method with reduce as suggested by #zerkms in the comments:
var lists = logArrayList.reduce(function(agg, curr) {
if(curr[2] === 'error') {
agg[0] = (agg[0] || []).concat([curr]);
} else {
agg[1] = (agg[1] || []).concat([curr]);
}
return agg;
}, []);
That does the same as the partition example.

Related

JavaScript | How can I remove an element of an array using it's value instead of it's index?

const currentMaterialsId = [1,2,3,4,5]
const materials = {
0: {
id: 1
},
1: {
id: 2
},
2: {
id: 3
},
3: {
id: 4
},
4: {
id: 5
}
}
I am trying to remove an element in the currenMaterialsId array but when I use the index of the materials object, things don't go as planned. If I use the id as the start number in splice, it still uses that number and searches for the matching index in the array instead of the value. Please help.
here's what I have at the moment.
let sortedMaterialIndex = currentMaterialsId.sort()
sortedMaterialIndex.splice(materialIndex, 1)
dispatch(removeElementCurrentMaterialsArray(selectedSheet,
sortedMaterialIndex))
ok I'm sorry it wasn't clear guys.
What I am trying to do is remove an element in currentMaterialsId that has the same value as the id in the object materials. However, when I use the id from materials as a starting number, for example
const materialId = dashboard.sheets[selectedSheet].materialProperties[materialIndex].id
currentMaterialsId.splice(materialId, 1)
it searches currentMaterialsId array for an index that matches the passed starting number(materialId), which is what I do not want.
so let's say I want to delete 2 from currentMaterialsId, could I use splice? and if I use splice, what should I pass as a starting number?
I hope this makes my question clearer.
Thanks for the responses!
What I am trying to do is remove an element in currentMaterialsId that
has the same value as the id in the object materials.
could I use splice?
You appear to be trying to do something like this:
so.js:
const materials = {
'0': { id: 1 },
'1': { id: 2 },
'2': { id: 3 },
'3': { id: 4 },
'4': { id: 5 }
};
console.log(materials);
// id from materials
let i = 1;
console.log(i);
let id = materials[i].id;
console.log(id);
function removeMaterialsId(id, materialsId) {
for (let i = 0; i < materialsId.length; i++) {
if (materialsId[i] === id) {
materialsId.splice(i--, 1);
}
}
}
let materialsId = [];
// remove materialsId elements with id from materials
console.log();
materialsId = [ 1, 2, 3, 4, 5 ];
console.log(id, materialsId);
removeMaterialsId(id, materialsId);
console.log(materialsId);
// remove materialsId elements with id from materials
console.log();
materialsId = [ 1, 2, 2, 3, 4, 2, 5 ];
console.log(id, materialsId);
removeMaterialsId(id, materialsId);
console.log(materialsId);
$ node so.js
{
'0': { id: 1 },
'1': { id: 2 },
'2': { id: 3 },
'3': { id: 4 },
'4': { id: 5 }
}
1
2
2 [ 1, 2, 3, 4, 5 ]
[ 1, 3, 4, 5 ]
2 [ 1, 2, 2, 3, 4, 2, 5 ]
[ 1, 3, 4, 5 ]
$
First off, perhaps you want to store your objects in an array, like this(?):
const materials = [
{
id: 1
},
{
id: 2
},
{
id: 3
},
{
id: 4
},
{
id: 5
}
];
Then you can remove from array using filter:
const materialToRemove = { id: 1 }
const materialsWithOneRemoved = materials
.filter(material => material.id !== materialToRemove.id);
Note that filter creates a new array, it does not change the existing array. You can however overwrite the existing array with a new one if you want to:
// materials like above, but with let instead of const
let materials = ...
const materialToRemove = { id: 1 }
materials = materials
.filter(material => material.id !== materialToRemove.id);
If you want to have your objects in an object like you have in your question, you need to first convert it to an array before you can filter. You can do that using e.g. Object.values.
Your question is far from clear, but indexOf may be a solution:
const sortedMaterialIndex = currentMaterialsId.sort();
const index = sortedMaterialIndex.indexOf(materialIndex);
if (index > -1) {
sortedMaterialIndex.splice(index, 1);
}
See How can I remove a specific item from an array?
I would recommend using the filter array function to achieve what you want.
let idToRemove = 1
let filteredMaterials = materials.filter((v) => v.id !== idToRemove);
console.log(filteredMaterials)

Determine if array element represents an object or a value

From a JSON object (containing stock data), I want to add certain elements to an array (in Google Sheets script editor):
var quote = JSON.parse(response.getContentText());
// Example of what quote object looks like:
{
"quoteSummary": {
"result": [
{
"Profile": {
"sector": "Technology",
"website": "www.test.com"
},
"Owners": [
{
"name": "Eric",
"age": "28"
},
{
"name": "Susan",
"age": "44"
}
],
"Profit": 100,
"Assets": 7000
}
]
}
}
Here is my current approach to read only some specific values:
var arr = [];
arr.push(quote.quoteSummary.result[0].Profile.sector); // Technology
arr.push(quote.quoteSummary.result[0].Owners[1].name); // Susan
arr.push(quote.quoteSummary.result[0].Profit); // 100
But since there are many specific properties to read, I'd like to use a loop:
var quote = JSON.parse(response.getContentText());
var arr = [];
var el = [
['Profile', 'sector'],
['Owners[1]', 'name'],
['Profit']
];
for (i = 0; i < el.length; i++)
{
if (quote.quoteSummary.result[0][el[i][0]][el[i][1]] !== undefined)
{
arr.push(quote.quoteSummary.result[0][el[i][0]][el[i][1]].value);
}
}
/*
Expected output (if I would loop through arr):
Technology
Susan
100
*/
The point is that different stocks, will have different properties. So el might define some non-existing elements or properties. Assume (in a bit of an other way of defining el -- as I wrote, I'm plexible here.. perhaps the paths are the easiest):
var el = [
'Profile.website',
'Profile.name',
'Assets'
]
/*
Expected output:
www.test.com
<----- "name" doesn't exist!
7000
Notice that in this example, there is no property "name" in Profile,
so I'd like to add an empty element to arr
*/
But this does not work. What is a generic loop that accomplishes what I'm trying to do here? The array defining what I want can also be constructed differently if that helps. But the point is that I don't end up with a script like:
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
arr.push(quote.quoteSummary.result[0].Profile.something);
I recommend you use variable-length chains of property names. Each name in a given chain represents a deeper property. You can "dive" into an object through an arbitrary number of property names with code like this:
let dive = (obj, propertyNames) => {
for (let pn of propertyNames) obj = obj[pn];
return obj;
};
Now you can say:
let dive = (obj, propertyNames) => {
for (let pn of propertyNames) obj = obj[pn];
return obj;
};
let quote = {
quoteSummary: {
result: [
{
Profile: {
sector: 'Technology',
website: 'www.test.com'
},
Owners: [
{
name: 'Eric',
age: '28'
},
{
name: 'Susan',
age: '44'
}
],
Profit: 100,
Assets: 7000
}
]
}
};
// Here are the "variable-length property chains":
let el = [
[ 'Profile', 'sector' ],
[ 'Owners', 1, 'name' ],
[ 'Profit' ]
];
// Here's how to combine `el`, `dive`, and your `quote` data to get a result:
let arr = el.map(propertyNames => dive(quote.quoteSummary.result[0], propertyNames));
console.log(arr);
You could even replace dive with Array.prototype.reduce, if you'd like to stay functional but avoid the function definition:
dive(someObj, propertyNames);
is equivalent to
propertyNames.reduce((obj, propName) => obj[propName], someObj);
Note the above code all assumes that a property exists for each term in the property chain (except the final property name, which may resolve to undefined without causing any errors). If some cases may have the, e.g., Profile key undefined or null you'll need to write some kind of if (propertyDoesntExist) / else statement which describes how to deal with missing properties.
For example you could modify dive to handle non-existent properties:
let dive = (obj, propertyNames, valueIfMissing=null) => {
for (let pn of propertyNames) {
// Intentional use of loose `==` operator!
if (obj == null) return valueIfMissing;
obj = obj[pn];
}
return obj;
};
This means that:
dive({ a: 1, b: 2, c: 3 }, [ 'd' ]) === null;
But we can substitute any default return value:
dive({ a: 1, b: 2, c: 3 }, [ 'd' ], 'ddd') === 'ddd';
Note this works at any depth:
dive({ a: { a: 1 }, b: { b: 2 }, c: { c: 3 }, d: null }, [ 'd', 'd' ]) === null;
dive({ a: { a: 1 }, b: { b: 2 }, c: { c: 3 }, d: null }, [ 'd', 'd' ], 'ddd') === 'ddd';
You should be able to do this:
if(y.value){
arr.push(y.value);
} else {
arr.push(y);
}
However, this will break if y.value happens to be something like 0, false, etc.
If this is the case you can do this:
if(y.hasOwnProperty("value")){
arr.push(y.value);
} else {
arr.push(y);
}
Based on the comment:
if (quote.quoteSummary.result[0][el[i][0]].hasOwnProperty("value")) {
arr.push(quote.quoteSummary.result[0][el[i][0]].value);
} else {
arr.push(quote.quoteSummary.result[0][el[i][0]]);
}

Why my copy Variable change the originale value variable In node js?

I don't know why when I change a variable that's a copy of an another var , both are changed? that doesn't make any sense for me ?
Can you please explain me why ? it's my first time that I face this reaction in node js , I know this in C or C++ when using Pointers but in node js I don't know why !
function getByType(where) {
var object = {
topMarque: null,
posModel: null,
};
var countPer = "titre";
var copywhere = where;
if (where.categorie == "voiture") {
countPer = "marqueNom";
copywhere.marqueId = {
[Op.ne]: null
}
} else {
console.log("------------------------------------------");
console.log(where)
console.log("------------------------------------------");
copywhere.titre = (where.titre) ? where.titre : {
[Op.ne]: null
}
console.log("******************************************");
console.log(where)
console.log("******************************************");
}
return db.stats.findAll({
attributes: [countPer, [db.sequelize.fn('COUNT', db.sequelize.col(countPer)), 'total']],
group: [countPer],
limit: 5,
where: copywhere,
order: [
[db.sequelize.fn('COUNT', db.sequelize.col(countPer)), 'DESC'],
],
}).then(function(count) {
object.topMarque = count;
return db.stats.findAll({
attributes: ['pos', [db.sequelize.fn('COUNT', db.sequelize.col('pos')), 'total']],
group: ['pos'],
where: where
}).then(function(countPosCu) {
object.posModel = countPosCu;
return Promise.resolve(object);
});
}).catch(error => {
return Promise.resolve(object);
})
}
In the first log i have this :
------------------------------------------
{ categorie: 'moto' }
------------------------------------------
In the seconde log I have this :
******************************************
{ categorie: 'moto', titre: { [Symbol(ne)]: null } }
******************************************
If you are dealing with the object or array in javascript it will referring the same reference no matter whether you are copy array/Object in another variable it will change value for all variable belong from the same reference
Please check below example:
// Declaring Array
var arr1 = [1,2,3,4,6];
//Trying to copy array into another variable
var arr2 = arr1;
console.log("========================");
//Display Array one
console.log(arr1);
//Adding more value in arra1
arr1.push(10);
arr1.push(11);
console.log("========================");
////Display Array Two
console.log(arr2);
console.log("========================");
O/P :
========================
[ 1, 2, 3, 4, 6 ]
========================
[ 1, 2, 3, 4, 6, 10, 11 ]
========================
As you can see in above example we are declaring one array and copy that same array into another array then we have push two array inside arr1 and we display arry2 it will display array value which is referring to arr1

Cartesian product without duplicates

I am using a cartesian product function that given [1], [1,2,3], [1,2,3] returns 9 combinations:
[ [ 1, 1, 1 ],
[ 1, 2, 1 ],
[ 1, 3, 1 ],
[ 1, 1, 2 ],
[ 1, 2, 2 ],
[ 1, 3, 2 ],
[ 1, 1, 3 ],
[ 1, 2, 3 ],
[ 1, 3, 3 ] ]
But I need to remove those with the same items regardless of the order, so [ 1, 3, 1 ] and [ 1, 1, 3 ] are the same to me. The result should contain 6 items:
[ [ 1, 1, 1 ],
[ 1, 2, 1 ],
[ 1, 3, 1 ],
[ 1, 2, 2 ],
[ 1, 3, 2 ],
[ 1, 3, 3 ] ]
I can write a function that compares all possible pairs with _.xor, but for larger numbers it will probably be very inefficient. Is there a good way in Javascript to do this? An efficient way to compare all possible pairs or an algorithm for cartesian product without duplicates?
sort each array of the cartesian product
[ 1, 2, 1 ] -> [1 , 1 , 2]
[ 1, 1, 2 ] -> [1 , 1 , 2]
then gather these sorted arrays into a set, that will remove the duplicates.
Of course, you can do that while constructing the cartesian product rather than afterward.
JavaScript has Set and Map, however they compare objects and arrays by reference rather than by value, so you cannot take advantage of it directly. The idea is to use a key function which sorts and json encodes the items before putting it in a set.
pure ES5:
function product(sets) {
if (sets.length > 0) {
var head = sets[0];
var tail = product(sets.slice(1));
var result = [];
head.forEach(function(x) {
tail.forEach(function(xs) {
var item = xs.slice(0);
item.unshift(x);
result.push(item);
});
});
return result;
} else {
return [[]];
}
}
function myKeyFn(item) {
return JSON.stringify(item.slice(0).sort());
}
function uniqBy(items, keyFn) {
var hasOwn = Object.prototype.hasOwnProperty, keyset = {};
return items.filter(function(item) {
var key = keyFn(item);
if (hasOwn.call(keyset, key)) {
return false;
} else {
keyset[key] = 1;
return true;
}
});
}
function uniqProduct(sets) {
return uniqBy(product(sets), myKeyFn);
}
function log(x) {
console.log(x);
var pre = document.createElement('pre');
pre.appendChild(document.createTextNode(x));
document.body.appendChild(pre);
}
log(uniqProduct([[1],[1,2,3],[1,2,3]]).map(JSON.stringify).join("\n"));
<pre></pre>
lodash + modern JavaScript:
// Note: This doesn't compile on current babel.io/repl due to a bug
function product(sets) {
if (sets.length > 0) {
const [x, ...xs] = sets;
const products = product(xs);
return _.flatMap(x, head => products.map(tail => [head, ...tail]));
} else {
return [[]];
}
}
function uniqProduct(sets) {
return _.uniqBy(product(sets), x => JSON.stringify(x.slice(0).sort()));
}
console.log(uniqProduct([[1],[1,2,3],[1,2,3]]).map(JSON.stringify).join("\n"));
JavaScript has set data structure.
So store your results in a set where each element of the set is a collection of pairs of numbers from the original sets along with the number of times that number occurs.
So your result would look something like this:
[
{1:3},
{1:2, 2: 1},
{ 1:2, 3:1},
{ 1:1, 2:2},
{ 1:1, 2:1, 3:1},
{ 1:1, 3:2 } ]
This way, you won't be able to add the object a second time to the set.

Merge two arrays with objects

I plan to merge two objects:
var c = {
name: "doo",
arr: [
{
id: 1,
ver: 1
},
{
id: 3,
ver: 3
}
]
};
var b = {
name: "moo",
arr: [
{
id: 1,
ver: 0
},
{
id: 2,
ver: 0
}
]
};
When using Object.assign({},b,c) what happens is, that the b.arr is simply being replaced with c.arr.
My question is, how do I preserve objects inside the b.arr that are not in c.arr but still merge objects from that array when they match b.arr[0].id === c.arr[0].id. The desired outcome would look like:
{
name: "doo",
arr: [
{
id: 1,
ver: 1
},
{
id: 2,
ver: 0
},
{
id: 3,
ver: 3
}
]
}
Thanks.
You could have a look at ArrayUtils.addAll() from the apache commons
As soon as you use lodash - you may use a combination of lodash's functions. It may look a bit complex but it's not:
_.assign({}, b, c, function(objectValue, sourceValue, key, object, source) {
//merging array - custom logic
if (_.isArray(sourceValue)) {
//if the property isn't set yet - copy sourceValue
if (typeof objectValue == 'undefined') {
return sourceValue.slice();
} else if (_.isArray(objectValue)) {
//if array already exists - merge 2 arrays
_.forEach(sourceValue, function(sourceArrayItem) {
//find object with the same ID's
var objectArrayItem = _.find(objectValue, {id: sourceArrayItem.id});
if (objectArrayItem) {
//merge objects
_.assign(objectArrayItem, sourceArrayItem);
} else {
objectValue.push(sourceArrayItem);
}
});
return objectValue;
}
}
//if sourceValue isn't array - simply use it
return sourceValue;
});
See the full demo here.
Try this function:
function mergeArrayObjects (a, b) {
var tmp, // Temporary array that will be returned
// Cache values
i = 0,
max = 0;
// Check if a is an array
if ( typeof a !== 'object' || typeof a.indexOf === 'undefined')
return false;
// Check if b is an array
if ( typeof b !== 'object' || typeof b.indexOf === 'undefined')
return false;
// Populate tmp with a
tmp = a;
// For each item in b, check if a already has it. If not, add it.
for (i = 0, max = b.length; i < max; i++) {
if (tmp.indexOf(b[i]) === -1)
tmp.push(b[i]);
}
// Return the array
return tmp;
}
JsFiddle here
Note: Because I'm anal, I decided to see if this function is faster than the alternative proposed. It is.
Using lodash, I would do something like this:
var first = {
name: 'doo',
arr: [
{ id: 1, ver: 1 },
{ id: 3, ver: 3 }
]
};
var second = {
name: 'moo',
arr: [
{ id: 1, ver: 0 },
{ id: 2, ver: 0 }
]
};
_.merge(first, second, function(a, b) {
if (_.isArray(a)) {
return _.uniq(_.union(a, b), 'id');
} else {
return a;
}
});
// →
// {
// name: 'doo',
// arr: [
// { id: 1, ver: 1 },
// { id: 2, ver: 0 },
// { id: 3, ver: 3 }
// ]
// }
The merge() function let's you specify a customizer callback for things like arrays. So we just need to check it it's an array we're dealing with, and if so, use the uniq() and union() functions to find the unique values by the id property.

Categories

Resources