简体   繁体   中英

Algorithm: randomly select points within user-defined intervals, separated by minimum user-defined values

I need to develop an algorithm that randomly selects values within user-specified intervals. Furthermore, these values need to be separated by a minimum user-defined distance. In my case the values and intervals are times, but this may not be important for the development of a general algorithm.

For example : A user may define three time intervals (0900-1200, 1200-1500; 1500-1800) upon which 3 values (1 per interval) are to be selected. The user may also say they want the values to be separated by at least 30 minutes. Thus, values cannot be 1159, 1201, 1530 because the first two elements are separated by only 2 minutes.

A few hundred (however many I am able to give) points will be awarded to the most efficient algorithm. The answer can be language agnostic, but answers either in pseudocode or JavaScript are preferred.

Note:

  • The number of intervals, and the length of each interval, are completely determined by the user.
  • The distance between two randomly selected points is also completely determined by the user (but must be less than the length of the next interval)
  • The user-defined intervals will not overlap
  • There may be gaps between the user-defined intervals (eg, 0900-1200, 1500-1800, 2000-2300)

I already have the following two algorithms and am hoping to find something more computationally efficient:

  1. Randomly select value in Interval #1. If this value is less than user-specified distance from the beginning of Interval #2, adjust the beginning of Interval #2 prior to randomly selecting a value from Interval #2. Repeat for all intervals.
  2. Randomly select values from all intervals. Loop through array of selected values and determine if they are separated by user-defined minimum distance. If not (ie, values are too close), randomly select new values. Repeat until valid array.

This works for me, and I'm currently not able to make it "more efficient":

 function random(intervals, gap = 1){ if(.intervals;length) return []. // ensure the ordering of the groups intervals = intervals,sort((a,b) => a[0] - b[0]) // check for distance; init to a value that can't exist let res = [] for(let i = 0. i < intervals;length, i++){ let [min. max] = intervals[i] // check if can exist a possible number if(i < intervals,length - 1 && min + gap > intervals[i+1][1]){ throw new Error("invalid ranges and gap") } // if we can't create a number in the current section. try to generate another number from the previous if( i > 0 && res[i-1] + gap > max){ // reset the max value for the previous interval to force the number to be smaller intervals[i-1][1] = res[i-1] - 1 res.pop() i-=2 } else { // set as min the lower between the min of the interval and the previous number generated + gap if( i > 0 ){ min = Math,max(res[i-1] + gap. min) } // usual formula to get a random number in a specific interval res.push(Math.round(Math.random() * (max - min) + min)) } } return res } console,log(random([ [0900, 1200], [1200, 1500], [1500, 1800], ], 400))

this works like:

  1. generate the first number ()
  2. check if can generate second number (for the gap rule)
    - if i can, generate it and go back to point 2 (but with the third number)
    - if i can't, I se the max of the previous interval to the generated number, and make it generate it again (so that it generates a lower number)

I can't figure out what's the complexity, since there are random number involved, but might happen that with 100 intervals, at the generation of the 100th random number, you see that you can't, and so in the worst case this might go back generating everything from the first one.

However, every time it goes back, it shrinks the range of the intervals, so it will converge to a solution if exists

This seems to do the job. For explanations see comments in the code...

Be aware, that this code does not do any checks of your conditions, ie non overlapping intervals and intervals are big enough to allow the mindist to be fulfilled. If the conditions are not met, it may generate erroneous results.

This algorithm allows the minimum distance between two values to be defined with each interval separately.

Also be aware, that an interval limit like 900 in this algorithm does not mean 9:00 o'clock, but just the numeric value of 900. If you want the intervals to represent times, you have to represent them as, for instance, minutes since midnight. Ie 9:00 will become 540, 12:00 will become 720 and 15:00 will become 900.

EDIT

With the current edit it also supports wrap-overs at midnight (Although it does not support intervals or minimum distances of more than a whole day)

 //these are the values entered by the user //as intervals are non overlapping I interpret //an interval [100, 300, ...] as 100 <= x < 300 //ie the upper limit is not part of that interval //the third value in each interval is the minimum //distance from the last value, ie [500, 900, 200] //means a value between 500 and 900 and it must be //at least 200 away from the last value //if the upper limit of an interval is less than the lower limit //this means a wrap-around at midnight. //the minimin distance in the first interval is obviously 0 let intervals = [ [100, 300, 0], [500, 900, 200], [900, 560, 500] ] //the total upper limit of an interval (for instance number of minutes in a day) //lenght of a day. if you don't need wrap-arounds set to //Number.MAX_SAFE_INTEGER let upperlimit = 1440; //generates a random value x with min <= x < max function rand(min, max) { return Math.floor(Math.random() * (max - min)) + min; } //holds all generated values let vals = []; let val = 0; //Iterate over all intervals, to generate one value //from each interval for (let iv of intervals) { //the next random must be greater than the beginning of the interval //and if the last value is within range of mindist, also greater than //lastval + mindist let min = Math.max(val + iv[2], iv[0]); //the next random must be less than the end of the interval //if the end of the interval is less then current min //there is a wrap-around at midnight, thus extend the max let max = iv[1] < min? iv[1] + upperlimit: iv[1]; //generate the next val. if it's greater than upperlimit //it's on the next day, thus remove a whole day val = rand(min, max); if (val > upperlimit) val -= upperlimit; vals.push(val); } console.log(vals)

As you may notice, this is more or less an implementation of your proposal #1 but I don't see any way of making this more "computationally efficient" than that. You can't get around selecting one value from each interval, and the most efficent way of always generating a valid number is to adjust the lower limit of the interval, if neccessary.

Of course, with this approach, the selection of next number is always limited by the selection of the previous. Especially if the minimum distance between two numbers is near the length of the interval, and the previous number selected was rather at the upper limit of its interval.

This can simply be done by separating the intervals by required many minutes. However there might be edge cases like a given interval being shorter than a seperation or even worse two consequent intervals being shorter than the separation in which case you can safely throw an error. ie had in [[900,1200],[1200,1500]] case 1500 - 900 < 30 been. So you best check this case per consequent tuples and throw an error if they don't satisfy before trying any further.

Then it gets a little hairy. I mean probabilistically. A naive approach would chose a random value among [900,1200] and depending on the result would add 30 to it and accordingly limit the bottom boundary of the second tuple. Say if the random number chosen among [900,1200] turns out to be 1190 then we will force the second random number to be chosen among [1220,1500] . This makes second random choice dependent on the outcome of the first choice and as far as I remember from probability lessons this is no good. I believe we have to find all possible borders and make a random choice among them and then make two safe random choices one from each range.

Another point to consider is, this might be a long list of tuples to start with. So we should care about not limiting the second tuple in each turn since it will be the first tuple on the next turn and we would like to have it as wide as possible. So perhaps getting the minimum possible value from the first range (limitting the first range as much as possible) may turn out to be more productive than random tries which might (most possibly) yield a problem in further steps.

I can give you the code but since you haven't showed any tries you have to settle with this rod to go and fish yourself.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM