Improvements in Algo/code for following HackerRank problem - javascript

I'm aware, SO is not a place for homework and hence, being very specific to the scope of question.
I was trying to solve this problem on HackerRank: Array Manipulation - Crush. The problem statement is quite simple and I implemented following code:
function arrayManipulation(n, queries) {
const arr = new Array(n).fill(0)
for (let j = 0; j < queries.length; j++) {
const query = queries[j];
const i = query[0] - 1;
const limit = query[1];
const value = query[2];
while (i < limit) {
arr[i++] += value;
}
}
return Math.max.apply(null, arr);
}
Now, it works fine for half the test-cases but breaks with following message: Terminated due to timeout for test-cases 7 - 13 as the time limit is 1 sec.
So the question is, what are the areas where I can improve this code. In my understanding, with current algo, there is not much scope (I may be wrong), so how can I improve algo?
Note: Not looking for alternates using array functions like .map or .reduce as for is faster. Also, using Math.max.apply(context, array) as it is faster that having custom loop. Attaching references for them.
References:
How might I find the largest number contained in a JavaScript array?
Javascript efficiency: 'for' vs 'forEach'

We could make some observations for this problem
Let's keep a running sum representing the current value when we iterate from start to end of the array.
If we break each operation into two other operation (a b k) -> (a k) and (b -k) with (a k) means adding k into the running sum at position a and (b -k) means subtracting k from the sum at position b.
We could sort all of these operations by first their position, then their operator (addition preceding subtraction) we could see that we could always obtain the correct result.
Time complexty O (q log q) with q is the amount of queries.
Example:
a b k
1 5 3
4 8 7
6 9 1
we will break it into
(1 3) (5 -3) (4 7) (8 -7) (6 1) (9 -1)
Sort them:
(1 3) (4 7) (5 -3) (6 1) (8 -7) (9 -1)
Then go through one by one:
Start sum = 0
-> (1 3) -> sum = 3
-> (4 7) -> sum = 10
-> (5 -3) -> sum = 7
-> (6 1) -> sum = 8
-> (8 -7) -> sum = 1
-> (9 -1) -> sum = 0
The max sum is 10 -> answer for the problem.
My Java code which passed all tests https://ideone.com/jNbKHa

This algorithm will help.
https://www.geeksforgeeks.org/difference-array-range-update-query-o1/
Using this algorithm you can solve the problen in O(n+q) where n = size of the array and q = no of queries.

Why your brute force solution will not pass all test cases?
Today generation system can perform 10^8 operation in one second. keep this in mind you have to process N=10^7 input per query in the worse case. as you are using two nested for loops(one for adding K element and other for processing m queries) then the complexity of your solution is O(NM).
if you use your solution with O(NM) complexity it has to handle (10^7 *10 ^5)= 10^12 operation in worse case (which can not be computed in 1 sec at all)
That is the reason you will get the time out error for your brute force solution.
So you need to optimise your code which can be done with the help of prefix sum array.
instead of adding k to all the elements within a range from a to b in an array, accumulate the difference array
Whenever we add anything at any index into an array and apply prefix sum algorithm the same element will be added to every element till the end of the array.
ex- n=5, m=1, a=2 b=5 k=5
i 0.....1.....2.....3.....4.....5.....6 //take array of size N+2 to avoid index out of bound
A[i] 0 0 0 0 0 0 0
Add k=5 to at a=2
A[a]=A[a]+k // start index from where k element should be added
i 0.....1.....2.....3.....4.....5.....6
A[i] 0 0 5 0 0 0 0
now apply prefix sum algorithm
i 0.....1.....2.....3.....4.....5.....6
A[i] 0 0 5 5 5 5 5
so you can see K=5 add to all the element till the end after applying prefix sum but we don't have to add k till the end. so to negate this effect we have to add -K also after b+1 index so that only from [a,b] range only will have K element addition effect.
A[b+1]=A[b]-k // to remove the effect of previously added k element after bth index.
that's why adding -k in the initial array along with +k.
i 0.....1.....2.....3.....4.....5.....6
A[i] 0 0 5 0 0 0 -5
Now apply prefix sum Array
i 0.....1.....2.....3.....4.....5.....6
A[i] 0 0 5 5 5 5 0
You can see now K=5 got added from a=2 to b=5 which was expected.
Here we are only updating two indices for every query so complexity will be O(1).
Now apply the same algorithm in the input
# 0.....1.....2.....3.....4.....5.....6 //taken array of size N+2 to avoid index out of bound
5 3 # 0 0 0 0 0 0 0
1 2 100 # 0 100 0 -100 0 0 0
2 5 100 # 0 100 100 -100 0 0 -100
3 4 100 # 0 100 100 0 0 -100 -100
To calculate the max prefix sum, accumulate the difference array to 𝑁 while taking the maximum accumulated prefix.
After performing all the operation now apply prefix sum Array
i 0.....1.....2.....3.....4.....5.....6
A[i] 0 100 200 200 200 100 0
Now you can traverse this array to find max which is 200.
traversing the array will take O(N) time and updating the two indices for each query will take O(1)* number of queries(m)
overall complexity=O(N)+O(M)
= O(N+M)
it means = (10^7+10^5) which is less than 10^8 (per second)
Note: If searching for video tutorial , you must check it out here for detailed explanation.

I think the trick is not to actually perform the manipulations on arrays.
You can simply track the changes in index-intervals.
Keep a sorted list of intervals ( sorted by begin-index).
e.g. Input: Internal representation
5 3 NOTHING TO DO
1 2 100 [1 2 value 100]
2 5 100 [1 1 value 100][2 2 value 200(100+100)][3 5 value 100]
3 4 100 [1 1 value 100][2 2 value 200(100+100)][3 4 value 200(100+100)][5 5 value 100]
as an optimization you could merge intervals with same value
-> [1 1 value 100][2 4 value 200][5 5 value 100]
In the last step you iterate through your intervals and take the highest value.

Related

Math.round(Math.random()) combination seems to be generating too many 0's in my javascript programme

I am trying to make a really simple script to generate a 2d array in which every nth array does not contain it's own index as an element but contains a random amount of other randomised index values as their elements and can not be empty. The following is a little code I wrote to attempt to achieve this:
totElList = []
numEls = 1000
for (i=0;i<numEls;i++) {
totElList[i] = []
for (j=0;j<numEls;j++) {
totElList[i][j] = j
}
}
for (i in totElList) {
totsplice = Math.round(Math.random()*(numEls-1))
totElList[i].splice(i,1)
for (j=0;j<totsplice;j++) {
rand = Math.round(Math.random()*totElList[i].length)
while (typeof(totElList[i][rand]) === undefined) {rand = Math.round(Math.random()*totElList[i].length)}
totElList[i].splice(rand,1)
}
}
The problem is when I run this the totElList array seems to contain more 0's than any other number even though I assumed elements would be removed at random. I did a test to confirm this. The amount of 0's is always the maximum out of all possible values for a given numEls. I am guessing this is something to do with the workings of Math.random() and Math.round() but I am unsure. Could somebody please give me some insight? Thank you.
Instead of Math.round, take Math.floor which works better for indices, because it is zero based.
Example with one digit and a factor of 2 as index for an array with length of 2.
As you see, the second half index is moved to the next index of every number, which results into a wrong index and with the greatest random value, you get an index outside of the wanted length.
value round floor comment for round
----- ----- ----- -----------------
0.0 0 0
0.1 0 0
0.2 0 0
0.3 0 0
0.4 0 0
0.5 1 0 wrong index
0.6 1 0 wrong index
0.7 1 0 wrong index
0.8 1 0 wrong index
0.9 1 0 wrong index
1.0 1 1
1.1 1 1
1.2 1 1
1.3 1 1
1.4 1 1
1.5 2 1 wrong index
1.6 2 1 wrong index
1.7 2 1 wrong index
1.8 2 1 wrong index
1.9 2 1 wrong index

& 1 JavaScript. How does it work? Clever or good? [duplicate]

This question already has answers here:
Is there a & logical operator in Javascript
(8 answers)
Closed 6 years ago.
I'm looking over the solutions for a CodeWars problem (IQ Test) in which you're given a string of numbers and all the numbers but 1 are either even or odd. You need to return the index plus 1 of the position of the number that's not like the rest of the numbers.
I'm confused about the line that says & 1 in the solution posted below. The code doesn't work w/ && or w/ the & 1 taken away.
function iqTest(numbers){
numbers = numbers.split(' ')
var evens = []
var odds = []
for (var i = 0; i < numbers.length; i++) {
if (numbers[i] & 1) { //PLEASE EXPLAIN THIS LINE!
odds.push(i + 1)
} else {
evens.push(i + 1)
}
}
return evens.length === 1 ? evens[0] : odds[0]
}
Also, would you consider using & 1 to be best practice or is it just "clever" code?
The single & is a 'bitwise' operator. This specific operator (&) is the bitwise AND operator - which returns a one in each bit position for which the corresponding bits of both operands are ones.
The way it's being used here is to test if numbers[i] is an even or odd number. As i loops from 0 to numbers.length, for the first iteration the if statement evaluates 0 & 1 which evaluates to 0, or false. On the next iteration of the loop, the statement will be 1 & 1, which evaluates to 1, or true.
The result - when numbers[i] & 1 evaluates to 0, or false, then numbers[i] is pushed to the odd array. If numbers[i] & 1 evaluates to 1, or true, then numbers[i] is pushed to the even array.
An alternative to the & operator to test for even and odd is to use the modulo operator. numbers[i] % 2 results in the same output. That is, 1 % 2 results in 1, or true as will any odd number, because an odd number divided by 2 results in a remainder of 1. And any even number, like 2 % 2 results in 0 or false because an even number divided by 2 results in a remainder of 0.
As for your second question, is it 'clever or good?'. It's definitely clever. Whether it's good depends on who you ask and what your goal is. Many would say its less logical and harder to read than using num % 2.
Binary number is 0 and 1 and each of them is called bit.
Single & is add operation and it works bitwise.
like
1 = 01
2 = 10
3 = 11
4 = 100
You can see that every last bit of odd number is 1 and even number is 0.
In add operation
0 & 0 = 0
0 & 1 = 0
1 & 0 = 0
1 & 1 = 1
So only odd number will return 1 and even number will return 0 and in programming only 0 consider falsy.
If we wanna to check 5 is a odd or even
5 = 101
and perform and(&) operation with 1
101
& 001
-----
001
and value of binary 001 is 1 in 10 base number
So it'll perform easy odd even process.

Math.random in regards to arrays

I am confused about how arrays work in tandem with functions like Math.random(). Since the Math.random() function selects a number greater than or equal to 0 and less than 1, what specific number is assigned to each variable in an array? For example, in the code below, what number would have to be selected to print out 1? What number would have to be selected to print out jaguar?
var examples= [1, 2, 3, 56, "foxy", 9999, "jaguar", 5.4, "caveman"];
var example= examples[Math.round(Math.random() * (examples.length-1))];
console.log(example);
Is each element in an array assigned a position number equal to x/n (x being the position number relative to the first element and n being the number of elements)? Since examples has 9 elements, would 1 be at position 1/9 and would 9999 be at position 6/9?
Math.round() vs. Math.floor()
The first thing to note: Math.round() is never the right function to use when you're dealing with a value returned by Math.random(). It should be Math.floor() instead, and then you don't need that -1 correction on the length. This is because Math.random() returns a value that is >= 0 and < 1.
This is a bit tricky, so let's take a specific example: an array with three elements. As vihan1086's excellent answer explains, the elements of this array are numbered 0, 1, and 2. To select a random element from this array, you want an equal chance of getting any one of those three values.
Let's see how that works out with Math.round( Math.random() * array.length - 1 ). The array length is 3, so we will multiply Math.random() by 2. Now we have a value n that is >= 0 and < 2. We round that number to the nearest integer:
If n is >= 0 and < .5, it rounds to 0.
If n is >= .5 and < 1.5, it rounds to 1.
If n is >= 1.5 and < 2, it rounds to 2.
So far so good. We have a chance of getting any of the three values we need, 0, 1, or 2. But what are the chances?
Look closely at those ranges. The middle range (.5 up to 1.5) is twice as long as the other two ranges (0 up to .5, and 1.5 up to 2). Instead of an equal chance for any of the three index values, we have a 25% chance of getting 0, a 50% chance of getting 1, and a 25% chance of 2. Oops.
Instead, we need to multiply the Math.random() result by the entire array length of 3, so n is >= 0 and < 3, and then floor that result: Math.floor( Math.random() * array.length ) It works like this:
If n is >= 0 and < 1, it floors to 0.
If n is >= 1 and < 2, it floors to 1.
If n is >= 2 and < 3, it floors to 2.
Now we clearly have an equal chance of hitting any of the three values 0, 1, or 2, because each of those ranges is the same length.
Keeping it simple
Here is a recommendation: don't write all this code in one expression. Break it up into simple functions that are self-explanatory and make sense. Here's how I like to do this particular task (picking a random element from an array):
// Return a random integer in the range 0 through n - 1
function randomInt( n ) {
return Math.floor( Math.random() * n );
}
// Return a random element from an array
function randomElement( array ) {
return array[ randomInt(array.length) ];
}
Then the rest of the code is straightforward:
var examples = [ 1, 2, 3, 56, "foxy", 9999, "jaguar", 5.4, "caveman" ];
var example = randomElement( examples );
console.log( example );
See how much simpler it is this way? Now you don't have to do that math calculation every time you want to get a random element from an array, you can simply call randomElement(array).
They're is quite a bit happening so I'll break it up:
Math.random
You got the first part right. Math.random will generate a number >= 0 and < 1. Math.random can return 0 but chances are almost 0 I think it's like 10^{-16} (you are 10 billion times more likely to get struck by lightning). This will make a number such as:
0.6687583869788796
Let's stop there for a second
Arrays and their indexes
Each item in an array has an index or position. This ranges from 0 - infinity. In JavaScript, arrays start at zero, not one. Here's a chart:
[ 'foo', 'bar', 'baz' ]
Now the indexes are as following:
name | index
-----|------
foo | 0
bar | 1
baz | 2
To get an item from it's index, use []:
fooBarBazArray[0]; // foo
fooBarBazArray[2]; // baz
Array length
Now the array length won't be the same as the largest index. It will be the length as if we counted it. So the above array will return 3. Each array has a length property which contains it's length:
['foo', 'bar', 'baz'].length; // Is 3
More Random Math
Now let's take a look at this randomizing thing:
Math.round(Math.random() * (mathematics.length-1))
They're is a lot going on. Let's break it down:
Math.random()
So first we generate a random number.
* mathematics.length - 1
The goal of this random is to generate a random array index. We need to subtract 1 from the length to get the highest index.
First Part conclusions
This now gives us a number ranging from 0 - max array index. On the sample array I showed earlier:
Math.random() * (['foo', 'bar', 'baz'].length - 1)
Now they're is a little problem:
This code makes a random number between 0 and the length. That means the -1 shouldn't be there. Let's fix this code:
Math.random() * ['foo', 'bar', 'baz'].length
Running this code, I get:
2.1972009977325797
1.0244733088184148
0.1671080442611128
2.0442249791231006
1.8239217158406973
Finally
To get out random index, we have to make this from an ugly decimal to a nice integer: Math.floor will basically truncate the decimal off.
Math.floor results:
2
0
2
1
2
We can put this code in the [] to select an item in the array at the random index.
More Information / Sources
Random Numbers
More solutions
You're looking at simple multiplication, and a bug in your code. It should reference the array 'examples' that you are selecting from, instead of some thing you haven't mentioned called 'mathematics':
var example = examples[Math.round(Math.random() * (examples.length-1))];
^^
Then you're just multiplying a random number by the number of things in the array. So the maximum random number is 1 and if there are 50 things in your array you multiply the random number by 50, and now the maximum random number is 50.
And all the smaller random numbers (0 to 1) are also scaled 50x and now spread from (0 to 50) with roughly the same randomness to them. Then you round it to the nearest whole number, which is a random index into your array from 1 to n, and you can do element[thatnumber] to pick it out.
Full examples:
Math.random() returns numbers between 0 and 1 (it can return 0 but chances of that are incredibly small):
Math.random()
0.11506261994225964
Math.random()
0.5607304393516861
Math.random()
0.5050221864582
Math.random()
0.4070177578793308
Math.random()
0.6352060229006462
Multiply those numbers by something to scale them up; 1 x 10 = 10 and so Math.random() * 10 = random numbers between 0 and 10.
Math.random() *n returns numbers between 0 and n:
Math.random() * 10
2.6186012867183326
Math.random() * 10
5.616868671026196
Math.random() * 10
0.7765205189156167
Math.random() * 10
6.299650241067698
Then Math.round(number) knocks the decimals off and leaves the nearest whole number between 1 and 10:
Math.round(Math.random() * 10)
5
Then you select that numbered element:
examples[ Math.round(Math.random() * 10) ];
And you use .length-1 because indexing counts from 0 and finishes at length-1, (see #vihan1086's explanation which has lots about array indexing).
This approach is not very good at being random - particularly it's much less likely to pick the first and last elements. I didn't realise when I wrote this, but #Michael Geary's answer is much better - avoiding Math.round() and not using length-1.
This is an old question but I will provide a new and shorter solution to get a random item from an array.
Math.random
It returns a number between 0 and 1 (1 not included).
Bitwise not ~
This operator behaves returning the oposite value that you are providing, so:
a = 5
~a // -5
It also forgets about decimals, so for instance:
a = 5.95
~a // -5
It is skipping the decimals, so somehow it behaves like Math.floor (without returning a negative value, of course).
Doubled operators
Negative logical operator !, used to coerce to a boolean type is !!null // false and we are forcing it by double negation.
If we use the same idea but for numbers, we are forcing a number to floor if we do: ~~5.999 // 5
Therefore,
TLDR;
getRandom = (arr, len = arr.length) => arr[~~(Math.random() * len)]
example:
getRandom([1,2,3,4,5]) // random item between 1 and 5

Combinatorial Optimisation with 0 and 1

I'm working on a way to solve this problem :
Given an amount of 0 and 1, generate the list containing all the possible combinations of 0 and 1.
Example : if we have one 1 and two 0, the algorithm will return
001
010
100
The problem is that the classic combination algorithms are not optimized for this purpose.
This is the result a non optimized combination algorithm would return :
001
001
010
010
100
100
As you can see, all combinations are repeated twice because 0 are interpreted as different elements.
How an algorithm can be made to generate the list of possible combination inputting the number of 0 and 1, without repeating combination ?
PS : This is will be used in Javascript
EDIT : Solved ! Using #Batch method.
function combin(o, i)
{
if (o == 0 && i > 0)
{
for (var j = 0, s = ''; j < i; j++)
{
s += '1';
}
return [s];
}
else if (i == 0 && o > 0)
{
for (var j = 0, s = ''; j < o; j++)
{
s += '0';
}
return [s];
}
else if (i == 0 && 0 == o)
{
return [''];
}
else if (i > 0 && o > 0)
{
var l = combin(o - 1, i);
for (var j in l)
{
l[j] = '0' + l[j];
}
var k = combin(o, i-1);
for (var j in k)
{
k[j] = '1' + k[j];
}
return l.concat(k);
}
}
Here's one way to do it: start out with the string putting all 0s to the front. Now shift the rightmost 0 through to the end. Afterwards, shift the second rightmost 0 one position to the right, put the last 0 right next to it. Again, shift the rightmost 0 through to the end. Shift the second rightmost 0 another step right, put the rightmost 0 next to it again, shift shift shift. Once you shift the pair of the two rightmost 0s, start shifting the third right most 0.... you get the picture.
Example: three 0s, three 1s:
000111
001011
001101
001110
010011
010101
010110
011001
011010
011100
100011
100101
100110
101001
101010
101100
110001
110010
110100
111000
Not sure how to write a nice loop for that, but once you grasp the idea, you can play around and try to figure one out if you'd like. Maybe you can find a nifty recursion though, can't think of one for this method right now though.
A more elegant approach would be the following; notice that the ways to generate all strings with n 0s and m 1s is:
Start the string with a 0, append all combinations of generating strings from n-1 0s and m 1s, or start the string with a 1 and append all combinations of generating strings from n 0s and m-1 1s. The sets of generated strings are disjoint, so a simple recursion will do, no worrying about strings being generated multiple times necessary. If you prefer, you can do it iteratively as well (iterate over the length of the generated string for that, for iteration i, keep sets for no 0 used, one 0 used, ..., i 0s used, etc.)
All in all, recursion seems the way to go to me. Base cases are simple: if n = 0, the only string you can get is 1^m, if m = 0 the only string you can get is 0^n (where ^ denotes repetition).
In case you want to implement that recursion and also a way to test it (to some degree), notice that the number of strings you can produce is n + m choose n = n + m choose m, so counting the number of strings you get will give you a hint whether what you're doing works as intended. Not sure whether javascript offers easy access to binomial coefficients.
I assume you want to do this fast. If you represent the 0/1 pattern as bits of an integer, the smallest one is "0...01..1" e.g. 0001111 if you have 3 0-bits and 4 1-bits - and the largest integer is "1..10..0". (e.g. 1111000 ).
Your problem is then a known problem of generating the lexicographically next bit permutation (i.e. the next integer with same number of 1 bits).
A simple implementation using code from http://graphics.stanford.edu/~seander/bithacks.html#NextBitPermutation is the following (in pseudocode):
v = '0..01..1'b // Initialize t as smallest integer of the pattern
while ( t <= '1..10..0'b ):
t = (v | (v - 1)) + 1
v = t | ((((t & -t) / (v & -v)) >> 1) - 1)
print v
see http://www.geeksforgeeks.org/next-higher-number-with-same-number-of-set-bits/ and references therein for a more detailed explanation on how the algorithm works.

What is the variable result doing in this javascript function

In the code below, if result is set to one, the code returns a number 1024 (2 to the power of 10). If result is set to 2, the code returns the number 2048 (or 2 to the power of 11), BUT if result is set to 3, the code doesn`t return the number 4096 (as I would expect, because 2 to the power of 12) but rather 3072. Why does it return 3072 if "result" is set to 3, but otherwise if set to 1 and 2 it followers the pattern of power to the 10th and 11th
function power(base, exponent) {
var result = 1;
for (var count = 0; count < exponent; count++)
result *= base;
return result;
}
show(power(2, 10));
In this code, result is used as an accumulator. The code computes result * base**exponent; result is not part of the exponent.
I think you're confusing yourself. The thing being raised to some power is your first parameter (in your example 2). The exponent is the 2nd parameter (10). Your example works correctly. Pass in 12 as your exponent and you will see your expected result.
The result parameter is just a place holder in which you accumulate your results (2x2x2x2....)
The numbers doing all the work are the ones you pass in as parameters to the function.
So, in your example, you're going to take 2 and multiply it by itself 10 times, each time storing the cumulative result in the variable "result". The thing that's throwing you off is that result is initially set to 1. This is never meant to be altered. It's built that way so that if you set your exponent to 0 you will end up with a result of 1 (because any number raised to the zeroth power = 1).
Anyway... don't worry about what result is set to. Focus on how the loop works with the interchangeable variable values that are being passed in, in the function call.
Do out the multiplication:
3 * 2 = 6
6 * 2 = 16
12 * 2 = 24
24 * 2 = 48
48 * 2 = 96
96 * 2 = 192
192 * 2 = 384
384 * 2 = 768
768 * 2 = 1536
1536 * 2 = 3072
Multiplication table perhaps? Also, what's wrong with Math.pow, just trying to accomplish it yourself?

Categories

Resources