Làm cách nào để chọn ngẫu nhiên (xáo trộn) một mảng JavaScript?


1265

Tôi có một mảng như thế này:

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

Làm thế nào tôi có thể ngẫu nhiên / xáo trộn nó?



6
Chỉ cần ném cái này vào đây là bạn có thể hình dung được chức năng xáo trộn ngẫu nhiên thực sự như thế nào với trình hiển thị này Mike Bostock đã thực hiện: bost.ocks.org/mike/shuffle/compare.html
aug

5
@Blazemonger jsPref đã chết. Bạn có thể chỉ cần đăng ở đây là nhanh nhất?
eo mờ

Một lớp lót thì sao? Các mảng trả lại được xáo trộn. mảng1.reduce ((a, v) => a.splice (Math.floor (Math.random () * a.length), 0, v) && a, [])
brunettdan

Dung dịch khử có độ phức tạp O (n ^ 2). Hãy thử chạy nó trên một mảng với một triệu phần tử.
RIV

Câu trả lời:


1542

Thuật toán xáo trộn không thiên vị trên thực tế là Shuffle Fisher-Yates (còn gọi là Knuth).

Xem https://github.com/coolaj86/knuth-shuffle

Bạn có thể thấy một hình ảnh tuyệt vời ở đây (và bài viết gốc được liên kết với điều này )

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

Một số thông tin thêm về thuật toán được sử dụng.


13
Câu trả lời ở trên bỏ qua yếu tố 0, điều kiện nên i--không --i. Ngoài ra, kiểm tra if (i==0)...là không cần thiết vì nếu i == 0các trong khi vòng lặp sẽ không bao giờ được nhập vào. Cuộc gọi đến Math.floorcó thể được thực hiện nhanh hơn bằng cách sử dụng ...| 0. Có thể loại bỏ tempi hoặc tempj và giá trị được gán trực tiếp cho myArray [i] hoặc j khi thích hợp.
RobG

23
@prometheus, tất cả các RNG đều là giả ngẫu nhiên trừ khi được kết nối với phần cứng đắt tiền.
Phil H

38
@RobG việc thực hiện ở trên là đúng chức năng. Trong thuật toán Fisher-Yates, vòng lặp không có nghĩa là chạy cho phần tử đầu tiên trong mảng. Kiểm tra wikipedia nơi có các triển khai khác cũng bỏ qua phần tử đầu tiên. Ngoài ra kiểm tra này bài viết mà nói về lý do tại sao điều quan trọng là vòng lặp không để chạy cho phần tử đầu tiên.
Theon

34
@nikola "hoàn toàn không ngẫu nhiên" là một phẩm chất mạnh mẽ đối với tôi. Tôi sẽ lập luận rằng nó hoàn toàn ngẫu nhiên trừ khi bạn là một nhà mật mã học, trong trường hợp đó có lẽ bạn không sử dụng Math.Random () ở vị trí đầu tiên.
toon81

20
Ugh, yoda ( 0 !== currentIndex).
ffxsam

745

Đây là một triển khai JavaScript của shuffle Durstenfeld , một phiên bản tối ưu của 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;
    }
}

Nó chọn một phần tử ngẫu nhiên cho mỗi phần tử mảng ban đầu và loại trừ nó khỏi lần rút tiếp theo, như chọn ngẫu nhiên từ một cỗ bài.

Loại trừ thông minh này hoán đổi phần tử được chọn với phần tử hiện tại, sau đó chọn phần tử ngẫu nhiên tiếp theo từ phần còn lại, lặp lại để đạt hiệu quả tối ưu, đảm bảo chọn ngẫu nhiên được đơn giản hóa (luôn có thể bắt đầu từ 0) và do đó bỏ qua phần tử cuối cùng.

Thời gian chạy thuật toán là O(n). Lưu ý rằng việc xáo trộn được thực hiện tại chỗ, vì vậy nếu bạn không muốn sửa đổi mảng ban đầu, trước tiên hãy tạo một bản sao của nó với .slice(0).


EDIT: Cập nhật lên ES6 / ECMAScript 2015

ES6 mới cho phép chúng ta gán hai biến cùng một lúc. Điều này đặc biệt hữu ích khi chúng ta muốn trao đổi các giá trị của hai biến, vì chúng ta có thể thực hiện nó trong một dòng mã. Đây là một hình thức ngắn hơn của cùng một chức năng, sử dụng tính năng này.

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 Thuật toán tương tự như câu trả lời của BarsheD, nhưng với lời giải thích và thực hiện sạch hơn.
Laurens Holst

12
Mọi người đang gán sai người cho thuật toán. Đó không phải là shuffle Fisher-Yates mà là shuffle Durstenfeld . Thuật toán Fisher-Yates ban đầu thực sự được chạy trong n ^ 2 lần chứ không phải n lần
Pacerier

7
Không bắt buộc return arrayvì JavaScript chuyển các mảng bằng tham chiếu khi được sử dụng làm đối số hàm. Tôi cho rằng điều này là để tiết kiệm không gian ngăn xếp, nhưng đó là một tính năng nhỏ thú vị. Thực hiện xáo trộn trên mảng sẽ xáo trộn mảng ban đầu.
Joel Trauger

5
Việc thực hiện trong câu trả lời này ủng hộ phần dưới của mảng. Tìm ra cách khó khăn . Math.random() should not be multiplied with the loop counter + 1, but with mảng.lengt () `. Xem Tạo số nguyên ngẫu nhiên trong JavaScript trong một phạm vi cụ thể? cho một lời giải thích rất toàn diện.
Marjan Venema

13
@MarjanVenema Không chắc bạn có còn xem không gian này không, nhưng câu trả lời này là chính xác và sự thay đổi mà bạn đề xuất thực sự mang đến sự thiên vị. Xem blog.codinghorror.com/the-danger-of-naivete để biết cách viết hay về lỗi này.
dùng94559

133

Cảnh báo!
Việc sử dụng thuật toán này không được khuyến khích , vì nó không hiệu quảsai lệch mạnh ; Xem ý kiến. Nó đang được để lại ở đây để tham khảo trong tương lai, vì ý tưởng không phải là hiếm.

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

13
tôi thích giải pháp này, đủ để đưa ra một sự ngẫu nhiên cơ bản
Alex K

147
Downvote vì điều này không thực sự ngẫu nhiên. Tôi không biết tại sao nó có rất nhiều upvote. Không sử dụng phương pháp này. Nó trông đẹp, nhưng không hoàn toàn chính xác. Đây là kết quả sau 10.000 lần lặp về số lần mỗi số trong mảng truy cập mảng của bạn [0] (Tôi cũng có thể đưa ra các kết quả khác): 1 = 29,19%, 2 = 29,53%, 3 = 20,06%, 4 = 11,91%, 5 = 5,99%, 6 = 3,32%
radtad

8
Sẽ tốt thôi nếu bạn cần chọn ngẫu nhiên mảng tương đối nhỏ và không xử lý những thứ mật mã. Tôi hoàn toàn đồng ý rằng nếu bạn cần sự ngẫu nhiên hơn, bạn cần sử dụng giải pháp phức tạp hơn.
bế tắc


12
Vấn đề là nó không mang tính quyết định, sẽ cho kết quả sai (nếu 1> 2 và 2> 3, nên đưa ra 1> 3, nhưng điều này sẽ không đảm bảo điều đó. Điều này sẽ gây nhầm lẫn cho loại và đưa ra kết quả nhận xét bởi @radtad).
MatsLindh

73

Người ta có thể (hoặc nên) sử dụng nó như một protoype từ Array:

Từ BarsheD:

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
Thực sự không có lợi cho việc này, IMOHO, ngoại trừ có thể dậm chân vào việc thực hiện của người khác ..
user2864740

2
Nếu được sử dụng trong nguyên mẫu Array, nó phải được đặt tên khác ngoài việc xáo trộn .
Sói

57
Người ta có thể (hoặc nên) tránh mở rộng Nguyên mẫu bản địa: javascriptweblog.wordpress.com/2011/12/05/ Lời
Wédney Yuri

12
Bạn không nên làm điều này; mỗi mảng bị ảnh hưởng bởi điều này không còn có thể được lặp lại một cách an toàn bằng cách sử dụng cho ... trong. Đừng mở rộng nguyên mẫu bản địa.

18
@TinyGiant Trên thực tế: không sử dụng for...incác vòng lặp để lặp qua các mảng.
Conor O'Brien

69

Bạn có thể làm điều đó một cách dễ dàng với bản đồ và sắp xếp:

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. Chúng tôi đặt từng phần tử trong mảng trong một đối tượng và cung cấp cho nó một khóa sắp xếp ngẫu nhiên
  2. Chúng tôi sắp xếp bằng cách sử dụng khóa ngẫu nhiên
  3. Chúng tôi hủy ánh xạ để có được các đối tượng ban đầu

Bạn có thể xáo trộn các mảng đa hình, và sắp xếp là ngẫu nhiên như Math.random, đủ tốt cho hầu hết các mục đích.

Vì các phần tử được sắp xếp theo các khóa nhất quán không được tạo lại mỗi lần lặp và mỗi phép so sánh lấy từ cùng một phân phối, nên mọi sự không ngẫu nhiên trong phân phối Math.random đều bị hủy bỏ.

Tốc độ

Độ phức tạp thời gian là O (N log N), giống như sắp xếp nhanh. Độ phức tạp không gian là O (N). Điều này không hiệu quả như xáo trộn Fischer Yates nhưng, theo tôi, mã này ngắn hơn đáng kể và nhiều chức năng hơn. Nếu bạn có một mảng lớn, bạn chắc chắn nên sử dụng Fischer Yates. Nếu bạn có một mảng nhỏ với vài trăm mục, bạn có thể làm điều này.


1
@superluminary Rất tiếc, bạn đã đúng. Lưu ý rằng câu trả lời này đã được sử dụng cùng một phương pháp.
Bergi

@Bergi - À đúng rồi, bạn nói đúng, mặc dù tôi nghĩ việc thực hiện của tôi hơi đẹp hơn.
siêu sáng

3
Rất đẹp. Đây là biến đổi Schwartzian trong js.
Mark Grimes

@torazaburo - Nó không hiệu quả như Fischer Yates, nhưng nó đẹp hơn và mã nhỏ hơn. Mã luôn là một sự đánh đổi. Nếu tôi có một mảng lớn, tôi sẽ sử dụng Knuth. Nếu tôi có một vài trăm mặt hàng, tôi sẽ làm điều này.
siêu sáng

1
@BenCarp - Đồng ý, Đây không phải là giải pháp nhanh nhất và bạn sẽ không muốn sử dụng nó trên một mảng lớn, nhưng có nhiều cân nhắc về mã hơn tốc độ thô.
siêu sáng

64

Sử dụng thư viện underscore.js. Phương pháp _.shuffle()này là tốt đẹp cho trường hợp này. Dưới đây là một ví dụ với phương thức:

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
Câu trả lời chính xác! Cảm ơn. Tôi thích nó cho các câu trả lời khác, vì nó khuyến khích mọi người sử dụng các thư viện hơn là sao chép và dán các chức năng có lỗi ở mọi nơi.
frabcus

60
@frabcus: Không có điểm nào trong việc bao gồm toàn bộ thư viện chỉ để có được một shufflechức năng.
Máy xay sinh tố

11
Tôi không đồng ý với @Blender. Có nhiều lý do để bao gồm toàn bộ thư viện chỉ để có được một chức năng bạn cần. Một trong số đó là ít có nguy cơ xảy ra lỗi khi bạn tự viết. Nếu đó là một vấn đề hiệu suất, thì bạn không nên sử dụng nó. Nhưng chỉ vì nó có thể là một vấn đề hiệu suất không có nghĩa là nó sẽ xảy ra.
Daniel Kaplan

7
@tieTYT: Vậy tại sao bạn cần phần còn lại của thư viện? Shuffle Fisher-Yates là tầm thường để thực hiện. Bạn không cần một thư viện để chọn một phần tử ngẫu nhiên trong một mảng (tôi hy vọng), vì vậy không có lý do gì để sử dụng thư viện trừ khi bạn thực sự sẽ sử dụng nhiều hơn một chức năng từ nó.
Máy xay sinh tố

18
@Blender: Tôi đã đưa ra một lý do tại sao. 1) Tôi đảm bảo với bạn, bạn có thể đưa một lỗi vào bất kỳ mã nào bạn viết, bất kể nó tầm thường như thế nào. Tại sao phải mạo hiểm? 2) Không tối ưu hóa trước. 3) 99% thời gian khi bạn cần một thuật toán xáo trộn, ứng dụng của bạn không phải là về việc viết một thuật toán xáo trộn. Đó là về một cái gì đó cần một algo shuffle. Tận dụng công việc của người khác. Đừng nghĩ về chi tiết thực hiện trừ khi bạn phải làm.
Daniel Kaplan

50

MỚI!

Thuật toán xáo trộn Fisher-Yates ngắn hơn và có thể nhanh hơn

  1. nó sử dụng trong khi ---
  2. bitwise to floor (số có tới 10 chữ số thập phân (32 bit))
  3. loại bỏ các đóng cửa không cần thiết và những thứ khác

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
}

kích thước tập lệnh (với fy là tên hàm): 90byte

DEMO http://jsfiddle.net/vvpoma8w/

* nhanh hơn có lẽ trên tất cả các trình duyệt ngoại trừ chrome.

Nếu bạn có bất kỳ câu hỏi chỉ cần hỏi.

BIÊN TẬP

vâng nó nhanh hơn

HIỆU SUẤT: http://jsperf.com/fyshuffle

sử dụng các chức năng được bình chọn hàng đầu.

EDIT Có một phép tính vượt quá (không cần --c + 1) và không ai chú ý

ngắn hơn (4byte) & nhanh hơn (kiểm tra nó!).

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
}

Bộ nhớ đệm ở một nơi khác var rnd=Math.randomvà sau đó sử dụng rnd()cũng sẽ tăng hiệu suất một chút trên các mảng lớn.

http://jsfiddle.net/vvpoma8w/2/

Phiên bản có thể đọc được (sử dụng phiên bản gốc. Phiên bản này chậm hơn, vars là vô dụng, như các bao đóng & ";", bản thân mã cũng ngắn hơn ... có thể đọc phần này Cách 'rút gọn' mã Javascript , btw bạn không thể nén mã sau đây trong một công cụ khai thác javascript như ở trên.)

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
kiểm tra hiệu suất ... nhanh hơn gấp đôi trên hầu hết các trình duyệt ... nhưng cần nhiều người thử nghiệm jsperf hơn ...
cocco

10
js là một ngôn ngữ chấp nhận nhiều phím tắt và nhiều cách khác nhau để viết nó .. trong khi có nhiều chức năng có thể đọc chậm ở đây tôi chỉ muốn chỉ ra cách nó có thể được thực hiện theo cách hiệu quả hơn, cũng tiết kiệm một số byte ... bitwise và tốc ký thực sự được đánh giá thấp ở đây và web chứa đầy lỗi và mã chậm.
cocco

Không phải là một slam dunk perf tăng. Hoán đổi fyshuffle prototype, tôi nhận được fymột cách nhất quán ở dưới cùng trong Chrome 37 trên OS X 10.9.5 (chậm hơn 81% so với ~ 100k so với ~ 100k) và Safari 7.1 chậm hơn tới 8%. YMMV, nhưng không phải lúc nào cũng nhanh hơn. jsperf.com/fyshuffle/3
Spig

kiểm tra lại số liệu thống kê ... tôi đã viết chrome là beacuse chậm hơn, họ đã tối ưu hóa Toán học, trên tất cả các sàn bitwise khác và trong khi nhanh hơn. kiểm tra IE, firefox nhưng cũng có thiết bị di động. Cũng thật tuyệt khi xem opera ...
cocco

1
Đây là một câu trả lời khủng khiếp. SO không phải là một cuộc cạnh tranh tối nghĩa.
Cún con

39

Chỉnh sửa: Câu trả lời này không chính xác

Xem bình luận và https://stackoverflow.com/a/18650169/28234 . Nó đang được để lại ở đây để tham khảo vì ý tưởng không phải là hiếm.


Một cách rất đơn giản cho các mảng nhỏ chỉ đơn giản là thế này:

const someArray = [1, 2, 3, 4, 5];

someArray.sort(() => Math.random() - 0.5);

Nó có thể không hiệu quả lắm, nhưng đối với các mảng nhỏ thì nó hoạt động tốt. Đây là một ví dụ để bạn có thể thấy nó ngẫu nhiên (hoặc không) và liệu nó có phù hợp với usecase của bạn hay không.

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>


Đẹp một, nhưng có tạo ra một yếu tố ngẫu nhiên hoàn toàn mỗi lần?
DDD

Không hoàn toàn chắc chắn nếu tôi hiểu bạn chính xác. Cách tiếp cận này thực sự sẽ xáo trộn mảng theo cách ngẫu nhiên (mặc dù giả ngẫu nhiên) mỗi khi bạn gọi mảng sắp xếp - đó không phải là một loại ổn định, vì những lý do rõ ràng.
Kris Selbekk

4
Vì những lý do tương tự như được giải thích tại stackoverflow.com/a/18650169/28234 . Điều này có nhiều khả năng để lại các phần tử đầu gần đầu mảng.
AlexC

7
Đây là một công cụ đơn giản, tuyệt vời khi bạn cần xáo trộn một mảng, nhưng đừng quan tâm quá nhiều đến việc có kết quả ngẫu nhiên về mặt học thuật. Đôi khi, vài inch cuối cùng để hoàn thiện mất nhiều thời gian hơn giá trị của nó.
Daniel Griscom

1
Nó sẽ là đáng yêu nếu điều này làm việc, nhưng nó không. Do cách thức tìm kiếm nhanh hoạt động, một bộ so sánh không nhất quán sẽ có khả năng để các phần tử mảng gần với vị trí ban đầu của chúng. Mảng của bạn sẽ không bị xáo trộn.
siêu sáng

39

Đáng tin cậy, hiệu quả, ngắn

Một số giải pháp trên trang này không đáng tin cậy (chúng chỉ ngẫu nhiên một phần mảng). Các giải pháp khác là ít hiệu quả hơn đáng kể. Với testShuffleArrayFun(xem bên dưới), chúng tôi có thể kiểm tra các chức năng xáo trộn mảng về độ tin cậy và hiệu suất. Các giải pháp sau đây là: đáng tin cậy, hiệu quả và ngắn gọn (sử dụng cú pháp ES6)

[Các thử nghiệm so sánh đã được thực hiện bằng cách sử dụng testShuffleArrayFunso với các giải pháp khác, trong Google Chrome]

Shuffle Array tại chỗ

    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 Tinh khiết, lặp đi lặp lại

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

Kiểm tra độ tin cậy và hiệu suất

Như bạn có thể thấy trong trang này, đã có những giải pháp không chính xác được cung cấp ở đây trong quá khứ. Tôi đã viết và đã sử dụng hàm sau để kiểm tra bất kỳ hàm ngẫu nhiên mảng thuần túy (không có tác dụng phụ) nào.

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

Giải pháp khác

Các giải pháp khác chỉ để cho vui.

ES6 thuần túy, đệ quy

    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 sử dụng mảng.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 sử dụng mảng.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]
        )
    }

Vậy, ES6 (ES2015) ở đâu? [array[i], array[rand]]=[array[rand], array[i]]? Có lẽ bạn có thể phác thảo cách làm việc đó. Tại sao bạn chọn lặp đi lặp lại?
sheriffderek

@sheriffderek Có, tính năng ES6 tôi đang sử dụng là gán hai vars cùng một lúc, cho phép chúng ta trao đổi hai vars trong một dòng mã.
Ben Carp

Tín dụng cho @sheriffderek, người đã đề xuất Thuật toán tăng dần. Thuật toán tăng dần có thể được chứng minh trong quy nạp.
Ben Carp

23

Thêm vào câu trả lời @Laurens Holsts. Đây là 50% nén.

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
Chúng ta nên khuyến khích mọi người sử dụng _.shuffle thay vì dán mã từ tràn ngăn xếp; và, chúng ta nên khuyến khích mọi người nén các câu trả lời tràn ngăn xếp của họ. Đó là những gì jsmin dành cho.
David Jones

45
@DavidJones: Tại sao tôi sẽ bao gồm toàn bộ thư viện 4kb chỉ để xáo trộn một mảng?
Máy xay sinh tố

1
Gọi tên @KingKongFrog cũng không dẫn đến một tập hợp của một cộng đồng hợp lý.
Wheaties

2
Có hiệu quả khi thực hiện var b = trong một vòng lặp thay vì khai báo b bên ngoài và gán nó b = trong một vòng lặp không?
Alex K

2
@Brian Sẽ không tạo ra sự khác biệt; việc nâng hàng xảy ra khi mã nguồn được phân tích cú pháp. Không có liên quan.
dùng2864740

23

Chỉnh sửa: Câu trả lời này không chính xác

Xem https://stackoverflow.com/a/18650169/28234 . Nó đang được để lại ở đây để tham khảo vì ý tưởng không phải là hiếm.

//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 là một số ngẫu nhiên có thể dương hoặc âm, vì vậy hàm sắp xếp sắp xếp lại các phần tử một cách ngẫu nhiên.


17

Với ES2015 bạn có thể sử dụng cái này:

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

Sử dụng:

[1, 2, 3, 4, 5, 6, 7].shuffle();

4
Để cắt ngắn, bạn nên sử dụng n >>> 0thay vì ~~n. Chỉ số mảng có thể cao hơn 2³¹-1.
Oriol

1
Việc phá hủy như thế này giúp cho việc thực hiện sạch như vậy +1
lukejacksonn

14

Tôi tìm thấy biến thể này treo trong câu trả lời "bị xóa bởi tác giả" trên một bản sao của câu hỏi này. Không giống như một số câu trả lời khác đã có nhiều upvote, đây là:

  1. Thực tế ngẫu nhiên
  2. Không tại chỗ (do đó shuffledtên chứ không phải shuffle)
  3. Chưa có mặt ở đây với nhiều biến thể

Đây là một jsfiddle cho thấy nó được sử dụng .

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


1
Vâng, nhưng cho rằng câu trả lời sai nổi tiếng vẫn còn với một loạt phiếu bầu, một giải pháp không hiệu quả nhưng đúng đắn ít nhất nên được đề cập.
Daniel Martin

[1,2,3,4,5,6].sort(function() { return .5 - Math.random(); });- nó không đưa ra một loại ngẫu nhiên, và nếu bạn sử dụng nó, bạn có thể sẽ bối rối: robweir.com/blog/2010/02/microsoft-random-browser-ballot.html
Daniel Martin

3
Bạn cần sử dụng .sort(function(a,b){ return a[0] - b[0]; }) nếu bạn muốn sắp xếp để so sánh các giá trị bằng số. Bộ .sort()so sánh mặc định là từ vựng, có nghĩa là nó sẽ được coi 10là ít hơn 21ít hơn 2.
4 lâu đài

@ 4castle Được rồi, tôi đã cập nhật mã, nhưng tôi sẽ hoàn nguyên nó: sự khác biệt giữa thứ tự từ vựng và thứ tự số không quan trọng đối với các số trong phạm vi Math.random()sản xuất. (nghĩa là, thứ tự từ điển giống như thứ tự số khi xử lý các số từ 0 (đã bao gồm) đến 1 (độc quyền))
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;
};

Điều này rõ ràng là không tối ưu như thuật toán Fisher-Yates, nhưng nó có hoạt động cho các cuộc phỏng vấn kỹ thuật không?
davidatthepark

@Andrea Mã bị hỏng do thực tế là độ dài mảng được thay đổi bên trong vòng lặp for. Với chỉnh sửa cuối cùng, điều này được sửa chữa.
Charlie Wallace

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

1
Tại sao trừ 0,5? Con số đó có ý nghĩa gì?
Sartheris Stormhammer

1
@SartherisStormhammer bởi vì chúng tôi đang sử dụng một hàm so sánh cho loại và nếu điều đó trả về một số lớn hơn 0, các phần tử được so sánh sẽ chỉ được sắp xếp theo hướng. -0,5 trên Math.random () sẽ cho chúng ta số âm ~ 50% thời gian, điều này cho chúng ta thứ tự ngược lại.
Sam Doidge

Giải pháp thẳng về phía trước và đơn giản nhất. Cảm ơn
deanwilliammills

9

Bạn có thể làm điều đó dễ dàng với:

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

Vui lòng tham khảo tại Mảng sắp xếp JavaScript


Thuật toán này từ lâu đã được chứng minh là khiếm khuyết.

Hãy chứng minh cho tôi. Tôi dựa trên w3schools
Tử Ngô Quang

4
Bạn có thể đọc chủ đề tại css-tricks.com/snippets/javascript/shuffle-array hoặc tại news.ycombinator.com/item?id=2728914 . W3schools luôn luôn là một nguồn thông tin khủng khiếp.

Để có một cuộc thảo luận tốt về lý do tại sao đây không phải là một cách tiếp cận tốt, hãy xem stackoverflow.com/questions/962802/iêu
Charlie Wallace

8

Một giải pháp đệ quy:

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

8

Fisher-Yates xáo trộn trong javascript. Tôi đang đăng bài này ở đây vì việc sử dụng hai hàm tiện ích (hoán đổi và randInt) làm rõ thuật toán so với các câu trả lời khác ở đây.

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

Trước hết, hãy xem ở đây để so sánh trực quan tuyệt vời về các phương pháp sắp xếp khác nhau trong javascript.

Thứ hai, nếu bạn xem nhanh liên kết ở trên, bạn sẽ thấy rằng random orderloại dường như hoạt động tương đối tốt so với các phương pháp khác, trong khi cực kỳ dễ dàng và nhanh chóng để thực hiện như dưới đây:

function shuffle(array) {
  var random = array.map(Math.random);
  array.sort(function(a, b) {
    return random[array.indexOf(a)] - random[array.indexOf(b)];
  });
}

Chỉnh sửa : như được chỉ ra bởi @gregers, hàm so sánh được gọi với các giá trị thay vì chỉ mục, đó là lý do tại sao bạn cần sử dụng indexOf. Lưu ý rằng thay đổi này làm cho mã ít phù hợp hơn với các mảng lớn hơn khi indexOfchạy trong thời gian O (n).


Array.prototype.sortvượt qua trong hai giá trịabkhông phải là chỉ số. Vì vậy, mã này không hoạt động.
Gregers

@gregers bạn đúng, tôi đã chỉnh sửa câu trả lời. Cảm ơn.
Milo Wielondek

1
Điều này không phải là rất ngẫu nhiên. Tùy thuộc vào việc thực hiện sắp xếp, một phần tử ở chỉ số mảng thấp nhất có thể yêu cầu nhiều so sánh hơn để có được chỉ số cao nhất so với phần tử bên cạnh chỉ mục cao nhất. Điều này có nghĩa là ít có khả năng phần tử ở chỉ số thấp nhất có được chỉ số cao nhất.
1 'HOẶC 1 -

7

một hàm xáo trộn không thay đổi mảng nguồn

Cập nhật : Ở đây tôi đề xuất một cách tương đối đơn giản (không phải từ sự phức tạp thuật toán góc ) và thuật toán ngắn sẽ hoạt động tốt với các mảng có kích thước nhỏ, nhưng chắc chắn sẽ tốn kém hơn nhiều so với thuật toán Durstenfeld cổ điển khi bạn xử lý các mảng lớn. Bạn có thể tìm thấy Durstenfeld trong một trong những câu trả lời hàng đầu cho câu hỏi này.

Câu trả lời gốc:

Nếu bạn không muốn hàm xáo trộn của mình biến đổi mảng nguồn , bạn có thể sao chép nó sang một biến cục bộ, sau đó thực hiện phần còn lại với logic xáo trộn đơn giản .

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

Xáo trộn logic : chọn một chỉ mục ngẫu nhiên, sau đó thêm phần tử tương ứng vào mảng kết quả và xóa nó khỏi bản sao mảng nguồn . Lặp lại hành động này cho đến khi mảng nguồn trống .

Và nếu bạn thực sự muốn nó ngắn, đây là khoảng cách tôi có thể nhận được:

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

Đây thực chất là thuật toán Fisher-Yates ban đầu, với việc bạn splicelà một cách không hiệu quả khủng khiếp để làm điều mà họ gọi là "nổi bật". Nếu bạn không muốn thay đổi mảng ban đầu, thì chỉ cần sao chép nó, sau đó xáo trộn bản sao đó tại chỗ bằng cách sử dụng biến thể Durstenfeld hiệu quả hơn nhiều.

@torazaburo, cảm ơn bạn đã phản hồi. Tôi đã cập nhật câu trả lời của mình, để làm rõ rằng tôi muốn đưa ra một giải pháp đẹp mắt hơn là một giải pháp siêu quy mô
Evgenia Manolova 20/07/18

Chúng tôi cũng có thể sử dụng splicephương pháp để tạo một bản sao như vậy : source = array.slice();.
Taiga

6

Đây là cái DỄ DÀNG NHẤT ,

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

ví dụ thêm, bạn có thể kiểm tra nó ở đây


5

một triển khai khác của Fisher-Yates, sử dụng chế độ nghiêm ngặt:

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

Giá trị nào của việc sử dụng nghiêm ngặt cung cấp qua câu trả lời được chấp nhận?
shortthingsushi

Để tìm hiểu thêm về chế độ nghiêm ngặt và cách nó ảnh hưởng đến hiệu suất, bạn có thể đọc về nó ở đây: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/
Raphael C

Hmm, bạn có thể chỉ ra một cái gì đó cụ thể từ tài liệu được tham chiếu? Dường như không có gì liên quan đến "cải thiện hiệu suất", ngoài một nhận xét mơ hồ ở đầu về khả năng gây khó khăn cho công cụ js để tối ưu hóa. Trong trường hợp này, tôi không rõ việc sử dụng nghiêm ngặt sẽ cải thiện điều gì.
shortthingsushi

Chế độ nghiêm ngặt đã xuất hiện từ khá lâu và có đủ số lượng người đọc để mọi người tự đưa ra ý kiến ​​của mình nếu họ luôn luôn nên sử dụng nó hay không và tại sao. Ví dụ, Jslint làm cho nó đủ rõ ràng rằng bạn phải luôn sử dụng chế độ nghiêm ngặt. Douglas Crockford đã viết khá nhiều bài báo và một số video tuyệt vời về lý do tại sao điều quan trọng là luôn luôn sử dụng chế độ nghiêm ngặt không chỉ là một cách thực hành tốt mà còn là cách nó được diễn giải khác nhau bởi các công cụ js của trình duyệt như V8. Tôi thực sự khuyên bạn nên Google và đưa ra ý kiến ​​của riêng bạn về nó.
Raphael C

Đây là một chủ đề cũ về sự hoàn hảo trong chế độ nghiêm ngặt, hơi cũ nhưng vẫn có liên quan: stackoverflow.com/questions/3145966/ Lần
Raphael C

5

Tất cả các câu trả lời khác đều dựa trên Math.random () nhanh nhưng không phù hợp với ngẫu nhiên mức mã hóa.

Đoạn mã dưới đây đang sử dụng Fisher-Yatesthuật toán nổi tiếng trong khi sử dụng Web Cryptography APIcho mức độ ngẫu nhiên mã hóa .

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

Giải pháp nội tuyến ngắn hiện đại sử dụng các tính năng ES6:

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

(cho mục đích giáo dục)


4

Một sửa đổi đơn giản cho câu trả lời của CoolAJ86 không sửa đổi mảng ban đầu:

 /**
 * 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

Mặc dù có một số cách triển khai đã được khuyên dùng nhưng tôi cảm thấy chúng ta có thể làm cho nó ngắn hơn và dễ dàng hơn bằng cách sử dụng vòng lặp forEach, vì vậy chúng ta không cần lo lắng về việc tính toán độ dài mảng và chúng ta cũng có thể tránh sử dụng biến tạm thời một cách an toàn.

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

Chỉ cần có một ngón tay trong chiếc bánh. Ở đây tôi trình bày một triển khai đệ quy của Fisher Yates shuffle (tôi nghĩ). Nó cho sự ngẫu nhiên thống nhất.

Lưu ý: ~~(toán tử dấu ngã kép) trên thực tế hoạt động giống như Math.floor()đối với các số thực dương. Chỉ cần một đoạn ngắn là nó.

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

Chỉnh sửa: Mã ở trên là O (n ^ 2) do việc làm của .splice()nhưng chúng ta có thể loại bỏ mối nối và xáo trộn trong O (n) bằng thủ thuật hoán đổi.

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");

Vấn đề là, JS không thể hợp tác với các cuộc thu hồi lớn. Trong trường hợp cụ thể này, kích thước mảng của bạn bị giới hạn với khoảng 3000 ~ 7000 tùy thuộc vào công cụ trình duyệt của bạn và một số sự kiện chưa biết.


3

Ngẫu nhiên mảng

 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; 

Không nên có một cái -1n như bạn đã sử dụng <không<=
Mohebifar

3

arrayShufflechức năng ngắn nhất

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

Rõ ràng bạn đang làm Sattolo thay vì Fisher-Yates (Knuth, không thiên vị).
Arthur2e5

3

Theo quan điểm lý thuyết, cách thức thanh lịch nhất, theo ý kiến ​​khiêm tốn của tôi, là lấy một số ngẫu nhiên duy nhất trong khoảng từ 0 đến n! -1 và tính toán một ánh xạ từ một {0, 1, …, n!-1}đến tất cả các hoán vị của(0, 1, 2, …, n-1) . Miễn là bạn có thể sử dụng một trình tạo ngẫu nhiên (giả) đủ tin cậy để có được một số như vậy mà không có bất kỳ sai lệch đáng kể nào, bạn có đủ thông tin trong đó để đạt được những gì bạn muốn mà không cần một số số ngẫu nhiên khác.

Khi tính toán với các số trôi nổi chính xác kép của IEEE754, bạn có thể mong đợi trình tạo ngẫu nhiên của mình cung cấp khoảng 15 số thập phân. Vì bạn có 15! = 1.307,674,368,000 (có 13 chữ số), bạn có thể sử dụng các hàm sau với mảng chứa tối đa 15 phần tử và giả sử sẽ không có sai lệch đáng kể với mảng chứa tối đa 14 phần tử. Nếu bạn làm việc với một vấn đề kích thước cố định cần phải tính toán nhiều lần thao tác xáo trộn này, bạn có thể muốn thử đoạn mã sau thể nhanh hơn các mã khác vì nó Math.randomchỉ sử dụng một lần (tuy nhiên nó liên quan đến một số thao tác sao chép).

Hàm sau sẽ không được sử dụng, nhưng dù sao thì tôi cũng cung cấp cho nó; nó trả về chỉ số của một hoán vị nhất định (0, 1, 2, …, n-1)theo ánh xạ từ một đến một được sử dụng trong thông điệp này (một cách tự nhiên nhất khi liệt kê các hoán vị); nó được dự định để làm việc với tối đa 16 yếu tố:

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

Đối ứng của hàm trước (cần thiết cho câu hỏi của riêng bạn) bên dưới; nó được dự định để làm việc với tối đa 16 yếu tố; nó trả về hoán vị của thứ tự n của (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;
}

Bây giờ, những gì bạn muốn chỉ là:

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

Nó nên hoạt động cho tối đa 16 yếu tố với một chút sai lệch về lý thuyết (mặc dù không được chú ý từ quan điểm thực tế); nó có thể được xem là hoàn toàn có thể sử dụng cho 15 yếu tố; với các mảng chứa ít hơn 14 phần tử, bạn có thể cân nhắc một cách an toàn sẽ không có sai lệch.


Chắc chắn là thanh lịch!
Gershom
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.