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

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.

Related

Is there a way to avoid number to string conversion & nested loops for performance?

I just took a coding test online and this one question really bothered me. My solution was correct but was rejected for being unoptimized. The question is as following:
Write a function combineTheGivenNumber taking two arguments:
numArray: number[]
num: a number
The function should check all the concatenation pairs that can result in making a number equal to num and return their count.
E.g. if numArray = [1, 212, 12, 12] & num = 1212 then we will have return value of 3 from combineTheGivenNumber
The pairs are as following:
numArray[0]+numArray[1]
numArray[2]+numArray[3]
numArray[3]+numArray[2]
The function I wrote for this purpose is as following:
function combineTheGivenNumber(numArray, num) {
//convert all numbers to strings for easy concatenation
numArray = numArray.map(e => e+'');
//also convert the `hay` to string for easy comparison
num = num+'';
let pairCounts = 0;
// itereate over the array to get pairs
numArray.forEach((e,i) => {
numArray.forEach((f,j) => {
if(i!==j && num === (e+f)) {
pairCounts++;
}
});
});
return pairCounts;
}
console.log('Test 1: ', combineTheGivenNumber([1,212,12,12],1212));
console.log('Test 2: ', combineTheGivenNumber([4,21,42,1],421));
From my experience, I know conversion of number to string is slow in JS, but I am not sure whether my approach is wrong/lack of knowledge or does the tester is ignorant of this fact. Can anyone suggest further optimization of the code snipped?
Elimination of string to number to string will be a significant speed boost but I am not sure how to check for concatenated numbers otherwise.
Elimination of string to number to string will be a significant speed boost
No, it won't.
Firstly, you're not converting strings to numbers anywhere, but more importantly the exercise asks for concatenation so working with strings is exactly what you should do. No idea why they're even passing numbers. You're doing fine already by doing the conversion only once for each number input, not every time your form a pair. And last but not least, avoiding the conversion will not be a significant improvement.
To get a significant improvement, you should use a better algorithm. #derpirscher is correct in his comment: "[It's] the nested loop checking every possible combination which hits the time limit. For instance for your example, when the outer loop points at 212 you don't need to do any checks, because regardless, whatever you concatenate to 212, it can never result in 1212".
So use
let pairCounts = 0;
numArray.forEach((e,i) => {
if (num.startsWith(e)) {
//^^^^^^^^^^^^^^^^^^^^^^
numArray.forEach((f,j) => {
if (i !== j && num === e+f) {
pairCounts++;
}
});
}
});
You might do the same with suffixes, but it becomes more complicated to rule out concatenation to oneself there.
Optimising further, you can even achieve a linear complexity solution by putting the strings in a lookup structure, then when finding a viable prefix just checking whether the missing part is an available suffix:
function combineTheGivenNumber(numArray, num) {
const strings = new Map();
for (const num of numArray) {
const str = String(num);
strings.set(str, 1 + (strings.get(str) ?? 0));
}
const whole = String(num);
let pairCounts = 0;
for (const [prefix, pCount] of strings) {
if (!whole.startsWith(prefix))
continue;
const suffix = whole.slice(prefix.length);
if (strings.has(suffix)) {
let sCount = strings.get(suffix);
if (suffix == prefix) sCount--; // no self-concatenation
pairCounts += pCount*sCount;
}
}
return pairCounts;
}
(the proper handling of duplicates is a bit difficile)
I like your approach of going to strings early. I can suggest a couple of simple optimizations.
You only need the numbers that are valid "first parts" and those that are valid "second parts"
You can use the javascript .startsWith and .endsWith to test for those conditions. All other strings can be thrown away.
The lengths of the strings must add up to the length of the desired answer
Suppose your target string is 8 digits long. If you have 2 valid 3-digit "first parts", then you only need to know how many valid 5-digit "second parts" you have. Suppose you have 9 of them. Those first parts can only combine with those second parts, and give you 2 * 9 = 18 valid pairs.
You don't actually need to keep the strings!
It struck me that if you know you have 2 valid 3-digit "first parts", you don't need to keep those actual strings. Knowing that they are valid 2-digit first parts is all you need to know.
So let's build an array containing:
How many valid 1-digit first parts do we have?,
How many valid 2-digit first parts do we have?,
How many valid 3-digit first parts do we have?,
etc.
And similarly an array containing the number of valid 1-digit second parts, etc.
X first parts and Y second parts can be combined in X * Y ways
Except if the parts are the same length, in which case we are reusing the same list, and so it is just X * (Y-1).
So not only do we not need to keep the strings, but we only need to do the multiplication of the appropriate elements of the arrays.
5 1-char first parts & 7 3-char second parts = 5 * 7 = 35 pairs
6 2-char first part & 4 2-char second parts = 6 * (4-1) = 18 pairs
etc
So this becomes extremely easy. One pass over the strings, tallying the "first part" and "second part" matches of each length. This can be done with an if and a ++ of the relevant array element.
Then one pass over the lengths, which will be very quick as the array of lengths will be very much shorter than the array of actual strings.
function combineTheGivenNumber(numArray, num) {
const sElements = numArray.map(e => "" + e);
const sTarget = "" + num;
const targetLength = sTarget.length
const startsByLen = (new Array(targetLength)).fill(0);
const endsByLen = (new Array(targetLength)).fill(0);
sElements.forEach(sElement => {
if (sTarget.startsWith(sElement)) {
startsByLen[sElement.length]++
}
if (sTarget.endsWith(sElement)) {
endsByLen[sElement.length]++
}
})
// We can now throw away the strings. We have two separate arrays:
// startsByLen[1] is the count of strings (without attempting to remove duplicates) which are the first character of the required answer
// startsByLen[2] similarly the count of strings which are the first 2 characters of the required answer
// etc.
// and endsByLen[1] is the count of strings which are the last character ...
// and endsByLen[2] is the count of strings which are the last 2 characters, etc.
let pairCounts = 0;
for (let firstElementLength = 1; firstElementLength < targetLength; firstElementLength++) {
const secondElementLength = targetLength - firstElementLength;
if (firstElementLength === secondElementLength) {
pairCounts += startsByLen[firstElementLength] * (endsByLen[secondElementLength] - 1)
} else {
pairCounts += startsByLen[firstElementLength] * endsByLen[secondElementLength]
}
}
return pairCounts;
}
console.log('Test 1: ', combineTheGivenNumber([1, 212, 12, 12], 1212));
console.log('Test 2: ', combineTheGivenNumber([4, 21, 42, 1], 421));
Depending on a setup, the integer slicing can be marginally faster
Although in the end it falls short
Also, when tested on higher N values, the previous answer exploded in jsfiddle. Possibly a memory error.
As far as I have tested with both random and hand-crafted values, my solution holds. It is based on an observation, that if X, Y concantenated == Z, then following must be true:
Z - Y == X * 10^(floor(log10(Y)) + 1)
an example of this:
1212 - 12 = 1200
12 * 10^(floor((log10(12)) + 1) = 12 * 10^(1+1) = 12 * 100 = 1200
Now in theory, this should be faster then manipulating strings. And in many other languages it most likely would be. However in Javascript as I just learned, the situation is a bit more complicated. Javascript does some weird things with casting that I haven't figured out yet. In short - when I tried storing the numbers(and their counts) in a map, the code got significantly slower making any possible gains from this logarithm shenanigans evaporate. Furthermore, storing them in a custom-crafted data structure isn't guaranteed to be faster since you have to build it etc. Also it would be quite a lot of work.
As it stands this log comparison is ~ 8 times faster in a case without(or with just a few) matches since the quadratic factor is yet to kick in. As long as the possible postfix count isn't too high, it will outperform the linear solution. Unfortunately it is still quadratic in nature with the breaking point depending on a total number of strings as well as their length.
So if you are searching for a needle in a haystack - for example you are looking for a few pairs in a huge heap of numbers, this can help. In the other case of searching for many matches, this won't help. Similarly, if the input array was sorted, you could use binary search to push the breaking point further up.
In the end, unless you manage to figure out how to store ints in a map(or some custom implementation of it) in a way that doesn't completely kill the performance, the linear solution of the previous answer will be faster. It can still be useful even with the performance hit if your computation is going to be memory heavy. Storing numbers takes less space then storing strings.
var log10 = Math.log(10)
function log10floored(num) {
return Math.floor(Math.log(num) / log10)
}
function combineTheGivenNumber(numArray, num) {
count = 0
for (var i=0; i!=numArray.length; i++) {
let portion = num - numArray[i]
let removedPart = Math.pow(10, log10floored(numArray[i]))
if (portion % (removedPart * 10) == 0) {
for (var j=0; j!=numArray.length; j++) {
if (j != i && portion / (removedPart * 10) == numArray[j] ) {
count += 1
}
}
}
}
return count
}
//The previous solution, that I used for timing, comparison and check purposes
function combineTheGivenNumber2(numArray, num) {
const strings = new Map();
for (const num of numArray) {
const str = String(num);
strings.set(str, 1 + (strings.get(str) ?? 0));
}
const whole = String(num);
let pairCounts = 0;
for (const [prefix, pCount] of strings) {
if (!whole.startsWith(prefix))
continue;
const suffix = whole.slice(prefix.length);
if (strings.has(suffix)) {
let sCount = strings.get(suffix);
if (suffix == prefix) sCount--; // no self-concatenation
pairCounts += pCount*sCount;
}
}
return pairCounts;
}
var myArray = []
for (let i =0; i!= 10000000; i++) {
myArray.push(Math.floor(Math.random() * 1000000))
}
var a = new Date()
t1 = a.getTime()
console.log('Test 1: ', combineTheGivenNumber(myArray,15285656));
var b = new Date()
t2 = b.getTime()
console.log('Test 2: ', combineTheGivenNumber2(myArray,15285656));
var c = new Date()
t3 = c.getTime()
console.log('Test1 time: ', t2 - t1)
console.log('test2 time: ', t3 - t2)
Small update
As long as you are willing to take a performance hit with the setup and settle for the ~2 times performance, using a simple "hashing" table can help.(Hashing tables are nice and tidy, this is a simple modulo lookup table. The principle is similar though.)
Technically this isn't linear, practicaly it is enough for the most cases - unless you are extremely unlucky and all your numbers fall in the same bucket.
function combineTheGivenNumber(numArray, num) {
count = 0
let size = 1000000
numTable = new Array(size)
for (var i=0; i!=numArray.length; i++) {
let idx = numArray[i] % size
if (numTable[idx] == undefined) {
numTable[idx] = [numArray[i]]
} else {
numTable[idx].push(numArray[i])
}
}
for (var i=0; i!=numArray.length; i++) {
let portion = num - numArray[i]
let removedPart = Math.pow(10, log10floored(numArray[i]))
if (portion % (removedPart * 10) == 0) {
if (numTable[portion / (removedPart * 10) % size] != undefined) {
let a = numTable[portion / (removedPart * 10) % size]
for (var j=0; j!=a.length; j++) {
if (j != i && portion / (removedPart * 10) == a[j] ) {
count += 1
}
}
}
}
}
return count
}
Here's a simplified, and partially optimised approach with 2 loops:
// let's optimise 'combineTheGivenNumber', where
// a=array of numbers AND n=number to match
const ctgn = (a, n) => {
// convert our given number to a string using `toString` for clarity
// this isn't entirely necessary but means we can use strict equality later
const ns = n.toString();
// reduce is an efficient mechanism to return a value based on an array, giving us
// _=[accumulator], na=[array number] and i=[index]
return a.reduce((_, na, i) => {
// convert our 'array number' to an 'array number string' for later concatenation
const nas = na.toString();
// iterate back over our array of numbers ... we're using an optimised/reverse loop
for (let ii = a.length - 1; ii >= 0; ii--) {
// skip the current array number
if (i === ii) continue;
// string + number === string, which lets us strictly compare our 'number to match'
// if there's a match we increment the accumulator
if (a[ii] + nas === ns) ++_;
}
// we're done
return _;
}, 0);
}

Generate random & unique 4 digit codes without brute force

I'm building an app and in one of my functions I need to generate random & unique 4 digit codes. Obviously there is a finite range from 0000 to 9999 but each day the entire list will be wiped and each day I will not need more than the available amount of codes which means it's possible to have unique codes for each day. Realistically I will probably only need a few hundred codes a day.
The way I've coded it for now is the simple brute force way which would be to generate a random 4 digit number, check if the number exists in an array and if it does, generate another number while if it doesn't, return the generated number.
Since it's 4 digits, the runtime isn't anything too crazy and I'm mostly generating a few hundred codes a day so there won't be some scenario where I've generated 9999 codes and I keep randomly generating numbers to find the last remaining one.
It would also be fine to have letters in there as well instead of just numbers if it would make the problem easier.
Other than my brute force method, what would be a more efficient way of doing this?
Thank you!
Since you have a constrained number of values that will easily fit in memory, the simplest way I know of is to create a list of the possible values and select one randomly, then remove it from the list so it can't be selected again. This will never have a collision with a previously used number:
function initValues(numValues) {
const values = new Array(numValues);
// fill the array with each value
for (let i = 0; i < values.length; i++) {
values[i] = i;
}
return values;
}
function getValue(array) {
if (!array.length) {
throw new Error("array is empty, no more random values");
}
const i = Math.floor(Math.random() * array.length);
const returnVal = array[i];
array.splice(i, 1);
return returnVal;
}
// sample code to use it
const rands = initValues(10000);
console.log(getValue(rands));
console.log(getValue(rands));
console.log(getValue(rands));
console.log(getValue(rands));
This works by doing the following:
Generate an array of all possible values.
When you need a value, select one from the array with a random index.
After selecting the value, remove it from the array.
Return the selected value.
Items are never repeated because they are removed from the array when used.
There are no collisions with used values because you're always just selecting a random value from the remaining unused values.
This relies on the fact that an array of integers is pretty well optimized in Javascript so doing a .splice() on a 10,000 element array is still pretty fast (as it can probably just be memmove instructions).
FYI, this could be made more memory efficient by using a typed array since your numbers can be represented in 16-bit values (instead of the default 64 bits for doubles). But, you'd have to implement your own version of .splice() and keep track of the length yourself since typed arrays don't have these capabilities built in.
For even larger problems like this where memory usage becomes a problem, I've used a BitArray to keep track of previous usage of values.
Here's a class implementation of the same functionality:
class Randoms {
constructor(numValues) {
this.values = new Array(numValues);
for (let i = 0; i < this.values.length; i++) {
this.values[i] = i;
}
}
getRandomValue() {
if (!this.values.length) {
throw new Error("no more random values");
}
const i = Math.floor(Math.random() * this.values.length);
const returnVal = this.values[i];
this.values.splice(i, 1);
return returnVal;
}
}
const rands = new Randoms(10000);
console.log(rands.getRandomValue());
console.log(rands.getRandomValue());
console.log(rands.getRandomValue());
console.log(rands.getRandomValue());
Knuth's multiplicative method looks to work pretty well: it'll map numbers 0 to 9999 to a random-looking other number 0 to 9999, with no overlap:
const hash = i => i*2654435761 % (10000);
const s = new Set();
for (let i = 0; i < 10000; i++) {
const n = hash(i);
if (s.has(n)) { console.log(i, n); break; }
s.add(n);
}
To implement it, simply keep track of an index that gets incremented each time a new one is generated:
const hash = i => i*2654435761 % (10000);
let i = 1;
console.log(
hash(i++),
hash(i++),
hash(i++),
hash(i++),
hash(i++),
);
These results aren't actually random, but they probably do the job well enough for most purposes.
Disclaimer:
This is copy-paste from my answer to another question here. The code was in turn ported from yet another question here.
Utilities:
function isPrime(n) {
if (n <= 1) return false;
if (n <= 3) return true;
if (n % 2 == 0 || n % 3 == 0) return false;
for (let i = 5; i * i <= n; i = i + 6) {
if (n % i == 0 || n % (i + 2) == 0) return false;
}
return true;
}
function findNextPrime(n) {
if (n <= 1) return 2;
let prime = n;
while (true) {
prime++;
if (isPrime(prime)) return prime;
}
}
function getIndexGeneratorParams(spaceSize) {
const N = spaceSize;
const Q = findNextPrime(Math.floor(2 * N / (1 + Math.sqrt(5))))
const firstIndex = Math.floor(Math.random() * spaceSize);
return [firstIndex, N, Q]
}
function getNextIndex(prevIndex, N, Q) {
return (prevIndex + Q) % N
}
Usage
// Each day you bootstrap to get a tuple of these parameters and persist them throughout the day.
const [firstIndex, N, Q] = getIndexGeneratorParams(10000)
// need to keep track of previous index generated.
// it’s a seed to generate next one.
let prevIndex = firstIndex
// calling this function gives you the unique code
function getHashCode() {
prevIndex = getNextIndex(prevIndex, N, Q)
return prevIndex.toString().padStart(4, "0")
}
console.log(getHashCode());
Explanation
For simplicity let’s say you want generate non-repeat numbers from 0 to 35 in random order. We get pseudo-randomness by polling a "full cycle iterator"†. The idea is simple:
have the indexes 0..35 layout in a circle, denote upperbound as N=36
decide a step size, denoted as Q (Q=23 in this case) given by this formula‡
Q = findNextPrime(Math.floor(2 * N / (1 + Math.sqrt(5))))
randomly decide a starting point, e.g. number 5
start generating seemingly random nextIndex from prevIndex, by
nextIndex = (prevIndex + Q) % N
So if we put 5 in we get (5 + 23) % 36 == 28. Put 28 in we get (28 + 23) % 36 == 15.
This process will go through every number in circle (jump back and forth among points on the circle), it will pick each number only once, without repeating. When we get back to our starting point 5, we know we've reach the end.
†: I'm not sure about this term, just quoting from this answer
‡: This formula only gives a nice step size that will make things look more "random", the only requirement for Q is it must be coprime to N
This problem is so small I think a simple solution is best. Build an ordered array of the 10k possible values & permute it at the start of each day. Give the k'th value to the k'th request that day.
It avoids the possible problem with your solution of having multiple collisions.

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

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

reordering objects without impacting other objects

I have a list of items (think, files in a directory), where the order of these items is arbitrarily managed by a user. The user can insert an item between other items, delete items, and move them around.
What is the best way to store the ordering as a property of each item so that when a specific item is inserted or moved, the ordering property of the other items is not affected? These objects will be stored in a database.
An ideal implementation would be able to support inifinite number of insertions/reorders.
The test I'm using to identify the limitations of the approach are as follows:
With 3 items x,y,z, repeatedly take the item on the left and put it between the other two; then take the object on the right and put it between the other two; keep going until some constraint is violated.
For others' reference, I have included some algorithms I have tried.
1.1. Decimals, double-precision
Store the order as a decimal. To insert an between two items with orders x and y, calculate its order as x/2+y/2.
Limitations:
Precision, or performance. Using doubles, when the denominator becomes too big, we end up with x/2+y/2==x . In Javascript, it can only handle 25 shuffles.
function doubles(x,y,z) {
for (var i = 0; i < 10000000; i++) {
//x,y,z
//x->v1: y,v1,z
//z->v2: y,v2,v1
var v1 = y/2 + z/2
var v2 = y/2 + v1/2
x = y
y = v2
z = v1
if (x == y) {
console.log(i)
break
}
}
}
>doubles(1, 1.5, 2)
>25
1.2. Decimals, BigDecimal
The same as above, but using BigDecimal from https://github.com/iriscouch/bigdecimal.js. In my test, the performance degraded unusably quickly. It might be a good choice for other frameworks, but not for client-side javascript.
I threw that implementation away and don't have it anymore.
2.1. Fractions
Store the order as a (numerator, denominator) integer tuple. To insert an item between items xN/xD and yN/yD, give it a value of (xN+yN)/(xD+yD) (which can easily be shown to be between the other two numbers).
Limitations:
precision or overflow.
function fractions(xN, xD, yN, yD, zN, zD){
for (var i = 0; i < 10000000; i++) {
//x,y,z
//x->v1: y,v1,z
//z->v2: y,v2,v1
var v1N = yN + zN, v1D = yD + zD
var v2N = yN + v1N, v2D = yD + v1D
xN = yN, xD=yD
yN = v2N, yD=v2D
zN = v1N, zd=v1D
if (!isFinite(xN) || !isFinite(xD)) { // overflow
console.log(i)
break
}
if (xN/xD == yN/yD) { //precision
console.log(i)
break
}
}
}
>fractions(1,1,3,2,2,1)
>737
2.2. Fractions with GCD reduction
The same as above, but reduce fractions using a Greatest Common Denomenator algorithm:
function gcd(x, y) {
if(!isFinite(x) || !isFinite(y)) {
return NaN
}
while (y != 0) {
var z = x % y;
x = y;
y = z;
}
return x;
}
function fractionsGCD(xN, xD, yN, yD, zN, zD) {
for (var i = 0; i < 10000000; i++) {
//x,y,z
//x->v1: y,v1,z
//z->v2: y,v2,v1
var v1N = yN + zN, v1D = yD + zD
var v2N = yN + v1N, v2D = yD + v1D
var v1gcd=gcd(v1N, v1D)
var v2gcd=gcd(v2N, v2D)
xN = yN, xD = yD
yN = v2N/v2gcd, yD=v2D/v2gcd
zN = v1N/v1gcd, zd=v1D/v1gcd
if (!isFinite(xN) || !isFinite(xD)) { // overflow
console.log(i)
break
}
if (xN/xD == yN/yD) { //precision
console.log(i)
break
}
}
}
>fractionsGCD(1,1,3,2,2,1)
>6795
3. Alphabetic
Use alphabetic ordering. The idea is to start with an alphabet (say, ascii printable range of [32..126]), and grow the strings. So, ('O' being the middle of our range), to insert between "a" and "c", use "b", to insert between "a" and "b", use "aO", and so forth.
Limitations:
The strings would get so long as to not fit in a database.
function middle(low, high) {
for(var i = 0; i < high.length; i++) {
if (i == low.length) {
//aa vs aaf
lowcode=32
hicode = high.charCodeAt(i)
return low + String.fromCharCode( (hicode - lowcode) / 2)
}
lowcode = low.charCodeAt(i)
hicode = high.charCodeAt(i)
if(lowcode==hicode) {
continue
}
else if(hicode - lowcode == 1) {
// aa vs ab
return low + 'O';
} else {
// aa vs aq
return low.slice(0,i) + String.fromCharCode(lowcode + (hicode - lowcode) / 2)
}
}
}
function alpha(x,y,z, N) {
for (var i = 0; i < 10000; i++) {
//x,y,z
//x->v1: y,v1,z
//z->v2: y,v2,v1
var v1 = middle(y, z)
var v2 = middle(y, v1)
x = y
y = v2
z = v1
if(x.length > N) {
console.log(i)
break
}
}
}
>alpha('?', 'O', '_', 256)
1023
>alpha('?', 'O', '_', 512)
2047
Perhaps I have missed something fundamental and I will admit I know little enough about javascript, but surely you can just implement a doubly-linked list to deal with this? Then re-ordering a,b,c,d,e,f,g,h to insert X between d and e you just unlink d->e, link d->X and then link X->e and so on.
Because in any of the scenarios above, either you will run out of precision (and your infinite ordering is lost) or you'll end up with very long sort identifiers and no memory :)
Software axiom #1: KEEP IT SIMPLE until you have found a compelling, real and proven reason to make it more complicated.
So, I'd argue that it's extra and unnecessary code and maintenance to maintain your own order property when the DOM is already doing it for you. Why not just let the DOM maintain the order and you can dynamically generate a set of brain-dead simple sequence numbers for the current ordering any time you need it? CPUs are plenty fast to generate new sequence numbers for all items anytime you need it or anytime it changes. And, if you want to save this new ordering on the server, just send the whole sequence to the server.
Implementing one of these splitting sequences so you can always insert more objects without ever renumbering anything is going to be a lot of code and a lot of opportunities for bugs. You should not go there until it's been proven that you really need that level of complication.
Store the items in an array, and use splice() to insert and delete elements.
Or is this not acceptable because of the comment you made in response to the linked list answer?
The problem you are trying to solve is potentially insertion sort which has a simple implementation of O(n^2). But there are ways to improve it.
Suppose there is an order variable associated to each element. You can assign these orders smartly with large gaps between variables and get an amortized O(n*log(n)) mechanism. Look at (Insertion sort is nlogn)

Categories

Resources