Picking A, C and M for Linear congruential generator - javascript

I am looking to implement a simple pseudorandom number generator (PRNG) that has a specified period and guaranteed no collisions for the duration of that period. After doing some research I came across the very famous LCG which is perfect. The problem is, I am having trouble understanding how to properly configure it. Here is my current implementation:
function LCG (state)
{
var a = ?;
var c = ?;
var m = ?;
return (a * state + c) % m;
}
It says that in order to have a full period for all seed values the following conditions must be met:
c and m are relatively prime
a-1 is divisible by all prime factors of m
a-1 is a multiple of 4 if m is a multiple of 4
1 and 3 are simple to understand and test for. However what about 2, I don't quite understand what that means or how to check for it. And what about C, can it be zero? what if it's non-zero?
Overall I need to select A, C and M in such a way that I have a period of 48^5 - 1. M is equal to the period, I am not sure about A and C.

From Wikipedia:
Provided that c is nonzero, the LCG will have a full period for all seed values if and only if:
c and m are relatively prime,
a-1 is divisible by all prime factors of m,
a-1 is a multiple of 4 if m is a multiple of 4.
You said you want a period of 485-1, so you must choose m≥485-1. Let's try choosing m=485-1 and see where that takes us. The conditions from the Wikipedia article prohibit you from choosing c=0 if you want the period to be m.
Note that 11, 47, 541, and 911 are the prime factors of 485-1, since they're all prime and 11*47*541*911 = 485-1.
Let's go through each of those conditions:
For c and m to be relatively prime, c and m must have no common prime factors. So, pick any prime numbers other than 11, 47, 541, and 911, then multiply them together to choose your c.
You'll need to choose a such that a-1 is divisible by all the prime factors of m, i.e., a = x*11*47*541*911 + 1 for any x of your choosing.
Your m is not a multiple of 4, so you can ignore the third condition.
In summary:
m = 485-1,
c = any product of primes other than 11, 47, 541, and 911 (also, c must be less than m),
a = x*11*47*541*911 + 1, for any nonnegative x of your choice (also, a must be less than m).
Here's a smaller test case (in Python) using a period of 482-1 (which has prime factors 7 and 47):
def lcg(state):
x = 1
a = x*7*47 + 1
c = 100
m = 48**2 - 1
return (a * state + c) % m
expected_period = 48**2 - 1
seeds = [5]
for i in range(expected_period):
seeds.append(lcg(seeds[-1]))
print(len(set(seeds)) == expected_period)
It outputs True, as it should. (If you have any trouble reading Python, let me know and I can translate it to JavaScript.)

Based on Snowball's answer and the comments I've created a complete example. You can use the set == list comparison for smaller numbers. I could not fit 48^5-1 into memory.
To circumvent the a < m problem, I'm incrementing the target a few times to find a number where a is able to be < m (where m has duplicated prime factors). Surprisingly +2 is enough for a lot of numbers. The few extra numbers are later skipped while iterating.
import random
def __prime_factors(n):
"""
https://stackoverflow.com/a/412942/6078370
Returns all the prime factors of a positive integer
"""
factors = []
d = 2
while n > 1:
while n % d == 0:
factors.append(d)
n //= d
d += 1
if d * d > n:
if n > 1: factors.append(n)
break
return factors
def __multiply_numbers(numbers):
"""multiply all numbers in array"""
result = 1
for n in numbers:
result *= n
return result
def __next_good_number(start):
"""
https://en.wikipedia.org/wiki/Linear_congruential_generator#c%E2%89%A00
some conditions apply for good/easy rotation
"""
number = start
factors = __prime_factors(number)
while len(set(factors)) == len(factors) or number % 4 == 0:
number += 1
factors = __prime_factors(number)
return number, set(factors)
# primes < 100 for coprime calculation. add more if your target is large
PRIMES = set([2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41,
43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97])
def create_new_seed(target):
"""be aware, m might become > target"""
m, factors = __next_good_number(target)
a = __multiply_numbers(factors) + 1
# https://en.wikipedia.org/wiki/Coprime_integers
otherPrimes = [p for p in PRIMES if p not in factors]
# the actual random part to get differnt results
random.shuffle(otherPrimes)
# I just used arbitary 3 of the other primes
c = __multiply_numbers(otherPrimes[:3])
# first number
state = random.randint(0, target-1)
return state, m, a, c
def next_number(state, m, a ,c, limit):
newState = (a * state + c) % m
# skip out of range (__next_good_number increases original target)
while newState >= limit:
newState = (a * newState + c) % m
return newState
if __name__ == "__main__":
target = 48**5-1
state, m, a, c = create_new_seed(target)
print(state, m, a, c, 'target', target)
# list and set can't fit into 16GB of memory
checkSum = sum(range(target))
randomSum = 0
for i in range(target):
state = newState = next_number(state, m, a ,c, target)
randomSum += newState
print(checkSum == randomSum) # true
LCG is quite fascinating and usable in things like games.
You can iterate a giant list of things in a deterministic random order. Shuffeling and saving the whole list is not required:
def riter(alist):
""" iterate list using LCG """
target = len(alist)
state, m, a, c = create_new_seed(target)
for _ in range(target):
yield alist[state]
state = next_number(state, m, a ,c, target)
It is easy to save the state in between iteration steps:
savedState = '18:19:25:6:12047269:20'
print('loading:', savedState)
i, state, m, a, c, target = (int(i) for i in savedState.split(':'))
state = next_number(state, m, a, c, target)
i += 1
print('i:', i, 'is random number:', state, 'list done:', i+1 == target)
print('saving:', '{}:{}:{}:{}:{}:{}'.format(i, state, m, a, c, target))

Related

algorithm to determine if a number is made of sum of multiply of two other number

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 solution does not work for values between 1 - 20

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.

JavaScript - Is there a way to find out whether the given characters are contained in a string without looping?

I have an array of combinations from 5 characters (order within a combination plays no role):
AB, ABDE, CDE, C, BE ...
On its basis I need to validate the input from user. The entered combination of characters should be contained in one of the combinations of the array.
If user enters "ADE" or "CE" the result should be yes, if e.g. "BCE" - no.
In a trivial case, when entered combination simply matches the one in array, I can use .inArray. If entered combination consists of neighbors, I can do .indexOf. How to be in the case above?
One of the solutions would be to extend the initial array by including all possible "child" combinations. Is there an alternative?
The first thing I could think of is grep'ping the array with a regex match.
var haystack = ["BCED","DBCE","CEB","ECBA","CB","BDCA"];
var needle = "CBE";
var re = new RegExp("(?=.*" + needle.split('').join(")(?=.*") + ").{" + needle.length+"}");
console.log(re);
console.log($.grep(haystack, function(str){
return str.match(re,"g");
}));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
I'll expand on the comment I've made to the quesion above: If you have a small number of fixed set elements, you can represent the sets as binary masks. So say you have the original sets as strings:
var sset = ["AB", "ABDE", "CDE", "C", "BE"];
Create a dictionary of possible elements and bits. The bits are powers of two, which can be created by bit-shifting: 1 << n is bit n:
dict = {
A: (1 << 0),
B: (1 << 1),
C: (1 << 2),
D: (1 << 3),
E: (1 << 4),
};
That dictionary can then be used to create the bitmask:
function bitmask(s, d) {
let res = 0;
for (let i = 0; i < s.length; i++) {
res |= d[s[i]]
}
return res;
}
Create a companion array to the sets that contains the masks:
var mset = sset.map(function(x) { return bitmask(x, dict); });
If you want to check an input, cnvert it to a mask first and then run the checks. A set s contains all bits of an input x if (s & x) == x:
var s = "ADE";
var m = bitmask(s, dict);
for (let i = 0; i < mset.length; i++) {
console.log(sset[i], s, (mset[i] & m) == m);
}
You can use this strategy for several conditions:
• (a & b) == b — all elements of b are contained in a;
• (a & b) == 0 — a and b have no common elements;
• (a & b) != 0 — at least one elements of b is in a;
• a == b — the sets a and b are identical.
In set parlance a & b is the intersection, a | b is the union and a ^ b is the symmetric difference of a and b.
As far as I know, jQuery is a library written in Javascript, so all bit-wise operators should be available.

How do I assign a probability (uniform increasing or decreasing linear distribution) to a array of values?

Given X=[1,2,3,4,5,6,7,8,9,10] -- but X could be any length(N).
I want to achieve the following:
I want to give the 1st value X[0], the highest probability.
I want to give the 2nd value X[1], a lesser probability than X[0].
I want to give the 3rd value X[2], a lesser probability than X[1].
...
I want to give the Last value X[N], a lesser probability than X[N-1]
All probabilities should sum up to 1.
For clarity with uniform probability distribution(1/(X.length)) looks like this:
{1:0.1, 2:0.1, 3:0.1, 4:0.1, 5:0.1, 6:0.1, 7:0.1, 8:0.1, 9:0.1, 10:0.1,}
If possible solution in javascript would be Great.
You could sum the indices (all values increased by one) and use the sum for calculation the probability.
For a reverse distribution, reverse the probability array.
var add = (a, b) => a + b,
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
sum = data.length * (data.length + 1) / 2,
probability = data.map((_, i) => (i + 1) / sum),
sumProbability = probability.reduce(add, 0);
console.log(probability);
console.log(sumProbability);
Let's probability for the last element is q, for previous element is 2*q, for previous is 3*q ... and for the first one is N*q
q + 2 * q + 3 * q +...+(N-1)*q + N * q = 1
q * Sum(1...N) = 1
q * N * (N + 1) / 2 = 1
q = 2 / (N * (N + 1))
So you can find q for any sequence length and get probabilities for every element.
Example for N = 3
q = 2 / (3 * 4) = 1/6
probabilities:
3/6, 2/6, 1/6 Sum = 1

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

Categories

Resources