简体   繁体   中英

Fill an array with random non-consecutive numbers

I want to create an array ( indexes ) that should be filled with random numbers from 0 to length - 1 .

So if length = 3; then the indexes should extend from 0 to 2 , for instance it could be something like: [0, 2, 1] .

And there is one condition that we should follow: (God, how can I describe this in English:)

The condition: We don't accept consecutive numbers.

So we shouldn't return :

[0, 1, 2]       => Because 0 and 1 and 2 are consecutive.

[2, 0, 1]       => Because 0 and 1 are consecutive.

[2, 3, 1, 0]    => Because 2 and 3 are consecutive.

Ok, I know there may be an exception, the last element may be consecutive because we have no choice at that point!

I wrote a code but unfortunately, it crashes the browser because of high CPU usage!

Please help, my laptop and I are so much confused!

// generate number from 0 to max (max could include in the result)
function getRandom(max) {
    return Math.floor(Math.random() * (max + 1));
};

// our array to be filled with unordered numbers
let indexes = [];

// we will fill the above array with 0, 1 ,2 ,3 I mean 0 to (length - 1) 
let length = 4;

// loop through indexes so that it is filled with 4 elements
while( indexes.length <= length){

      // get a number randomally from 0 to 3
      let result = getRandom(length - 1);
       // we don't want any repeated number so..
       if(!indexes.includes(result)){     
          // finally here is our condition, we should   
          if( result !== indexes[indexes.length - 1] + 1 ){
              indexes.push(result);
          } else if(indexes.length == length){
            // push the last element to the array despite the condition
            indexes.push(result);
          }
       }

};

console.log(indexes);

You could adapt the modern Fisher-Yates algorithm in the following way:

if a swap of values at indexes i and j would bring a value at index i that is not allowed there because of what was already placed at index i+1 , then don't do that swap, but instead do a triplet rotation between those three involved indexes.

Here is the implementation, together with a test of 400 runs:

 function shuffled(n) { let a = [...Array(n).keys()]; for (let i = n - 1; i > 0; i--) { let j = Math.floor(Math.random() * (i + 1)); if (a[j]+1 === a[i+1]) { // a swap is going to violate the rule at i/i+1 // Triple rotation at j, i, i+1 (or, when j == i, swap at i, i+1) a[j] = a[i]; a[i] = a[i+1]--; } else { // standard swap let temp = a[i]; a[i] = a[j]; a[j] = temp; } } // Finally check first two values: if (a[0]+1 === a[1]) { let temp = a[0]; a[0] = a[1]; a[1] = temp; } return a; } // Test this solution: function verify(a) { let set = new Set(a); return !a.some((v, i) => v === a[i-1]+1 || !set.has(i)); } for (let size = 2; size < 6; size++) { for (let i = 0; i < 100; i++) { let a = shuffled(size); if (!verify(a)) throw "not correct"; } } console.log("tests successful");

This has a predictable linear time complexity. No risk of multiple failing attempts.

Your algorithm takes random values and you have a problem with the last value, if the only left over value is the incremented value of the value before the last value.

For example, the taken values are 1 , 0 , 2 and leftover value is 3

          v
[1, 0, 2, 3]

In this case, there is no other value available, than an unwanted value. This yields to an infinite loop.


You could take an array of all indices and use a Fisher-Yates shuffle algorithm and shuffle until no consecutive incrementing values are in the array.

 function shuffle(array) { // https://stackoverflow.com/a/2450976/1447675 var currentIndex = array.length, temporaryValue, randomIndex; // While there remain elements to shuffle... while (0 !== currentIndex) { // Pick a remaining element... randomIndex = Math.floor(Math.random() * currentIndex); currentIndex -= 1; // And swap it with the current element. temporaryValue = array[currentIndex]; array[currentIndex] = array[randomIndex]; array[randomIndex] = temporaryValue; } return array; } let length = 4, indices = Array.from({ length }, (_, i) => i); do indices = shuffle(indices); while (indices.some((v, i, a) => v + 1 === a[i + 1])) console.log(...indices);

With a length of 4, your result array only has a few possible results (see below).

The problem with your function is that if you start with digit(s) that result in no possible answer, it will run forever, and with only 4 possible numbers, the chance of that happening is very high. If 0 or 3 is returned as the first number, it will never find a solution. Same if 1 is returned and the second number is not 3, or if 2 is first and the second number is not 0, etc. updated because comments clarified that descending consecutive numbers are allowed (which seems weird).

You're also going to run forever because your while loop should use indexes.length < length instead of <=

[0, 1, 2, 3] // no
[0, 1, 3, 2] // no
[0, 2, 1, 3] // yes
[0, 2, 3, 1] // no
[0, 3, 1, 2] // no
[0, 3, 2, 1] // yes
[1, 0, 2, 3] // no
[1, 0, 3, 2] // yes
[1, 2, 3, 0] // no
[1, 2, 0, 3] // no
[1, 3, 0, 2] // yes
[1, 3, 2, 0] // yes
[2, 0, 1, 3] // no
[2, 0, 3, 1] // yes
[2, 1, 0, 3] // yes
[2, 1, 3, 0] // yes
[2, 3, 0, 1] // no
[2, 3, 1, 0] // no
[3, 0, 1, 2] // no
[3, 0, 2, 1] // yes
[3, 1, 0, 2] // yes
[3, 1, 2, 0] // no
[3, 2, 0, 1] // no
[3, 2, 1, 0] // yes

What this tells us is that this algorithm is likely to fail because it has no "failure" condition handling when there are no numbers left that can satisfy the conditions. You could have a check for that, and restart if it's the case, but it's still likely to be slow. A different approach would be better.

This maintains a set of possible numbers to choose from, for the next number.

The next number picked from this set is randomly chosen.

If a consecutive number is chosen, then the order is swapped.

No dupes are permitted.

 function randomNonConsecutive(upto) { const result = [] let set = Object.keys([...Array(upto)]) for(let x = 0; x < upto; x++) { let index = Math.floor(Math.random()*set.length) let insert = set[index] if(x > 0 && Number(result[x-1]) === insert-1) { [result[x-1], insert] = [insert, result[x-1]] } result[x] = insert set.splice(index,1) } return result } let result = randomNonConsecutive(10) console.log(result)

Updated:

I was comparing the wrong number to check for the non consecutive rule

You can create a list of indexes, take the first item randomly. then remove one item from the indexes at a time if the condition is meet or if it is the last element

 const range = (n) => { const array = []; for (let i = 0; i < n; i++) array.push(i); return array; }; const randomIndex = (array) => ( parseInt(array.length * Math.random()) ); const randomNonConsecutive = (n) => { const array = range(n); const result = array.splice(randomIndex(array), 1); while (array.length) { const last = result[result.length - 1]; const next = last + 1; const index = randomIndex(array); const item = array[index]; if (item !== next || array.length === 1) { result.push(item) array.splice(index, 1); } } return result; } console.log(randomNonConsecutive(3)) console.log(randomNonConsecutive(4)) console.log(randomNonConsecutive(5))

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