ฉันมีอาร์เรย์เช่นนี้:
var arr1 = ["a", "b", "c", "d"];
ฉันจะสุ่ม / สุ่มได้อย่างไร
ฉันมีอาร์เรย์เช่นนี้:
var arr1 = ["a", "b", "c", "d"];
ฉันจะสุ่ม / สุ่มได้อย่างไร
คำตอบ:
อัลกอริธึมการสับเปลี่ยนที่ไม่มีอคติโดยแท้จริงคือการสลับแบบ Fisher-Yates (Knuth)
ดูhttps://github.com/coolaj86/knuth-shuffle
คุณสามารถเห็นการสร้างภาพที่ยอดเยี่ยมได้ที่นี่ (และโพสต์ต้นฉบับที่เชื่อมโยงกับสิ่งนี้ )
function shuffle(array) {
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;
}
// Used like so
var arr = [2, 11, 37, 42];
shuffle(arr);
console.log(arr);
ข้อมูลเพิ่มเติมเกี่ยวกับอัลกอริทึมที่ใช้
i--
--i
นอกจากนี้ผลการทดสอบif (i==0)...
เป็นฟุ่มเฟือยเพราะถ้าขณะที่วงจะไม่ได้รับการป้อน เรียกร้องให้สามารถทำได้เร็วขึ้นโดยใช้ อย่างใดอย่างหนึ่งtempiหรือtempjสามารถถอดออกและค่าที่จะได้รับมอบหมายโดยตรงกับmyArray [I]หรือเจตามความเหมาะสม i == 0
Math.floor
...| 0
0 !== currentIndex
)
นี่คือการใช้งาน JavaScript ของDurstenfeld shuffleซึ่งเป็นรุ่นที่ได้รับการปรับปรุงของ Fisher-Yates:
/* Randomize array in-place using Durstenfeld shuffle algorithm */
function shuffleArray(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
มันเลือกองค์ประกอบแบบสุ่มสำหรับองค์ประกอบอาร์เรย์เดิมและแยกออกจากการจับรางวัลครั้งต่อไปเช่นเลือกสุ่มจากสำรับไพ่
การยกเว้นที่ชาญฉลาดนี้จะแลกเปลี่ยนองค์ประกอบที่เลือกกับองค์ประกอบปัจจุบันจากนั้นเลือกองค์ประกอบสุ่มถัดไปจากส่วนที่เหลือวนซ้ำไปด้านหลังเพื่อประสิทธิภาพที่ดีที่สุดมั่นใจได้ว่าการเลือกแบบสุ่มนั้นง่าย (สามารถเริ่มที่ 0) และข้ามองค์ประกอบสุดท้าย
O(n)
รันไทม์อัลกอริทึม หมายเหตุ.slice(0)
ว่าการสับเปลี่ยนจะทำในสถานที่ดังนั้นหากคุณไม่ต้องการที่จะปรับเปลี่ยนอาร์เรย์ต้นฉบับแรกให้สำเนาของมันด้วย
ES6 ใหม่ช่วยให้เราสามารถกำหนดตัวแปรสองตัวพร้อมกัน สิ่งนี้มีประโยชน์อย่างยิ่งเมื่อเราต้องการสลับค่าของตัวแปรสองตัวเนื่องจากเราสามารถทำได้ในหนึ่งบรรทัดของโค้ด นี่คือรูปแบบที่สั้นลงของฟังก์ชั่นเดียวกันโดยใช้คุณสมบัตินี้
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
return array
เนื่องจาก JavaScript ผ่านอาร์เรย์โดยอ้างอิงเมื่อใช้เป็นอาร์กิวเมนต์ของฟังก์ชัน ฉันคิดว่านี่คือการประหยัดพื้นที่สแต็ค แต่มันเป็นคุณสมบัติเล็ก ๆ ที่น่าสนใจ การดำเนินการสลับบนอาเรย์จะเป็นการสับเปลี่ยนอาเรย์เดิม
Math.random() should not be multiplied with the loop counter + 1, but with
array.lengt () ` ดูการสร้างตัวเลขสุ่มทั้งหมดใน JavaScript ในช่วงที่ต้องการหรือไม่ สำหรับคำอธิบายที่ครอบคลุมมาก
คำเตือน!
การใช้ขั้นตอนวิธีการนี้จะไม่แนะนำเพราะมันเป็นที่ไม่มีประสิทธิภาพและลำเอียงอย่างมาก ; ดูความคิดเห็น มันถูกทิ้งไว้ที่นี่เพื่ออ้างอิงในอนาคตเพราะความคิดนั้นหาได้ยากมาก
[1,2,3,4,5,6].sort(function() {
return .5 - Math.random();
});
หนึ่งสามารถ (หรือควร) ใช้เป็น protoype จาก Array:
จาก ChristopheD:
Array.prototype.shuffle = function() {
var i = this.length, j, temp;
if ( i == 0 ) return this;
while ( --i ) {
j = Math.floor( Math.random() * ( i + 1 ) );
temp = this[i];
this[i] = this[j];
this[j] = temp;
}
return this;
}
for...in
วนซ้ำเพื่อทำซ้ำในอาร์เรย์
คุณสามารถทำได้อย่างง่ายดายด้วยแผนที่และการจัดเรียง:
let unshuffled = ['hello', 'a', 't', 'q', 1, 2, 3, {cats: true}]
let shuffled = unshuffled
.map((a) => ({sort: Math.random(), value: a}))
.sort((a, b) => a.sort - b.sort)
.map((a) => a.value)
คุณสามารถสลับอาเรย์แบบ polymorphic และการเรียงแบบสุ่มนั้นเป็น Math.random ซึ่งดีพอสำหรับวัตถุประสงค์ส่วนใหญ่
เนื่องจากองค์ประกอบจะถูกจัดเรียงเทียบกับคีย์ที่สอดคล้องกันซึ่งไม่ได้สร้างใหม่ในการวนซ้ำแต่ละครั้งและการเปรียบเทียบแต่ละครั้งดึงจากการแจกแจงแบบเดียวกันความไม่สุ่มใด ๆ ในการแจกแจงของ Math.random จึงถูกยกเลิก
ความเร็ว
ความซับซ้อนของเวลาคือ O (N log N) เหมือนกับการจัดเรียงด่วน ความซับซ้อนของพื้นที่คือ O (N) สิ่งนี้ไม่ได้มีประสิทธิภาพเท่ากับการสับเปลี่ยน Fischer Yates แต่ในความคิดของฉันรหัสสั้นลงและมีประโยชน์มากกว่า หากคุณมีอาร์เรย์ขนาดใหญ่คุณควรใช้ Fischer Yates หากคุณมีอาร์เรย์ขนาดเล็กที่มีรายการไม่กี่ร้อยรายการคุณอาจทำเช่นนี้
ใช้ไลบรารี underscore.js วิธี_.shuffle()
นี้ดีสำหรับกรณีนี้ นี่คือตัวอย่างของวิธีการ:
var _ = require("underscore");
var arr = [1,2,3,4,5,6];
// Testing _.shuffle
var testShuffle = function () {
var indexOne = 0;
var stObj = {
'0': 0,
'1': 1,
'2': 2,
'3': 3,
'4': 4,
'5': 5
};
for (var i = 0; i < 1000; i++) {
arr = _.shuffle(arr);
indexOne = _.indexOf(arr, 1);
stObj[indexOne] ++;
}
console.log(stObj);
};
testShuffle();
shuffle
ฟังก์ชัน
ใหม่!
ขั้นตอนวิธีการสุ่มแบบสั้นลงและเร็วกว่า Fisher-Yates
function fy(a,b,c,d){//array,placeholder,placeholder,placeholder
c=a.length;while(c)b=Math.random()*(--c+1)|0,d=a[c],a[c]=a[b],a[b]=d
}
ขนาดสคริปต์ (ที่มี fy เป็นชื่อฟังก์ชัน): 90bytes
DEMO http://jsfiddle.net/vvpoma8w/
* อาจเร็วกว่าสำหรับเบราว์เซอร์ทั้งหมดยกเว้น Chrome
หากคุณมีคำถามใด ๆ เพียงแค่ถาม
แก้ไข
ใช่มันเร็วกว่า
ประสิทธิภาพ: http://jsperf.com/fyshuffle
ใช้ฟังก์ชั่นโหวตด้านบน
แก้ไข มีการคำนวณเกิน (ไม่จำเป็นต้อง - c + 1) และไม่มีใครสังเกตเห็น
สั้นกว่า (4bytes) และเร็วกว่า (ทดสอบ!)
function fy(a,b,c,d){//array,placeholder,placeholder,placeholder
c=a.length;while(c)b=Math.random()*c--|0,d=a[c],a[c]=a[b],a[b]=d
}
การแคชที่อื่นvar rnd=Math.random
แล้วใช้rnd()
จะเพิ่มประสิทธิภาพในอาร์เรย์ขนาดใหญ่ขึ้นเล็กน้อย
http://jsfiddle.net/vvpoma8w/2/
เวอร์ชันที่อ่านได้ (ใช้รุ่นเดิมซึ่งช้ากว่า vars จะไร้ประโยชน์เช่นเดียวกับการปิด & ";" โค้ดเองก็สั้นกว่า ... อาจอ่านได้วิธีการ 'ย่อหย่อน' โค้ด Javascript , btw คุณไม่สามารถ บีบอัดรหัสต่อไปนี้ในตัวสร้างจาวาสคริปต์เช่นเดียวกับข้างบน)
function fisherYates( array ){
var count = array.length,
randomnumber,
temp;
while( count ){
randomnumber = Math.random() * count-- | 0;
temp = array[count];
array[count] = array[randomnumber];
array[randomnumber] = temp
}
}
fy
และshuffle prototype
ฉันได้รับfy
อย่างต่อเนื่องที่ด้านล่างใน Chrome 37 บน OS X 10.9.5 (ช้าลง 81% ~ 20k ops เทียบกับ ~ 100k) และ Safari 7.1 มันช้ากว่า ~ 8% YMMV แต่มันไม่ได้เร็วกว่าเสมอไป jsperf.com/fyshuffle/3
แก้ไข: คำตอบนี้ไม่ถูกต้อง
ดูความคิดเห็นและhttps://stackoverflow.com/a/18650169/28234 มันถูกทิ้งไว้ที่นี่เพื่อการอ้างอิงเพราะความคิดไม่ได้หายาก
วิธีที่ง่ายมากสำหรับอาร์เรย์ขนาดเล็กคือ:
const someArray = [1, 2, 3, 4, 5];
someArray.sort(() => Math.random() - 0.5);
มันอาจจะไม่ได้มีประสิทธิภาพมากนัก แต่สำหรับอาร์เรย์ขนาดเล็กมันใช้งานได้ดี นี่คือตัวอย่างเพื่อให้คุณสามารถดูว่าสุ่ม (หรือไม่) มันเป็นอย่างไรและไม่ว่ามันจะเหมาะกับ usecase ของคุณหรือไม่
const resultsEl = document.querySelector('#results');
const buttonEl = document.querySelector('#trigger');
const generateArrayAndRandomize = () => {
const someArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
someArray.sort(() => Math.random() - 0.5);
return someArray;
};
const renderResultsToDom = (results, el) => {
el.innerHTML = results.join(' ');
};
buttonEl.addEventListener('click', () => renderResultsToDom(generateArrayAndRandomize(), resultsEl));
<h1>Randomize!</h1>
<button id="trigger">Generate</button>
<p id="results">0 1 2 3 4 5 6 7 8 9</p>
วิธีแก้ไขปัญหาบางอย่างในหน้านี้ไม่น่าเชื่อถือ (เป็นเพียงการสุ่มอาร์เรย์บางส่วนเท่านั้น) โซลูชันอื่น ๆ นั้นมีประสิทธิภาพลดลงอย่างมาก ด้วยtestShuffleArrayFun
(ดูด้านล่าง) เราสามารถทดสอบฟังก์ชันการสับอาร์เรย์เพื่อความน่าเชื่อถือและประสิทธิภาพ โซลูชันต่อไปนี้: เชื่อถือได้มีประสิทธิภาพและสั้น (ใช้ไวยากรณ์ ES6)
[ทำการทดสอบเปรียบเทียบtestShuffleArrayFun
กับโซลูชันอื่น ๆ ใน Google Chrome]
สลับแถว
function getShuffledArr (array){
for (var i = array.length - 1; i > 0; i--) {
var rand = Math.floor(Math.random() * (i + 1));
[array[i], array[rand]] = [array[rand], array[i]]
}
}
ES6 บริสุทธิ์ซ้ำ ๆ
const getShuffledArr = arr => {
const newArr = arr.slice()
for (let i = newArr.length - 1; i > 0; i--) {
const rand = Math.floor(Math.random() * (i + 1));
[newArr[i], newArr[rand]] = [newArr[rand], newArr[i]];
}
return newArr
};
อย่างที่คุณเห็นในหน้านี้มีการแก้ไขที่ไม่ถูกต้องในอดีต ฉันเขียนและใช้ฟังก์ชันต่อไปนี้เพื่อทดสอบฟังก์ชั่นการสุ่มอาเรย์แบบบริสุทธิ์ (ไม่มีผลข้างเคียง)
function testShuffleArrayFun(getShuffledArrayFun){
const arr = [0,1,2,3,4,5,6,7,8,9]
var countArr = arr.map(el=>{
return arr.map(
el=> 0
)
}) // For each possible position in the shuffledArr and for
// each possible value, we'll create a counter.
const t0 = performance.now()
const n = 1000000
for (var i=0 ; i<n ; i++){
// We'll call getShuffledArrayFun n times.
// And for each iteration, we'll increment the counter.
var shuffledArr = getShuffledArrayFun(arr)
shuffledArr.forEach(
(value,key)=>{countArr[key][value]++}
)
}
const t1 = performance.now()
console.log(`Count Values in position`)
console.table(countArr)
const frequencyArr = countArr.map( positionArr => (
positionArr.map(
count => count/n
)
))
console.log("Frequency of value in position")
console.table(frequencyArr)
console.log(`total time: ${t1-t0}`)
}
โซลูชันอื่น ๆ เพื่อความสนุกสนาน
ES6 บริสุทธิ์แบบเรียกซ้ำ
const getShuffledArr = arr => {
if (arr.length === 1) {return arr};
const rand = Math.floor(Math.random() * arr.length);
return [arr[rand], ...getShuffledArr(arr.filter((_, i) => i != rand))];
};
ES6 Pure ใช้ array.map
function getShuffledArr (arr){
return [...arr].map( (_, i, arrCopy) => {
var rand = i + ( Math.floor( Math.random() * (arrCopy.length - i) ) );
[arrCopy[rand], arrCopy[i]] = [arrCopy[i], arrCopy[rand]]
return arrCopy[i]
})
}
ES6 Pure ใช้ array.reduce
function getShuffledArr (arr){
return arr.reduce(
(newArr, _, i) => {
var rand = i + ( Math.floor( Math.random() * (newArr.length - i) ) );
[newArr[rand], newArr[i]] = [newArr[i], newArr[rand]]
return newArr
}, [...arr]
)
}
[array[i], array[rand]]=[array[rand], array[i]]
? บางทีคุณอาจจะร่างวิธีการทำงาน ทำไมคุณถึงเลือกทำซ้ำลง
กำลังเพิ่มคำตอบของ @Laurens Holsts นี่คือการบีบอัด 50%
function shuffleArray(d) {
for (var c = d.length - 1; c > 0; c--) {
var b = Math.floor(Math.random() * (c + 1));
var a = d[c];
d[c] = d[b];
d[b] = a;
}
return d
};
var b =
ในวงแทนที่จะประกาศ b นอกวงและกำหนดมันด้วยb =
ในวง?
ดูhttps://stackoverflow.com/a/18650169/28234 มันถูกทิ้งไว้ที่นี่เพื่อการอ้างอิงเพราะความคิดไม่ได้หายาก
//one line solution
shuffle = (array) => array.sort(() => Math.random() - 0.5);
//Demo
let arr = [1, 2, 3];
shuffle(arr);
alert(arr);
https://javascript.info/task/shuffle
Math.random() - 0.5
เป็นตัวเลขสุ่มที่อาจเป็นค่าบวกหรือลบดังนั้นฟังก์ชันการเรียงลำดับจะจัดเรียงองค์ประกอบแบบสุ่ม
ด้วย ES2015 คุณสามารถใช้สิ่งนี้:
Array.prototype.shuffle = function() {
let m = this.length, i;
while (m) {
i = (Math.random() * m--) >>> 0;
[this[m], this[i]] = [this[i], this[m]]
}
return this;
}
การใช้งาน:
[1, 2, 3, 4, 5, 6, 7].shuffle();
n >>> 0
~~n
ดัชนีอาร์เรย์สามารถสูงกว่า2³¹-1
ฉันพบตัวแปรนี้อยู่ในคำตอบ "ลบโดยผู้แต่ง" ในสำเนาของคำถามนี้ ต่างจากคำตอบอื่น ๆ ที่มี upvotes มากมายแล้วนี่คือ:
shuffled
ชื่อมากกว่าshuffle
)นี่เป็น jsfiddle แสดงในการใช้งาน
Array.prototype.shuffled = function() {
return this.map(function(n){ return [Math.random(), n] })
.sort().map(function(n){ return n[1] });
}
[1,2,3,4,5,6].sort(function() { return .5 - Math.random(); });
- มันไม่ได้จัดเรียงแบบสุ่มและถ้าคุณใช้คุณสามารถจบลงด้วยความอับอาย: robweir.com/blog/2010/02/microsoft-random-browser-ballot.html
.sort(function(a,b){ return a[0] - b[0]; })
ถ้าคุณต้องการเรียงลำดับเพื่อเปรียบเทียบค่าตัวเลข เริ่มต้น.sort()
เปรียบเทียบเป็นพจนานุกรมความหมายมันจะพิจารณา10
จะน้อยกว่า2
ตั้งแต่น้อยกว่า1
2
Math.random()
สร้าง (นั่นคือคำสั่งทำพจนานุกรมเท่ากับคำสั่งที่เป็นตัวเลขเมื่อจัดการกับตัวเลขจาก 0 (รวม) ถึง 1 (พิเศษ))
var shuffle = function(array) {
temp = [];
originalLength = array.length;
for (var i = 0; i < originalLength; i++) {
temp.push(array.splice(Math.floor(Math.random()*array.length),1));
}
return temp;
};
arr1.sort(() => Math.random() - 0.5);
คุณสามารถทำได้อย่างง่ายดายด้วย:
// array
var fruits = ["Banana", "Orange", "Apple", "Mango"];
// random
fruits.sort(function(a, b){return 0.5 - Math.random()});
// out
console.log(fruits);
โปรดอ้างอิงที่JavaScript เรียงลำดับอาร์เรย์
โซลูชันแบบเรียกซ้ำ:
function shuffle(a,b){
return a.length==0?b:function(c){
return shuffle(a,(b||[]).concat(c));
}(a.splice(Math.floor(Math.random()*a.length),1));
};
สับเปลี่ยนFisher-Yatesใน javascript ฉันกำลังโพสต์สิ่งนี้ไว้ที่นี่เพราะการใช้ฟังก์ชั่นยูทิลิตี้สองฟังก์ชั่น (สลับและ randInt) ชี้แจงอัลกอริทึมเมื่อเปรียบเทียบกับคำตอบอื่นที่นี่
function swap(arr, i, j) {
// swaps two elements of an array in place
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
function randInt(max) {
// returns random integer between 0 and max-1 inclusive.
return Math.floor(Math.random()*max);
}
function shuffle(arr) {
// For each slot in the array (starting at the end),
// pick an element randomly from the unplaced elements and
// place it in the slot, exchanging places with the
// element in the slot.
for(var slot = arr.length - 1; slot > 0; slot--){
var element = randInt(slot+1);
swap(arr, element, slot);
}
}
ก่อนอื่นมาดูที่นี่เพื่อเปรียบเทียบภาพที่ดีเยี่ยมของวิธีการเรียงลำดับที่แตกต่างกันในจาวาสคริปต์
ประการที่สองหากคุณดูลิงค์ด้านบนอย่างรวดเร็วคุณจะพบว่าการrandom order
เรียงลำดับนั้นมีประสิทธิภาพค่อนข้างดีเมื่อเทียบกับวิธีอื่น ๆ ในขณะที่ใช้งานได้ง่ายและรวดเร็วมากดังที่แสดงด้านล่าง:
function shuffle(array) {
var random = array.map(Math.random);
array.sort(function(a, b) {
return random[array.indexOf(a)] - random[array.indexOf(b)];
});
}
แก้ไข : เป็นแหลมออกโดย @gregers indexOf
ฟังก์ชันเปรียบเทียบเรียกว่ามีค่ามากกว่าดัชนีซึ่งเป็นเหตุผลที่คุณจำเป็นต้องใช้ โปรดทราบว่าการเปลี่ยนแปลงนี้ทำให้รหัสไม่เหมาะสำหรับอาร์เรย์ขนาดใหญ่เช่นindexOf
ทำงานในเวลา O (n)
Array.prototype.sort
ส่งผ่านค่าสองค่าเป็นa
และb
ไม่ใช่ดัชนี ดังนั้นรหัสนี้ใช้งานไม่ได้
Update : ที่นี่ฉันขอแนะนำค่อนข้างง่าย (ไม่ใช่จากมุมมองที่ซับซ้อน ) และอัลกอริธึมสั้น ๆที่จะทำได้ดีกับอาร์เรย์ขนาดเล็ก แต่แน่นอนว่าต้องเสียค่าใช้จ่ายมากกว่าอัลกอริธึมDurstenfeldแบบคลาสสิกเมื่อคุณจัดการกับอาร์เรย์ขนาดใหญ่ คุณสามารถค้นหาDurstenfeld ได้จากการตอบคำถามยอดนิยมข้อใดข้อหนึ่ง
คำตอบเดิม:
หากคุณไม่ต้องการฟังก์ชั่นการสับเปลี่ยนของคุณเพื่อกลายพันธุ์อาร์เรย์แหล่งที่คุณสามารถคัดลอกไปยังตัวแปรท้องถิ่นแล้วทำส่วนที่เหลือด้วยง่ายตรรกะสับ
function shuffle(array) {
var result = [], source = array.concat([]);
while (source.length) {
let index = Math.floor(Math.random() * source.length);
result.push(source[index]);
source.splice(index, 1);
}
return result;
}
สับตรรกะ : รับดัชนีสุ่มแล้วเพิ่มองค์ประกอบที่เกี่ยวข้องกับอาร์เรย์ผลและลบจากการคัดลอกแหล่งอาร์เรย์ ทำซ้ำการกระทำนี้จนกว่าอาร์เรย์แหล่งที่ได้รับการว่างเปล่า
และถ้าคุณต้องการให้มันสั้นจริงๆนี่คือวิธีที่ฉันจะได้รับ:
function shuffle(array) {
var result = [], source = array.concat([]);
while (source.length) {
let index = Math.floor(Math.random() * source.length);
result.push(source.splice(index, 1)[0]);
}
return result;
}
splice
เป็นวิธีที่ไม่มีประสิทธิภาพอย่างน่ากลัวในการทำสิ่งที่พวกเขาเรียกว่า "โดดเด่น" หากคุณไม่ต้องการที่จะกลายพันธุ์อาเรย์ดั้งเดิมแล้วเพียงแค่คัดลอกแล้วสับเปลี่ยนสำเนานั้นในสถานที่โดยใช้ตัวแปร Durstenfeld ที่มีประสิทธิภาพมากขึ้น
splice
source = array.slice();
นี่คือง่ายที่สุดหนึ่ง
function shuffle(array) {
array.sort(() => Math.random() - 0.5);
}
สำหรับตัวอย่างเพิ่มเติมคุณสามารถตรวจสอบได้ที่นี่
อีกหนึ่งการใช้งานของ Fisher-Yates โดยใช้โหมดเข้มงวด:
function shuffleArray(a) {
"use strict";
var i, t, j;
for (i = a.length - 1; i > 0; i -= 1) {
t = a[i];
j = Math.floor(Math.random() * (i + 1));
a[i] = a[j];
a[j] = t;
}
return a;
}
คำตอบอื่น ๆ ทั้งหมดนั้นมาจาก Math.random () ซึ่งเร็ว แต่ไม่เหมาะสำหรับการสุ่มระดับ cryptgraphic
รหัสข้างล่างนี้คือการใช้ที่รู้จักกันดีFisher-Yates
ขั้นตอนวิธีการในขณะที่ใช้Web Cryptography API
สำหรับระดับการเข้ารหัสลับของการสุ่ม
var d = [1,2,3,4,5,6,7,8,9,10];
function shuffle(a) {
var x, t, r = new Uint32Array(1);
for (var i = 0, c = a.length - 1, m = a.length; i < c; i++, m--) {
crypto.getRandomValues(r);
x = Math.floor(r / 65536 / 65536 * m) + i;
t = a [i], a [i] = a [x], a [x] = t;
}
return a;
}
console.log(shuffle(d));
โซลูชั่นแบบอินไลน์แบบสั้นที่ทันสมัยโดยใช้คุณสมบัติ ES6:
['a','b','c','d'].map(x => [Math.random(), x]).sort(([a], [b]) => a - b).map(([_, x]) => x);
(เพื่อการศึกษา)
การดัดแปลงคำตอบของ CoolAJ86 อย่างง่าย ๆที่ไม่ได้แก้ไขอาร์เรย์เดิม:
/**
* Returns a new array whose contents are a shuffled copy of the original array.
* @param {Array} The items to shuffle.
* https://stackoverflow.com/a/2450976/1673761
* https://stackoverflow.com/a/44071316/1673761
*/
const shuffle = (array) => {
let currentIndex = array.length;
let temporaryValue;
let randomIndex;
const newArray = array.slice();
// While there remains elements to shuffle...
while (currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
// Swap it with the current element.
temporaryValue = newArray[currentIndex];
newArray[currentIndex] = newArray[randomIndex];
newArray[randomIndex] = temporaryValue;
}
return newArray;
};
แม้ว่าจะมีการใช้งานจำนวนมากที่แนะนำไว้แล้ว แต่ฉันรู้สึกว่าเราสามารถทำให้สั้นลงและง่ายขึ้นสำหรับการใช้วนรอบแต่ละครั้งดังนั้นเราจึงไม่ต้องกังวลเกี่ยวกับการคำนวณความยาวของอาร์เรย์และเราสามารถหลีกเลี่ยงการใช้ตัวแปรชั่วคราวได้อย่างปลอดภัย
var myArr = ["a", "b", "c", "d"];
myArr.forEach((val, key) => {
randomIndex = Math.ceil(Math.random()*(key + 1));
myArr[key] = myArr[randomIndex];
myArr[randomIndex] = val;
});
// see the values
console.log('Shuffled Array: ', myArr)
เพียงแค่มีนิ้วอยู่ในวงกลม ที่นี่ฉันนำเสนอการดำเนินการแบบสุ่มของ Fisher Yates shuffle (ฉันคิดว่า) มันให้ความสม่ำเสมอของการสุ่มตัวอย่าง
หมายเหตุ: ~~
ตัวดำเนินการ (ตัวหนอนตัวที่สอง) ทำงานเหมือนกับMath.floor()
ตัวเลขจริงที่เป็นบวก เพียงแค่ตัดสั้น ๆ ก็คือ
var shuffle = a => a.length ? a.splice(~~(Math.random()*a.length),1).concat(shuffle(a))
: a;
console.log(JSON.stringify(shuffle([0,1,2,3,4,5,6,7,8,9])));
แก้ไข:รหัสข้างต้นคือ O (n ^ 2) เนื่องจากการจ้างงานของ.splice()
แต่เราสามารถกำจัดรอยต่อและสลับใน O (n) โดยเคล็ดลับการแลกเปลี่ยน
var shuffle = (a, l = a.length, r = ~~(Math.random()*l)) => l ? ([a[r],a[l-1]] = [a[l-1],a[r]], shuffle(a, l-1))
: a;
var arr = Array.from({length:3000}, (_,i) => i);
console.time("shuffle");
shuffle(arr);
console.timeEnd("shuffle");
ปัญหาคือ JS ไม่สามารถสุ่มในการเรียกซ้ำครั้งใหญ่ ในกรณีนี้ขนาดอาร์เรย์ของคุณจะถูก จำกัด ด้วยเช่น 3,000 ~ 7000 ขึ้นอยู่กับเครื่องมือเบราว์เซอร์ของคุณและข้อเท็จจริงบางอย่างที่ไม่ทราบ
อาร์เรย์แบบสุ่ม
var arr = ['apple','cat','Adam','123','Zorro','petunia'];
var n = arr.length; var tempArr = [];
for ( var i = 0; i < n-1; i++ ) {
// The following line removes one random element from arr
// and pushes it onto tempArr
tempArr.push(arr.splice(Math.floor(Math.random()*arr.length),1)[0]);
}
// Push the remaining item onto tempArr
tempArr.push(arr[0]);
arr=tempArr;
-1
ว่า n อย่างที่คุณ<
ไม่เคยใช้<=
arrayShuffle
ฟังก์ชั่นที่สั้นที่สุด
function arrayShuffle(o) {
for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
return o;
}
จากจุดทฤษฎีในมุมมองของวิธีที่สง่างามมากที่สุดในการทำมันในความต่ำต้อยของฉันคือการได้รับเพียงครั้งเดียวจำนวนสุ่มระหว่าง0และn! -1และการคำนวณหนึ่งในการทำแผนที่หนึ่งจากการพีชคณิตทั้งหมดของ{0, 1, …, n!-1}
(0, 1, 2, …, n-1)
ตราบใดที่คุณสามารถใช้ตัวสร้างแบบสุ่ม (หลอก) เชื่อถือได้เพียงพอสำหรับการรับหมายเลขดังกล่าวโดยไม่มีอคติที่สำคัญคุณมีข้อมูลเพียงพอสำหรับการบรรลุสิ่งที่คุณต้องการโดยไม่ต้องใช้ตัวเลขสุ่มอื่น ๆ
เมื่อคำนวณด้วยเลขทศนิยมที่มีความแม่นยำสองเท่าของ IEEE754 คุณสามารถคาดหวังว่าเครื่องกำเนิดแบบสุ่มของคุณจะให้ประมาณ 15 ทศนิยม เนื่องจากคุณมี15! = 1,307,674,368,000 (ด้วยตัวเลข 13 หลัก) คุณสามารถใช้ฟังก์ชันต่อไปนี้กับอาร์เรย์ที่มีองค์ประกอบมากถึง 15 องค์ประกอบและถือว่าไม่มีอคติอย่างมีนัยสำคัญที่มีอาร์เรย์ที่มีองค์ประกอบมากถึง 14 องค์ประกอบ หากคุณทำงานกับปัญหาที่มีขนาดคงที่ซึ่งต้องใช้การคำนวณการสลับหลายครั้งคุณอาจต้องการลองใช้รหัสต่อไปนี้ซึ่งอาจเร็วกว่ารหัสอื่นเนื่องจากใช้Math.random
เพียงครั้งเดียว (มันเกี่ยวข้องกับการคัดลอกหลายครั้ง)
ฟังก์ชั่นต่อไปนี้จะไม่ถูกใช้ แต่ฉันจะให้มันอยู่ดี มันจะส่งคืนดัชนีของการเปลี่ยนแปลงที่กำหนด(0, 1, 2, …, n-1)
ตามการทำแผนที่แบบหนึ่งต่อหนึ่งที่ใช้ในข้อความนี้ มันมีวัตถุประสงค์เพื่อทำงานกับองค์ประกอบสูงสุด 16 รายการ:
function permIndex(p) {
var fact = [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000];
var tail = [];
var i;
if (p.length == 0) return 0;
for(i=1;i<(p.length);i++) {
if (p[i] > p[0]) tail.push(p[i]-1);
else tail.push(p[i]);
}
return p[0] * fact[p.length-1] + permIndex(tail);
}
ส่วนกลับของฟังก์ชันก่อนหน้า (จำเป็นสำหรับคำถามของคุณเอง) อยู่ด้านล่าง มันตั้งใจที่จะทำงานกับองค์ประกอบมากถึง 16 ชิ้น ก็จะส่งกลับการเปลี่ยนแปลงของคำสั่งที่nของ(0, 1, 2, …, s-1)
:
function permNth(n, s) {
var fact = [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000];
var i, j;
var p = [];
var q = [];
for(i=0;i<s;i++) p.push(i);
for(i=s-1; i>=0; i--) {
j = Math.floor(n / fact[i]);
n -= j*fact[i];
q.push(p[j]);
for(;j<i;j++) p[j]=p[j+1];
}
return q;
}
ตอนนี้สิ่งที่คุณต้องการคือ:
function shuffle(p) {
var fact = [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600, 6227020800, 87178291200, 1307674368000, 20922789888000];
return permNth(Math.floor(Math.random()*fact[p.length]), p.length).map(
function(i) { return p[i]; });
}
ควรทำงานได้มากถึง 16 องค์ประกอบด้วยอคติทางทฤษฎีเล็กน้อย มันสามารถมองเห็นได้ว่าใช้งานได้อย่างเต็มที่สำหรับ 15 องค์ประกอบ; ด้วยอาร์เรย์ที่มีองค์ประกอบน้อยกว่า 14 รายการคุณสามารถพิจารณาได้อย่างปลอดภัยว่าจะไม่มีอคติอย่างแน่นอน