I'm trying to solve Euler's fifth problem.
2520 is the smallest number that can be divided by each of the numbers from 1 to 10 without any remainder.
What is the smallest positive number that is evenly divisible by all of the numbers from 1 to 20?
It works well with the sample number 2520.
But not with a number from 1 - 20, and it does not give me anything back, so what is my mistake?
function findFactor(n, max) {
var acc = 0;
for(var i = 2; i <= max; ++i) {
acc += !(n%i) ? 1 : 0;
}
return acc === max - 1 ? true : false;
}
for(var i = 2520; true; ++i) {
if(findFactor(i, 20)) {
console.log(i)
}
if(i > 1e7) console.log("Limit")
}
You have some flaws in your code:
You never exit your loop for (var i = 2520; true; ++i). The browser freezes and doesn't log anything even if there is a match.
You increment i only by one, it's redundant. Increment by 20, as your answer must be divisible by 20.
acc += !(n%i) ? 1 : 0; is redundant too. You don't need to iterate further if n % i !== 0, just return false.
Taking into account all these corrections you may have something like this:
function findFactor(n, max) {
for (let i = 2; i <= max; i++) {
if (n % i !== 0) return false;
}
return true;
}
let n = 20;
//measuring performance
let start = performance.now();
for (let i = n; true; i += n) {
if (findFactor(i, n)) {
console.log(i);
break;
} else if (i > 1e10) {
console.log("Limit");
break;
}
}
console.log(`time spent: ${performance.now() - start}`);
There is another way to calculate the least common multiple (LCM) of more than two numbers - by iteratively computing the LCM of two numbers:
lcm(a, b, c) = lcm(a, lcm(b, c))
The least common multiple of two numbers can be computed as follows:
lcm(a, b) = a * b / gcd(a, b)
where gcd(a, b) is the greatest common divisor. To find it you can use the Euclidean algorithm
//greatest common divisor
const gcd = (a,b) => {
while (a !== 0 && b !== 0) {
if (a > b) a %= b;
else b %= a;
}
return a + b;
};
//least common multiple
const lcm = (a, b) => a * b / gcd(a, b);
const leastMultipleOfRange = n => {
if (n < 3) return n;
let acc = 2;
for (let i = 3; i <= n ; i++) {
acc = lcm(acc, i);
}
return acc;
};
let start = performance.now();
console.log(leastMultipleOfRange(20));
console.log(`time spent: ${performance.now() - start}`);
Most likely, there are some more effective ways of calculating the least common multiple of several numbers, for example, mentioned by Paul, but my knowledge of mathematics is not so deep to explain them.
A more efficient solution to this problem would be to calculate the least common multiple (lcm). The basic idea we can use is similar to calculating the lcm via factorization (though we don't use factorization directly).
Some basics
We can denote that a evenly divides b by a|b and that it doesn't by a∤b. Two numbers are coprime if they don't have a common factor; this also entails that lcm(a, b) = a * b, if a and b are coprime. m = lcm(a, b) has the properties a|m and b|m and there doesn't exists m_<m such that a|m_ and b|m_. Since for each integer a unique factorization exists (as stated in the fundamental theorem of arithmetic) we can express a, b and m as products of primes:
The factorization of m shows that there are no superfluous factors in m. It is exactly as large as it has to be to be divisible by both a and b.
The lcm of multiple numbers can be calculated from the lcm of two numbers recursively:
lcm(a, b, c) = lcm(lcm(a, b), c)
These are the basic mathematical tools required to solve the problem efficiently. Now we're left with two problems: which primes have a power > 0 in the factorization of our lcm, and what values have the corresponding expontents?
Finding the set of primes
We can determine which primes are in the factorization of lcm([1..n]) using the following fact: let p ∊ P and p <= n, then p is obviously in the sequence, so it must also be a factor of the least common multiple. Now how about p > n? Let's start off with the lcm of two values: a and b, where a < p and b < p. From this we can conclude that p∤a and p∤b, so p|lcm(a, b) can't hold either. In general, if p∤a and p∤b, then p∤lcm(a, b) must hold. Proof:
Assume m = lcm(a, b) and p | m
m = a * n1 = b * n2
but since p∤a and p∤b we also get
m = a * p * n1_ = b * p * n2_
n1_ * p = n1
n2_ * p = n2
and thus we can construct m_ with the following properties:
m_ * p = m
a|m_
b|m_
So a prime that is larger than a and b can never be a factor of lcm(a, b). Thanks to the recursive definition of the lcm of more than two integers, we can easily show that this entails that any prime larger that n can't be a factor of lcm([1..n]).
So the primes our factorization will consist of are all in the range [1..n]. E.g. for n=20, like in the problem on project euler, this would be the primes [2, 3, 5, 7, 11, 13, 17, 19].
Exponents
Remains one last problem to solve: the exponent of each prime in the factorization. In a first step we can look at powers of a single numbers:
lcm(x^e1,x^e2) = x^e2, if e1 < e2
So for example in our problem the exponent of 2 must be 4:
The largest power of 2 in the range [1..20] is 16 = 2^4. Any smaller power of 2 divides 16. So for a given n we could calculate the exponent as
So now we have the lcm of a part of the sequence (all powers of primes):
lcm(2,4,8,16, 3,9, 5, 7, 11, 13, 17, 19) =
lcm(lcm(2, 4, 8, 16), lcm(3, 9), 5, 7, 11, 13, 17, 19) =
lcm(16, 9, 5, 7, 11, 13, 17, 19) =
2^4 * 3^2 * 5^1 * 7^1 * ... * 19^1
The last lines of the above equation results from the fact that primes and their powers are always coprime to each other (see above).
But what about the remaining numbers in the sequence? We actually don't need them. Each number that isn't a power of a prime itself is a product of powers of primes (unique factorization). So let's say we have c = p1^e1 * p2^e2 and also a = p1^f1 and b = p2^f2, where a, b, and c are in the range [1..n] and f1 and f2 are maximal. Then e1 <= f1 and e2 <= f2 must hold, as otherwise c <= n couldn't possibly hold (remember that f1 and f2 are already the maximum-exponents for the corresponding primes, so e.g. p1^(f1 + 1) > n). Thus c | lcm(a, b) for a, b and c as defined above, which can be derived from the factorization of lcm(a, b) based on a, b (see above).
The actual implementation
Well, that's been the number theoretical part, time for some actual code (just in case you still read this :D ). At least we have some really pretty code now:
run = function(){
document.getElementById('output_id').innerHTML = 'Calculating...'
var n = document.getElementById('input_n_id').value;
// sieve of eratosthenes, the quick and dirty way
var primes = Array((n - 1) >> 1).fill(0).map((v, i) => i * 2 + 3).reduce((p, v) => {!~p.findIndex(p_ => !(v % p_)) && p.push(v); return p;}, [2]);
// actually calculating n
var sol = primes.map(p => Math.pow(p, Math.floor(Math.log(n) / Math.log(p))))
.reduce((a, b) => a * b, 1);
// output
document.getElementById('output_id').innerHTML = 'Solution: ' + sol;
}
<input maxlength="512" id="input_n_id" placeholder="Enter a value here"/>
<button onclick="run()">Start</button>
<p id="output_id">Waiting for input...</p>
So now there remains only one question to answer: what's the point of all the math? The answer: speed (and beauty ;) ). With this code you can calculate the least common multiple of any range of numbers up to [1..708] (in fact you could go further, but from 709 upwards the solution is beyond the range of javascripts floatingpoint-numbers).
You're setting max to 20, and then your loop relies on all the the numbers between 2 and max (20) being factors of n. That method won't work if n<20 because clearly a number larger than n can't be a factor of n. You'd need to set max to n if n < 20.
This is about primes. Think of which prime numbers make all the numbers between 1 and 20, remember to count the minimum number of each prime you would need and multiply them all together to get the solution. For example, for 9, we'll need two 3's, for 16, we'll need 4 2's, etc.
Related
The code below compares two arrays and checks that the elements at matching indices on both arrays have similar prime factors. If that is true, the count of matching factors ("matching") increases by 1.
/* eslint-disable no-console */
const primeFactors = (n) => {
let number = n;
const factors = [];
let divisor = 2;
while (number >= 2) {
if (number % divisor === 0) {
factors.push(divisor);
number /= divisor;
} else {
divisor += 1;
}
}
return factors;
};
const solution = (A, B) => {
let matching = 0;
for (let index = 0; index < A.length; index += 1) {
const a = A[index];
const b = B[index];
let aFactors = primeFactors(a);
aFactors = new Set(aFactors);
aFactors = Array.from(aFactors);
aFactors = aFactors.sort((first, second) => first - second);
let bFactors = primeFactors(b);
bFactors = new Set(bFactors);
bFactors = Array.from(bFactors);
bFactors = bFactors.sort((first, second) => first - second);
if (JSON.stringify(aFactors) === JSON.stringify(bFactors)) {
matching += 1;
}
}
return matching;
};
This will return 1 since only 15 and 75 at matching indices have similar prime factors (3 and 5 each)
console.log(solution([15, 10, 3], [75, 30, 5]));
How can I make this algorithm more efficient? It currently has an efficiency score of 84%, having failed two optimization tests for large data sets.
Converting an array to a set and back to an array seems like much of a wasted effort. Why not eliminate duplicates right in primeFactors?
while (number >= 2) {
if (number % divisor === 0) {
factors.push(divisor);
while (number % divisor === 0) {
number /= divisor;
}
} else {
divisor += 1;
}
}
There is no need to sort the arrays obtained as above. They are already sorted. There is also no need to stringify them. Just compare them element by element.
The fundamental speedup comes from the observation that the two numbers have the same prime composition if and only if they have the same prime compositions with their gcd. The gcd is very easy to compute; it also tends to be much smaller, and hence much easier to decompose, than its arguments. Besides, it requires only one decomposition, rather than two as in your solution. Consider
same_prime_composition(a, b)
g = gcd(a, b)
primes = primeFactors(g)
return is_decomposable(a/g, primes) && is_decomposable(b/g, primes)
prime_decomposable(x, primes)
for p in primes
while (x % p == 0)
x /= p
return x === 1
It might be beneficial to compute the prime numbers beforehand.
I don't know if javascript supports divmod. If it does, there is even more room for optimization.
A quick speedup is to have PrimeFactors stop at sqrt(n). The value of number at that time is necessarily the last prime factor.
I am wondering if there is a general formula of some sort that can take a single incrementing integer, and run it through a modulus sort of thing to shift it to a random place, so as you increment the counter, its output value jumps around and appears random, yet no value is ever hit twice. Assuming some limit on the set of numbers like 16-bit integers (65536 integers), or 32-bit integers, etc.. Perhaps there is a way to spiral numbers down somehow, I don't know. The sequence would be predictable, but to a layman it would appear random without thinking much of it.
For example, you can multiply a number by 2 to make it not appear directly incremented. But that's not very sophisticated. You could perhaps start the number at the middle of the set (like 30103 for 16-bit integers), then multiply by 2 and rotate the numbers using a modulus, and this would appear even less incremented. But you could still see a pattern.
I'm wondering what sorts of patterns or equations you could run an incremented number through (in a bounded set of integers) so that the output appears the least predictable as possible, and at the same time it never hits the same number twice. This way you could make IDs appear randomly generated to the layman without having to store all the IDs in a database in random order in advance. The formula would generate them from a single stored integer. What is possible in this regard, and what is the equation? How far can it theoretically go?
Maybe you could make the set odd, and skip every 20th number, and somehow prove that it will eventually revolve through the whole set without repeats. I can't figure this out though.
Update: This seems to be in the field of pseudorandom number generation, like this, but I'm not sure if they fit the added constraint of never repeating the number.
Here is what I found and implemented, but it's giving some duplicates :/.
const fetch = (x, o) => {
if (x >= o) {
return x
} else {
const v = (x * x) % o
return (x <= o / 2) ? v : o - v
}
}
const fetch32 = (x) => fetch(x, 4294967291)
const fetch16 = (x) => fetch(x, 65519)
const fetch8 = (x) => fetch(x, 251)
// the last number can be anything.
const build32 = (x, o) => fetch32((fetch32(x) + o) ^ 1542469173)
const build16 = (x, o) => fetch16((fetch16(x) + o) ^ 42703)
const build8 = (x, o) => fetch8((fetch8(x) + o) ^ 101)
let i = 0
let n = Math.pow(2, 32)
while (i < n) {
let j = 0
let r = {}
while (j < n) {
let x = build32(j, i)
if (r[x]) throw `${i}:${j}:${x}`
r[x] = true
j++
}
i++
}
The other linked question in the comment doesn't show a JavaScript implementation that adheres the the uniqueness constraint.
If you are looking for a sequence, where one value is produced from knowing what the previous value was, then what you are looking for could be a Linear congruential generator, with a modulus of a power of 2. There are a few parameters involved:
m: the modulus, which in your case is 28, 216, or 232.
a: the multiplier. To ensure that all values are produced before the first duplicate is generated, this must be a multiple of 4 plus 1 (assuming m is a power of 2).
c: the increment. It must be odd.
You can play with these numbers to arrive at a series that you are satisfied with in terms of "randomness".
The above referenced Wikipedia article lists some parameter choices that are used in some pseudo random generators. I have just selected a=97 and c some odd number half way the range.
Here is some code to prove the uniqueness:
/*
Assuming that m is a power of 2:
- c must be odd
- a % 4 must be 1
*/
function createFetch(m, a, c) { // Returns a function
return x => (a * x + c) % m;
}
const m = 2**16;
const fetch16 = createFetch(m, 97, (m>>1)-1);
const r = new Set;
let x = 1;
for (let i = 0; i < m; i++) {
x = fetch16(x);
if (i < 10) console.log(x);
if (r.has(x)) throw `${i}:${x}`
r.add(x);
}
console.log("...");
console.log(`generated ${r.size} unique numbers`);
NB/ this is a good use case for a generator, which in JavaScript looks like this:
function * fetch(m, a, c, x=1) {
while (true) {
x = (a * x + c) % m;
yield x;
}
}
const m = 2**16;
const fetch16 = fetch(m, 97, (m>>1)-1);
const r = new Set;
for (let i = 0; i < m; i++) {
x = fetch16.next().value;
if (i < 10) console.log(x);
if (r.has(x)) throw `${i}:${x}`
r.add(x);
}
console.log("...");
console.log(`generated ${r.size} unique numbers`);
Any block cipher whose block size is n bits is a permutation of {0,1,2, ..., 2n-1}. Thus, if E is such a block cipher and k is a valid key for E, then Ek(0), Ek(1), ..., Ek(2n-1) are all distinct. If the block cipher is good then the values appear "random" to the naked eye. If you change the key k you get a different permutation.
This is actually mentioned in the link you provided.
Consider this answer as well.
var bottomLimit = 1
var topLimit = 10
var arr = []
for (var i = bottomLimit; i < topLimit; i++) {
arr.push(i)
}
arr = shuffle(arr);
console.log(arr);
//https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array#answer-2450976
function shuffle(array) {
var currentIndex = array.length,
temporaryValue, randomIndex;
while (0 !== currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
Well,you can generate random no. inside the range of two no.
public static int getRandomVal(int min, int max) {
Random random = new Random();
return random.nextInt((max - min) + 1) + min;
}
public static void getRandomNumbers(int size, int min,
int max) {
ArrayList<Integer> numbers = new ArrayList<Integer>();
while (numbers.size() < size) {
int random = getRandomVal(min, max);
if (!numbers.contains(random)) {
numbers.add(random);
System.out.println(" "+random);
}
}
}
now to generate 10 different no. between 50 and 100 you can use
getRandomNumbers(10, 50,100);
This approach is very easy I am creating an array and just checking the random value if it is already present or not. If it is not present I am pushing it to the array and outputting it.
Get yourself a seeded random number generator.
Seed with 1, return next random number. Seed with 2, return next random number. Seed with 3, return next random number...
If you seed with an integer then the next random number will be repeatable and pseudo random.
let say it's given 2k+2+3p=n as the test, how to find out the test is true for a number is valid for a number when k>=0, p>=0, n>=0:
example1 : n=24 should result true since k=5 & p=4 => 2(5)+2+3(4)=24
example2 : n=11 should result true since k=0 & p=3 => 2(0)+2+3(3)=11
example3 : n=15 should result true since k=5 & p=1 => 2(5)+2+3(1)=15
i wonder if there is a mathematic solution to this. i solved it like bellow:
//let say 2k+2+3p=n
var accepted = false;
var betterNumber= n-2;
//assume p=0
var kReminder= (betterNumber)%2==0;
//assume k=0
var pReminder= (betterNumber)%3==0;
if (kReminder || pReminder){
accepted=true;
}else{
var biggerChunk= Math.Max(2,3); //max of 2k or 3p, here i try to find the bigger chunk of the
var smallerChunk= Math.Min(2,3);
if ((betterNumber%bigger)%smallerChunk==0){
accepted=true;
}else
{
accepted=false;
}
}
still there are edge cases that i didn't see. so i wonder if it has a better solution or not.
Update
the test above is just an example. the solution should be efficient enough for big numbers or any combination of number like 1000000k+37383993+37326328393p=747437446239902
By inspection, 2 is the smallest valid even number and 5 is the smallest valid odd number:
2 is valid (k=0, p=0)
5 is valid (k=0, p=1)
All even numbers >= 2 and all odd numbers >= 5 are valid.
Even numbers: k=n/2-1, p=0
odd numbers: k=(n-3)/2-1, p=1
What we're doing here is incrementing k to add 2s to the smallest valid even and odd numbers to get all larger even and odd numbers.
All values of n >= 2 are valid except for 3.
Dave already gave a constructive and efficient answer but I'd like to share some math behind it.
For some time I'll ignore the + 2 part as it is of less significance and concentrate on a generic form of this question: given two positive integers a and b check whether number X can be represented as k*a + m*b where k and m are non-negative integers. The Extended Euclidean algorithm essentially guarantees that:
If number X is not divisible by GCD(a,b), it can't be represented as k*a + m*b with integer k and m
If number X is divisible by GCD(a,b) and is greater or equal than a*b, it can be represented as k*a + m*b with non-negative integer k and m. This follows from the fact that d = GCD(a,b) can be represented in such a form (let's call it d = k0*a + m0*b). If X = Y*d then X = (Y*k0)*a + (Y*m0)*b. If one of those two coefficients is negative you can trade one for the other adding and subtracting a*b as many times as required as in X = (Y*k0 + b)*a + (Y*m0 - a)*b. And since X >= a*b you can always get both coefficients to be non-negative in such a way. (Note: this is obviously not the most efficient way to find a suitable pair of those coefficients but since you only ask for whether such coefficients exist it should be sufficient.)
So the only gray area is numbers X divisible by GCD(a,b) that lie between in the (0, a*b) range. I'm not aware of any general rule about this area but you can check it explicitly.
So you can just do pre-calculations described in #3 and then you can answer this question pretty much immediately with simple comparison + possibly checking against pre-calculated array of booleans for the (0, a*b) range.
If you actual question is about k*a + m*b + c form where a, b and c are fixed, it is easily converted to the k*a + m*b question by just subtracting c from X.
Update (Big values of a and b)
If your a and b are big so you can't cache the (0, a*b) range beforehand, the only idea I have is to do the check for values in that range on demand by a reasonably efficient algorithm. The code goes like this:
function egcd(a0, b0) {
let a = a0;
let b = b0;
let ca = [1, 0];
let cb = [0, 1];
while ((a !== b) && (b !== 0)) {
let r = a % b;
let q = (a - r) / b;
let cr = [ca[0] - q * cb[0], ca[1] - q * cb[1]];
a = b;
ca = cb;
b = r;
cb = cr;
}
return {
gcd: a,
coef: ca
};
}
function check(a, b, x) {
let eg = egcd(a, b);
let gcd = eg.gcd;
let c0 = eg.coef;
if (x % gcd !== 0)
return false;
if (x >= a * b)
return true;
let c1a = c0[0] * x / gcd;
let c1b = c0[1] * x / gcd;
if (c1a < 0) {
let fixMul = -Math.floor(c1a / (b / gcd));
let c1bFixed = c1b - fixMul * (a / gcd);
return c1bFixed >= 0;
}
else { //c1b < 0
let fixMul = -Math.floor(c1b / (a / gcd));
let c1aFixed = c1a - fixMul * (b / gcd);
return c1aFixed >= 0;
}
}
The idea behind this code is based on the logic described in the step #2 above:
Calculate GCD and Bézout coefficients using the Extended Euclidean algorithm (if a and b are fixed, this can be cached, but even if not this is fairly fast anyway).
Check for conditions #1 (definitely no) and #2 (definitely yes) from the above
For value in the (0, a*b) range fix some coefficients by just multiplying Bézout coefficients by X/gcd. F
Find which of the two is negative and find the minimum multiplier to fix it by trading one coefficient for another.
Apply this multiplier to the other (initially positive) coefficient and check if it remains positive.
This algorithm works because all the possible solutions for X = k*a + m*b can be obtained from some base solution (k0, m0) using as (k0 + n*b/gcd, m0 + n*a/gcd) for some integer n. So to find out if there is a solution with both k >= 0 and m >= 0, all you need is to find the solution with minimum positive k and check m for it.
Complexity of this algorithm is dominated by the Extended Euclidean algorithm which is logarithmic. If it can be cached, everything else is just constant time.
Theorem: it is possible to represent number 2 and any number >= 4 using this formula.
Answer: the easiest test is to check if the number equals 2 or is greater or equals 4.
Proof: n=2k+2+3p where k>=0, p>=0, n>=0 is the same as n=2m+3p where m>0, p>=0 and m=k+1. Using p=0 one can represent any even number, e.g. with m=10 one can represent n=20. The odd number to the left of this even number can be represented using m'=m-2, p=1, e.g. 19=2*8+3. The odd number to the right can be represented with m'=m-1, p=1, e.g. 21=2*9+3. This rule holds for m greater or equal 3, that is starting from n=5. It is easy to see that for p=0 two additional values are also possible, n=2, n=4.
The following code results by combing common values into one:
//3,6,9,12,15------
//5,10,15----------
//result: sum of (3,5,6,9,10,12,15) = 60
int = 0;
for(var i=1;i<16;i++){
if(i%3==0 || i%5==0){
int +=i;
}
}
alert(int);//60
But I wanted to get the output without combining:
//3,6,9,12,15------|should ressult:
//5,10,15----------|75
//result without combining: sum of (3,6,9,12,15,5,10,15) == 75
If you say I should do && instead of || then it will just result common sum i.e. 15 here in example.
So, how can I do?
This will do the trick:
int = 0;
for(var i=1;i<16;i++){
if(i%3==0){
int +=i;
}
if(i%5==0){
int +=i;
}
}
alert(int);//75
Or if you prefer a more compact solution:
int = 0;
for(var i=1;i<16;i++){
int += (i % 3 ? 0 : i) + (i % 5 ? 0 : i);
}
alert(int);//75
Basically, for values that are multiples of 15, you count them twice.
Hints regarding a solution without using a loop: We can break the problem down to just list summing all the multiples of n between a and b-1. This can be computed directly without going through each number by taking a look at the sequence it produces. For n=3, a=1, b=28, this is:
3, 6, 9, 12, 15, 18, 21, 24, 27
Notice now that 27+3=30, 24+6=30, 21+9=30, 18+12=30, and there's only one outlier in the middle, 15. So really all you need to know in order to solve this is the minimum element in the sequence, the maximum element, the number of elements, and the middle element if the the number of elements is odd. (In fact you don't even really need to know the middle number once you realize that it's precisely half of the sum the min and the max).
To help you out a bit more:
var max = Math.floor((b - 1) / n) * n;
var min = a + n - (a % n);
var count = (max - min) / n + 1;
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