Javascript double nested for loop traversing - javascript

I am fairly new to JavaScript and I am trying to find a way to write this without using a double for loop by either using higher order function or data structures.
I have these two defined type here
List = {
'start' ? : Position
'end' ? : Position
}
Position = {
row?: number;
column?: number;
}
This is the for loop that I believe can be optimized or avoid using for loop for.
for (let row = list.start.row; row <= list.end.row; row++) {
for (let col = list.start.column; col <= list.end.column; col++) {
console.log(`row: ${row}, col: ${col}`);
}
}
So if the I have the list defined as
list.start.row = 2, list.start.column = 4
list.end.row = 3, list.start.column = 6
I should have (2,4) (2,5) (2,6) (3,4) (3,5) (3,6) output as the console messages and order does not matter. What would be the most optimal way of doing this? Thank you in advance!

You can absolutely do this with a single loop. In fact its a very common way to navigate multi dimensional arrays in memory. For example, you have a chunk of raw image data, and you want to get the pixel information at point(80,120). You'd have to be able to convert the (x,y) coordinates to some offset. This is trivial if you know the width of a row. To get the pixel information, assuming each pixel is 8 bits simply calculate i = y * width + x. You can use this same technique to create a multi dimensional array using one loop. You'd have to apply the above formula in reverse to find the (x,y) points from (i, width). Computing the width is easy, its the end.column - start.column and calculating the number of elements would be the height * width. The height is obviously the difference in rows. You would then loop through your entire linear domain and project the i back to (x,y) in your array.
It might be easier to just look at the sample code.
const list = {
start: { row: 2, column: 4 },
end: { row: 3, column: 6 }
};
const x0 = list.start.column;
const y0 = list.start.row;
const x1 = list.end.column;
const y1 = list.end.row;
const width = x1 - x0 + 1; // range is inclusive so [2,2] has a width of 1.
const height = y1 - y0 + 1; // same here.
const results = Array.from({ length: width * height }, (_, n) => {
const row = (n - n % width) / width + y0;
const column = n % width + x0;
return [row, column];
});
console.log(results);
Should you do it instead of a nested loop? Probably not in javascript. Complexity is still O(mn) and so its no faster algorithmically then a nested loop. The compiler/vm will likely optimize a [x,y] nested loop into something like this for you anyway.

It always will be a nested loop if you want to print all values of x for all values of y. You can use different syntax, such as forEach, but it'll always be the same concept - and the solution you have is actually the most optimal and efficient (as there'll always be xy combinations for x and y values; so if your time complexity is O(mn), it's fine).

Related

Referencing a previous index in a 2D generated array - Javascript

I am trying to build an application that generates a random map using square tiles. To do this my plan is to use a 2D array that randomly selects the first tile in a row then, when randomly selecting the second tile, it references the right edge value of the previous tile and filters the possible random tiles it can pick from down to an array of only those that match. This process repeats till the row is complete, then when starting a subsequent row the filter will apply to both the right edge of the previous tile and the bottom edge of the one above.
I have gotten to the point where I can generate an array filled with random tiles but I can't get the filtering process to work. I have tried using a .filter in the random selection function but when it tries to reference the previous index that index is undefined.
The tiles are an array of objects like this:
1 means a connected edge, 0 means no connection. Essentially the tiles just have to match 1's with 1's and 0's with 0's.
[
{
"top": 1,
"right": 0,
"bottom": 1,
"left": 0,
"img": "../public/images/1.png"
},
{
"top": 0,
"right": 1,
"bottom": 0,
"left": 1,
"img": "../public/images/2.png"
}
]
My current code looks like this:
// Setting the number of row and columns and creating the empty array
const rows = 5;
const columns = 5
const map = [];
// Populate the empty array with a 2D array of random tile objects.
function generateMap() {
for (let y = 0; y < rows; y++) {
map[y] = [];
for (let x = 0; x < columns; x++) {
map[y][x] = randomlyPopulateMap(x, y);
}
}
}
function randomlyPopulateMap(x, y) {
const randNum = Math.floor(Math.random() * tiles.length);
const randTile = tiles[randNum];
return randTile;
}
I've tried what feels like a dozen solutions but I'm still new to coding and I'm uncertain if I'm even taking the right approach to this problem. Any guidance is greatly appreciated.
I can imagine randomlyPopulateMap first filtering the tiles based on whether they match left-to-right with the tile to the left and top-to-bottom with the tile above. I'm using the condition that if there is no tile above to restrict then any tile will do. You can decide if the boundary itself imposes any additional restrictions.
function randomlyPopulateMap(x, y) {
const candidates = tiles.filter(tile =>
(x == 0 || tile.left == map[y][x-1].right) &&
(y == 0 || tile.top == map[y-1][x].bottom))
and then choose a random tile from among the remaining candidates.
const randNum = Math.floor(Math.random() * candidates.length);
const randTile = candidates[randNum];
return randTile;
}

Is there a way to see if a value in a 2D array matches any value in another 2D array?

I'm building a battleship game in Javascript and React and I've been stuck on this issue for a while now even after much Googling and StackOverflowing.
Basically my board is a 2D array, with 10 arrays inside of one array. I'm trying to randomly place ships and I'm having difficulties checking if a ship intersects another ship.
Here's what I have for my ships:
placeShips = () => {
// Logic to place boats randomly below
// Checks required before placing a boat:
// 1. Does the boat go off the board
// 2. Does the boat overlap another boat
// 3. If checks above pass then place boat
let placedPosition = []
let board = this.state.board.slice()
let i
for (i = 0; i < this.state.ships.length; i++) {
// First randomly select coordinates for where the boat will start
let xcoord = Math.floor(Math.random() * 10)
let ycoord = Math.floor(Math.random() * 10)
// Get positions in array where a boat will be
let potentialBoat = []
let newCoords
let j
for (j = 0; j < this.state.ships[i].getLength(); j++) {
newCoords = [xcoord, ycoord + j]
potentialBoat.push(newCoords)
The first for loop repeats for each ship left in my state to place and the second for loop takes a ship's length, gets the intended coordinate ([[0, 1], [0,2]] for a 2 length ship for example) and stores it in the potentialBoat array.
My idea is to use this potentialBoat array and see if there's any [xcoordinate, ycoordinate] that exists already in the placedPosition array and if so, to loop again for the current boat and get new coordinates until they don't intersect.
Is this possible? Or should I rethink my entire implementation? Thanks!
Inside the inner loop, when in the process of creating a ship, consider creating a string representing the coordinates. Eg, for newCoords of 1, 3, create a string 1_3. To validate the location, check to see if that string exists in an array (or Set) of the locations of the validated ships. At the end of the inner loop, once all positions for the length of the ship have been validated, combine the possible-locations into the validated-locations array:
placeShips = () => {
const placedPosition = [];
const board = this.state.board.slice();
const validatedPositionStrings = []; // <---- Create this array
for (const ship of this.state.ships) {
const thisShipLength = ship.getLength();
tryShip:
while (true) {
const thisBoatPossiblePositionStrings = [];
// Generate ship positions until valid
const xcoord = Math.floor(Math.random() * 10);
const ycoord = Math.floor(Math.random() * 10);
const potentialBoat = [];
for (let j = 0; j < thisShipLength; j++) {
// Then check to see if the below position is already in it
const thisCoordinateString = `${x}_${y}`;
if (validatedPositionStrings.includes(thisCoordinateString)) {
// Invalid
continue tryShip;
}
thisBoatPossiblePositionStrings.push(thisCoordinateString);
// If this point is reached, then this particular coordinate is valid
// do whatever you need to do:
const newCoords = [xcoord, ycoord + j];
potentialBoat.push(newCoords);
}
// All positions for ship are valid
// do something with potentialBoat here?
// push positions to placedPosition?
validatedPositionStrings.push(...thisBoatPossiblePositionStrings);
break;
}
}
}
It could be made less computationally complex by using a Set instead of an array, but that probably doesn't matter unless there are a very large number of iterations.
It would also be possible to search your array of arrays to see if the position has already been placed, but that would require an unnecessary amount of code IMO.
If possible, you might consider changing your data structure around so that rather than an array of arrays, you have just a single object representing the coordinates, whose values indicate the ship at that position (and possibly other attributes needed for a particular point), eg:
{
1_3: { ship: 'destroyer', 'attackedYet': 'false' }
// ...
Such an object would probably be easier to look up and work through than an array of arrays of X-Y pairs.

Javascript Easing not working correctly - EaseInCirc?

using Javascript in Photoshop Scripting, I wish to change the Opacity of X layers from Opacity 0 to Opacity 100, but in a gradual/slow to lastly hastened manner e.g. 'EaseInCirc' I believe.
The code I am using (not successfully) is: https://jsfiddle.net/09onhmy7/
numberOfLayers = 20;
myOpacity = 0;
x=0;
for(i = 0 ; i < numberOfLayers ; i++){
myIncrements = EaseExpo(i, 1, x, numberOfLayers);
x = myIncrements + myIncrements;
myOpacity = myOpacity + parseInt(myIncrements,10);
if (myOpacity > 100) {myOpacity = 100 ;}
document.write(myOpacity+",");
}
I found the EaseExpo(EaseInCirc) code on-line (http://gizma.com/easing/):
// t = current time
// b = start value
// c = change in value
// d = duration
// (t and d) can be frames
function EaseExpo(t, b, c, d) {
//EaseInCirc
return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
}
…but so far the code returns values that don't distribute as expected from 0 - 100 -- and most likely never will, as I don't know how to go about transposing them to suit the 0-100 bounds.
Basically, I'm trying to create an iteration of values (that aren't lineal) from 0 - X to represent a rate of change that slowly ramps up to maximum velocity (I'm pretty sure it’s a squareroot graph/function?) that I'm trying to replicate.
The values that will be input will always start at 0, but could have a maximum of any value.
The trick is, I need to be able to always transpose the end values into a 0-100 result.
e.g. A) 40 values = 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,30,32,34,36,39,42,46,51,58,68,83,100
e.g. B) 18 values = 1,3,5,7,9,11,13,15,17,20,23,27,32,39,49,64,88,100
I cant for the life of me work out how to do it -- or should I be using different math to achieve what I want?
Many thanks in advance, Livy

Array position difference beetween two elements

I've got this array:
[1,2,3,4,5]
I want to determine the position difference between two elements, but in a "rotation" mode.
Example:
I have 3 and I want to know how far is 2, I do a 3.position - 2.position, and I've got 1.
But, if I have 5, and I want to know the position difference beetween 5.position and 1.position, with the precedent difference, I will have 5-1=4, and because there is array rotation, I want 1.
Do you have an idea of how I can do that ? (I work in Javascript)
EDIT:
Here is a draw that can explain clearer what I want to do
EDIT 2: Better draw
Calculate both the distance within the array, and the distance when wrapping around, and use the smallest.
This code uses pos1 and pos2 as the index of the items in the array arr, and assumes that pos1 < pos2:
var distance = Math.min(pos2 - pos1, pos1 + arr.length - pos2);
Guffa's answer is far more succinct and simple, but I already wrote the JSfiddle so I'll share.
var testArray = [1,2,3,4,5];
function getShortestDistance(element1Name, element2Name){
var result = -1;
var position1 = testArray.indexOf(element1Name);
var position2 = testArray.indexOf(element2Name);
// The distance should never be greater than half of the array length, half being defined as integer division by 2
var maxDistance = Math.floor(testArray.length/2);
if(Math.abs(position1 - position2) >= maxDistance){
result = Math.abs(testArray.length - Math.abs(position1 - position2));
}
else{
result = Math.abs(position1-position2);
}
alert('position1 is ' + position1);
alert('position2 is ' + position2);
alert('Distance is ' + result);
}
getShortestDistance(2,3); // Will return 1
getShortestDistance(1,4); // Will return 2
Here's the JSFiddle in case you want to modify it: http://jsfiddle.net/u71vbeau/16/

How to find selected elements within a javascript marquee selection box without using a loop?

I am writing my own drag and drop file manager. This includes a javascript marquee selection box which when active calculates the elements (files) that are intersected and selects them by adding a class to them.
I currently perform the check during a mousemove handler, loop through an array of element coordinates and determine which ones are intersected by the drag and drop selection box.
The function currently looks like this:
selectItems : function(voidindex){
var self = this;
var coords = self.cache.selectioncoords;
for(var i=0, len = self.cache.items.length; i<len; i++){
var item = self.cache.items[i];
var itemcoords = item.box_pos;
if(coords.topleft.x < (itemcoords.x+201) && coords.topright.x > itemcoords.x && coords.topleft.y < (itemcoords.y+221) && coords.bottomleft.y > itemcoords.y){
if(!item.selected){
item.selected = true;
item.html.addClass('selected').removeClass('activebutton');
self.cache.selecteditems.push(i);
self.setInfo();
}
}
else{
if(item.selected){
item.selected = false;
if(!voidindex || voidindex !== i){
item.html.removeClass('selected');
}
var removeindex = self.cache.selecteditems.indexOf(i);
self.cache.selecteditems.splice(removeindex, 1);
self.setInfo();
}
}
}
},
There is lots of dirty logic in the code above which ensures that the DOM is only manipulated when the selection changes. This is not relevant to the question and can be exluded. The important part is the intersection logic which checks the coordinates of the element versus the coordinates of the marquee selection box.
Also please note that the item dimensions are fixed at 201px width by 221px height.
I have tested this and all works perfectly, however I have the need to support potentially thousands of files which would mean that at some point we will start seeing UI performance decrease.
I would like to know if there is anyway to perform intersection detection without looping through the coordinates of each element.
The coordinates of the marquee box are defined as follows at any given time:
selectioncoords : {
topleft : {
x : 0,
y : 0
},
topright : {
x : 0,
y : 0
},
bottomleft : {
x : 0,
y : 0
},
bottomright : {
x : 0,
y : 0
},
width : 0,
height : 0
}
And the coordinates of each item, stored in the self.cache.items array are defined as follows:
item : {
box_pos : {
x : 0,
y : 0
},
grid_pos : {
row : 1,
column : 1
}
}
So the information available will always be the actual grid position (row/column) as well as the physical item position (left and top offsets in pixels within the grid).
So to summarize, the question is, is there anyway to detect item intersection from a set of marquee selection box coordinates as defined above without looping through the whole array of item coordinates every time the mousemove event fires?
Thanks in advance for any help.
The following depends upon a locked grid with the dimensions as described.
You are comparing a mouse-defined rectangle against a grid with static edge sizes. Thus, given an x coordinate or a y coordinate, you should be able to derive pretty easily which column or row (respectively) the coordinate falls into.
When the user starts the select box, grab that x and y, and find the row/column of the start. When the mouse moves while pulling the select box, you find (and then update) the row/column of the finish. anything that is both within the rows defined by that box and within the columns defined by that box (inclusive) is selected. If you then keep your selectable elements in a two-dimensional array according to rows and columns, you should be able to just grab the ones you want that way.
Mind, how much more (or less) efficient this is depends on the size of your expected selection boxes as compared to the total size, and the degree to which you expect the grid to be populated. Certainly, if the average use case is selecting half or so of the objects at a time, there's not a whole lot you can do to cut down efficiently on the number of objects you have to look at each time.
Also, though it is kludgy, you can have the mousemove handler not fire every time. Letting it pause a bit between updates will reduce the responsiveness of this particular function a fair bit, but it'll cut down significantly on the amount of resources that are used.
There are several ways you could approach this. Here's one. First you need the items in some kind of organized structure that you can look up quickly by row and column. You could use a two-dimensional array, or for simplicity I'm going to use a hash table. You could do this at the same time that you create the self.cache.items, or later, something like this:
var cacheLookup = {};
function initCacheLookup() {
var items = self.cache.items;
for( var i = 0, n = items.length; i < n; i++ ) {
var item = items[i];
var key = [ item.grid_pos.row, item.grid_pos.column ].join(',');
cacheLookup[key] = item;
}
}
Then when you want to get the items intersecting the rectangle, you could do something like this:
var itemWidth = 201, itemHeight = 221;
var tl = selectioncoords.topleft, br = selectioncoords.bottomright;
var left = Math.floor( tl.x / itemWidth ) + 1;
var right = Math.floor( br.x / itemWidth ) + 1;
var top = Math.floor( tl.y / itemHeight ) + 1;
var bottom = Math.floor( br.y / itemHeight ) + 1;
var selecteditems = [];
for( var row = top; row <= bottom; row++ ) {
for( var col = left; col <= right; col++ ) {
var key = [ row, col ].join(',');
var item = cacheLookup[key];
if( item ) {
selecteditems.push( item );
}
}
}
// Now selecteditems has the items intersecting the rectangle
There's probably an off-by-one error or two here, but this should be close.
Well, as I said, that is one way to do it. And it has the possibly interesting property that it doesn't depend on the order of items in the self.cache.items array. But that cacheLookup hash table smells like it might not be the most efficient solution.
Let me take a guess: isn't that array already in the correct order by rows and columns (or vice versa)? For example, if your grid is four wide, then the top row would be array elements 0-3, the second row 4-7, the third row 8-11, etc. Or it could be a similar arrangement going down the columns.
Assuming it's in row-by-row order, then you don't need the hash table at all. That initCacheLookup() function goes away, and instead the search code looks like this:
var nCols = 4/*whatever*/; // defined somewhere else
var itemWidth = 201, itemHeight = 221;
var tl = selectioncoords.topleft, br = selectioncoords.bottomright;
var left = Math.floor( tl.x / itemWidth );
var right = Math.floor( br.x / itemWidth );
var top = Math.floor( tl.y / itemHeight ) * nCols;
var bottom = Math.floor( br.y / itemHeight ) * nCols;
var items = self.cache.items;
var selecteditems = [];
for( var iRow = top; iRow <= bottom; iRow += nCols ) {
for( var col = left; col <= right; col++ ) {
var index = iRow + col;
if( index < items.length ) {
selecteditems.push( items[index] );
}
}
}
// Now selecteditems has the items intersecting the rectangle
This code will be a little faster, and it's simpler too. Also it doesn't depend at all on the item.box_pos and item.grid_pos. You may not need those data fields at all, because they are easily calculated from the item index, grid column count, and item height and width.
Some related notes:
Don't hard code 201 and 221 in the code. Store those in variables once, only, and then use those variables when you need the item height and width.
There is a lot of duplication in your data structures. I recommend that you ruthlessly eliminate all duplicated data unless there is a specific need for it. Specifically:
selectioncoords: {
topleft: {
x: 0,
y: 0
},
topright: {
x: 0,
y: 0
},
bottomleft: {
x: 0,
y: 0
},
bottomright: {
x: 0,
y: 0
},
width: 0,
height: 0
}
More than half the data here is duplicated or can be calculated. This is all you need:
selectioncoords: {
left: 0,
right: 0,
top: 0,
bottom: 0
}
The reason I bring this up is that was a bit confusing when working on the code: "I want the left edge. Do I get that from topleft.x or bottomleft.x? Are they really the same like they seem? How do I pick?"
Also, as mentioned above, the item.box_pos and item.grid_pos may not be needed at all if the items are stored in a sequential array. If they are needed, you could store just one and calculate the other from it, since there's a direct relationship between the two:
box_pos.x === ( grid_pos.column - 1 ) * itemWidth
box_pos.y === ( grid_pos.row - 1 ) * itemHeight
You can limit the scope of your checks by indexing each item in a grid, as often as necessary and no more often. You can use the grid to give you a list of elements near an X, Y coordinate or that might be in an X1, Y2, X1, Y2 range.
To get you started ...
var Grid = function(pixelWidth, pixelHeight, boxSize) {
this.cellsIn = function(x1, y1, x2, y2) {
var rv = [];
for (var x = x1; x < x2; x += boxSize) {
for (var y = y1; y < y2; y += boxSize) {
var gx = Math.ceil(x/boxSize);
var gy = Math.ceil(y/boxSize);
rv.push(this.cells[gx][gy]);
}
}
return rv;
} // cellsIn()
this.add = function(x1, y1, x2, y2, o) {
var cells = this.cellsIn(x1, y1, x2, y2);
for (var i in cells) {
cells[i].push(o);
}
} // add()
this.get = function(x1, y1, x2, y2) {
var rv = [];
var rv_index = {};
var cells = this.cellsIn(x1, y1, x2, y2);
for (var i in cells) {
var cell = cells[i];
for (var oi in cell) {
if (!rv_index[cell[oi]]) {
rv_index[cell[oi]] = 1;
rv.push(cell[oi]);
}
}
}
return rv;
} // get()
this.cells = [];
for (var x = 0; x < Math.ceil(pixelWidth/boxSize); x++) {
this.cells[x] = [];
for (var y = 0; y < Math.ceil(pixelHeight/boxSize); y++) {
this.cells[x][y] = [];
}
}
};
So, rather than iterating through all possible objects, whatever they may be, you iterate over all the objects that are near or potentially in the given coordinates.
This requires that you maintain/re-index the grid as item coordinates change. And you'll likely want to add some functionality to the above (or similar) Grid class to modify/move existing objects. But, to the best of my knowledge, an index of this sort is the best, if not only, way to index objects "in space."
Disclaimer: The code above isn't tested. But, I have similar code that is. See the DemoGrid function class here: http://www.thepointless.com/js/ascii_monsters.js
The functionality of my DemoGrid is similar (as far as I remember, it's been awhile), but accepts x, y, radius as parameters instead. Also notable, my mouse events don't touch the grid every time the event fires. Checks are rate-limited by a game/main loop.
If the system is set up such that
self.cache.items is ordered from left to right and top to bottom
(0,0),(1,0),(2,0),(0,1),(1,1),(1,2),(0,2),(1,2),(2,2)
There is an item in each space
GOOD - (0,0),(1,0),(2,0),(0,1),(1,1),(1,2),(0,2),(1,2),(2,2)
BAD - (0,0),(2,0)(1,2),(1,3),(2,1),(2,3)
We need to know the total number of columns.
So the code to get you started.
// Some 'constants' we'll need.
number_of_columns = 4;
item_width = 201;
item_height = 221;
// First off, we are dealing with a grid system,
// so that means that if given the starting x and y of the marquee,
// we can determine which element in the cache to start where we begin.
top_left_selected_index = Math.floor(selectioncoords.topleft.x / item_width) + (Math.floor(selectioncoords.topright.y / item_height) * number_of_columns );
// Now, because the array is in order, and there are no empty cache points,
// we know that the lower bound of the selected items is `top_left_selected_index`
// so all we have to do is walk the array to grab the other selected.
number_columns_selected = (selectioncoords.bottomright.x - selectioncoords.topleft.x) / item_width;
// if it it doesn't divide exactly it means there is an extra column selected
if((selectioncoords.bottomright.x - selectioncoords.topleft.x) % item_width > 0){
number_columns_selected += 1;
}
// if it it doesn't divide exactly it means there is an extra column selected
number_rows_selected = (selectioncoords.bottomright.y - selectioncoords.topleft.y) / item_height;
if((selectioncoords.bottomright.y - selectioncoords.topleft.y) % item_height > 0){
number_rows_selected += 1;
}
// Outer loop handles the moving the pointer in terms of the row, so it
// increments by the number of columns.
// EX: Given my simple example array, To get from (1,0) to (1,1)
// requires an index increase of 3
for(i=0; i < number_rows_selected; i++){
// Inner loop marches through the the columns, so it is just one at a time.
// Added j < number_of_columns in case your marquee stretches well past your content
for(j=0; j < number_columns_selected && j < number_of_columns; j++){
// Do stuff to the selected items.
self.cache.items[top_left_selected_index + (i * number_of_columns) + j];
}
}

Categories

Resources