JavaScript 배열을 무작위 화하는 방법은 무엇입니까?


1264

다음과 같은 배열이 있습니다.

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

랜덤 화 / 셔플하는 방법은 무엇입니까?



6
Mike Bostock이 만든이 비주얼 라이저에서 셔플 기능이 실제로 얼마나 랜덤한지 시각화 할 수 있도록 여기에 던졌습니다. bost.ocks.org/mike/shuffle/compare.html
aug

5
@Blazemonger jsPref가 죽었습니다. 가장 빠른 것을 여기에 게시 할 수 있습니까?
eozzy

원 라이너는 어떻습니까? 반환 된 배열이 뒤섞입니다. arr1.reduce ((a, v) => a.splice (Math.floor (Math.random () * a.length), 0, v) && a, [])
brunettdan

환원 솔루션에는 O (n ^ 2) 복잡성이 있습니다. 백만 개의 요소가있는 배열에서 실행하십시오.
riv

답변:


1540

사실상의 비 편향 셔플 알고리즘은 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)...경우 때문에 불필요 동안의 루프가 입력되지 않습니다. 를 사용하여 더 빠르게 전화를 걸 수 있습니다 . 어느 템포 또는 tempj는 제거 될 수 있고, 값은 직접 할당 될 myArray의 [I] 또는 J 적절. i == 0Math.floor...| 0
RobG

23
@prometheus, 모든 RNG는 값 비싼 하드웨어에 연결되어 있지 않으면 의사 랜덤입니다.
Phil H

38
@RobG 위의 구현은 기능적으로 정확합니다. Fisher-Yates 알고리즘에서 루프는 배열의 첫 번째 요소에 대해 실행되지 않습니다. 첫 번째 요소를 건너 뛰는 다른 구현이있는 위키 백과를 확인하십시오 . 또한 첫 번째 요소에 대해 루프를 실행하지 않는 것이 중요한 이유에 대해 설명하는 기사를 확인하십시오 .
theon

34
@nikola "아무것도 무작위가 아닙니다"는 나에게 약간의 자질입니다. 나는 당신이 암호 작성자가 아닌 한, 그것은 무작위로 충분하다고 주장합니다.
toon81

20
야, 요다 ( 0 !== currentIndex).
ffxsam

744

Fisher-Yates의 최적화 된 버전 인 Durstenfeld shuffle 의 JavaScript 구현은 다음과 같습니다 .

/* 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 shuffle 입니다. 진정한 원래 피셔 - 예이츠 알고리즘의 실행 N ^ 2 시간,하지 N 시간
Pacerier

7
return arrayJavaScript는 함수 인수로 사용될 때 참조로 배열을 전달하므로 필요하지 않습니다 . 이것이 스택 공간을 절약하는 것이라고 가정하지만 흥미로운 작은 기능입니다. 어레이에서 셔플을 수행하면 원래 어레이가 셔플됩니다.
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
나는이 솔루션을 좋아한다. 기본 랜덤을 제공하기에 충분하다
Alex K

147
이처럼 다운 보팅은 실제로 그다지 무작위가 아닙니다. 나는 왜 그렇게 많은 공여가 있는지 모르겠다. 이 방법을 사용하지 마십시오. 예쁘게 보이지만 완전히 정확하지는 않습니다. 다음은 배열의 각 숫자가 색인에 도달 한 횟수에 대해 10,000 회 반복 한 결과입니다 [0] (다른 결과도 제공 할 수 있음). 1 = 29.19 %, 2 = 29.53 %, 3 = 20.06 %, 4 = 11.91 %, 5 = 5.99 %, 6 = 3.32 %
radtad

8
상대적으로 작은 배열을 무작위 화하고 암호화 사물을 다루지 않으면 괜찮습니다. 나는 무작위성 이 더 필요하면 더 복잡한 솔루션을 사용해야 한다는 것에 전적으로 동의합니다 .
deadrunk


12
문제는 결정적이지 않아 잘못된 결과를 제공한다는 것입니다 (1> 2 및 2> 3 인 경우 1> 3으로 지정해야하지만 이것이 보장되지는 않습니다. 정렬을 혼동하고 결과를 주석으로 표시합니다. @radtad에 의해).
MatsLindh

73

Array의 protoype로 사용할 수 있거나 사용해야합니다.

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는 다른 사람의 구현 .. 짓밟고 가능하게 제외시켰다 없습니다
user2864740

2
Array 프로토 타입에서 사용되는 경우 shuffle 이외의 이름을 지정해야합니다 .
Wolf

57
한 수 (또는해야한다)을 피하기 기본 프로토 타입 확장 : javascriptweblog.wordpress.com/2011/12/05/...
Wédney 유리

12
당신은 이것을해서는 안됩니다; 이것에 영향을받는 모든 단일 배열은 더 이상 for ... in을 사용하여 안전하게 반복 할 수 없습니다. 기본 프로토 타입을 확장하지 마십시오.

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. 원본 객체를 얻기 위해 매핑을 해제합니다

다형성 배열을 섞을 수 있으며 정렬은 Math.random만큼 임의적이며 대부분의 목적에 충분합니다.

요소는 각 반복을 재생성하지 않는 일관된 키에 대해 정렬되고 각 비교는 동일한 분포에서 가져 오기 때문에 Math.random 분포의 비 랜덤 성은 취소됩니다.

속도

시간 복잡도는 빠른 정렬과 마찬가지로 O (N log N)입니다. 공간 복잡도는 O (N)입니다. 이것은 Fischer Yates shuffle만큼 효율적이지 않지만 제 의견으로는 코드가 상당히 짧고 기능적입니다. 배열이 큰 경우 Fischer Yates를 사용해야합니다. 수백 개의 항목이있는 작은 배열이있는 경우이 작업을 수행 할 수 있습니다.


1
@superluminary 죄송합니다. 알 이 대답은 이미 동일한 방법을 사용했다.
Bergi

@ Bergi-아, 그렇습니다. 제 구현이 약간 더 아름답다고 생각합니다.
superluminary

3
아주 좋아요 이것은 js 의 Schwartzian 변환 입니다.
Mark Grimes

@torazaburo-Fischer Yates만큼 성능이 좋지 않지만 더 예쁘고 코드가 작습니다. 코드는 항상 절충안입니다. 배열이 큰 경우 Knuth를 사용합니다. 내가 몇 백 개의 아이템을 가지고 있다면, 나는 이것을 할 것이다.
superluminary

1
@ BenCarp-Agreed, 가장 빠른 솔루션은 아니며 대규모 배열에서 사용하고 싶지 않지만 원시 속도보다 코드에서 더 많은 고려 사항이 있습니다.
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함수 를 얻기 위해 전체 라이브러리를 포함시킬 필요는 없습니다 .
Blender

11
@Blender에 동의하지 않습니다. 필요한 기능을 얻기 위해 전체 라이브러리를 포함시켜야하는 많은 이유가 있습니다. 그중 하나는 직접 작성할 때 버그의 위험이 적다는 것입니다. 경우 는 성능 문제입니다, 당신은 그것을 사용하지 말아야합니다. 그러나 그것이 성능 문제 일 수 있다고 해서 반드시 그런 것은 아닙니다.
Daniel Kaplan

7
@tieTYT : 왜 나머지 도서관이 필요한가요? Fisher-Yates 셔플은 구현하기가 쉽지 않습니다. 배열에서 임의의 요소를 선택하기 위해 라이브러리가 필요하지 않으므로 (실제로) 실제로 둘 이상의 함수를 사용하지 않는 한 라이브러리를 사용할 이유가 없습니다.
Blender

18
@ 블렌더 : 나는 이유를 주었다. 1) 나는 당신이 작성한 코드에 아무리 사소한 버그라도 버그를 도입 할 수 있다고 확신합니다. 왜 위험합니까? 2) 사전 최적화하지 마십시오. 3) 셔플 알고가 필요한 시간의 99 %, 앱은 셔플 알고를 작성하는 것이 아닙니다. 셔플 알고리즘 이 필요한 것입니다 . 다른 사람의 일을 활용하십시오. 필요한 경우가 아니라면 구현 세부 사항에 대해 생각하지 마십시오.
다니엘 카플란

50

새로운!

더 짧고 아마도 더 빠른 Fisher-Yates shuffle 알고리즘

  1. 그것은 동안 사용합니다 ---
  2. 비트 단위부터 바닥까지 (십진수 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) : 90 바이트

데모 http://jsfiddle.net/vvpoma8w/

* 크롬을 제외한 모든 브라우저에서 더 빠를 것입니다.

궁금한 점이 있으면 물어보십시오.

편집하다

네 빠릅니다

성능 : http://jsperf.com/fyshuffle

최고 투표 기능 사용.

편집 초과 계산이 있었으며 (-c + 1 필요 없음) 아무도 눈치 채지 못했습니다.

더 짧고 (4 바이트) 더 빠릅니다 (테스트 해보세요!).

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/

읽기 가능 버전 (원래 버전을 사용이 느린, 바르가 폐쇄 및처럼 쓸모. ";", 코드 자체도 짧은 ... 어쩌면이 글을 읽을 방법 '작게하다'자바 스크립트 코드 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
성능을 확인하십시오 ... 대부분의 브라우저에서 2 배 빠르지 만 ... 더 많은 jsperf 테스터가 필요합니다 ...
cocco

10
js는 많은 단축키와 그것을 작성하는 다른 방법을 받아들이는 언어입니다 .. 여기에는 느리게 읽을 수있는 많은 기능이 있지만 더 성능이 좋은 방법으로 수행 할 수있는 방법을 보여주고 싶습니다. 속기는 실제로 여기에서 과소 평가되었고 웹은 버그가 많고 느린 코드로 가득합니다.
cocco

슬램 덩크 성능이 증가하지 않습니다. 스와핑 fyshuffle prototype내가 얻을 fy는 OS X 10.9.5에 크롬 (37)의 바닥 (81 % 느리게 ~ 20K 작전이 ~ 100,000에 비해) 및 Safari 7.1에서 일관되게 8 % 느린 ~까지입니다. YMMV이지만 항상 빠르지는 않습니다. jsperf.com/fyshuffle/3
Spig

통계를 다시 확인하십시오 ... 크롬은 수학을 최적화 했으므로 비트 비트 플로어에서 더 느리기 때문에 이미 느립니다. ... IE, 파이어 폭스를 확인뿐만 아니라 모바일 devices.Would 오페라를 볼 좋은 또한 수
코코

1
이것은 끔찍한 대답입니다. SO는 모호한 경쟁이 아닙니다.
강아지

39

편집 :이 답변이 잘못되었습니다

의견 및 https://stackoverflow.com/a/18650169/28234를 참조하십시오 . 아이디어가 드물지 않기 때문에 참조를 위해 여기에 남아 있습니다.


작은 배열을위한 매우 간단한 방법은 다음과 같습니다.

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

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

아마 효율적이지 않을 수도 있지만 작은 배열의 경우에는 잘 작동합니다. 다음은 그 예가 임의적이든 아니든, 사용 사례에 맞는지 여부를 확인할 수 있습니다.

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 18

1
이것이 효과가 있으면 사랑 스럽겠지만 그렇지 않습니다. 빠른 검색이 작동하는 방식으로 인해 일관되지 않은 비교기는 배열 요소를 원래 위치에 가깝게 남겨 둘 수 있습니다. 배열이 스크램블되지 않습니다.
superluminary

39

안정적이고 효율적이며 짧은

이 페이지의 일부 솔루션은 신뢰할 수 없습니다 (어레이의 일부만 무작위 화). 다른 솔루션은 효율성이 크게 떨어집니다. 와testShuffleArrayFun 우리 안정성 및 성능 어레이 셔플 기능을 테스트 할 수있다 (아래 참조). 신뢰할 수 있고 효율적이며 짧은 솔루션은 다음과 같습니다 (ES6 구문 사용).

[ testShuffleArrayFunGoogle 크롬에서 다른 솔루션을 사용하여 비교 테스트를 수행함]

배열 셔플

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

array.map을 사용하는 ES6 Pure

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

array.reduce를 사용하는 ES6 Pure

    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 예, 내가 사용하는 ES6 기능은 한 번에 두 가지 변수를 할당하는 것이므로 두 가지 변수를 한 줄의 코드로 바꿀 수 있습니다.
벤 잉어

오름차순 알고리즘을 제안한 @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
사람들이 스택 오버플로에서 코드를 붙여 넣는 대신 _.shuffle을 사용하도록 권장해야합니다. 또한 사람들이 스택 오버플로 답변을 압축하지 못하게해야합니다. 그것이 jsmin을위한 것입니다.
David Jones

45
@DavidJones : 왜 배열을 섞기 위해 전체 4kb 라이브러리를 포함시켜야합니까?
Blender

1
@KingKongFrog 이름 부름은 또한 합리적인 공동체의 조립에 전도성이 없습니다.
wheaties

2
var b = b 외부 루프를 선언 b = 하고 루프에서 할당하는 대신 루프에서 수행하는 것이 효율적 입니까?
Alex K

2
@ 브라이언 차이를 만들지 않습니다 . 호이 스팅은 소스 코드가 구문 분석 될 때 발생합니다. 아마 관련이 없습니다.
user2864740

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

이 질문의 복제본에 대한 "저자에 의해 삭제됨"답변에서이 변형이 발견되었습니다. 많은 공표가 이미있는 다른 답변들과 달리 다음과 같습니다.

  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 알고리즘만큼 최적은 아니지만 기술 인터뷰에는 적합합니까?
davidatthepark

@Andrea for 루프 내에서 배열 길이가 변경되어 코드가 손상되었습니다. 마지막 편집으로이 문제가 해결되었습니다.
Charlie Wallace

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

1
왜 마이너스 0.5입니까? 그 숫자는 무엇을 의미합니까?
Sartheris Stormhammer

1
@SartherisStormhammer는 sortFunction을 정렬에 사용하고 있기 때문에 0보다 큰 숫자를 반환하면 비교되는 요소는 방향으로 만 정렬됩니다. Math.random ()에서 -0.5는 ~~ 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ính Ngô Quang를

4
당신은 스레드를 읽을 수 css-tricks.com/snippets/javascript/shuffle-array , 또는에서 news.ycombinator.com/item?id=2728914 . W3schools는 항상 끔찍한 정보원이었습니다.

이것이 좋은 접근 방식이 아닌 이유에 대한 자세한 설명은 stackoverflow.com/questions/962802/…를
Charlie Wallace

8

재귀 솔루션 :

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 는 자바 스크립트로 섞습니다. 두 개의 유틸리티 함수 (swap 및 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합니다. 이 변경으로 indexOfO (n) 시간에 실행되는 큰 배열에는 코드가 적합하지 않습니다 .


Array.prototype.sort인덱스가 아닌 및 로 두 을 전달 합니다. 따라서이 코드는 작동하지 않습니다. ab
수집가

@gregers 당신 말이 맞아요, 나는 답변을 편집했습니다. 감사.
Milo Wielondek

1
이것은 매우 무작위가 아닙니다. 정렬 구현에 따라 가장 낮은 배열 인덱스에있는 요소는 가장 높은 인덱스 옆에있는 요소보다 가장 높은 인덱스에 도달하기 위해 더 많은 비교가 필요할 수 있습니다. 이는 가장 낮은 인덱스의 요소가 가장 높은 인덱스에 도달 할 가능성이 적음을 의미합니다.
1 'OR

7

소스 배열을 바꾸지 않는 셔플 기능

업데이트 : 여기 에서는 작은 크기의 배열에서 잘 작동 하는 비교적 간단한 ( 복잡성 관점이 아닌 ) 짧은 알고리즘을 제안 하지만 거대한 배열을 처리 할 때 고전적인 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"스트라이크 아웃 (striking out)"이라고하는 것을 수행하는 끔찍한 비효율적 인 방법입니다. 원래 배열을 변경하지 않으려면 복사 한 다음 훨씬 더 효율적인 Durstenfeld 변형을 사용하여 해당 복사본을 제자리에 섞으십시오.

@torazaburo, 귀하의 의견에 감사드립니다. 답변을 업데이트했습니다. 슈퍼 스케일링 솔루션보다 멋진 솔루션을 제공하고 있음을 분명히하기 위해
Evgenia Manolova

splice방법을 사용하여 다음과 같은 사본을 만들 수도 있습니다 source = array.slice();.
Taiga


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는 항상 엄격 모드를 사용해야한다는 것을 분명히합니다. Douglas Crockford는 항상 엄격한 모드를 좋은 방법으로 사용하는 것뿐만 아니라 V8과 같은 브라우저 js 엔진에서 다르게 해석하는 방법이 중요한 이유에 대해 많은 기사와 훌륭한 비디오를 작성했습니다. Google에 강력하게 조언하고 이에 대한 의견을 제시합니다.
Raphael C

다음은 엄격 모드의 perfs에 관한 오래된 스레드입니다. 조금 오래되었지만 여전히 관련이 있습니다. stackoverflow.com/questions/3145966/…
Raphael C

5

다른 모든 답변은 Math.random () 기반으로 빠르지 만 암호화 수준 무작위 화에는 적합하지 않습니다.

아래 코드는 암호화 수준의 무작위 화Fisher-YatesWeb 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

이미 많은 구현이 권장되었지만 forEach 루프를 사용하면 더 짧고 쉽게 만들 수 있다고 생각하므로 배열 길이 계산에 대해 걱정할 필요가 없으며 임시 변수 사용을 안전하게 피할 수 있습니다.

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가 큰 재귀를 처리 할 수 ​​없다는 것입니다. 이 특정 경우 배열 크기는 브라우저 엔진 및 일부 알 수없는 사실에 따라 3000 ~ 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;
}

분명히 당신은 Fisher-Yates (Knuth, unbiased) 대신 Sattolo 's 를 하고 있습니다.
Arthur2e5

3

이론적 인 관점에서, 가장 겸손한 견해로, 그것을 수행하는 가장 우아한 방법은 0n 사이 의 단일 난수 를 구하고 모든 순열 에서 일대일 매핑을 계산하는 것 입니다.{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.