Avoid nested in loops - javascript

I have an issue with in loops, while I'm trying to look for two same-value-pairs in an array and an object:
for (features in geodata.features) {
if (geodata.features[features].geometry.type == 'Point') {
.....
} else if (geodata.features[features].geometry.type == 'LineString') {
for (itema in networkElemente) { //Here is the part whrere it gets problematic
for (itemb in networkElemente) {
if (networkElemente[itema].uuid == geodata.features[features].properties.a.ne.uuid && networkElemente[itemb].uuid == geodata.features[features].properties.b.ne.uuid) {
console.log('klappt');
var intraOrtsVerbindung = L.polyline([[networkElemente[Number(itemb)].coords.lat,networkElemente[Number(itemb)].coords.lng],[networkElemente[Number(itema)].coords.lat,networkElemente[Number(itema)].coords.lng]], {
weight: 5,
color: 'green',
opacity: 1,
}).addTo(map);
}
}
}
}
}
I have the array networkElemente and I have geodata.fea... .a and .b. Now I want to look, if geodata...a has one entry the same as networkElemente and ...b has also one entry like networkElemente. This works fine with these nested loops, which will execute the part unnecessarily often. I want to seperate the loops so that if networkElemente.length wouuld be 1000 that it does not log 'klappt' 1million time, but only 2k time. So just the same result, but not so often.
Thanks!

Welcome to StackOverflow
You problem here, apart for using for...in when you probably want for...of, is combinatorics.
The easiest optimization is to never check the same pair twice. You can do this by using a regular for...loop and offset the index of the nested loop:
var networkElemente = [];
while (networkElemente.length < 1000) {
networkElemente.push(networkElemente.length + 1);
}
var count = 0;
for (var a = 0; a < networkElemente.length; a++) {
var itema = networkElemente[a];
for (var b = a + 1; b < networkElemente.length; b++) {
var itemb = networkElemente[b];
count++;
}
}
console.log("Count: " + count);
As for your problem with logging too often, simplest solution is to simple hold a counting variable and log once after the loop is done.
Logging 2K times isn't good for performance anyway :-)

Instead of using nested loops, you could use a Map and take the uuid as accessor for the wanted data. Then check if both exists and make your poyline.
var networkElementeMap = new Map(networkElemente.map(o => [o.uuid, o])),
elementA = networkElementeMap.get(geodata.features[features].properties.a.ne.uuid),
elementB = networkElementeMap.get(geodata.features[features].properties.b.ne.uuid);
if (elementA && elementB) {
console.log('klappt');
var intraOrtsVerbindung = L.polyline([
[elementB.coords.lat, elementB.coords.lng],
[elementA.coords.lat, elementA.coords.lng]
], {
weight: 5,
color: 'green',
opacity: 1,
}).addTo(map);
}

Related

Can we change the iteration direction of Array.find?

Look at this crazy question... I have an array with 30.000 items, and I have to run something like this over it:
const bigArray = [
{ createdAt: 1 },
{ createdAt: 2 },
{ createdAt: 3 },
// And so on... 30.000
];
const found = bigArray.find(x => x.createdAt > 29950)
And the thing here, is that I know that 100% of the time, that element will be in the index 29.950 (approx). Because that array is already sorted by createdAt (coming from the backend)
How does .find works? Does it iterates starting from the first element? Is there a way to say "I know it's closer to the end... Change your behavior"?
Of course there is the alternative of doing something like:
bigArray.reverse()
const prevIndex = bigArray.findIndex(x => x.createdAt <= 29950);
const found = bigArray[prevIndex - 1];
bigArray.reverse()
But I'm not sure if that's gonna be actually worst (because of the fact that there we'll also have some multiples unnecessary iterations... I guess).
Who can give me some clues about this?
It's not that I have a bug here... Not even a performance issue (because 30.000 is not that much), but, feels like there should be something there, and I never hear about it in ~16 years working on JavaScript
Thanks so much!
Based upon the documentation here, it appears that find is O(n) time complexity, where n is length of the array.
Since your elements are sorted, you can try to do binary search and reduce time complexity to O(log n).
This is the basic binary search iterative algorithm:
function binarySearchIterative (nums, target) {
let res = -1;
let left = 0;
let right = nums.length;
while (left <= right && res === -1) {
const mid = Math.floor(left + (right - left)/2);
if (nums[mid] === target) {
res = mid;
}
else if (nums[mid] > target) {
right--;
}
else {
left++;
}
}
return res;
};
I'm not aware of any options for Array.prototype.findIndex that start from the end... I do know for sure that using Array.prototype.reverse is very expensive and you could make your own algorithm like this if you know that you're likely to find the result you need near the end:
const bigArray = [
{ createdAt: 1 },
{ createdAt: 2 },
{ createdAt: 3 }
];
// Add the function to Array.prototype
Array.prototype.findIndexFromEnd = function (cond) {
for(let i = this.length - 1; i >= 0; i--) {
if(cond(this[i])) return i;
}
return -1;
}
// Gives 1 as expected
console.log(bigArray.findIndexFromEnd(x => x.createdAt == 2));
// Or use an external function if you don't want to edit the prototype
function findIndexFromEnd(array, cond) {
for(let i = array.length - 1; i >= 0; i--) {
if(cond(array[i])) return i;
}
return -1;
}
// Gives 1 as expected
console.log(findIndexFromEnd(bigArray, (x) => x.createdAt == 2));

How to loop through an array and display each value next to eachother with a 500ms interval using pureJS?

The loop below basically loops through my array which is sumFormat then displays each value of the array with a 500ms interval, which is fine, but its displaying the array values only once at time so
for example if the array contains [1, 2, 3]
it displays 1 then 500ms later displays 2 in place of the 1,
but I want it to display 1 then 500ms later display 2 next to
the 1 so 1 2
and then 1 2 3 and so on,
I've tried to figure it out but can't seem to get it right.
for (var i = 0; i < sumFormat.length; i++) {
(function(i) {
setTimeout(function() {
factOut.innerHTML = sumFormat[i];
}, 500 * i);
})(i);
}
Concatenate with the existing innerHTML instead of just assigning to it, since that will overwrite whatever was there previously. You can also use let instead of var to make the code much neater. Also, since you're only inserting text, not HTML markup, better to use textContent instead:
const sumFormat = [1, 2, 3, 4, 5];
for (let i = 0; i < sumFormat.length; i++) {
setTimeout(function() {
factOut.textContent += ' ' + sumFormat[i];
}, 500 * i);
}
<div id="factOut"></div>
Another method, by joining the array items from 0 to the current index:
const sumFormat = [1, 2, 3, 4, 5];
for (let i = 0; i < sumFormat.length; i++) {
setTimeout(function() {
factOut.textContent = sumFormat.slice(0, i + 1).join(' ');
}, 500 * i);
}
<div id="factOut"></div>
There are a couple of things I would change here.
Using multiple setTimeout's and setting the timeout's as increasing values could cause you issues if you wanted lots of them, timeout's are a limited resource in JS.
One option is possibly using setInterval as someone here as shown, but my favourite is using async / await.
Also if your list got very long, constantly changing the innerHTML would not be very kind to the DOM, instead creating another DOM element and adding those would be more efficient. Another advantage of them been separate is it make's it much simpler to style them too.
Below is an example using async/await and adding to the DOM, instead of constantly updating the innerHTML, and for fun just putting a bit of style to it.
const delay=ms=>new Promise(r=>setTimeout(r,ms));
(async function() {
const sumFormat = [1,2,3,4];
for (const i of sumFormat) {
const num = document.createElement('span');
num.innerText = i;
factOut.appendChild(num);
await delay(500);
}
}());
span {
padding: 0.2rem 0.4rem;
margin: 0.2rem 0.3em;
background-color: lightblue;
border: 1px solid black;
border-radius: 0.5rem;
}
<div id="factOut"></div>
Here Example with setInterval()
function SetValue(text, at){
factOut.innerHTML += text[at] + (text.length-1 == at ?"":" ");
}
var sumFormat = [1,2,3,4,5];
var i = 0;
var myinterval = setInterval(function(){
if(i == sumFormat.length){
clearInterval(myinterval);
return;
}
SetValue(sumFormat,i);
i++;
},500)
<div id="factOut" />

JS Object Array Pagination

i need some help manipulating/paginating a JS array object by 5:
var musicItems = [
["assets/audio/1.wav"],
["assets/audio/2.wav"],
["assets/audio/3.wav"],
["assets/audio/4.wav"],
["assets/audio/5.wav"],
["assets/audio/6.wav"],
["assets/audio/7.wav"],
["assets/audio/8.wav"],
];
I want to know in JS the number of sets (2) and which elements belong to which set (set1->1-5, set2->6-8). Appreciate any help.
EDIT
I am looking to do something like Split array into chunks
But as you can see, my array is different.
This is just a simple demo on how you could achieve this with the modulo operator %. There will be more efficient and smaller solutions.
var musicItems = [
["assets/audio/1.wav"],
["assets/audio/2.wav"],
["assets/audio/3.wav"],
["assets/audio/4.wav"],
["assets/audio/5.wav"],
["assets/audio/6.wav"],
["assets/audio/7.wav"],
["assets/audio/8.wav"],
];
var sets = new Object();
var set = new Array();
var setCounter = 0;
for(var i = 0; i < musicItems.length; i++) {
set.push(musicItems[i]);
if((i + 1) % 5 == 0 || (i + 1) >= musicItems.length) {
setCounter++;
sets['set' + setCounter] = set;
set = new Array();
}
}
console.log(sets['set1']);
console.log(sets['set2']);
Basically what this does is to iterate through the musicItems, with modulo % 5 we check if the current item can be devided by 5 without rest, so we know a set is complete as we collected 5 items. Then we add the set to the overall sets object to use it later on as a dictionary (as wished with 'set1', 'set2' etc.) and we clear the current set, to fill it with the next n-5 items.
Bref :
max=5,j=0;
for(var i=0;i<musicItems.length;i=i+max){
j++;
sets['set' + j] = musicItems.offset(i).max(max);
}
Explanation
Pagination concept rely on two parameters:
Offset :
Max :
Then :
musicItems.offset(0).max(5) ; // 1st set
// > [ ["assets/audio/1.wav"],["assets/audio/2.wav"],["assets/audio/3.wav"],["assets/audio/4.wav"],["assets/audio/5.wav"] ]
musicItems.offset(0+5).max(5) ; // second set
// > [ ["assets/audio/6.wav"],["assets/audio/7.wav"],["assets/audio/8.wav"] ]
Required API :
Array.prototype.max = function(mx) {
return this.filter(function(e,i){return i < mx;}); }
};
Array.prototype.offset=function(os){
return this.filter(function(e,i){return i> os-1 });
};

Uncontrolled iteration in loop

Thats my code:
var randomCoord = function(cells) {
var step = $('.workplace').innerWidth()/cells;
var xCord = (Math.floor(Math.random() * (cells+1)))*step;
var yCord = (Math.floor(Math.random() * (cells+1)))*step;
if(plants.length != 0) {
for (var i=0; i<plants.length; i++) {
if (plants[i].left != xCord && plants[i].top != yCord) {
plants.push({"top": yCord, "left": xCord});
}
}
} else {
plants.push({"top": yCord, "left": xCord});
}
};
var multiplayer = function(func, endIteration, cells) {
for (var i=0; i<endIteration; i++) {
func(cells);
};
};
multiplayer(randomCoord, 5, 10) // will iterate diferent times
Function, multiplayer have to run "randomCoords" 5 times, but it's not working. Why quantity of iteration is uncontroled? How can I fix it?
It looks like your for loop in randomCoord() is supposed to be only pushing an entry into the array if the coordinates don't already exist in the array, but that isn't how your logic works. Instead, you check each and every item in the array and if it's not equal to that item in the array, you push it and you do that for each item in the array so you end up with lots of duplicates in the array (exactly what you're trying to prevent).
So, the first time you call randomCoord, you get one item. The next time you call it, you get two items. The third time you call it you get 4 items, then 8, then 16. This is a fairly simple logic error.
If you just want to add one unique item each time you call randomCoord, then you could use logic like this:
var randomCoord = function(cells) {
var step = $('.workplace').innerWidth()/cells;
var xCord = (Math.floor(Math.random() * (cells+1)))*step;
var yCord = (Math.floor(Math.random() * (cells+1)))*step;
var found = false;
for (var i=0; i<plants.length; i++) {
if (plants[i].left == xCord && plants[i].top == yCord) {
found = true;
break;
}
}
if (!found) {
plants.push({"top": yCord, "left": xCord});
}
};
Note, you don't need the separate if (plants.length != 0) because the for loop already checks that and our new found variable handles the case where the array is initially empty.
If you happen to generate a coordinate conflict, this will add no item on that function call though the odds of generating two conflicting random values are fairly low as long as cells*step is a decent size number (the range of your random number generator). If you want to try it again in that case, then you need another loop to try again if a conflict is found.

Small Straight (Yahtzee) Algorithm

I have created a working javascript function to check an array of 5 numbers for a small straight, in a Yahtzee game I'm making. I've tested it to no end and I'm confident it works 100% of the time, but it is also probably the worst algorithm of all time in terms of being efficient. Here is what it looks like:
function calcSmstraight() {
var sum = 0;
var r = new Array();
var r2 = new Array();
var counter = 0;
var temp;
var bool = false;
var bool2 = false;
r[0] = document.getElementById('setKeep1').value;
r[1] = document.getElementById('setKeep2').value;
r[2] = document.getElementById('setKeep3').value;
r[3] = document.getElementById('setKeep4').value;
r[4] = document.getElementById('setKeep5').value;
// Move non-duplicates to new array
r2[0] = r[0];
for(var i=0; i<r.length; i++) {
for(var j=0; j<r2.length; j++) {
if(r[i] == r2[j]) {
bool2 = true; // Already in new list
}
}
// Add to new list if not already in it
if(!bool2) {
r2.push(r[i]);
}
bool2 = false;
}
// Make sure list has at least 4 different numbers
if(r2.length >= 4) {
// Sort dice from least to greatest
while(counter < r2.length) {
if(r2[counter] > r2[counter+1]) {
temp = r2[counter];
r2[counter] = r2[counter+1];
r2[counter+1] = temp;
counter = 0;
} else {
counter++;
}
}
// Check if the dice are in order
if(((r2[0] == (r2[1]-1)) && (r2[1] == (r2[2]-1)) && (r2[2] == (r2[3]-1)))
|| ((r2[1] == (r2[2]-1)) && (r2[2] == (r2[3]-1)) && (r2[3] == (r2[4]-1)))) {
bool = true;
}
}
if(bool) {
// If small straight give 30 points
sum = 30;
}
return sum;
}
My strategy is to:
1) Remove duplicates by adding numbers to a new array as they occur
2) Make sure the new array is at least 4 in length (4 different numbers)
3) Sort the array from least to greatest
4) Check if the first 4 OR last 4 (if 5 in length) numbers are in order
My question:
Does anyone know a way that I can improve this method? It seems ridiculously terrible to me but I can't think of a better way to do this and it at least works.
Given that you're implementing a Yahtzee game you presumably need to test for other patterns beyond just small straights, so it would be better to create the array of values before calling the function so that you can use them in all tests, rather than getting the values from the DOM elements inside the small straight test.
Anyway, here's the first way that came to my mind to test for a small straight within an array representing the values of five six-sided dice:
// assume r is an array with the values from the dice
r.sort();
if (/1234|2345|3456/.test(r.join("").replace(/(.)\1/,"$1") {
// is a small straight
}
Note that you can sort an array of numbers using this code:
r2.sort(function(a,b){return a-b;});
...but in your case the values in the array are strings because they came from the .value attribute of DOM elements, so a default string sort will work with r2.sort(). Either way you don't need your own sort routine, because JavaScript provides one.
EDIT: If you assume that you can just put the five values as a string as above you can implement tests for all possible combinations as a big if/else like this:
r.sort();
r = r.join("");
if (/(.)\1{4}/.test(r)) {
alert("Five of a Kind");
} else if (/(.)\1{3}/.test(r)) {
alert("Four of a Kind");
} else if (/(.)\1{2}(.)\2|(.)\3(.)\4{2}/.test(r)) {
alert("Full House");
} else if (/(.)\1{2}/.test(r)) {
alert("Three of a Kind");
} else if (/1234|2345|3456/.test( r.replace(/(.)\1/,"$1") ) {
alert("Small Straight");
} // etc.
Demo: http://jsfiddle.net/4Qzfw/
Why don't you just have a six-element array of booleans indicating whether a number is present, then check 1-4, 2-5, and 3-6 for being all true? In pseudocode:
numFlags = array(6);
foreach(dice)
numFlags[die.value-1] = true;
if(numFlags[0] && numFlags[1] && numFlags[2] && numFlags[3]) return true
//Repeat for 1-4 and 2-5
return false
This wouldn't be a useful algorithm if you were using million-sided dice, but for six-siders there are only three possible small straights to check for, so it's simple and straightforward.
I do not play Yahtzee, but I do play cards, and it would appear the algorithm might be similar. This routine, written in ActionScript (my JavaScript is a bit rusty) has been compiled but not tested. It should accept 5 cards for input, and return a message for either straights greater than 3 cards or pairs or higher.
private function checkCards(card1:int,card2:int,card3:int,card4:int,card5:int):String
{
// Assumes that the 5 cards have a value between 0-12 (Ace-King)
//The key to the routine is using the card values as pointers into an array of possible card values.
var aryCardValues:Array = new Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
aryCardValues[card1] += 1;
aryCardValues[card1] += 1;
aryCardValues[card1] += 1;
aryCardValues[card1] += 1;
aryCardValues[card1] += 1;
var aryCardNames:Array = new Array("Ace", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Jack", "Queen", "King");
var strOutMessage:String;
var intCardCount:int = 0;
var strSeperator:String;
var strHighCard:String;
for (var i:int = 0;i < aryCardValues.length;i++)
{
//Check for runs of three of a kind or greater.
if (aryCardValues[i] >= 2)
{
strOutMessage = strOutMessage + strSeperator + i + "-" + aryCardNames[i] + "s";
strSeperator = " & ";
}
//Check for values in a straight.
if (aryCardValues[i] > 0)
{
intCardCount++;
if (intCardCount > 3)strHighCard = aryCardNames[i];
}
else
{
if (intCardCount < 3)intCardCount = 0;
}
}
if (intCardCount > 3) strOutMessage = intCardCount + " run " + strHighCard + " High."
return strOutMessage;
}
It may not be as concise as the regular expressions used above, but it might be more readable and easily modified. One change that could be made is to pass in an array of cards rather than discrete variables for each card.

Categories

Resources