Building a grid using a string in Javascript - javascript

I've been working with a JavaScript textbook and going through some preliminary exercises. I'm self teaching myself currently.
One exercise calls to create an 8 X 8 board using "#". I did it very simply and was able to produce what the book looks like, and then I looked at the answer. I have a couple questions around the logic
let board2 = " # # # #\n# # # #";
for (let n = 0; n < 4; n++) {
console.log(board2);
}
The book does the following
let size = 8;
let board = "";
for (let y = 0; y < size; y++) {
for (let x = 0; x < size; x++) {
if ((x + y) % 2 == 0) {
board += " ";
} else {
board += "#";
}
}
board += "\n";
}
console.log(board);
What I understand is that if the sum of the X and Y coordinates are even it will add a " " to the board. How do you reach this logic? Is it for every other position in the string to have a space, and then odd numbers contain a "#"?

In even-numbered rows you want space in the even columns and # in the odd columns. Adding an even row and an even column always produces an even sum, so even columns will be when x + y is even.
In odd-numbered rows you want the opposite: space in the odd columns and # in the even columns. Adding an odd row and an odd column will produce an even sum, so odd columns will be when x + y is even.
So from this simple math we can see that we always want to add a space when x + y is even, otherwise add #, and it will alternate the layout in the rows.
Your method is fine when you can hard-code the grid size. The method in the book is more general -- it works with any grid size, and doesn't depend on there being an even number of rows.

Related

Big-O cost of pyramid algorithm

I'm working through trying to understand big-O notation, and I was wondering: what would be the big-O cost of a pyramid algorithm?
pyramid(2) results in:
#
###
I know one way of solving it is using nested for-loops like:
function pyramid(n) {
const totalLengthOfRow = n * 2 - 1
for (let row = 0; row < n; row++) {
var line = ''
var middleCol = Math.floor(totalLengthOfRow / 2)
for (let col = 0; col < totalLengthOfRow; col++) {
if (col >= middleCol - row && col <= middleCol + row) {
line += '#'
} else {
line += ' '
}
}
console.log(line)
}
}
So that should be O(n2) right? Since both for-loops grow as n grows. But what if I use string.repeat and get rid of the inner for loop?
Something like:
const numberOfHashes = 1 + row * 2
const numberOfSpaces = n * 2 - 1 - numberOfHashes
var line = ' '.repeat(numberOfSpaces / 2) + '#'.repeat(numberOfHashes) + ' '.repeat(numberOfSpaces / 2)
Is repeat just like a for-loop, since it also repeats based on the size of n?
Let's say the second algorithm is a bit smarter, because at each step you don't need to check (with the conditional statement) which character you have to print.
Nevertheless, the JS repeat function is no more than syntactic sugar for a loop. Thus, the two algorithms are (not only semantically, but also asymptotically) equivalent and, in particular, both are O(n^2).

Unable to get index, NaN was returned

I am trying to fetch a tile's neighbour in a hexagonal grid.
Both grids and tiles are React components, and I have a method handy in my Grid component to find all of a tile's neighbours
The method works fine for neighbours within the index, and I set up a modulus to wrap around to the other side of the grid if a tile was to get out of bounds. Those indexes return NaN.
/**
* Returns all neighbouring tiles of this tile
*/
getTileNeighbours(tile) {
// Checks all arrays in the two-dimensional grid if this tile exists
for (let i in this.state.grid) {
let array = this.state.grid[i]
for (let j in array) {
console.log("Looking...")
let item = array[j]
if (item.state.name === tile.state.name) {
console.log("Found you!")
// Gets the position of the tile
let j = array.indexOf(tile)
//console.log(`Tile was found at position [${i}, ${j}]. Returning neighbours.`)
let neighbours = []
// All possible permutations of neighbours
let positions = [
{i:0,j:-2}, {i:1,j:-1}, {i:1,j:1}, {i:0,j:2}, {i:-1,j:1}, {i:-1,j:-1}
]
// If neighbouring indexes are out of bounds, wraps around to the other edge of the arrays
for (let k in positions) {
let position = positions[k]
let xIndex = (i + position.i) % this.state.grid.length
let yIndex = (j + position.j) % array.length
console.log(`Resolving '(${i} + ${position.i}) % ${this.state.grid.length}': ${(i + position.i) % this.state.grid.length}`)
console.log(`Actual indexes used: 'this.state.grid[${xIndex}][${yIndex}]'`)
let match = this.state.grid[xIndex][yIndex]
if (match) neighbours.push(match)
}
return neighbours
}
}
}
}
Here's a capture of my JavaScript console in Chrome
I figured out what went wrong, it all came down to the variable types not all being numbers. i, j and k are all strings (since they are used in a for (x in y) context), at k = 5, we end up with i + position.i being equal to 0-1 as a string, which cannot be used with a modulus. By forcing i and j to be numbers, we no longer encounter our NaN.
Similarly, using the modulus to wrap around was a bad idea altogether.
It has been replaced with a ternary to add the length of the array when the resulting index was negative
Two lines were all I needed to change, here they are
let xIndex = Number(i) + position.i < 0 ? Number(i) + position.i + this.state.grid.length : Number(i) + position.i
let yIndex = Number(j) + position.j < 0 ? Number(j) + position.j + array.length : Number(j) + position.j
This was all due to a gross lack of attention on my end, I should have troubleshooted better.

Trying to optimize my code to either remove nested loop or make it more efficient

A friend of mine takes a sequence of numbers from 1 to n (where n > 0)
Within that sequence, he chooses two numbers, a and b
He says that the product of a and b should be equal to the sum of all numbers in the sequence, excluding a and b
Given a number n, could you tell me the numbers he excluded from the sequence?
Have found the solution to this Kata from Code Wars but it times out (After 12 seconds) in the editor when I run it; any ideas as too how I should further optimize the nested for loop and or remove it?
function removeNb(n) {
var nArray = [];
var sum = 0;
var answersArray = [];
for (let i = 1; i <= n; i++) {
nArray.push(n - (n - i));
sum += i;
}
var length = nArray.length;
for (let i = Math.round(n / 2); i < length; i++) {
for (let y = Math.round(n / 2); y < length; y++) {
if (i != y) {
if (i * y === sum - i - y) {
answersArray.push([i, y]);
break;
}
}
}
}
return answersArray;
}
console.log(removeNb(102));
.as-console-wrapper { max-height: 100% !important; top: 0; }
I think there is no reason for calculating the sum after you fill the array, you can do that while filling it.
function removeNb(n) {
let nArray = [];
let sum = 0;
for(let i = 1; i <= n; i++) {
nArray.push(i);
sum += i;
}
}
And since there could be only two numbers a and b as the inputs for the formula a * b = sum - a - b, there could be only one possible value for each of them. So, there's no need to continue the loop when you find them.
if(i*y === sum - i - y) {
answersArray.push([i,y]);
break;
}
I recommend looking at the problem in another way.
You are trying to find two numbers a and b using this formula a * b = sum - a - b.
Why not reduce the formula like this:
a * b + a = sum - b
a ( b + 1 ) = sum - b
a = (sum - b) / ( b + 1 )
Then you only need one for loop that produces the value of b, check if (sum - b) is divisible by ( b + 1 ) and if the division produces a number that is less than n.
for(let i = 1; i <= n; i++) {
let eq1 = sum - i;
let eq2 = i + 1;
if (eq1 % eq2 === 0) {
let a = eq1 / eq2;
if (a < n && a != i) {
return [[a, b], [b, a]];
}
}
}
You can solve this in linear time with two pointers method (page 77 in the book).
In order to gain intuition towards a solution, let's start thinking about this part of your code:
for(let i = Math.round(n/2); i < length; i++) {
for(let y = Math.round(n/2); y < length; y++) {
...
You already figured out this is the part of your code that is slow. You are trying every combination of i and y, but what if you didn't have to try every single combination?
Let's take a small example to illustrate why you don't have to try every combination.
Suppose n == 10 so we have 1 2 3 4 5 6 7 8 9 10 where sum = 55.
Suppose the first combination we tried was 1*10.
Does it make sense to try 1*9 next? Of course not, since we know that 1*10 < 55-10-1 we know we have to increase our product, not decrease it.
So let's try 2*10. Well, 20 < 55-10-2 so we still have to increase.
3*10==30 < 55-3-10==42
4*10==40 < 55-4-10==41
But then 5*10==50 > 55-5-10==40. Now we know we have to decrease our product. We could either decrease 5 or we could decrease 10, but we already know that there is no solution if we decrease 5 (since we tried that in the previous step). So the only choice is to decrease 10.
5*9==45 > 55-5-9==41. Same thing again: we have to decrease 9.
5*8==40 < 55-5-8==42. And now we have to increase again...
You can think about the above example as having 2 pointers which are initialized to the beginning and end of the sequence. At every step we either
move the left pointer towards right
or move the right pointer towards left
In the beginning the difference between pointers is n-1. At every step the difference between pointers decreases by one. We can stop when the pointers cross each other (and say that no solution can be obtained if one was not found so far). So clearly we can not do more than n computations before arriving at a solution. This is what it means to say that the solution is linear with respect to n; no matter how large n grows, we never do more than n computations. Contrast this to your original solution, where we actually end up doing n^2 computations as n grows large.
Hassan is correct, here is a full solution:
function removeNb (n) {
var a = 1;
var d = 1;
// Calculate the sum of the numbers 1-n without anything removed
var S = 0.5 * n * (2*a + (d *(n-1)));
// For each possible value of b, calculate a if it exists.
var results = [];
for (let numB = a; numB <= n; numB++) {
let eq1 = S - numB;
let eq2 = numB + 1;
if (eq1 % eq2 === 0) {
let numA = eq1 / eq2;
if (numA < n && numA != numB) {
results.push([numA, numB]);
results.push([numB, numA]);
}
}
}
return results;
}
In case it's of interest, CY Aries pointed this out:
ab + a + b = n(n + 1)/2
add 1 to both sides
ab + a + b + 1 = (n^2 + n + 2) / 2
(a + 1)(b + 1) = (n^2 + n + 2) / 2
so we're looking for factors of (n^2 + n + 2) / 2 and have some indication about the least size of the factor. This doesn't necessarily imply a great improvement in complexity for the actual search but still it's kind of cool.
This is part comment, part answer.
In engineering terms, the original function posted is using "brute force" to solve the problem, iterating every (or more than needed) possible combinations. The number of iterations is n is large - if you did all possible it would be
n * (n-1) = bazillio n
Less is More
So lets look at things that can be optimized, first some minor things, I'm a little confused about the first for loop and nArray:
// OP's code
for(let i = 1; i <= n; i++) {
nArray.push(n - (n - i));
sum += i;
}
??? You don't really use nArray for anything? Length is just n .. am I so sleep deprived I'm missing something? And while you can sum a consecutive sequence of integers 1-n by using a for loop, there is a direct and easy way that avoids a loop:
sum = ( n + 1 ) * n * 0.5 ;
THE LOOPS
// OP's loops, not optimized
for(let i = Math.round(n/2); i < length; i++) {
for(let y = Math.round(n/2); y < length; y++) {
if(i != y) {
if(i*y === sum - i - y) {
Optimization Considerations:
I see you're on the right track in a way, cutting the starting i, y values in half since the factors . But you're iterating both of them in the same direction : UP. And also, the lower numbers look like they can go a little below half of n (perhaps not because the sequence start at 1, I haven't confirmed that, but it seems the case).
Plus we want to avoid division every time we start an instantiation of the loop (i.e set the variable once, and also we're going to change it). And finally, with the IF statements, i and y will never be equal to each other the way we're going to create the loops, so that's a conditional that can vanish.
But the more important thing is the direction of transversing the loops. The smaller factor low is probably going to be close to the lowest loop value (about half of n) and the larger factor hi is probably going to be near the value of n. If we has some solid math theory that said something like "hi will never be less than 0.75n" then we could make a couple mods to take advantage of that knowledge.
The way the loops are show below, they break and iterate before the hi and low loops meet.
Moreover, it doesn't matter which loop picks the lower or higher number, so we can use this to shorten the inner loop as number pairs are tested, making the loop smaller each time. We don't want to waste time checking the same pair of numbers more than once! The lower factor's loop will start a little below half of n and go up, and the higher factor's loop will start at n and go down.
// Code Fragment, more optimized:
let nHi = n;
let low = Math.trunc( n * 0.49 );
let sum = ( n + 1 ) * n * 0.5 ;
// While Loop for the outside (incrementing) loop
while( low < nHi ) {
// FOR loop for the inside decrementing loop
for(let hi = nHi; hi > low; hi--) {
// If we're higher than the sum, we exit, decrement.
if( hi * low + hi + low > sum ) {
continue;
}
// If we're equal, then we're DONE and we write to array.
else if( hi * low + hi + low === sum) {
answersArray.push([hi, low]);
low = nHi; // Note this is if we want to end once finding one pair
break; // If you want to find ALL pairs for large numbers then replace these low = nHi; with low++;
}
// And if not, we increment the low counter and restart the hi loop from the top.
else {
low++;
break;
}
} // close for
} // close while
Tutorial:
So we set the few variables. Note that low is set slightly less than half of n, as larger numbers look like they could be a few points less. Also, we don't round, we truncate, which is essentially "always rounding down", and is slightly better for performance, (though it dosenit matter in this instance with just the single assignment).
The while loop starts at the lowest value and increments, potentially all the way up to n-1. The hi FOR loop starts at n (copied to nHi), and then decrements until the factor are found OR it intercepts at low + 1.
The conditionals:
First IF: If we're higher than the sum, we exit, decrement, and continue at a lower value for the hi factor.
ELSE IF: If we are EQUAL, then we're done, and break for lunch. We set low = nHi so that when we break out of the FOR loop, we will also exit the WHILE loop.
ELSE: If we get here it's because we're less than the sum, so we need to increment the while loop and reset the hi FOR loop to start again from n (nHi).

Printing 2d array in javascript

Like the picture above, how would it be possible to create four separate lines of text, given the word defendtheeastwallofthecastle while using Javascript?
Math solution
Take a closer look at your output:
a.....g.....m.....s.
.b...f.h...l.n...r.t
..c.e...i.k...o.q...
...d.....j.....p....
Note that it can be splitted into similiar repeating blocks:
a..... g..... m..... s.
.b...f .h...l .n...r .t
..c.e. ..i.k. ..o.q. ..
...d.. ...j.. ...p.. ..
The length of this blocks is calculable: every row, except for the first one and the last one, has 2 letters. The total length will be: rows * 2 - 2. Let's call it blockLength. By the way, x * 2 - 2 is always even - it is important.
Now, you can see that in every block the letters are "sinking" in the left half, and arising in the second one. So, if you make some observations and analysis, you will understand that for blockLength == 6 you need to output letters at i:
row | i % blockLength
----------------------------
0 | 0
1 | 1, blockLength - 1
2 | 2, blockLength - 2
3 | 3
After i exceeds blockLength, it will repeat again and again, until the end of the string. This regularity can be easily converted to a JavaScript loop, if you know its basics.
Lazy solution
Within a loop set values in zig-zag order:
var str = 'abcdefghijklmopqrst';
var rows = 4, letterRows = [], currentRow = 0, direction = 1;
for (var i = 0; i < str.length; i++)
{
letterRows.push(currentRow);
currentRow += direction;
if ((direction === 1 && currentRow == rows - 1) // bottom limit
|| (direction === -1 && currentRow == 0)) // top limit
{
direction = direction * -1; // invert direction
}
}
Then, within nested loops simply output your letters according to letterRows:
for (var row = 0; row < rows; row++)
{
for (var i = 0; i < str.length; i++)
{
output(letterRows[i] == row ? str[i] : '.'); // output is any possible output in your case
}
output('\n');
}

Calculate maximum available rows and columns to fill with N amount of items

By reviewing this and this, I've come up with a function, that's probably more complex than it should be, but, man, my math sux:
function tablize(elements)
{
var root = Math.floor(Math.sqrt(elements));
var factors = [];
for (var i = 1; i <= root; i++)
{
if (elements % i === 0)
{
factors.push([i, elements / i]);
}
}
var smallest = null;
for (var f = 0; f < factors.length; f++)
{
var factor = factors[f];
var current = Math.abs(factor[0] - factor[1]);
if (!smallest || factors[smallest] > factor)
{
smallest = f;
}
}
return factors[smallest];
}
While this does work, it provides results I'm not satisfied with.
For instance - 7, it's divided in 1x7, where I'd like it to be 3x3. That's the minimum, optimal, grid size needed to fill with 7 elements.
Also - 3, it's divided in 1x3, where I'd like it to be 2x2.
I need this for a live camera feed frame distribution on a monitor, but I'm totally lost. The only way I can think of is building an extra function to feed with previously generated number and divide again, but that seems wrong.
What is the optimal solution to solve this?
For squares:
function squareNeeded(num) {
return Math.ceil(Math.sqrt(num));
}
http://jsfiddle.net/aKNVq/
(I think you mean the smallest square of a whole number that is bigger than the given amount, because if you meant a rectangle, then your example for seven would be 2*4 instead of 3*3.)

Categories

Resources