วิธีการสุ่ม (สับเปลี่ยน) อาร์เรย์ JavaScript


1265

ฉันมีอาร์เรย์เช่นนี้:

var arr1 = ["a", "b", "c", "d"];

ฉันจะสุ่ม / สุ่มได้อย่างไร



6
เพียงการขว้างปาที่นี่ที่คุณสามารถเห็นภาพว่าสุ่มฟังก์ชั่นสลับเป็นจริงกับ Visualizer นี้ไมค์ทำสต็อค: bost.ocks.org/mike/shuffle/compare.html
สิงหาคม

5
@ Blazemonger jsPref ตายแล้ว คุณช่วยโพสต์ที่นี่เร็วที่สุดได้ไหม
eozzy

สิ่งที่เกี่ยวกับหนึ่งซับ? อาร์เรย์ที่ส่งคืนถูกสับเปลี่ยน arr1.reduce ((a, v) => a.splice (Math.floor (Math.random () * a.length), 0, v) && a, [])
brunettdan

โซลูชันการลดมีความซับซ้อน O (n ^ 2) ลองเรียกใช้บนอาเรย์ที่มีองค์ประกอบนับล้าน
riv

คำตอบ:


1541

อัลกอริธึมการสับเปลี่ยนที่ไม่มีอคติโดยแท้จริงคือการสลับแบบ 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);

ข้อมูลเพิ่มเติมเกี่ยวกับอัลกอริทึมที่ใช้


13
คำตอบข้างต้นข้ามองค์ประกอบ 0, สภาพที่ควรจะเป็นไม่ได้i-- --iนอกจากนี้ผลการทดสอบif (i==0)...เป็นฟุ่มเฟือยเพราะถ้าขณะที่วงจะไม่ได้รับการป้อน เรียกร้องให้สามารถทำได้เร็วขึ้นโดยใช้ อย่างใดอย่างหนึ่งtempiหรือtempjสามารถถอดออกและค่าที่จะได้รับมอบหมายโดยตรงกับmyArray [I]หรือเจตามความเหมาะสม i == 0Math.floor...| 0
RobG

23
@prometheus RNG ทั้งหมดสุ่มหลอกยกเว้นเชื่อมต่อกับฮาร์ดแวร์ราคาแพง
Phil H

38
@RobG การดำเนินการด้านบนถูกต้องตามหน้าที่ ในอัลกอริทึม Fisher-Yates การวนซ้ำไม่ได้หมายถึงการเรียกใช้สำหรับองค์ประกอบแรกในอาร์เรย์ ดูวิกิพีเดียที่มีการใช้งานอื่น ๆ ที่ข้ามองค์ประกอบแรก ยังตรวจสอบนี้บทความที่พูดเกี่ยวกับเหตุผลที่มันเป็นสิ่งสำคัญสำหรับวงที่จะไม่วิ่งองค์ประกอบแรก
theon

34
@ Nikola "ไม่สุ่มเลย" เป็นคุณสมบัติที่แข็งแกร่งเล็กน้อยสำหรับฉัน ฉันจะยืนยันว่ามันเป็นการสุ่มอย่างเพียงพอเว้นแต่คุณจะเป็น cryptographer ซึ่งในกรณีนี้คุณอาจไม่ได้ใช้ Math.Random () ในตอนแรก
toon81

20
ฮึโยดา ( 0 !== currentIndex)
ffxsam

745

นี่คือการใช้งาน 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 / ECMAScript 2015

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]];
    }
}

22
ps อัลกอริทึมเช่นเดียวกับคำตอบของ ChristopheD แต่มีคำอธิบายและการใช้งานที่สะอาด
Laurens Holst

12
คนกำลังประกอบกับคนผิดสำหรับอัลกอริทึม มันไม่ได้ Fisher-Yates สับเปลี่ยน แต่Durstenfeld สับเปลี่ยน อัลกอริทึมดั้งเดิมของ Fisher-Yates ที่แท้จริงนั้นทำงานใน n ^ 2 ครั้งไม่ใช่เวลา n
Pacerier

7
ไม่จำเป็นต้องใช้return arrayเนื่องจาก JavaScript ผ่านอาร์เรย์โดยอ้างอิงเมื่อใช้เป็นอาร์กิวเมนต์ของฟังก์ชัน ฉันคิดว่านี่คือการประหยัดพื้นที่สแต็ค แต่มันเป็นคุณสมบัติเล็ก ๆ ที่น่าสนใจ การดำเนินการสลับบนอาเรย์จะเป็นการสับเปลี่ยนอาเรย์เดิม
Joel Trauger

5
การใช้งานในคำตอบนี้สนับสนุนจุดสิ้นสุดที่ต่ำกว่าของอาร์เรย์ พบวิธีที่ยาก Math.random() should not be multiplied with the loop counter + 1, but with array.lengt () ` ดูการสร้างตัวเลขสุ่มทั้งหมดใน JavaScript ในช่วงที่ต้องการหรือไม่ สำหรับคำอธิบายที่ครอบคลุมมาก
Marjan Venema

13
@MarjanVenema ไม่แน่ใจว่าคุณยังคงดูพื้นที่นี้อยู่หรือไม่ แต่คำตอบนี้ถูกต้องและการเปลี่ยนแปลงที่คุณแนะนำจะนำมาซึ่งอคติ ดูblog.codinghorror.com/the-danger-of-naiveteเพื่อดูข้อผิดพลาดนี้
user94559

133

คำเตือน!
การใช้ขั้นตอนวิธีการนี้จะไม่แนะนำเพราะมันเป็นที่ไม่มีประสิทธิภาพและลำเอียงอย่างมาก ; ดูความคิดเห็น มันถูกทิ้งไว้ที่นี่เพื่ออ้างอิงในอนาคตเพราะความคิดนั้นหาได้ยากมาก

[1,2,3,4,5,6].sort(function() {
  return .5 - Math.random();
});

13
ฉันชอบวิธีนี้พอที่จะให้การสุ่มพื้นฐาน
อเล็กซ์ K

147
Downvoting เนื่องจากไม่ได้สุ่มแบบนั้นจริงๆ ฉันไม่รู้ว่าทำไมมันมี upvotes มากมาย อย่าใช้วิธีนี้ ดูสวย แต่ไม่ถูกต้องสมบูรณ์ ต่อไปนี้คือผลลัพธ์หลังจาก 10,000 ซ้ำในแต่ละครั้งที่จำนวนในดัชนีการเข้าชมอาร์เรย์ของคุณ [0] (ฉันสามารถให้ผลลัพธ์อื่น ๆ ด้วย): 1 = 29.19%, 2 = 29.53%, 3 = 20.06%, 4 = 11.91%, 5 = 5.99%, 6 = 3.32%
ช่วง

8
ไม่เป็นไรถ้าคุณต้องการสุ่มอาเรย์ที่ค่อนข้างเล็กและไม่จัดการกับสิ่งที่เข้ารหัสลับ ฉันเห็นด้วยอย่างยิ่งว่าหากคุณต้องการการสุ่มมากขึ้นคุณจำเป็นต้องใช้โซลูชันที่ซับซ้อนมากขึ้น
deadrunk

21
นอกจากนี้ยังเป็นที่มีประสิทธิภาพน้อยที่สุดของทุกวิธีการที่มีอยู่
Blazemonger

12
ปัญหาคือมันไม่ได้กำหนดไว้ซึ่งจะให้ผลลัพธ์ที่ผิด (ถ้า 1> 2 และ 2> 3 ก็ควรได้รับที่ 1> 3 แต่สิ่งนี้จะไม่รับประกันได้ว่านี้จะสร้างความสับสนให้เรียงลำดับและให้ความเห็นผล โดย @radtad)
MatsLindh

73

หนึ่งสามารถ (หรือควร) ใช้เป็น 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;
}

42
IMOHO ไม่มีประโยชน์กับเรื่องนี้จริงๆยกเว้นอาจจะกระทืบการใช้งานของคนอื่น ..
2864740

2
ถ้าใช้ในต้นแบบอาร์เรย์ก็ควรจะตั้งชื่ออื่นที่ไม่ใช่เพียงแค่การสับเปลี่ยน
Wolf

57
หนึ่งสามารถ (หรือควร) หลีกเลี่ยงการขยายต้นแบบพื้นเมือง: javascriptweblog.wordpress.com/2011/12/05/ …
Wédney Yuri

12
คุณไม่ควรทำสิ่งนี้ ทุกอาร์เรย์เดียวที่ได้รับผลกระทบจะไม่สามารถทำซ้ำได้อย่างปลอดภัยอีกต่อไปโดยใช้สำหรับ ... ใน อย่าขยายต้นแบบดั้งเดิม

18
@TinyGiant จริง: อย่าใช้การfor...inวนซ้ำเพื่อทำซ้ำในอาร์เรย์
Conor O'Brien

69

คุณสามารถทำได้อย่างง่ายดายด้วยแผนที่และการจัดเรียง:

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)
  1. เราใส่แต่ละองค์ประกอบในอาร์เรย์ในวัตถุและให้มันเป็นกุญแจสำคัญในการเรียงลำดับแบบสุ่ม
  2. เราเรียงลำดับโดยใช้ปุ่มสุ่ม
  3. เราถอดแผนที่ออกเพื่อรับวัตถุต้นฉบับ

คุณสามารถสลับอาเรย์แบบ polymorphic และการเรียงแบบสุ่มนั้นเป็น Math.random ซึ่งดีพอสำหรับวัตถุประสงค์ส่วนใหญ่

เนื่องจากองค์ประกอบจะถูกจัดเรียงเทียบกับคีย์ที่สอดคล้องกันซึ่งไม่ได้สร้างใหม่ในการวนซ้ำแต่ละครั้งและการเปรียบเทียบแต่ละครั้งดึงจากการแจกแจงแบบเดียวกันความไม่สุ่มใด ๆ ในการแจกแจงของ Math.random จึงถูกยกเลิก

ความเร็ว

ความซับซ้อนของเวลาคือ O (N log N) เหมือนกับการจัดเรียงด่วน ความซับซ้อนของพื้นที่คือ O (N) สิ่งนี้ไม่ได้มีประสิทธิภาพเท่ากับการสับเปลี่ยน Fischer Yates แต่ในความคิดของฉันรหัสสั้นลงและมีประโยชน์มากกว่า หากคุณมีอาร์เรย์ขนาดใหญ่คุณควรใช้ Fischer Yates หากคุณมีอาร์เรย์ขนาดเล็กที่มีรายการไม่กี่ร้อยรายการคุณอาจทำเช่นนี้


1
@superluminary โอ๊ะคุณพูดถูก ขอให้สังเกตว่าคำตอบนี้ใช้วิธีเดียวกันแล้ว
Bergi

@Bergi - อ่าใช่คุณพูดถูก แต่ฉันคิดว่าการติดตั้งของฉันดีขึ้นเล็กน้อย
superluminary

3
ดีมาก. นี่คือการแปลง Schwartzianใน js
Mark Grimes

@torazaburo - มันไม่ได้เป็นนักแสดงเหมือน Fischer Yates แต่มันสวยกว่าและรหัสก็เล็กกว่า รหัสอยู่เสมอการแลกเปลี่ยน ถ้าฉันมีชุดใหญ่ฉันจะใช้ Knuth หากฉันมีสิ่งของสองสามร้อยรายการฉันจะทำสิ่งนี้
superluminary

1
@BenCarp - เห็นด้วยไม่ใช่วิธีแก้ปัญหาที่เร็วที่สุดและคุณไม่ต้องการที่จะใช้กับอาเรย์ขนาดใหญ่ แต่มีข้อควรพิจารณาในโค้ดมากกว่าความเร็วดิบ
superluminary

64

ใช้ไลบรารี 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();

12
คำตอบที่ดี! ขอบคุณ ฉันชอบที่จะตอบคำถามอื่น ๆ เพราะมันกระตุ้นให้ผู้คนใช้ห้องสมุดมากกว่าทำสำเนาและวางฟังก์ชั่นบั๊กกี้ทุกที่
frabcus

60
@ frabcus: ไม่มีจุดรวมทั้งไลบรารีเพียงเพื่อรับ shuffleฟังก์ชัน
เครื่องปั่น

11
ฉันไม่เห็นด้วยกับ @Blender มีเหตุผลหลายประการที่จะรวมไลบรารีทั้งหมดเพื่อให้ได้ฟังก์ชันที่คุณต้องการ หนึ่งในนั้นคือมีความเสี่ยงน้อยกว่าข้อผิดพลาดเมื่อคุณเขียนมันเอง หากเป็นปัญหาด้านประสิทธิภาพคุณไม่ควรใช้ แต่เพียงเพราะอาจเป็นปัญหาประสิทธิภาพไม่ได้หมายความว่าจะเป็น
Daniel Kaplan

7
@tietyT: ทำไมคุณต้องการห้องสมุดที่เหลือ การสับเปลี่ยน Fisher-Yates เป็นเรื่องเล็กน้อยที่จะนำไปใช้ คุณไม่จำเป็นต้องมีห้องสมุดในการเลือกองค์ประกอบแบบสุ่มจากอาร์เรย์ (ฉันหวังว่า) ดังนั้นจึงไม่มีเหตุผลที่จะใช้ห้องสมุดเว้นแต่ว่าคุณจะใช้มากกว่าหนึ่งฟังก์ชั่นจากจริง
เครื่องปั่น

18
@ ผู้หักบัญชี: ฉันให้เหตุผลว่าทำไม 1) ฉันรับรองว่าคุณสามารถแนะนำบั๊กในโค้ดที่คุณเขียนไม่ว่ามันจะเป็นเรื่องเล็กน้อยก็ตาม ทำไมต้องเสี่ยง? 2) อย่าเพิ่มประสิทธิภาพล่วงหน้า 3) 99% ของเวลาที่คุณต้องการ algo shuffle แอพของคุณไม่ได้เกี่ยวกับการเขียน shuffle algo มันเกี่ยวกับบางสิ่งบางอย่างที่จำเป็นต้องมีการสลับสับเปลี่ยน ยกระดับการทำงานของผู้อื่น อย่าคิดเกี่ยวกับรายละเอียดการปฏิบัติเว้นแต่คุณจะต้อง
Daniel Kaplan

50

ใหม่!

ขั้นตอนวิธีการสุ่มแบบสั้นลงและเร็วกว่า Fisher-Yates

  1. มันใช้ในขณะที่ ---
  2. bitwise to floor (ตัวเลขสูงสุด 10 หลักทศนิยม (32 บิต))
  3. ลบการปิดที่ไม่จำเป็นและสิ่งอื่น ๆ

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
 }
}

6
ตรวจสอบประสิทธิภาพ ... เร็วขึ้น 2x บนเบราว์เซอร์ส่วนใหญ่ ... แต่ต้องการ jsperf มากขึ้น ...
cocco

10
js เป็นภาษาที่ยอมรับทางลัดและวิธีการเขียนต่าง ๆ มากมายในขณะที่มีฟังก์ชั่นที่อ่านได้ช้าจำนวนมากในที่นี้ฉันต้องการแสดงให้เห็นว่ามันสามารถทำได้ในทางที่มีประสิทธิภาพมากกว่าประหยัดบิตบางส่วน ... การประเมินย่ออยู่ที่นี่จริงๆและเว็บเต็มไปด้วยโค้ด buggy และช้า
cocco

ไม่ใช่การเพิ่มปริมาณสแลมดังค์ การเปลี่ยนfyและshuffle prototypeฉันได้รับfyอย่างต่อเนื่องที่ด้านล่างใน Chrome 37 บน OS X 10.9.5 (ช้าลง 81% ~ 20k ops เทียบกับ ~ 100k) และ Safari 7.1 มันช้ากว่า ~ 8% YMMV แต่มันไม่ได้เร็วกว่าเสมอไป jsperf.com/fyshuffle/3
Spig

ตรวจสอบสถิติอีกครั้ง ... ฉันได้เขียนโครเมี่ยมช้ากว่าเพราะมันเพิ่มประสิทธิภาพคณิตศาสตร์บนพื้น bitwise อื่น ๆ และขณะที่เร็ว ตรวจสอบ IE, Firefox แต่ยัง devices.Would มือถือจะยังดีที่จะเห็นโอเปร่า ...
Cocco

1
นี่คือคำตอบที่น่ากลัว ดังนั้นไม่ใช่การแข่งขันที่คลุมเครือ
ลูกสุนัข

39

แก้ไข: คำตอบนี้ไม่ถูกต้อง

ดูความคิดเห็นและ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>


เป็นสิ่งที่ดี แต่จะสร้างองค์ประกอบแบบสุ่มที่สมบูรณ์ทุกครั้งหรือไม่
DDD

ไม่แน่ใจถ้าฉันเข้าใจคุณถูกต้อง วิธีการนี้จะสุ่มสับเปลี่ยนอาเรย์แบบสุ่ม (แม้ว่าจะหลอกแบบสุ่ม) ทุกครั้งที่คุณเรียกใช้อาเรย์เรียง - มันไม่ใช่การเรียงแบบเสถียรเนื่องจากเหตุผลที่ชัดเจน
Kris Selbekk

4
ด้วยเหตุผลเดียวกันตามที่อธิบายไว้ในstackoverflow.com/a/18650169/28234 สิ่งนี้มีแนวโน้มมากที่จะปล่อยให้องค์ประกอบก่อนหน้าอยู่ใกล้กับจุดเริ่มต้นของอาร์เรย์
AlexC

7
นี่เป็นหนึ่งซับที่ดีและง่ายสำหรับเมื่อคุณต้องการแย่งอาร์เรย์ แต่ไม่สนใจมากเกินไปเกี่ยวกับการให้ผลลัพธ์ที่ได้ในเชิงวิชาการโดยการสุ่ม บางครั้งการที่ความสมบูรณ์แบบไม่กี่นิ้วสุดท้ายใช้เวลามากกว่าที่คุ้มค่า
Daniel Griscom

1
มันจะน่ารักถ้าใช้งานได้ แต่มันก็ไม่ดี เนื่องจากวิธีการค้นหาที่รวดเร็วทำงานตัวเปรียบเทียบที่ไม่สอดคล้องกันจึงมีแนวโน้มที่จะปล่อยให้องค์ประกอบของอาร์เรย์ใกล้เคียงกับตำแหน่งเดิม อาร์เรย์ของคุณจะไม่ถูกรบกวน
superluminary

39

เชื่อถือได้มีประสิทธิภาพสั้น

วิธีแก้ไขปัญหาบางอย่างในหน้านี้ไม่น่าเชื่อถือ (เป็นเพียงการสุ่มอาร์เรย์บางส่วนเท่านั้น) โซลูชันอื่น ๆ นั้นมีประสิทธิภาพลดลงอย่างมาก ด้วย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]
        )
    }

ดังนั้น ES6 (ES2015) อยู่ที่ไหน? [array[i], array[rand]]=[array[rand], array[i]]? บางทีคุณอาจจะร่างวิธีการทำงาน ทำไมคุณถึงเลือกทำซ้ำลง
sheriffderek

@sheriffderek ใช่คุณสมบัติ ES6 ที่ฉันใช้อยู่คือการมอบหมายสอง vars ในครั้งเดียวซึ่งช่วยให้เราสามารถสลับสอง vars ในหนึ่งบรรทัดของรหัส
Ben Carp

ขอมอบเครดิตให้แก่ @sheriffderek ผู้แนะนำอัลกอริทึมจากน้อยไปหามาก อัลกอริทึมจากน้อยไปมากสามารถพิสูจน์ได้ในการเหนี่ยวนำ
เบ็นคาร์พ

23

กำลังเพิ่มคำตอบของ @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
};

3
เราควรสนับสนุนให้ผู้คนใช้ _. สับเปลี่ยนแทนที่จะวางรหัสจากสแตกล้น และเราควรกีดกันผู้คนจากการบีบอัดคำตอบที่มากเกินไป นั่นคือสิ่งที่ jsmin มีไว้สำหรับ
David Jones

45
@DavidJones: ทำไมฉันต้องรวมทั้งไลบรารีขนาด 4kb เพื่อสับเปลี่ยนอาร์เรย์
Blender

1
@KingKongFrog การเรียกชื่อยังไม่ได้นำไปสู่การรวมตัวกันของชุมชนที่เหมาะสม
wheaties

2
มันมีประสิทธิภาพที่จะทำvar b = ในวงแทนที่จะประกาศ b นอกวงและกำหนดมันด้วยb = ในวง?
Alex K

2
@Brian จะไม่สร้างความแตกต่าง; การชักรอกเกิดขึ้นเมื่อมีการแยกวิเคราะห์ซอร์สโค้ด อาจไม่มีส่วนเกี่ยวข้อง
2864740

23

แก้ไข: คำตอบนี้ไม่ถูกต้อง

ดู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 เป็นตัวเลขสุ่มที่อาจเป็นค่าบวกหรือลบดังนั้นฟังก์ชันการเรียงลำดับจะจัดเรียงองค์ประกอบแบบสุ่ม


17

ด้วย 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();

4
ที่จะตัดคุณควรใช้แทนn >>> 0 ~~nดัชนีอาร์เรย์สามารถสูงกว่า2³¹-1
Oriol

1
การทำลายล้างเช่นนี้ทำให้การติดตั้ง
ทำได้ง่าย

14

ฉันพบตัวแปรนี้อยู่ในคำตอบ "ลบโดยผู้แต่ง" ในสำเนาของคำถามนี้ ต่างจากคำตอบอื่น ๆ ที่มี upvotes มากมายแล้วนี่คือ:

  1. สุ่มจริง
  2. ไม่อยู่ในตำแหน่ง (ด้วยเหตุนี้จึงเป็นshuffledชื่อมากกว่าshuffle)
  3. ยังไม่ได้นำเสนอที่นี่พร้อมกับหลากหลายรูปแบบ

นี่เป็น jsfiddle แสดงในการใช้งาน

Array.prototype.shuffled = function() {
  return this.map(function(n){ return [Math.random(), n] })
             .sort().map(function(n){ return n[1] });
}

(ฉันสงสัยว่ามันถูกลบเนื่องจากเป็นวิธีที่ไม่มีประสิทธิภาพมากในการสุ่มอาร์เรย์โดยเฉพาะอย่างยิ่งสำหรับอาร์เรย์ที่มีขนาดใหญ่กว่า ... ในขณะที่คำตอบที่ยอมรับและจำนวนโคลนอื่น ๆ ของคำตอบนั้นสุ่มแบบแทนที่)
WiredPrairie

1
ใช่ แต่เนื่องจากคำตอบที่ผิดที่รู้จักกันดีนั้นยังคงมีคะแนนโหวตจำนวนมากควรมีการพูดถึงวิธีแก้ปัญหาที่ไม่มีประสิทธิภาพ แต่ถูกต้อง
Daniel Martin

[1,2,3,4,5,6].sort(function() { return .5 - Math.random(); });- มันไม่ได้จัดเรียงแบบสุ่มและถ้าคุณใช้คุณสามารถจบลงด้วยความอับอาย: robweir.com/blog/2010/02/microsoft-random-browser-ballot.html
Daniel Martin

3
คุณจำเป็นต้องใช้.sort(function(a,b){ return a[0] - b[0]; })ถ้าคุณต้องการเรียงลำดับเพื่อเปรียบเทียบค่าตัวเลข เริ่มต้น.sort()เปรียบเทียบเป็นพจนานุกรมความหมายมันจะพิจารณา10จะน้อยกว่า2ตั้งแต่น้อยกว่า1 2
4castle

@ 4castle โอเคฉันได้อัปเดตโค้ดแล้ว แต่จะเปลี่ยนกลับ: ความแตกต่างระหว่างคำสั่งทำพจนานุกรมและลำดับตัวเลขไม่สำคัญสำหรับตัวเลขในช่วงที่Math.random()สร้าง (นั่นคือคำสั่งทำพจนานุกรมเท่ากับคำสั่งที่เป็นตัวเลขเมื่อจัดการกับตัวเลขจาก 0 (รวม) ถึง 1 (พิเศษ))
Daniel Martin

14
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;
};

เห็นได้ชัดว่านี่ไม่ใช่วิธีที่ดีที่สุดเท่า Fisher-Yates algorithm แต่มันจะใช้ได้กับการสัมภาษณ์ทางเทคนิคหรือ
davidatthepark

@Andrea รหัสเสียเนื่องจากความจริงที่ว่ามีการเปลี่ยนแปลงความยาวของแถวภายในวงวน ด้วยการแก้ไขครั้งล่าสุดนี้ถูกแก้ไข
Charlie Wallace

11
arr1.sort(() => Math.random() - 0.5);

1
ทำไมลบ 0.5 หมายเลขนั้นหมายถึงอะไร
Sartheris Stormhammer

1
@SartherisStormhammer เพราะเราใช้การเปรียบเทียบฟังก์ชั่นสำหรับการเรียงลำดับและถ้าที่ส่งกลับจำนวนมากกว่า 0 องค์ประกอบที่ถูกเปรียบเทียบจะสั่งในทิศทางเดียว -0.5 ใน Math.random () จะให้จำนวนลบ ~ 50% ของเวลาซึ่งทำให้เรามีลำดับย้อนกลับ
Sam Doidge

ทางออกที่ตรงไปตรงมาและง่ายที่สุด ขอบคุณ
deanwilliammills

9

คุณสามารถทำได้อย่างง่ายดายด้วย:

// array
var fruits = ["Banana", "Orange", "Apple", "Mango"];
// random
fruits.sort(function(a, b){return 0.5 - Math.random()});
// out
console.log(fruits);

โปรดอ้างอิงที่JavaScript เรียงลำดับอาร์เรย์


อัลกอริทึมนี้ได้รับการพิสูจน์แล้วว่ามีข้อบกพร่อง

โปรดพิสูจน์ให้ฉันเห็น ฉันใช้ w3schools
TínhNgô Quang

4
คุณสามารถอ่านกระทู้ที่css-tricks.com/snippets/javascript/shuffle-arrayหรือที่news.ycombinator.com/item?id=2728914 W3schools เป็นแหล่งข้อมูลที่น่ากลัวอยู่เสมอ

สำหรับการอภิปรายที่ดีว่าเหตุใดจึงไม่ใช่วิธีการที่ดีโปรดดูstackoverflow.com/questions/962802/…
Charlie Wallace


8

สับเปลี่ยน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);
  }
}

7

ก่อนอื่นมาดูที่นี่เพื่อเปรียบเทียบภาพที่ดีเยี่ยมของวิธีการเรียงลำดับที่แตกต่างกันในจาวาสคริปต์

ประการที่สองหากคุณดูลิงค์ด้านบนอย่างรวดเร็วคุณจะพบว่าการ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ไม่ใช่ดัชนี ดังนั้นรหัสนี้ใช้งานไม่ได้
gregers

@ ผู้ติดตามถูกต้องฉันได้แก้ไขคำตอบแล้ว ขอบคุณ
Milo Wielondek

1
มันไม่ได้สุ่มมาก ขึ้นอยู่กับการนำไปใช้ของการเรียงลำดับองค์ประกอบที่ดัชนีแถวลำดับต่ำสุดอาจต้องการการเปรียบเทียบเพิ่มเติมเพื่อที่จะไปยังดัชนีที่สูงกว่าองค์ประกอบถัดจากดัชนีสูงสุด ซึ่งหมายความว่ามีโอกาสน้อยกว่าที่องค์ประกอบที่ดัชนีต่ำสุดเพื่อไปที่ดัชนีสูงสุด
1 'หรือ 1 -

7

ฟังก์ชั่นสลับที่ไม่เปลี่ยนอาเรย์ของแหล่งข้อมูล

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;
}

นี่เป็นหลักอัลกอริทึม Fisher-Yates ดั้งเดิมโดยที่คุณspliceเป็นวิธีที่ไม่มีประสิทธิภาพอย่างน่ากลัวในการทำสิ่งที่พวกเขาเรียกว่า "โดดเด่น" หากคุณไม่ต้องการที่จะกลายพันธุ์อาเรย์ดั้งเดิมแล้วเพียงแค่คัดลอกแล้วสับเปลี่ยนสำเนานั้นในสถานที่โดยใช้ตัวแปร Durstenfeld ที่มีประสิทธิภาพมากขึ้น

@torazaburo ขอขอบคุณสำหรับความคิดเห็น ผมได้ปรับปรุงคำตอบของฉันจะทำให้มันชัดเจนว่าผมค่อนข้างนำเสนอโซลูชั่นสวยกว่าซุปเปอร์ปรับหนึ่ง
Evgenia Manolova

นอกจากนี้เรายังสามารถใช้วิธีการในการสร้างสำเนาชอบโดย:splice source = array.slice();
Taiga

6

นี่คือง่ายที่สุดหนึ่ง

function shuffle(array) {
  array.sort(() => Math.random() - 0.5);
}

สำหรับตัวอย่างเพิ่มเติมคุณสามารถตรวจสอบได้ที่นี่


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;
}

การเพิ่มขึ้นของการใช้อย่างเข้มงวดให้คุณค่ามากกว่าคำตอบที่ยอมรับคืออะไร
shortstuffsushi

หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับโหมดเข้มงวดและวิธีที่มันมีผลต่อประสิทธิภาพคุณสามารถอ่านได้ที่นี่: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Raphael C

อืมคุณช่วยชี้บางสิ่งที่เฉพาะเจาะจงจากเอกสารอ้างอิงได้ไหม? ไม่มีสิ่งใดในที่นี้ที่อ้างอิงถึง "การปรับปรุงประสิทธิภาพ" นอกเหนือจากความคิดเห็นที่คลุมเครือที่ด้านบนเกี่ยวกับการทำให้เครื่องยนต์ js ยากที่จะปรับให้เหมาะสม ในกรณีนี้มันไม่ชัดเจนสำหรับฉันว่าการใช้งานที่เข้มงวดจะปรับปรุงได้อย่างไร
shortstuffsushi

โหมดที่เข้มงวดได้รับรอบสำหรับบางเวลาและมีเพียงพออ่านออกมีให้ทุกคนแสดงความคิดเห็นของตนเองถ้าพวกเขาควรใช้หรือไม่และทำไม เช่น Jslint ทำให้ชัดเจนพอที่คุณควรใช้โหมดเข้มงวด ดักลาสคร็อคฟอร์ดเขียนบทความค่อนข้างมากและมีวิดีโอที่ยอดเยี่ยมว่าทำไมการใช้โหมดที่เข้มงวดไม่เพียง แต่เป็นการฝึกฝนที่ดีเท่านั้น ฉันขอแนะนำให้คุณใช้ Google และแสดงความคิดเห็นของคุณเอง
Raphael C

นี่คือเธรดเก่าเกี่ยวกับ perfs ในโหมดเข้มงวดเก่าไปหน่อย แต่ก็ยังมีความเกี่ยวข้อง: stackoverflow.com/questions/3145966/…
Raphael C

5

คำตอบอื่น ๆ ทั้งหมดนั้นมาจาก 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));


4

โซลูชั่นแบบอินไลน์แบบสั้นที่ทันสมัยโดยใช้คุณสมบัติ ES6:

['a','b','c','d'].map(x => [Math.random(), x]).sort(([a], [b]) => a - b).map(([_, x]) => x);

(เพื่อการศึกษา)


4

การดัดแปลงคำตอบของ 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;
};

4

แม้ว่าจะมีการใช้งานจำนวนมากที่แนะนำไว้แล้ว แต่ฉันรู้สึกว่าเราสามารถทำให้สั้นลงและง่ายขึ้นสำหรับการใช้วนรอบแต่ละครั้งดังนั้นเราจึงไม่ต้องกังวลเกี่ยวกับการคำนวณความยาวของอาร์เรย์และเราสามารถหลีกเลี่ยงการใช้ตัวแปรชั่วคราวได้อย่างปลอดภัย

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)

4

เพียงแค่มีนิ้วอยู่ในวงกลม ที่นี่ฉันนำเสนอการดำเนินการแบบสุ่มของ 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 ขึ้นอยู่กับเครื่องมือเบราว์เซอร์ของคุณและข้อเท็จจริงบางอย่างที่ไม่ทราบ


3

อาร์เรย์แบบสุ่ม

 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 อย่างที่คุณ<ไม่เคยใช้<=
Mohebifar

3

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;
}

เห็นได้ชัดว่าคุณกำลังทำSattoloแทนFisher-Yates (Knuth, เป็นกลาง)
Arthur2e5

3

จากจุดทฤษฎีในมุมมองของวิธีที่สง่างามมากที่สุดในการทำมันในความต่ำต้อยของฉันคือการได้รับเพียงครั้งเดียวจำนวนสุ่มระหว่าง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 รายการคุณสามารถพิจารณาได้อย่างปลอดภัยว่าจะไม่มีอคติอย่างแน่นอน


สง่างามแน่นอน!
Gershom
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.