简体   繁体   中英

How to copy and randomize copied elements in the same loop?

I'm try to copy the even numbers in an array and randomize the newly copied element inside the same for loop:

var arr=[0,1,2,3,4,5,6,7,8];
var arr2=[];
for(var i=0;i<arr.length;i++){
  if(arr[i]%2==0){
    arr2.splice(Math.random()*arr2.length,0,arr[i]);
  }
}
document.write(arr2);

The concept is simple: insert the newly copied element at random position of the new array, but the serval output indicates it's not correct:

2,6,4,8,0
4,8,6,2,0
6,2,8,4,0

which always have 0 at the last. What's wrong with the code? Or is my concept wrong?

Here's a scopedShuffle function:

function scopedShuffle(a){
  var n = a.slice(), l = n.length;
  n.sort(function(b, c){
    return 0.5 - Math.floor(Math.random()*(l+1))/l;
  });
  return n;
}
van resultArray = scopedShuffle(yourArrayHere);

The key here is the scoped use of n where var n = a.slice() , this way the original Array does not get changed.

I would use a Constuctor, though:

 function ShuffleMaster(inputArray){ var a = inputArray; if(!(a instanceof Array)){ throw new Error('inputArray must be an Array'); } this.getInputArray = function(){ return a; } this.setInputArray = function(inputArray){ if(!(inputArray instanceof Array)){ throw new Error('inputArray must be an Array'); } a = inputArray; return this; // allows for daisy-chaining } this.shuffle = function(){ var n = a.slice(), l = n.length; n.sort(function(b, c){ return 0.5 - Math.floor(Math.random()*(l+1))/l; }); return n; } } var myArray = [0, 7, 21, 26, 78, 756]; var sM = new ShuffleMaster(myArray); console.log(sM.shuffle()); console.log(sM.shuffle()); console.log(sM.setInputArray([4, 5, 85, 46, 11]).shuffle()); console.log(sM.getInputArray()); 

Here's a simple closure style, where a Self-executing function scopes off the new Array, so it can be accessed upon the next call without passing the original argument... almost static like.

 var scopedShuffle = (function(){ var a; return function(inputArray){ if(inputArray){ a = inputArray.slice(); } var n = a.slice(), l = n.length; n.sort(function(b, c){ return 0.5 - Math.floor(Math.random()*(l+1))/l; }); return n; } })(); console.log(scopedShuffle(['a', 2, 4, 'g', 'apes'])); console.log(scopedShuffle()); console.log(scopedShuffle()); console.log(scopedShuffle(['learning', 'you', 'now', 'are', 'monkeys', 42, 'life', 'itself', 'the Universe'])); console.log(scopedShuffle()); console.log(scopedShuffle()); 

Math.random() returns a number between 0, inclusive, and 1, exclusive . Which means Math.random()*arr2.length will always be less than arr2.length , so no element will ever be spliced into the end of the array - except on the first iteration of your loop when the new array is empty so 0 ends up as its first and only element. But subsequent iterations insert to a random position anywhere other than at the end, thus always pushing the 0 to the right.

You can fix it by changing Math.random()*arr2.length to Math.random() * (arr2.length + 1) .

Note also that .splice() doesn't seem to care if you pass it a number that isn't an integer, but for neatness I'd prefer to use Math.floor(Math.random() * (arr2.length + 1)) .

Demo:

 function makeArray() { var arr=[0,1,2,3,4,5,6,7,8]; var arr2=[]; for(var i=0;i<arr.length;i++){ if(arr[i]%2==0){ arr2.splice(Math.floor(Math.random()*(arr2.length+1)), 0, arr[i]); } } return arr2; } console.log(JSON.stringify(makeArray())); console.log(JSON.stringify(makeArray())); console.log(JSON.stringify(makeArray())); console.log(JSON.stringify(makeArray())); console.log(JSON.stringify(makeArray())); console.log(JSON.stringify(makeArray())); console.log(JSON.stringify(makeArray())); 

The simple solution: Long chain of methods ( slice , filter , sort ).

First use .slice(0) to clone, then .filter() to filter to out the odd numbers, finally use .sort and Math.random() to randomly shuffle.

var arr2 = arr.filter(function(a) {
  return (a % 2) === 1; // Return true if a is even
}).sort(function() {
  return 0.5 - Math.random();
};

See example:

 function shuffleIt() { var arr = document.getElementById('numberlist').value.split(' '); var arr2 = arr.filter(function(a){return a%2 === 0}).sort(function(){return 0.5-Math.random()}); document.getElementById('output').innerHTML = arr2.join(','); } 
 Type a list of numbers (separate with spaces) <input type="text" id="numberlist" value="1 2 3 4 5 6 7 8"/> <button onclick="shuffleIt()">Shuffle evens (click multiple times to get different results) </button> <br/> <b>Output:</b> <div id="output"></div> 

Verbose explanation:

  1. .filter() calls a callback function passed to it on each element of the array. If the function returns true, it preserves the element; if false, it removes it. a % 2 === 0 is a condition to check whether a is even or not, so this removes all non-even numbers. This method does not mutate the array and instead returns a new array.

    The filter() method creates a new array with all elements that pass the test implemented by the provided function.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

  1. .sort sorts the array. It compare two items at a time, deciding which one goes before the other using the sign of a number returned fro ma function:

If compareFunction(a, b) is less than 0, sort a to a lower index than b, ie a comes first. If compareFunction(a, b) [the callback function passed in] returns 0, leave a and b unchanged with respect to each other, but sorted with respect to all different elements. Note: the ECMAscript standard does not guarantee this behaviour, and thus not all browsers (eg Mozilla versions dating back to at least 2003) respect this. If compareFunction(a, b) is greater than 0, sort b to a lower index than a. compareFunction(a, b) must always return the same value when given a specific pair of elements a and b as its two arguments. If inconsistent results are returned then the sort order is undefined.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

As you can see, the sign of the returned number determines which array item comes first. (0.5-Math.random()) randomly returns a positive or negative number (technically 0 might be possible, but very unlikely), therefore sorting the array randomly. Note: this function mutates the array, but we created a new array using .filter() , so the original is still untouched.

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