So at the moment I'm trying to create a javascript application that uses quite a few nested for loops eg.
for (var i = 0; i < a; i++) {
for (var j = 0; j < b; j++) {
//Code to run
}
}
So for the sake of neatness I thought I could make some sort of function for this purpose that could be called upon like this
doubleLoop(conditionA, conditionB) {
//Code to execute
}
Is this at all possible in Javascript or am I just gonna have to make do with loads of double for loops?
I suppose you could do something like make conditionA and conditionB functions or something, but a simpler alternative would be to just split your functionality up into functions.
Something like this:
function draw(){
doOuterTask();
}
function doOuterTask(){
for (var i = 0; i < a; i++) {
doInnerTask();
}
}
function doInnerTask(){
for (var j = 0; j < b; j++) {
//Code to run
}
}
Here's a more specific example:
function setup() {
createCanvas(400, 400);
}
function draw() {
background(0);
drawGrid(10, 10);
}
function drawGrid(rowCount, columnCount){
for(var rowIndex = 0; rowIndex < rowCount; rowIndex++){
drawRow(rowIndex, rowCount, columnCount);
}
}
function drawRow(rowIndex, rowCount, columnCount){
for(var colIndex = 0; colIndex < columnCount; colIndex++){
var circleWidth = width/columnCount;
var circleHeight = height/rowCount;
var x = colIndex * circleWidth;
var y = rowIndex * circleHeight;
fill(255);
ellipse(x, y, circleWidth, circleHeight);
}
}
This code draws a grid of circles, which you might do using a nested for loop. Instead, it creates a drawRow() function which draws a single row of circles, and then calls that from a drawGrid() function. The drawGrid() function loops over each row and calls the drawRow() function, which loops over each circle in that particular row.
The benefit of this is that you only ever have to worry about one loop at a time, and it also makes testing things much easier because you can directly call the "inner function" to test that it's working the way you expected.
Related
I am trying to create a sorting visualizer on Angular, and I decided to use the chart.js bar chart. I am trying to visualize bubble sort at the moment, and I would like to add a delay in every iteration of the inner loop. I am sure that you are familiar with the loop structure of bubble sort. I want to do the comparison, then call the draw function I made to draw the updated chart, and then after a 0.5 second delay, move onto the next iteration.
for (let i = 0; i < this.data.length; i++) {
for (let j = 0; j < this.data.length; j++) {
//comparison
this.draw();
// 0.5 SECOND DELAY
}
}
I'm sure there are better ways to manage the event loop in javascript than this, but i'll get you started:
jsfiddle: https://jsfiddle.net/zdfo5tce/
var data=[1,2,3,4,5]
var counter=0
let draw=(c) => document.body.innerHTML=c;
for (let i = 0; i < this.data.length; i++) {
for (let j = 0; j < this.data.length; j++) {
//comparison
setTimeout(draw.bind(this, counter), ++counter*500);
// 0.5 SECOND DELAY
}
}
this will delay each iteration by 500 ms
for (let i = 0; i < this.data.length; i++) {
for (let j = 0; j < this.data.length; j++) {
setTimeout(function(){
this.draw();
}, 500 *j);
}
}
I'm new to JavaScript so I'm currently working through MDN's official 2D Breakout Game tutorial and changing stuff around on my own to better understand how things work. As part of my changes in the 6th Step of the tutorial (called "Build the brick field"), I wrote the following code that works exactly how I want it to, but I feel I could make this code shorter in a way, and I don't know how.
function drawBricks(){
for(c = 0; c < bricksColCount-3; c++) {
for(r = 0; r < bricksRowCount-1; r++) {
setRowsCol();
}
}
for(c = 2; c < bricksColCount-2; c++) {
for(r = 0; r < bricksRowCount-4; r++) {
setRowsCol();
}
}
for(c = 3; c < bricksColCount; c++) {
for(r = 0; r < bricksRowCount; r++) {
setRowsCol();
}
}
}
The setRowsCol() function I'm referring to in this code is written like this:
function setRowsCol() {
bricks[c][r].x = bricksOffsetLeft + (c*(bricksWidth + bricksPadding));
bricks[c][r].y = bricksOffsetTop + (r*(bricksHeight + bricksPadding));
ctx.beginPath();
ctx.rect(bricks[c][r].x, bricks[c][r].y, bricksWidth, bricksHeight);
ctx.fillStyle = "#fff";
ctx.fill();
ctx.closePath();
}
Any idea to reduce the lines and make the code shorter without changing the way it works? Since I'm new to JavaScript, I want to make sure I apply proper techniques. Thanks guys.
The number of lines of code does not directly correlate to how fast your code runs. Certain operations are more expensive than others.
In your example, the canvas operations are by far the most expensive. Your goal should be to improve speed by reducing the number of canvas operations.
I see that you are invoking setRowsCol() in loops and inside setRowsCol() you execute beginPath() and closePath(). Because you only need to execute beginPath() once you start drawing and closePath() once you're done drawing, it would be a good idea to take those two lines out of the looped function. Additionally, the fillStyle need only be set once and fill() need only be executed once. They can also be removed from the setRowsCol() function. You could write it as such:
function setRowsCol() {
bricks[c][r].x = bricksOffsetLeft + (c*(bricksWidth + bricksPadding));
bricks[c][r].y = bricksOffsetTop + (r*(bricksHeight + bricksPadding));
ctx.rect(bricks[c][r].x, bricks[c][r].y, bricksWidth, bricksHeight);
}
function drawBricks(){
for(c = 0; c < bricksColCount-3; c++) {
for(r = 0; r < bricksRowCount-1; r++) {
setRowsCol();
}
}
for(c = 2; c < bricksColCount-2; c++) {
for(r = 0; r < bricksRowCount-4; r++) {
setRowsCol();
}
}
for(c = 3; c < bricksColCount; c++) {
for(r = 0; r < bricksRowCount; r++) {
setRowsCol();
}
}
}
var ctx=c.getContext("2d");
ctx.beginPath();
ctx.fillStyle = "#fff";
drawBricks();
ctx.fill();
ctx.closePath();
The code could be better (e.g., not relying on global scope variables), but the idea is that you've moved some expensive canvas operations outside of looping code.
function drawBricks(){
function loop(startCol,endColOffset,endRowOffset){
for(c = startCol; c < bricksColCount+endColOffset; c++) {
for(r = 0; r < bricksRowCount+endRowOffset; r++) {
setRowsCol();
}
}
}
loop(0,-3,-1);
loop(2,-2,-4);
loop(3,0,0);
}
You asked how to make the code shorter and more efficient, but I think there are more important things that you should address.
Write correct, readable code
The for-loops over the columns only work when bricksColumnCount is 5. To see why, try running the code in your head with bricksColumnCount equal to 10. You'll notice that some columns get drawn multiple times.
As a first step, I would forget about bricksColumnCount and explicitly use five columns:
function drawBricks() {
// columns 0 and 1 each have (bricksRowCount-1) bricks
for (c = 0; c < 2; c++) {
for (r = 0; r < bricksRowCount-1; r++) {
setRowsCol();
}
}
// column 2 has (bricksRowCount-4) bricks
for (c = 2; c < 3; c++) {
for (r = 0; r < bricksRowCount-4; r++) {
setRowsCol();
}
}
// columns 3 and 4 each have bricksRowCount bricks
for (c = 3; c < 5; c++) {
for (r = 0; r < bricksRowCount; r++) {
setRowsCol();
}
}
}
To generalise the code so that it works with arbitrary column counts, you need to decide how you want to partition the columns and write it as a formula involving bricksColumnCount. The original code in the question is a step in that direction, but isn't quite right.
Prefer function parameters to global variables
The behaviour of the function setRowsCol is controlled by the global variables c and r, which the calling code needs to set. This kind of programming style is very fragile. See http://wiki.c2.com/?GlobalVariablesAreBad for some of the reasons.
The alternative is to use function parameters. See the MDN Guide to Functions for an introduction.
You have already seen how values are passed to a function during a function call:
ctx.rect(bricks[c][r].x, bricks[c][r].y, bricksWidth, bricksHeight);
The function ctx.rect needs to know the location and dimensions of the rectangle to draw, and they are passed in by writing them inside the parentheses of the function call.
To declare that setRowsCol takes two parameters, change the declaration to the following:
function setRowsCol(c, r) {
// the body remains the same
}
The calls to setRowsCol in drawBricks need to be updated so they look like this:
setRowsCol(c, r);
The order of the values in the call has to match the order of the parameters in the function declaration, but the names don't need to match. In fact, any expression can be used in a function call:
setRowsCol(1, 4);
This calls setRowsCol, and inside the body of setRowsCol, c will have the value 1 and r will have the value 4.
With these changes, the variables c and r don't need to be global any more so you should declare them as local variables in drawBricks:
function drawBricks() {
var c;
var r;
// columns 0 and 1 each have (bricksRowCount-1) bricks
for (c = 0; c < 2; c++) {
for(r = 0; r < bricksRowCount-1; r++) {
setRowsCol(c, r);
}
}
// etc.
}
Don't worry about efficiency at this stage
If you've written a program that takes more than three seconds to run and you want to make it run faster, that's when you need to improve its efficiency. But for now, efficiency should be very low on your priority list.
I am trying to assign each colour of my c[] array of colours to each of my six Orb objects. My loop just assigns all the colours to all of the objects. Any help greatly appreciated.
var orb = [];
var c = ["#C460E0",
"#F469A9",
"#69F5E7",
"#687DF2",
"#69F591",
"#F1Ea67"];
var col;
var num;
function setup() {
createCanvas(600, 600);
for (var i = 0; i < 6; i++)
orb[i] = new SoftOrb();
}
function draw() {
background(1);
for (var i = 0; i < orb.length; i++) {
orb[i].colour(); // <-----???!
orb[i].edges();
orb[i].display();
orb[i].move();
}
}
function SoftOrb() {
this.loc = createVector(random(width), random(height));
this.vel = createVector(0, 0);
this.col = col;
this.display = function() {
ellipse(this.loc.x, this.loc.y, 100, 100);
}
this.colour = function() {
noStroke();
for (var j = 0; j < c.length; j++) {
var index = c.indexOf(j);
fill(c[j]);
}
Step 1: Fix your syntax. You're missing closing curly brackets at the end of your code. Hopefully this is just a copy-paste error. But be careful with those, because they make it harder to answer you!
Step 2: You need to pass the color from the c array into the SoftOrb constructor. That might look like this:
function setup() {
createCanvas(600, 600);
for (var i = 0; i < 6; i++) {
orb[i] = new SoftOrb(c[i]);
}
}
Step 3: You need to modify your SoftOrb constructor to actually accept the color argument.
function SoftOrb(col) {
You're already doing the assignment of this.col = col; so now col is a specific color for a specific SoftOrb.
Step 4: Now you just need to use col to set the color.
this.colour = function() {
noStroke();
fill(col);
}
Note that you could just move this into the display() function, but that's more personal preference than anything.
Step 5: After you fix that, you'll still get errors because the edges() and move() functions don't exist. Get rid of them to test that your colors work before trying to move on to coding the next thing.
I am trying to create many droppable elements inside a loop. Here is the code:
for (var i = 0; i < 10; i++) {
for(var j = 0; j < 20; j++){
$("#main").append( '<a "href="javascript:void(0);" id="click'+i+'-'+j+'" onclick="change_to_blocked('+i+','+j+')"><img id="image'+i+'-'+j+'" src="http://localhost/free.png" />');
$("#main").append('');
tmp1 = i;
tmp2 = j;
$('#image'+i+'-'+j).droppable({
drop: function(e,ui) {
$('#image'+(i)+'-'+(j)).attr('src','/bot.png');
console.log(i);
}
});
}
$("#main").append('<br>'); }
However, it only applies to the last value of the loop.
You need to create a closure otherwise at the time the events occur the values of i and j will be the values of the last iteration of the loop.
One way is to wrap the code within loop in an IIFE - Immediately Invoked Function Expression
for (var i = 0; i < 10; i++) {
for (var j = 0; j < 20; j++) {
(function (i, j) {
$("#main").append('<a "href="javascript:void(0);" onclick="return showIndexes('+i +','+j+')">Item # '+i+'-'+j+'</a><br>');
})(i, j); //params used in the IIFE
}
}
By passing the values as arguments of the function they are closed in the function and won't be changed by subsequent iterations
Some of the html rendering was left out for clarity
When looping over arrays with jQuery, you can create a closure by using $.each which will provide you the index as first argument of the callback
DEMO
I have a situation where I'm writing multiple functions that share a method of work and would really like to refactor and tidy up this code, rather than having these same blocks occur countless times only to execute slight differences. Given a block of information, I scan through each entry and perform operations on the block in question. An example of the recurrent function is as follows:
function basicFunc() {
var totalX = 2000, totalY = 2000;
for (var y = 0; y < totalY; y++) {
for (var x = 0; x < totalX; x++) {
/* Fake operation for theory */
var fakeVar = (y * x);
}
}
};
Comparative to:
function eachBlock(whatToDo, totalX, totalY) {
for (var y = 0; y < totalY; y++) {
for (var x = 0; x < totalX; x++) {
/* Fake operation for theory */
return whatToDo;
}
}
};
function basicFunc() {
var totalX = 2000, totalY = 2000;
eachoBlock("var fakeVar = (y * x)", totalX, totalY);
};
This works fine for single lines to be passed as the whatToDo parameter. How would you go about passing multiple lines to eachBlock(), say if you have a bunch of operations to do instead of the one var FakeVar operation?
TLDR: How can you use a function call within a function to wrap the original block of code operations it performed before becoming a separate function?
return whatToDo; in your “faster” example causes both loops to be skipped immediately and the function to exit. They don’t have the same effect, so your benchmark doesn’t apply.
How to do it, anyways: pass a function, call the function. There’s not a huge distinction in JavaScript, which is one of its most useful features.
function eachBlock(whatToDo, totalX, totalY) {
for (var y = 0; y < totalY; y++) {
for (var x = 0; x < totalX; x++) {
/* Fake operation for theory */
whatToDo(y, x);
}
}
}
function basicFunc() {
var totalX = 2000, totalY = 2000;
eachoBlock(function(y, x) {
var fakeVar = (y * x);
…
}, totalX, totalY);
}