I want to push elements to array in loop but when my method returns a value, it always rewrites every element of array(probably returned value refers to the same object). I'm stuck with this problem for one day and I can't understand where is the problem because I've always tried to create new objects and assign them to 'var' not to 'let' variables. Here is my code:
setSeason(competitions, unions) {
var categories = this.sortCategories(competitions);
var unionsByCategories = new Array();
let k = 0;
for (; k < categories.length; k++) {
unionsByCategories[k] = this.assignCompetitionsToUnions(unions[0], categories[k]);
}
this.setState({categories: unionsByCategories, refreshing: false})
}
and
assignCompetitionsToUnions(unions1, competitions) {
var unions2 = this.alignUnions(unions1);
let tempUnions = [];
for (var i = 0; i < unions2.length; i++) {
var tempUnionsCompetitions = new Array();
var tempSubsCompetitions = new Array();
if (Globals.checkNested(unions2[i], 'union')) {
tempUnionsCompetitions = unions2[i].union;
tempUnionsCompetitions['competitions'] = this.getCompetitionsById(unions2[i].union.id, competitions);
}
if (Globals.checkNested(unions2[i], 'subs')) {
for (var j = 0; j < unions2[i].subs.length; j++) {
if (Globals.checkNested(unions2[i].subs[j], 'union')) {
tempSubsCompetitions[tempSubsCompetitions.length] = {union: unions2[i].subs[j].union};
tempSubsCompetitions[tempSubsCompetitions.length - 1]['union']['competitions'] =
this.getCompetitionsById(unions2[i].subs[j].union.id, competitions)
}
}
}
tempUnions.push({union: tempUnionsCompetitions, subs: tempSubsCompetitions});
}
return tempUnions;
}
Many thanks for any help.
Answer updated by #Knipe request
alignUnions(unions3) {
let newUnions = unions3.subs;
newUnions = [{union: unions3.union}].concat(newUnions);
return newUnions.slice(0, newUnions.length - 1);
}
getCompetitionsById(id, competitions) {
let tempCompetitions = [];
for (let i = 0; i < competitions.length; i++) {
if (competitions[i].union.id === id) {
tempCompetitions.push(competitions[i]);
}
}
return tempCompetitions;
}
sortCategories(competitions) {
if (competitions.length === 0) return [];
let categories = [];
categories.push(competitions.filter((item) => {
return item.category === 'ADULTS' && item.sex === 'M'
}));
categories.push(competitions.filter((item) => {
return item.category === 'ADULTS' && item.sex === 'F'
}));
categories.push(competitions.filter((item) => {
return item.category !== 'ADULTS'
}));
return categories;
}
it always rewrites every element of array(probably returned value
refers to the same object).
You are probably unintended mutating the content of the source array. I would recommend creating a copy of the array.
This is example of array mutation.
let array1 = [1,2,3];
let array2 = array1;
array2[0] = 4; // oops, now the content of array1 is [4,2,3]
To avoid mutating the source array you can create a copy of it
let array1 = [1,2,3];
let array2 = array1.slice();
array2[0] = 4; // the content of array1 is still the same [1,2,3]
I've always tried to create new objects and assign them to 'var' not
to 'let' variables.
Using let/var will not prevent from rewrites. Creating new object with new Array() will not prevent rewrites.
It's hard to read where the bug is exactly from your code and description but you could try to avoid passing an array by reference and instead create a copy and pass the copy in function calls.
this.assignCompetitionsToUnions(unions[0].slice(), categories[k])
This is a shallow copy example, you might need to apply deep copy to make it work for your case.
Related
I have a recursivce function that takes a dom tree and converts it to JSON.
However I want to exclude any nodes that have a specific data attribute data-exclude
const htmlToJSON = node => {
const exclude = node.attributes?.getNamedItem('data-exclude');
if (!exclude) {
let obj = {
nodeType: node.nodeType
};
if (node.tagName) {
obj.tagName = node.tagName.toLowerCase();
} else if (node.nodeName) {
obj.nodeName = node.nodeName;
}
if (node.nodeValue) {
obj.nodeValue = node.nodeValue;
}
let attrs = node.attributes;
if (attrs) {
length = attrs.length;
const arr = (obj.attributes = new Array(length));
for (let i = 0; i < length; i++) {
const attr = attrs[i];
arr[i] = [attr.nodeName, attr.nodeValue];
}
}
let childNodes = node.childNodes;
if (childNodes && childNodes.length) {
let arr = (obj.childNodes = []);
for (let i = 0; i < childNodes.length; i++) {
arr[i] = htmlToJSON(childNodes[i]);
}
}
return obj;
}
};
const parser = new DOMParser();
const { body } = parser.parseFromString(page, 'text/html');
let jsonOutput = htmlToJSON(body);
console.log(jsonOutput);
I am clearly missing something with the way I am excluding because when I log the results it is returning undefined instead of just excluding it.
It's most likely because you're not returning anything from htmlToJSON in the case of "exclude == true". Notice how your lambda function doesn't have a "return " in that case. So the function will by default return "undefined."
And if you fill an array element with "undefined" it becomes a sparse array. So those elements in the array with "undefined" values become interpreted as "empty" slots by console.log() when printing the contents of any array to the console.
Update: I tried your code and, yup, my explanation above is correct. However, if you don't care about implicitly returning undefined from your htmlToJSON(), then you can just modify your inner for loop:
for (let i = 0; i < childNodes.length; i++) {
let json = htmlToJSON(childNodes[i]);
json && arr.push(json);
}
This way, only if json is truthy, will it add an element to the arr array.
I tried this code with your original function, and also with a modified version that returns null if exclude == true, and both ways worked.
Here's a Codepen example.
Did not execute the code. As far as I can see htmlToJSON will return obj or undefined. If exclude is truthy, the function will return undef, thats what you are seeing.
Change your for loop:
for (let i = 0, temp; i < childNodes.length; i++) {
temp = htmlToJSON(childNodes[i]);
temp && (arr[i] = temp);
}
that way you make sure if temp is defined you assign, otherwise not.
Another option is to use Array.prototype.filter on the resultant array.
Consider the following code:
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
var globalList = Array();
globalList[0] = "Test";
globalList[1] = "Another Test";
async function coreFunc(promiseName, sleepTime) {
console.log("Started CoreFunc: "+promiseName);
var localList = globalList;
console.log("Length of local array: "+localList.length);
console.log("Length of global array: "+globalList.length);
if (promiseName != "Promise0") {
for (i = 0; i < localList.length; i++) {
console.log(localList[i]);
}
}
if (promiseName == "Promise0") {
var testList = new Array();
testList[0] = "Changed";
globalList = testList;
}
await sleep(sleepTime);
console.log("Length of local array: "+localList.length);
console.log("Length of global array: "+globalList.length);
console.log("Done with CoreFunc: "+promiseName);
}
async function testMultiplePromises() {
var thArray = Array();
for (i = 0; i < 4; i++) {
var pr = new Promise(resolve => coreFunc("Promise" + i, 3000));
thArray[i] = pr;
}
for (i = 0; i < thArray.length; i++) {
await thArray[i];
}
}
globalList is an array that is global. When the above code is invoked like the following:
await testMultiplePromises();
The code goes into an infinite loop. The problem is definitely in the following segment where I am reinitializing the global variable to some different array:
if (promiseName == "Promise0") {
var testList = new Array();
testList[0] = "Changed";
globalList = testList;
}
Is there a way to copy the global datastructure to a local variable without leading to index out of bounds or infinite loop kind of issues? The following code is definitely not doing the job:
var localList = globalList;
What should be done in order to ensure that the Promises either get the older array or get the newer array? To rephrase, how do I make sure the code inside coreFunc (Promise0) that changes the global data structure is protected?
Infinite loop is caused by global i variable on for loops. You should type it like this
...
for (var i = 0; i < localList.length; i++) {
...
for (var i = 0; i < 4; i++) {
...
for (var i = 0; i < thArray.length; i++) {
...
To "protect" array you can just copy it like this
var localList = JSON.parse(JSON.stringify(globalList));
There are a few problems in your code.
var globalList = Array(); - missing the new keyword here.
var localList = globalList; - this does NOT create a copy of the Array, it just creates a reference to the outer array. So whatever you change in localList will also be changed in globalList. Try this: var localList = [...globalList];. This creates a (shallow) copy.
globalList = testList; - same here.
A good read about pass by value vs. pass by reference can be found here.
I am trying to compare the items in "item" array and the copyofOpList array to retrieve the data occurrences in copyofOpList
this is my try:
var _deleteUsedElement1 = function(item) {
for (var i = 0; i < item.length-1; i++){
for (var j = 0; j< $scope.copyofOpList.length-1; j++){
if (item[i].operationCode == $scope.copyofOpList[j].code) {
$scope.copyofOpList.splice(j, 1);
} } } };
$scope.compareArrays = function() {
...Get data from web Service
_deleteUsedElement1(item);
}
the copyofOpList array has 14 elements,and the item array has 2 array
but my code deletes only one occurrence (the first),so please how can I correct my code,to retrieve any occurances in the copyofOpList array comparing to the item array
thanks for help
I'd try to avoid looping inside a loop - that's neither a very elegant nor a very efficient way to get the result you want.
Here's something more elegant and most likely more efficient:
var item = [1,2], copyofOpList = [1,2,3,4,5,6,7];
var _deleteUsedElement1 = function(item, copyofOpList) {
return copyofOpList.filter(function(listItem) {
return item.indexOf(listItem) === -1;
});
};
copyofOpList = _deleteUsedElement1(item, copyofOpList);
console.log(copyofOpList);
//prints [3,4,5,6,7]
}
And since I just noticed that you're comparing object properties, here's a version that filters on matching object properties:
var item = [{opCode:1},{opCode:2}],
copyofOpList = [{opCode:1},{opCode:2},{opCode:3},{opCode:4},{opCode:5},{opCode:6},{opCode:7}];
var _deleteUsedElement1 = function(item, copyofOpList) {
var iOpCodes = item.map(function (i) {return i.opCode;});
return copyofOpList.filter(function(listItem) {
return iOpCodes.indexOf(listItem.opCode) === -1;
});
};
copyofOpList = _deleteUsedElement1(item, copyofOpList);
console.log(copyofOpList);
//prints [{opCode:3},{opCode:4},{opCode:5},{opCode:6},{opCode:7}]
Another benefit of doing it in this manner is that you avoid modifying your arrays while you're still operating on them, a positive effect that both JonSG and Furhan S. mentioned in their answers.
Splicing will change your array. Use a temporary buffer array for new values like this:
var _deleteUsedElement1 = function(item) {
var _temp = [];
for (var i = 0; i < $scope.copyofOpList.length-1; i++){
for (var j = 0; j< item.length-1; j++){
if ($scope.copyofOpList[i].code != item[j].operationCode) {
_temp.push($scope.copyofOpList[j]);
}
}
}
$scope.copyofOpList = _temp;
};
I broke down my code to a simplified jsFiddle. The problem is that the attribute is is only set for one object but in the end every object gets the value of the last iteration (in this case it is false but id05 should be true). Why is it? Do I overlook something?
jsFiddle (see in the console)
var reminder = {
id0: {
id: 0,
medId: 0
}
};
var chart = {
id0: {
medId: 0,
values: [[5,1]]
}
}
var tmp = {};
for(var i = 0; i < 10; i++) {
for (id in reminder) {
tmp[id + i] = reminder[id];
tmp[id + i].is = false;
for(var j = 0; j < chart["id" + reminder[id].medId].values.length; j++) {
if (chart["id" + reminder[id].medId].values[j][0] === i) {
tmp[id + i].is = true;
}
}
}
}
tmp[id + i] = reminder[id]; will copy the reference to the object and not clone the object itself.
Consider this:
var a = { a: [] };
var b = a.a;
b.push(1);
console.log(a.a); // [1]
This means that all your objects are the same and they share the same properties (tmp.id05 === tmp.id06 etc...)
tmp.id00.__my_secret_value__ = 1234;
console.log(tmp.id09.__my_secret_value__); // 1234
To clone objects in JavaScript you can use Object.create but this will only make a shallow clone (only clone top level properties)
I need to know if one or more duplicates exist in a list. Is there a way to do this without travelling through the list more than once?
Thanks guys for the suggestions. I ended up using this because it was the simplest to implement:
var names = [];
var namesLen = names.length;
for (i=0; i<namesLen; i++) {
for (x=0; x<namesLen; x++) {
if (names[i] === names[x] && (i !== x)) {alert('dupe')}
}
}
Well the usual way to do that would be to put each item in a hashmap dictionary and you could check if it was already inserted. If your list is of objects they you would have to create your own hash function on the object as you would know what makes each one unique. Check out the answer to this question.
JavaScript Hashmap Equivalent
This method uses an object as a lookup table to keep track of how many and which dups were found. It then returns an object with each dup and the dup count.
function findDups(list) {
var uniques = {}, val;
var dups = {};
for (var i = 0, len = list.length; i < len; i++) {
val = list[i];
if (val in uniques) {
uniques[val]++;
dups[val] = uniques[val];
} else {
uniques[val] = 1;
}
}
return(dups);
}
var data = [1,2,3,4,5,2,3,2,6,8,9,9];
findDups(data); // returns {2: 3, 3: 2, 9: 2}
var data2 = [1,2,3,4,5,6,7,8,9];
findDups(data2); // returns {}
var data3 = [1,1,1,1,1,2,3,4];
findDups(data3); // returns {1: 5}
Since we now have ES6 available with the built-in Map object, here's a version of findDups() that uses the Map object:
function findDups(list) {
const uniques = new Set(); // set of items found
const dups = new Map(); // count of items that have dups
for (let val of list) {
if (uniques.has(val)) {
let cnt = dups.get(val) || 1;
dups.set(val, ++cnt);
} else {
uniques.add(val);
}
}
return dups;
}
var data = [1,2,3,4,5,2,3,2,6,8,9,9];
log(findDups(data)); // returns {2 => 3, 3 => 2, 9 => 2}
var data2 = [1,2,3,4,5,6,7,8,9];
log(findDups(data2)); // returns empty map
var data3 = [1,1,1,1,1,2,3,4];
log(findDups(data3)); // returns {1 => 5}
// display resulting Map object (only used for debugging display in snippet)
function log(map) {
let output = [];
for (let [key, value] of map) {
output.push(key + " => " + value);
}
let div = document.createElement("div");
div.innerHTML = "{" + output.join(", ") + "}";
document.body.appendChild(div);
}
If your strings are in an array (A) you can use A.some-
it will return true and quit as soon as it finds a duplicate,
or return false if it has checked them all without any duplicates.
has_duplicates= A.some(function(itm){
return A.indexOf(itm)===A.lastIndexOf(itm);
});
If your list was just words or phrases, you could put them into an associative array.
var list=new Array("foo", "bar", "foobar", "foo", "bar");
var newlist= new Array();
for(i in list){
if(newlist[list[i]])
newlist[list[i]]++;
else
newlist[list[i]]=1;
}
Your final array should look like this:
"foo"=>2, "bar"=>2, "foobar"=>1