Get the next and previous element of an array given an index - javascript

Given I have an array in Javascript, with values as such;
0 => 0x0000FF
1200 => 0x00CCFF
28800 => 0xFF0AFF
36000 => 0xFFFFFF
How can I determine which elements a given index value falls between? With the previous example, if I have the value 31073, I need to retrieve 28800 => 0xFF0AFF and 36000 => 0xFFFFFF

There's no "built-in" way to accomplish this with Javascript's sparse arrays. The simplest way to do this for arbitrary sparse indexes while maintaining some efficiency is to keep another lookaside sorted array of the indexes into the main array. Then you can walk the lookaside list to find the right neighbor indexes, and go back to the main array to get their values.
If your array will be huge or accesses need to be faster than O(items), you could look into various tree structures for the lookaside object.

Here's one way that simply uses a couple while() loops with no body.
It assumes the starting point will always be in between. You'll need a couple of quick additional tests if that's not the case.
Also, I wasn't sure what should happen if the starting point is directly on a color, so I didn't account for that.
Example: http://jsfiddle.net/tcVxP/4/
var num = 31234,
curr = num,
prev,
next;
while( !colors[--curr] && curr );
prev = colors[curr];
curr = num;
while( !colors[++curr] );
next = colors[curr];

I just wanted to follow up here;
Thanks to both quixoto and patrick dw for your detailed comments and answers. I have however, gone with a slightly different solution. While I wanted to maintain my initial approach of using a one dimensional array, it's much easier and efficient from what I can see, to add another dimension as shown below. The array used here does exhibit predictability, but that may not be the case once the project is finalized.
var colors = [
[0, '121D4A'],
[10800, '000000'],
[21600, 'FF5900'],
[32400, 'D3EEF0'],
[43200, '7DCDFF'],
[54000, '7DA6FF'],
[64800, 'FF5900'],
[75600, '31428C'],
[86399, '121D4A'],
];
function gradientStop(color1, color2, gradStop){
var r = Math.floor(gradStop * parseInt(color2.substr(0, 2), 16) + (1 - gradStop) * parseInt(color1.substr(0, 2), 16)).toString(16);
var g = Math.floor(gradStop * parseInt(color2.substr(2, 2), 16) + (1 - gradStop) * parseInt(color1.substr(2, 2), 16)).toString(16);
var b = Math.floor(gradStop * parseInt(color2.substr(4, 2), 16) + (1 - gradStop) * parseInt(color1.substr(4, 2), 16)).toString(16);
return (r.length < 2 ? '0' + r : r) + (g.length < 2 ? '0' + g : g) + (b.length < 2 ? '0' + b : b);
}
function getColor(colors, currentIndex){
for(var i = 0, m = colors.length; i < m; i++){
if(currentIndex >= colors[i][0]){
if(typeof(colors[i + 1]) !== 'undefined'){
if(currentIndex <= colors[i + 1][0]){
return gradientStop(colors[i][1], colors[i + 1][1], (currentIndex - colors[i][0]) / (colors[i + 1][0] - colors[i][0]));
}
}
}
}
}
And also; Yes Hemlock it was an expansion on my question at Programmatic gradient stops with Javascript, hence the gradientStop() function.

Related

simplify javascript array with loop syntax

I have a javascript array defined as below:
var hexgon = ['M',r*Math.cos(0/180*Math.PI),r*Math.sin(0/180*Math.PI)
,r*Math.cos(30/180*Math.PI),r*Math.sin(30/180*Math.PI)
,r*Math.cos(90/180*Math.PI),r*Math.sin(90/180*Math.PI)
,r*Math.cos(150/180*Math.PI),r*Math.sin(150/180*Math.PI)
,r*Math.cos(210/180*Math.PI),r*Math.sin(210/180*Math.PI)
,r*Math.cos(270/180*Math.PI),r*Math.sin(270/180*Math.PI),
,r*Math.cos(330/180*Math.PI),r*Math.sin(330/180*Math.PI),'Z']
How to use a loop to simplify this logic?
If you did not intend two commas in a row after the twelth element then this:
var hexgon = ['M', ...[0,30,90,150,210,270,330].flatMap(d => [r*Math.cos(d/180*Math.PI),r*Math.sin(d/180*Math.PI)]), 'Z']
If you can have a constant value as a step that gets added to Math.cos() and Math.sin() statements we might be able to do something.
Let's say we want to add 30 each time to the each array's element, we can do something like this: (Also noticed there are M and Z characters at the beginning and end of your array)
const newHexgon = new Array(16);
newHexgon[0] = 'M';
newHexgon[newHexgon.length - 1] = 'Z';
let counter = 0;
let step = 30;
for (let i = 1; i < newHexgon.length - 1; i += 2) {
newHexgon[i] = Math.cos(((counter * step) / 180) * Math.PI);
newHexgon[i + 1] = Math.sin(((counter * step) / 180) * Math.PI);
counter++;
}
console.log(newHexgon);
I created an array with a length of 16 and set the first and last elements to "M" and "Z" as in your array. Then I will loop every two elements at a time i += 2 and set the Math calculations and after finished in each iteration the counter gets added by one.

How to optimize code for HackerRank's Fraudulent Activity Notification problem

I have been working to solve this problem on the HackerRank site: Fraudulent Activity Notifications.
Below is the code I have written which satisfies the three sample test cases; however, it does not satisfy the larger test cases since it seems to take longer than 10 seconds.
The 10 second constraint is taken from here: HackerRank Environment.
function activityNotifications(expenditure, d) {
let notifications = 0;
let tmp = [];
let median = 0, medianEven = 0, iOfMedian = 0;
// Begin looping thru 'expenditure'
for(let i = 0; i < expenditure.length; i++) {
// slice from 'expenditure' beginning at 'i' and ending at 'i + d' where d = number of days
// sort 'tmp' in ascending order after
tmp = expenditure.slice(i, i + d);
tmp.sort();
// edge case, make sure we do not exceed boundaries of 'expenditure'
if((i + d) < expenditure.length) {
// if length of 'tmp' is divisible by 2, then we have an even length
// compute median accordingly
if(tmp.length % 2 == 0) {
medianEven = tmp.length / 2;
median = (tmp[medianEven - 1] + tmp[medianEven]) / 2;
// test if expenditures > 2 x median
if(expenditure[i + d] >= (2 * median)) {
notifications++;
}
}
// otherwise, we have an odd length of numbers
// therefore, compute median accordingly
else {
iOfMedian = (tmp.length + 1) / 2;
// test if expenditures > 2 x median
if(expenditure[i + d] >= (2 * tmp[iOfMedian - 1])) {
notifications++;
}
}
}
}
return notifications;
}
I am familiar with O notation for computing time complexity, so initially it seems the problem is either the excessive amount of variables declared or conditional statements used. Only one for loop is being used so I don't think the loop is where I should look to optimize the code. Unless, of course, we were to include the .sort() function used on 'tmp' which would definitely add to the time it takes to compute efficiently.
Is there anything I have not realized which is causing the code to take longer than expected? Any other hints would be greatly appreciated, thanks.

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).

Combining different number ranges in O(n)

I'm currently tracking user play times of videos, and I'm trying to determine the % of a video a user watches. I've generalised the problem to given a series of number ranges that potentially overlap, how to combine them into a series of non-overlapping number ranges (i.e. converting "0-10, 5-15, 30-45, 20-25" into "0-15, 20-25, 30-45".
I have a relatively long-winded solution based on the premise that if the number ranges are sorted, then it is relatively trivial to combine two adjacent number ranges (either combine them if they overlap or they remain separate). Thus, we sort the number ranges first then iterate through the ranges and combining them.
Since sorting is worst case O(nlgn), this means my solution should be O(nlgn), and I was wondering if anyone knows of a O(n) solution to the problem?
http://jsfiddle.net/457PH/2
var testcase = [
[0, 30], [40, 50], [5, 15], [70, 95], [45, 75], [0, 10],
[110, 115], [115, 120], [140, 175], [125, 160]
];
//sorts the array in ascending order (based on first element)
//if the first elements are the same, order based on second element (prioritising elements that are bigger)
testcase.sort(function(a, b) {
if (a[0] !== b[0]) return a[0] - b[0];
return b[1] - a[1]
})
function evaluate(a, b) {
var result = [];
//tests that the array is sorted properly
if ((a[0] > b[0]) || ((a[0] === b[0] ) && (a[1] < b[1]))) throw new Error('Array not sorted properly');
//if a and b do not overlap, then push both in the result
if(b[0] > a[1]) {
result.push(a, b);
}
//if a and b overlap
else {
var newElement = [a[0], Math.max(a[1], b[1])];
result.push(newElement);
}
return result;
}
console.log(testcase)
var combinedArr = [testcase[0]];
for (var i = 1; i < testcase.length; i++) {
var popped = combinedArr.pop();
combinedArr = combinedArr.concat(evaluate(popped, testcase[i]));
}
console.log(combinedArr);
An alternative solution that is O(W+n*|S|) where |S| is the average size of each interval and W is the maximal value in the list will be using a bitset, and iterate each element and set all relevant bits.
In another iteration - print all intervals in the bitset (which is sorted).
So, the algorithm for this approach is basically:
Create a bitset of size W where a bit is set only if it is in some interval.
Iterate the bitset and print the intervals - this is fairly easy now.
While this could be much worse in terms of asymptotic complexity if W or |S| are large - note that the constants here are fairly small, since bit operations are fairly easy to implement.
Choosing which is actually better should be done using empirical benchmark and achieving statistical significance.
Pseudo-code:
//create the bitset:
b <- new bitset
for each interval [x1,x2]:
for each element i from x1 to x2:
b[i] = 1
//print intervals:
first <- -1
for each element i from 0 to W+1: //regard b[W] as 0
if b[i] == 0 and first != -1:
print (first,i-1)
first = -1
else if b[i] == 1 and first == -1:
first = i
If you just restrict to the case where each of the first half of the intervals are overlapping a distinct member of the second half of the intervals, then the number of possibilities for overlapping combinations of intervals is at least Omega((n/2)!) (i.e. n/2 factorial). Thus, in any comparison based algorithm, you will need at least log((n/2)!) = Omega(n log n) comparisons to distinguish between all these cases. Thus, in any comparison based algorithm, you will need Omega(n log n) time in the worst case.
Here's an attempt at the bitset implementation in JavaScript:
function percentWatched(ranges,totalMinutes){
var numPartitions = Math.ceil(totalMinutes / 31),
bitset = new Array(numPartitions)
for (var i in ranges){
var top = ranges[i][1]
, bottom = ranges[i][0]
, m, shift, k
while (bottom < top){
m = Math.floor(bottom / 31)
shift = bottom % 31
k = shift + top - bottom <= 31 ? top - bottom : 31 - shift
bitset[m] |= (1 << k) - 1 << shift
bottom += k
}
}
var minutesWatched = 0
for (var i in bitset)
minutesWatched += numSetBits(bitset[i])
return {percent: 100 * minutesWatched / totalMinutes
, ranges: bitset}
}
function numSetBits(i) //copied from http://stackoverflow.com/questions/109023
{
i = i - ((i >> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
}
Console output:
> var a = percentWatched([[0,10], [5,15], [30,45], [20,25]],100)
> for (var i in a.ranges) console.log(a.ranges[i].toString(2))
"1000001111100000111111111111111"
"11111111111111"
> a.percent
35

Generate all combinations for pair of bits set to 1?

I'm trying to generate all possible combinations for pair of 1's within given bit width.
Let's say the bit width is 6, i.e. number 32. This is what I would like to generate:
000000
000011
000110
001100
001111
011000
011011
011110
110000
110011
110110
111100
111111
If I have variables:
var a = 1,
b = 2;
num = a | b;
and create a loop that I'll loop over width - 1 times, and where I shift both a << 1 and b << 1, I'll get all combinations for one pair. After that, I'm pretty much stuck.
Could someone , please, provide some help.
Update: working example
Based on Barmar's mathematical approach, this is what I managed to implement
var arr = [],
arrBits = [];
function getCombs(pairs, startIdx) {
var i, j, val = 0, tmpVal, idx;
if (startIdx + 2 < pairs) {
startIdx = arr.length - 1;
pairs -= 1;
}
if (pairs < 2) {
return;
}
for (i = 0; i < pairs-1; i++) {
idx = startIdx - (i * 2);
val += arr[idx];
}
for (j = 0; j < idx - 1; j++) {
arrBits.push((val + arr[j]).toString(2));
}
getCombs(pairs, startIdx-1);
}
(function initArr(bits) {
var i, val, pairs, startIdx;
for (i = 1; i < bits; i++) {
val = i == 1 ? 3 : val * 2;
arr.push(val);
arrBits.push(val.toString(2));
}
pairs = Math.floor(bits / 2);
startIdx = arr.length - 1;
getCombs(pairs, startIdx);
console.log(arrBits);
}(9));
Working example on JSFiddle
http://jsfiddle.net/zywc5/
The numbers with exactly one pair of 1's are the sequence 3, 6, 12, 24, 48, ...; they start with 3 and just double each time.
The numbers with two pairs of 1's are 12+3, 24+3, 24+6, 48+3, 48+6, 48+12, ...; these are the above sequence starting at 12 + the original sequence up to n/4.
The numbers with three pairs of 1's are 48+12+3, 96+12+3, 96+24+3, 96+24+6, ...
The relationship between each of these suggests a recursive algorithm making use of the original doubling sequence. I don't have time right now to write it, but I think this should get you going.
if the bit width isn't that big then you'll be way better off creating bit representations for all numbers from 0 to 31 in a loop and simply ignore the ones that have an odd number of "ones" in the bit representation.
Maybe start counting normally in binary and replace all 1's with 11's like this:
n = 5
n = n.toString(2) //= "101"
n = n.replace(/1/g, "11") //= "11011"
n = parseInt(n, 2) //= 27
So you'll get:
0 -> 0
1 -> 11
10 -> 110
11 -> 1111
100 -> 1100
101 -> 11011
110 -> 11110
111 -> 111111
And so on. You'll have to count up to 31 or so on the left side, and reject ones longer than 6 bits on the right side.
See http://jsfiddle.net/SBH6R/
var len=6,
arr=[''];
for(var i=0;i<len;i++){
for(var j=0;j<arr.length;j++){
var k=j;
if(getNum1(arr[j])%2===1){
arr[j]+=1;
}else{
if(i<len-1){
arr.splice(j+1,0,arr[j]+1);
j++;
}
arr[k]+=0;
}
}
}
function getNum1(str){
var n=0;
for(var i=str.length-1;i>=0;i--){
if(str.substr(i,1)==='1'){n++;}
else{break;}
}
return n;
}
document.write(arr.join('<br />'));
Or maybe you will prefer http://jsfiddle.net/SBH6R/1/. It's simpler, but then you will have to sort() the array:
var len=6,
arr=[''];
for(var i=0;i<len;i++){
for(var k=0,l=arr.length;k<l;k++){
if(getNum1(arr[k])%2===1){
arr[k]+=1;
}else{
if(i<len-1){
arr.push(arr[k]+1);
}
arr[k]+=0;
}
}
}
function getNum1(str){
var n=0;
for(var i=str.length-1;i>=0;i--){
if(str.substr(i,1)==='1'){n++;}
else{break;}
}
return n;
}
document.write(arr.sort().join('<br />'));
See http://jsperf.com/generate-all-combinations-for-pair-of-bits-set-to-1 if you want to compare the performance. It seems that the fastest code is the first one on Chrome but the second one on Firefox.
You can also do this with bit twiddling. If the lowest two bits are zero, we need to set them, which is equivalent to adding 3. Otherwise, we need to replace the lowest block of ones by its top bit and a 1-bit to the left of it. This can be done as follows, where x is the current combination:
x3 = x + 3;
return (((x ^ x3) - 2) >> 2) + x3;

Categories

Resources