การใช้เมธอด JavaScript Array.sort () ในการสับถูกต้องหรือไม่


126

ฉันกำลังช่วยใครบางคนด้วยโค้ด JavaScript ของเขาและสายตาของฉันก็ถูกจับโดยส่วนที่ดูเหมือนว่า:

function randOrd(){
  return (Math.round(Math.random())-0.5);
}
coords.sort(randOrd);
alert(coords);

สิ่งแรกของฉันคือ: เดี๋ยวก่อนนี่อาจใช้ไม่ได้! แต่แล้วฉันก็ได้ทำการทดลองและพบว่าอย่างน้อยมันก็ดูเหมือนจะให้ผลลัพธ์แบบสุ่ม

จากนั้นฉันก็ค้นหาเว็บและเกือบที่ด้านบนพบบทความที่มีการคัดลอกโค้ดนี้มากที่สุด ดูเหมือนเว็บไซต์และผู้เขียนที่น่านับถือทีเดียว ...

แต่ความรู้สึกในใจของฉันบอกฉันว่าสิ่งนี้ต้องผิด โดยเฉพาะอย่างยิ่งเมื่ออัลกอริทึมการเรียงลำดับไม่ได้ระบุไว้ตามมาตรฐาน ECMA ฉันคิดว่าการเรียงลำดับ algoritms ที่แตกต่างกันจะส่งผลให้เกิดการสับเปลี่ยนที่ไม่เหมือนกัน อัลกอริธึมการเรียงลำดับบางอย่างอาจวนซ้ำไม่สิ้นสุด ...

แต่คุณคิดอย่างไร?

และเป็นอีกคำถาม ... ตอนนี้ฉันจะไปวัดผลสุ่มของเทคนิคการสับไพ่นี้ได้อย่างไร?

อัปเดต:ฉันทำการวัดผลและโพสต์ผลลัพธ์ด้านล่างเป็นหนึ่งในคำตอบ


เพียงเพื่อสังเกตว่ามันไม่มีประโยชน์ที่จะปัดเศษผลลัพธ์เฉพาะการนับเครื่องหมาย
bormat

2
" ฉันพบว่าดูเหมือนจะให้ผลลัพธ์แบบสุ่ม " - จริง
Bergi

คำตอบ:


110

ไม่เคยมีวิธีที่ชื่นชอบของสับส่วนหนึ่งเป็นเพราะมันคือการดำเนินการเฉพาะตามที่คุณพูด โดยเฉพาะอย่างยิ่งฉันดูเหมือนจะจำได้ว่าไลบรารีมาตรฐานที่เรียงลำดับจาก Java หรือ. NET (ไม่แน่ใจว่าอันไหน) มักจะตรวจพบว่าคุณลงเอยด้วยการเปรียบเทียบที่ไม่สอดคล้องกันระหว่างองค์ประกอบบางอย่าง (เช่นคุณอ้างสิทธิ์ครั้งแรกA < BและB < Cแต่หลังจากนั้นC < A)

นอกจากนี้ยังลงเอยด้วยการสับเปลี่ยน (ในแง่ของเวลาดำเนินการ) ที่ซับซ้อนกว่าที่คุณต้องการจริงๆ

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

นี่คือ O (n) และต้องการเพียงการเรียก n-1 ไปยังเครื่องสร้างตัวเลขสุ่มซึ่งดีมาก นอกจากนี้ยังสร้างการสับเปลี่ยนของแท้ - องค์ประกอบใด ๆ มีโอกาส 1 / n ที่จะสิ้นสุดในแต่ละช่องว่างโดยไม่คำนึงถึงตำแหน่งเดิม (สมมติว่าเป็น RNG ที่เหมาะสม) เวอร์ชันที่เรียงลำดับจะใกล้เคียงกับการแจกแจงแบบคู่ (สมมติว่าตัวสร้างตัวเลขสุ่มไม่ได้เลือกค่าเดียวกันสองครั้งซึ่งไม่น่าเป็นไปได้อย่างมากหากส่งคืนค่าสุ่มสองเท่า) แต่ฉันคิดว่ามันง่ายกว่าที่จะให้เหตุผลเกี่ยวกับเวอร์ชันสุ่ม :)

วิธีการนี้เรียกว่าการสับเปลี่ยน Fisher-Yates

ฉันถือว่าเป็นแนวทางปฏิบัติที่ดีที่สุดในการเขียนโค้ดการสุ่มครั้งเดียวและใช้ซ้ำได้ทุกที่ที่คุณต้องการสับเปลี่ยนรายการ จากนั้นคุณไม่จำเป็นต้องกังวลเกี่ยวกับการจัดเรียงในแง่ของความน่าเชื่อถือหรือความซับซ้อน เป็นโค้ดเพียงไม่กี่บรรทัด (ซึ่งฉันจะไม่ลองใช้ JavaScript!)

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


5
Raymond Chen เจาะลึกถึงความสำคัญที่ฟังก์ชันการเปรียบเทียบการเรียงลำดับเป็นไปตามกฎ: blogs.msdn.com/oldnewthing/archive/2009/05/08/9595334.aspx
Jason Kresowaty

1
หากเหตุผลของฉันถูกต้องเวอร์ชันที่เรียงลำดับจะไม่ทำให้เกิดการสุ่มแบบ 'ของแท้'!
Christoph

@Christoph: คิดเกี่ยวกับมันแม้ Fisher-Yates จะเพียง แต่ให้กระจาย "ดี" ถ้าแรนด์ (x) รับประกันได้ว่าจะว่าแม้ในช่วงของ เนื่องจากโดยปกติแล้วจะมีสถานะที่เป็นไปได้ 2 ^ x สำหรับ RNG สำหรับ x บางตัวฉันไม่คิดว่ามันจะตรงกับแรนด์ (3)
Jon Skeet

@ จอน: แต่ฟิชเชอร์ - เยตส์จะสร้าง2^xสถานะสำหรับดัชนีอาร์เรย์แต่ละรายการกล่าวคือจะมีสถานะทั้งหมด 2 ^ (xn) ซึ่งควรจะมากกว่า 2 ^ c ดูคำตอบที่แก้ไขของฉันสำหรับรายละเอียด
คริสตอฟ

@ คริสตอฟ: ฉันอาจอธิบายตัวเองไม่ถูก สมมติว่าคุณมีแค่ 3 องค์ประกอบ คุณเลือกองค์ประกอบแรกแบบสุ่มจากทั้งหมด 3 เพื่อให้ได้การแจกแจงที่สม่ำเสมออย่างสมบูรณ์คุณจะต้องสามารถเลือกตัวเลขสุ่มในช่วง [0,3) โดยสิ้นเชิง - และถ้า PRNG มี 2 ^ n สถานะที่เป็นไปได้คุณไม่สามารถทำได้ - ความเป็นไปได้หนึ่งหรือสองอย่างจะมีโอกาสเกิดขึ้นได้สูงกว่าเล็กน้อย
Jon Skeet

118

หลังจากจอนได้กล่าวถึงทฤษฎีแล้วนี่คือการนำไปใช้:

function shuffle(array) {
    var tmp, current, top = array.length;

    if(top) while(--top) {
        current = Math.floor(Math.random() * (top + 1));
        tmp = array[current];
        array[current] = array[top];
        array[top] = tmp;
    }

    return array;
}

อัลกอริทึมคือในขณะที่การเรียงลำดับที่ควรจะเป็นO(n) O(n log n)ขึ้นอยู่กับค่าใช้จ่ายในการเรียกใช้โค้ด JS เมื่อเทียบกับsort()ฟังก์ชันเนทีฟสิ่งนี้อาจนำไปสู่ความแตกต่างที่เห็นได้ชัดเจนในประสิทธิภาพซึ่งควรเพิ่มขึ้นตามขนาดอาร์เรย์


ในความคิดเห็นต่อคำตอบของ Boboboboฉันระบุว่าอัลกอริทึมที่เป็นปัญหาอาจไม่สร้างความน่าจะเป็นแบบกระจายเท่า ๆ กัน (ขึ้นอยู่กับการนำไปใช้sort())

อาร์กิวเมนต์ของฉันดำเนินไปตามบรรทัดเหล่านี้: อัลกอริทึมการเรียงลำดับต้องการการcเปรียบเทียบจำนวนหนึ่งเช่นc = n(n-1)/2สำหรับ Bubblesort ฟังก์ชันการเปรียบเทียบแบบสุ่มของเราทำให้ผลลัพธ์ของการเปรียบเทียบแต่ละครั้งมีโอกาสเท่ากันกล่าวคือมีผลลัพธ์ที่2^c เป็นไปได้เท่า ๆ กัน ตอนนี้ผลลัพธ์แต่ละรายการจะต้องสอดคล้องกับหนึ่งในการn!เรียงสับเปลี่ยนของรายการของอาร์เรย์ซึ่งทำให้การแจกแจงแบบสม่ำเสมอเป็นไปไม่ได้ในกรณีทั่วไป (นี่เป็นการทำให้เข้าใจง่ายเนื่องจากจำนวนการเปรียบเทียบที่แท้จริงต้องขึ้นอยู่กับอาร์เรย์อินพุต แต่การยืนยันยังคงมีอยู่)

ดังที่จอนชี้ให้เห็นเพียงอย่างเดียวก็ไม่มีเหตุผลที่จะชอบใช้ฟิชเชอร์ - เยตส์มากกว่าการใช้sort()เนื่องจากตัวสร้างตัวเลขสุ่มจะแมปค่าสุ่มหลอกจำนวน จำกัด กับการn!เรียงสับเปลี่ยน แต่ผลลัพธ์ของ Fisher-Yates น่าจะดีกว่า:

Math.random()[0;1[ผลิตจำนวนสุ่มหลอกในช่วง เนื่องจาก JS ใช้ค่าทศนิยมที่มีความแม่นยำสองเท่าจึงสอดคล้องกับ2^xค่าที่เป็นไปได้โดยที่52 ≤ x ≤ 63(ฉันขี้เกียจหาจำนวนจริง) การแจกแจงความน่าจะเป็นที่สร้างขึ้นโดยใช้Math.random()จะหยุดทำงานได้ดีหากจำนวนเหตุการณ์อะตอมมีลำดับความสำคัญเท่ากัน

เมื่อใช้ Fisher-Yates พารามิเตอร์ที่เกี่ยวข้องคือขนาดของอาร์เรย์ซึ่งไม่ควรเข้าใกล้2^52เนื่องจากข้อ จำกัด ในทางปฏิบัติ

เมื่อจัดเรียงด้วยฟังก์ชันการเปรียบเทียบแบบสุ่มโดยพื้นฐานแล้วฟังก์ชันจะสนใจว่าค่าที่ส่งคืนเป็นบวกหรือลบเท่านั้นดังนั้นสิ่งนี้จะไม่มีปัญหา แต่มีสิ่งที่คล้ายกัน: เนื่องจากฟังก์ชันการเปรียบเทียบทำงานได้ดี2^cผลลัพธ์ที่เป็นไปได้จึงเป็นไปได้ตามที่ระบุไว้ ถ้าอย่างc ~ n log nนั้น2^c ~ n^(a·n)ที่ไหนa = constซึ่งทำให้อย่างน้อยเป็นไปได้ที่2^cมีขนาดเท่ากับ (หรือน้อยกว่า) n!และทำให้เกิดการแจกแจงที่ไม่สม่ำเสมอแม้ว่าอัลกอริธึมการเรียงลำดับจะแมปลงบนพีชคณิตเท่า ๆ กันก็ตาม หากสิ่งนี้มีผลกระทบในทางปฏิบัติก็เกินเลย

ปัญหาที่แท้จริงคือไม่รับประกันว่าอัลกอริทึมการเรียงลำดับจะแมปลงบนการเรียงสับเปลี่ยนอย่างเท่าเทียมกัน เป็นเรื่องง่ายที่จะเห็นว่า Mergesort ทำตามแบบสมมาตร แต่การให้เหตุผลเกี่ยวกับบางสิ่งเช่น Bubblesort หรือที่สำคัญกว่า Quicksort หรือ Heapsort นั้นไม่ใช่


บรรทัดล่าง: ตราบใดที่sort()ใช้ Mergesort คุณควรจะปลอดภัยพอสมควรยกเว้นในกรณีเตะมุม (อย่างน้อยฉันก็หวังว่า2^c ≤ n!จะเป็นลูกเตะมุม) หากไม่เป็นเช่นนั้นการเดิมพันทั้งหมดจะถูกปิด


ขอบคุณสำหรับการใช้งาน เร็วมาก! โดยเฉพาะอย่างยิ่งเมื่อเทียบกับเรื่องไร้สาระที่ฉันเขียนด้วยตัวเองในระหว่างนี้
Rene Saarsoo

1
หากคุณใช้ไลบรารี underscore.js ต่อไปนี้เป็นวิธีขยายด้วยวิธีการสุ่มแบบ Fisher-Yates ด้านบน: github.com/ryantenney/underscore/commit/…
Steve

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

16

ฉันได้ทำการวัดผลการสุ่มว่าผลลัพธ์ของการจัดเรียงแบบสุ่มนี้เป็นอย่างไร ...

เทคนิคของฉันคือใช้อาร์เรย์ขนาดเล็ก [1,2,3,4] และสร้างการเรียงสับเปลี่ยน (4! = 24) ทั้งหมดของมัน จากนั้นฉันจะใช้ฟังก์ชันการสลับกับอาร์เรย์เป็นจำนวนมากและนับจำนวนครั้งที่สร้างการเรียงสับเปลี่ยนแต่ละครั้ง algoritm สับเปลี่ยนที่ดีจะกระจายผลลัพธ์อย่างเท่าเทียมกันในการเรียงสับเปลี่ยนทั้งหมดในขณะที่อันที่ไม่ดีจะไม่สร้างผลลัพธ์ที่เหมือนกัน

ใช้โค้ดด้านล่างที่ฉันทดสอบใน Firefox, Opera, Chrome, IE6 / 7/8

น่าแปลกใจสำหรับฉันการเรียงลำดับแบบสุ่มและการสุ่มแบบสุ่มทั้งคู่สร้างการแจกแจงที่สม่ำเสมอเท่า ๆ กัน ดังนั้นดูเหมือนว่า (ตามที่หลายคนแนะนำ) เบราว์เซอร์หลักกำลังใช้การจัดเรียงแบบผสาน แน่นอนว่านี่ไม่ได้หมายความว่าจะไม่มีเบราว์เซอร์อยู่ที่นั่นซึ่งแตกต่างออกไป แต่ฉันจะบอกว่ามันหมายความว่าวิธีการเรียงลำดับแบบสุ่มนี้มีความน่าเชื่อถือเพียงพอที่จะใช้ในทางปฏิบัติ

แก้ไข:การทดสอบนี้ไม่ได้วัดความสุ่มอย่างถูกต้องหรือขาดไป ดูคำตอบอื่น ๆ ที่ฉันโพสต์

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

// ฟังก์ชั่นสับเปลี่ยนโพสต์โดย Cristoph
var shuffle = function (อาร์เรย์) {
    var tmp ปัจจุบัน top = array.length;

    ถ้า (บน) ในขณะที่ (- บนสุด) {
        ปัจจุบัน = Math.floor (Math.random () * (top + 1));
        tmp = array [ปัจจุบัน];
        อาร์เรย์ [ปัจจุบัน] = อาร์เรย์ [บนสุด];
        อาร์เรย์ [บนสุด] = tmp;
    }

    ส่งคืนอาร์เรย์
};

// ฟังก์ชันการจัดเรียงแบบสุ่ม
var rnd = function () {
  กลับ Math.round (Math.random ()) - 0.5;
};
var randSort = ฟังก์ชัน (A) {
  ส่งกลับ A.sort (rnd);
};

การเรียงสับเปลี่ยน var = ฟังก์ชัน (A) {
  ถ้า (A.length == 1) {
    กลับ [A];
  }
  else {
    var perms = [];
    สำหรับ (var i = 0; i <A.length; i ++) {
      var x = A.slice (ผม, ผม + 1);
      var xs = A.slice (0, i) .concat (A.slice (i + 1));
      var subperms = วิธีการเรียงสับเปลี่ยน (xs);
      สำหรับ (var j = 0; j <subperms.length; j ++) {
        perms.push (x.concat (subperms [เจ]));
      }
    }
    ผลตอบแทน perms;
  }
};

var test = function (A, การทำซ้ำ, func) {
  // เริ่มการเรียงสับเปลี่ยน
  var stats = {};
  var perms = การเรียงสับเปลี่ยน (A);
  สำหรับ (var i in perms) {
    สถิติ ["" + perms [i]] = 0;
  }

  // สับเปลี่ยนหลาย ๆ ครั้งและรวบรวมสถิติ
  var start = วันที่ใหม่ ();
  สำหรับ (var i = 0; i <iterations; i ++) {
    var สับ = func (A);
    สถิติ [ "" + สับ] ++;
  }
  var end = วันที่ใหม่ ();

  // จัดรูปแบบผลลัพธ์
  var arr = [];
  สำหรับ (var i ในสถิติ) {
    arr.push (i + "" + สถิติ [i]);
  }
  กลับ arr.join ("\ n") + "\ n \ n เวลาที่ถ่าย:" + ((สิ้นสุด - เริ่ม) / 1,000) + "วินาที";
};

alert ("การจัดเรียงแบบสุ่ม:" + การทดสอบ ([1,2,3,4], 100000, randSort));
alert ("shuffle:" + test ([1,2,3,4], 100000, shuffle));

11

ที่น่าสนใจคือMicrosoft ใช้เทคนิคเดียวกันนี้ในหน้าเลือกเบราว์เซอร์แบบสุ่ม

พวกเขาใช้ฟังก์ชันการเปรียบเทียบที่แตกต่างกันเล็กน้อย:

function RandomSort(a,b) {
    return (0.5 - Math.random());
}

ดูเหมือนเกือบจะเหมือนกันสำหรับฉัน แต่กลับกลายเป็นไม่สุ่ม ...

ดังนั้นฉันจึงทำการทดสอบอีกครั้งด้วยวิธีการเดียวกับที่ใช้ในบทความที่เชื่อมโยงและปรากฎว่าวิธีการเรียงลำดับแบบสุ่มให้ผลลัพธ์ที่มีข้อบกพร่อง รหัสทดสอบใหม่ที่นี่:

function shuffle(arr) {
  arr.sort(function(a,b) {
    return (0.5 - Math.random());
  });
}

function shuffle2(arr) {
  arr.sort(function(a,b) {
    return (Math.round(Math.random())-0.5);
  });
}

function shuffle3(array) {
  var tmp, current, top = array.length;

  if(top) while(--top) {
    current = Math.floor(Math.random() * (top + 1));
    tmp = array[current];
    array[current] = array[top];
    array[top] = tmp;
  }

  return array;
}

var counts = [
  [0,0,0,0,0],
  [0,0,0,0,0],
  [0,0,0,0,0],
  [0,0,0,0,0],
  [0,0,0,0,0]
];

var arr;
for (var i=0; i<100000; i++) {
  arr = [0,1,2,3,4];
  shuffle3(arr);
  arr.forEach(function(x, i){ counts[x][i]++;});
}

alert(counts.map(function(a){return a.join(", ");}).join("\n"));

ฉันไม่เห็นว่าทำไมมันต้องเป็น 0.5 - Math.random () ทำไมไม่แค่ Math.random () ล่ะ?
Alexander Mills

1
@AlexanderMills: ฟังก์ชันเปรียบเทียบผ่านไปsort()ควรจะกลับมาเป็นจำนวนมากขึ้นกว่าน้อยกว่าหรือเท่ากับศูนย์ขึ้นอยู่กับการเปรียบเทียบและa b( developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… )
LarsH

@LarsH ใช่นั่นทำให้รู้สึก
Alexander Mills

9

ฉันได้วางหน้าทดสอบอย่างง่ายบนเว็บไซต์ของฉันที่แสดงอคติของเบราว์เซอร์ปัจจุบันของคุณเทียบกับเบราว์เซอร์ยอดนิยมอื่น ๆ โดยใช้วิธีการต่างๆในการสับเปลี่ยน มันแสดงให้เห็นถึงอคติที่น่ากลัวของการใช้เพียงการMath.random()-0.5สุ่มแบบสุ่มอีกแบบหนึ่งที่ไม่เอนเอียงและวิธีการของ Fisher-Yates ที่กล่าวถึงข้างต้น

คุณจะเห็นได้ว่าในเบราว์เซอร์บางตัวมีโอกาสสูงถึง 50% ที่องค์ประกอบบางอย่างจะไม่เปลี่ยนตำแหน่งเลยในระหว่างการ 'สุ่ม'!

หมายเหตุ: คุณสามารถดำเนินการสับเปลี่ยน Fisher-Yates โดย @Christoph ได้เร็วขึ้นเล็กน้อยสำหรับ Safari โดยเปลี่ยนรหัสเป็น:

function shuffle(array) {
  for (var tmp, cur, top=array.length; top--;){
    cur = (Math.random() * (top + 1)) << 0;
    tmp = array[cur]; array[cur] = array[top]; array[top] = tmp;
  }
  return array;
}

ผลการทดสอบ: http://jsperf.com/optimized-fisher-yates


5

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

ใน JavaScript (ที่แหล่งที่มาถูกส่งอย่างต่อเนื่อง) ขนาดเล็กสร้างความแตกต่างในต้นทุนแบนด์วิดท์


2
สิ่งนี้คือคุณมักจะเลือกเรื่องการแจกจ่ายมากกว่าที่คุณคิดและสำหรับ "โค้ดขนาดเล็ก" ก็มีอยู่เสมอarr = arr.map(function(n){return [Math.random(),n]}).sort().map(function(n){return n[1]});ซึ่งมีข้อดีคือไม่ยาวมากเกินไปและกระจายอย่างถูกต้องจริง นอกจากนี้ยังมีรูปแบบการสับเปลี่ยน Knuth / FY ที่บีบอัดมาก
Daniel Martin

@DanielMartin ซับเดียวน่าจะเป็นคำตอบ arr = arr.map(function(n){return [Math.random(),n];}).sort().map(function(n){return n[1];});นอกจากนี้เพื่อหลีกเลี่ยงข้อผิดพลาดในการแยกวิเคราะห์สองอัฒภาคต้องมีการเพิ่มดังนั้นจึงมีลักษณะเช่นนี้
Giacomo1968

2

มันเป็นการแฮ็กอย่างแน่นอน ในทางปฏิบัติไม่น่าจะเป็นไปได้ว่าอัลกอริทึมการวนซ้ำแบบไม่สิ้นสุด หากคุณกำลังจัดเรียงวัตถุคุณสามารถวนลูปผ่านอาร์เรย์ coords และทำสิ่งต่างๆเช่น:

for (var i = 0; i < coords.length; i++)
    coords[i].sortValue = Math.random();

coords.sort(useSortValue)

function useSortValue(a, b)
{
  return a.sortValue - b.sortValue;
}

(แล้ววนซ้ำอีกครั้งเพื่อลบ sortValue)

ยังคงเป็นแฮ็คแม้ว่า ถ้าจะให้ดีต้องทำแบบสุด ๆ :)


2

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

พิสูจน์:

  1. สำหรับอาร์เรย์ของnองค์ประกอบมีn!การเรียงสับเปลี่ยนที่แน่นอน(เช่นการสับเปลี่ยนที่เป็นไปได้)
  2. ทุกการเปรียบเทียบระหว่างการสุ่มเป็นการเลือกระหว่างสองชุดของการเรียงสับเปลี่ยน สำหรับตัวเปรียบเทียบแบบสุ่มมีโอกาส 1/2 ในการเลือกแต่ละชุด
  3. ดังนั้นสำหรับการเรียงสับเปลี่ยน p แต่ละครั้งโอกาสที่จะลงท้ายด้วยการเรียงสับเปลี่ยน p คือเศษส่วนที่มีตัวหาร 2 ^ k (สำหรับ k บางตัว) เนื่องจากเป็นผลรวมของเศษส่วนดังกล่าว (เช่น 1/8 + 1/16 = 3/16 )
  4. สำหรับ n = 3 มีการเรียงสับเปลี่ยนที่มีแนวโน้มเท่ากันหกรายการ โอกาสของการเปลี่ยนแปลงแต่ละครั้งคือ 1/6 1/6 ไม่สามารถแสดงเป็นเศษส่วนโดยมีกำลัง 2 เป็นตัวส่วน
  5. ดังนั้นการเรียงลำดับการพลิกเหรียญจะไม่ส่งผลให้มีการกระจายสับเปลี่ยนอย่างยุติธรรม

ขนาดเดียวที่สามารถกระจายได้อย่างถูกต้องคือ n = 0,1,2


ในแบบฝึกหัดให้ลองวาดแผนผังการตัดสินใจของอัลกอริธึมการเรียงลำดับต่างๆสำหรับ n = 3


มีช่องว่างในการพิสูจน์: หากอัลกอริทึมการจัดเรียงขึ้นอยู่กับความสอดคล้องของตัวเปรียบเทียบและมีรันไทม์ที่ไม่ถูกผูกไว้กับตัวเปรียบเทียบที่ไม่สอดคล้องกันก็สามารถมีผลรวมของความน่าจะเป็นได้ไม่ จำกัด ซึ่งอนุญาตให้เพิ่มได้ถึง 1/6 แม้ว่า ตัวส่วนทุกตัวในผลรวมเป็น 2 ลองหาหนึ่ง

นอกจากนี้หากผู้เปรียบเทียบมีโอกาสคงที่ในการให้คำตอบอย่างใดอย่างหนึ่ง (เช่น(Math.random() < P)*2 - 1ค่าคงที่P) หลักฐานข้างต้นจะถือ หากตัวเปรียบเทียบเปลี่ยนอัตราต่อรองตามคำตอบก่อนหน้านี้อาจเป็นไปได้ที่จะสร้างผลลัพธ์ที่ยุติธรรม การค้นหาตัวเปรียบเทียบดังกล่าวสำหรับอัลกอริทึมการเรียงลำดับที่กำหนดอาจเป็นเอกสารวิจัย


1

หากคุณใช้ D3 จะมีฟังก์ชั่นสับเปลี่ยนในตัว (โดยใช้ Fisher-Yates):

var days = ['Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi','Dimanche'];
d3.shuffle(days);

และนี่คือ Mike จะลงรายละเอียดเกี่ยวกับเรื่องนี้:

http://bost.ocks.org/mike/shuffle/


0

นี่คือแนวทางที่ใช้อาร์เรย์เดียว:

ตรรกะพื้นฐานคือ:

  • เริ่มต้นด้วยอาร์เรย์ของ n องค์ประกอบ
  • ลบองค์ประกอบสุ่มออกจากอาร์เรย์และผลักดันไปยังอาร์เรย์
  • ลบองค์ประกอบแบบสุ่มออกจากองค์ประกอบ n - 1 แรกของอาร์เรย์และผลักดันไปยังอาร์เรย์
  • นำองค์ประกอบแบบสุ่มออกจากองค์ประกอบ n - 2 แรกของอาร์เรย์และผลักดันไปยังอาร์เรย์
  • ...
  • ลบองค์ประกอบแรกของอาร์เรย์และผลักดันไปยังอาร์เรย์
  • รหัส:

    for(i=a.length;i--;) a.push(a.splice(Math.floor(Math.random() * (i + 1)),1)[0]);

    การติดตั้งของคุณมีความเสี่ยงสูงที่จะปล่อยให้องค์ประกอบจำนวนมากไม่ถูกแตะต้อง พวกมันจะถูกเลื่อนไปในอาร์เรย์ทั้งหมดตามจำนวนขององค์ประกอบที่ด้อยกว่าที่ถูกผลักไปด้านบน มีรูปแบบที่วาดในการสับนั้นทำให้ไม่น่าเชื่อถือ
    Kir Kanos

    @KirKanos ฉันไม่แน่ใจว่าฉันเข้าใจความคิดเห็นของคุณ วิธีแก้ปัญหาที่ฉันเสนอคือ O (n) มันจะ "สัมผัส" ทุกองค์ประกอบแน่นอน นี่คือซอที่จะแสดงให้เห็น
    ic3b3rg

    0

    คุณสามารถใช้ไฟล์ Array.sort()ฟังก์ชันเพื่อสลับอาร์เรย์ได้หรือไม่ - ใช่

    ผลลัพธ์สุ่มเพียงพอหรือไม่ - ไม่

    พิจารณาข้อมูลโค้ดต่อไปนี้:

    var array = ["a", "b", "c", "d", "e"];
    var stats = {};
    array.forEach(function(v) {
      stats[v] = Array(array.length).fill(0);
    });
    //stats = {
    //    a: [0, 0, 0, ...]
    //    b: [0, 0, 0, ...]
    //    c: [0, 0, 0, ...]
    //    ...
    //    ...
    //}
    var i, clone;
    for (i = 0; i < 100; i++) {
      clone = array.slice(0);
      clone.sort(function() {
        return Math.random() - 0.5;
      });
      clone.forEach(function(v, i) {
        stats[v][i]++;
      });
    }
    
    Object.keys(stats).forEach(function(v, i) {
      console.log(v + ": [" + stats[v].join(", ") + "]");
    })

    ตัวอย่างผลลัพธ์:

    a [29, 38, 20,  6,  7]
    b [29, 33, 22, 11,  5]
    c [17, 14, 32, 17, 20]
    d [16,  9, 17, 35, 23]
    e [ 9,  6,  9, 31, 45]

    ตามหลักการแล้วควรกระจายจำนวนเท่า ๆ กัน (สำหรับตัวอย่างข้างต้นจำนวนทั้งหมดควรอยู่ที่ประมาณ 20) แต่พวกเขาไม่ได้ เห็นได้ชัดว่าการกระจายขึ้นอยู่กับว่าเบราว์เซอร์ใช้อัลกอริทึมการเรียงลำดับใดและวิธีการวนซ้ำรายการอาร์เรย์สำหรับการเรียงลำดับ

    บทความนี้มีข้อมูลเชิงลึกเพิ่มเติม:
    ไม่ควรใช้ Array.sort () เพื่อสับเปลี่ยนอาร์เรย์


    -3

    มันไม่มีอะไรผิดปกติ

    ฟังก์ชันที่คุณส่งผ่านไปยัง. short () โดยปกติมีลักษณะดังนี้

    ฟังก์ชัน sortingFunc (ครั้งแรกวินาที)
    {
      // ตัวอย่าง:
      กลับก่อน - วินาที;
    }
    

    งานของคุณใน sortingFunc คือการส่งคืน:

    • จำนวนลบถ้าเกิดก่อนวินาที
    • จำนวนบวกถ้าตัวแรกควรไปหลังจากที่สอง
    • และ 0 ถ้ามันเท่ากันหมด

    ฟังก์ชันการเรียงลำดับข้างต้นจะทำให้สิ่งต่างๆเป็นระเบียบ

    หากคุณส่งคืนและ + แบบสุ่มตามที่คุณมีคุณจะได้รับคำสั่งแบบสุ่ม

    เช่นเดียวกับใน MySQL:

    เลือก * จากตาราง ORDER BY Rand ()
    

    5
    มีเป็นบางอย่างผิดปกติด้วยวิธีนี้: ขึ้นอยู่กับขั้นตอนวิธีการเรียงลำดับในการใช้งานโดยการดำเนินงาน JS น่าจะไม่ถูกกระจายอย่างเท่าเทียมกัน!
    Christoph

    นั่นเป็นสิ่งที่เรากังวลจริงหรือ?
    bobobobo

    4
    @bobobobo: ขึ้นอยู่กับการใช้งานใช่บางครั้งเราก็ทำ; นอกจากนี้การทำงานที่ถูกต้องshuffle()จะต้องเขียนเพียงครั้งเดียวดังนั้นจึงไม่ใช่ปัญหาจริงๆเพียงแค่ใส่ข้อมูลโค้ดลงในห้องนิรภัยของคุณและค้นพบเมื่อใดก็ตามที่คุณต้องการ
    Christoph
    โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
    Licensed under cc by-sa 3.0 with attribution required.