나는 그의 JavaScript 코드로 누군가를 돕고 있었고 내 눈은 다음과 같은 섹션에 매료되었습니다.
function randOrd(){
return (Math.round(Math.random())-0.5);
}
coords.sort(randOrd);
alert(coords);
내 첫 번째는 : 이봐, 아마도 작동하지 않을 수 있습니다! 그러나 나는 몇 가지 실험을 해본 결과 적어도 무작위로 무작위로 결과를 제공하는 것으로 보입니다.
그런 다음 웹 검색을 수행하고 거의 맨 위에이 코드가 가장 정교하게 복사 된 기사 를 찾았습니다 . 꽤 존경받는 사이트와 저자처럼 보였습니다 …
그러나 내 직감은 이것이 잘못되었다는 것을 말해줍니다. 특히 정렬 알고리즘이 ECMA 표준에 의해 지정되지 않았기 때문에. 다른 정렬 알고리즘으로 인해 다른 균일하지 않은 셔플이 발생한다고 생각합니다. 일부 정렬 알고리즘은 아마도 무한 반복 될 수도 있습니다 …
하지만 어떻게 생각하세요?
그리고 또 다른 질문으로 … 이제 어떻게이 셔플 링 기법의 결과가 무작위인지 측정 할 수 있습니까?
업데이트 : 몇 가지 측정을 수행하고 아래 결과를 답변 중 하나로 게시했습니다.
답변
그것은 부분적으로 있기 때문에, 셔플의 내가 가장 좋아하는 방법은 본 적이 없네 입니다 당신이 말한대로 구현 고유의. 특히, Java 또는 .NET에서 정렬하는 표준 라이브러리 (어떤 것이 확실하지 않은지)는 종종 일부 요소 (예 : 먼저 청구 A < B
및 B < C
, 그러나 C < A
) 간의 비교가 일치하지 않는지를 감지 할 수 있음을 기억합니다 .
또한 실제로 필요한 것보다 더 복잡한 (실행 시간 측면에서) 셔플로 끝납니다.
컬렉션을 “셔플 링”(컬렉션 시작시 처음에는 비어 있음)과 “셔플되지 않은”(컬렉션 나머지)으로 효과적으로 분할하는 셔플 알고리즘을 선호합니다. 알고리즘의 각 단계에서 무작위 셔플되지 않은 요소 (첫 번째 요소 일 수 있음)를 선택하고 첫 번째 셔플되지 않은 요소와 교체 한 다음 셔플 된 것으로 처리합니다 (즉, 파티션을 포함하도록 정신적으로 이동).
이것은 O (n)이며 난수 생성기에 대한 n-1 호출 만 필요합니다. 또한 진정한 셔플을 생성합니다. 모든 요소는 원래 위치에 관계없이 1 / n의 확률로 종료됩니다 (적절한 RNG 가정). 정렬 된 버전은 근사 하지만 셔플 버전에 대한 이유를 쉽게 찾을 수 있습니다 (이 임의의 두 배를 돌려 않다면 난수 생성기는 매우 가능성이있는, 두 번 같은 값을 선택하지 않는 가정) 더 분포 🙂
이 접근법을 Fisher-Yates shuffle 이라고합니다 .
이 셔플을 한 번 코딩하고 항목을 셔플하는 데 필요한 모든 곳에서 재사용하는 것이 가장 좋습니다. 그러면 안정성이나 복잡성 측면에서 정렬 구현에 대해 걱정할 필요가 없습니다. 그것은 단지 몇 줄의 코드입니다 (JavaScript에서는 시도하지 않을 것입니다!)
셔플 링 (특히 셔플 알고리즘 섹션) 에 대한 Wikipedia 기사에서는 랜덤 프로젝션을 정렬하는 방법에 대해 설명합니다. 일반적으로 셔플 링의 잘못된 구현에 대한 섹션을 읽으면 가치가 있습니다.
답변
Jon이 이미 이론을 다루고 나면 구현은 다음과 같습니다.
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)
. 네이티브 sort()
함수 와 비교하여 JS 코드를 실행하는 오버 헤드에 따라 성능에 눈에 띄는 차이 가 발생하여 배열 크기가 커질 수 있습니다.
bobobobo의 답변에 대한 의견 에서 문제의 알고리즘이 (의 구현에 따라) 균등하게 분산 된 확률을 생성하지 않을 수 있다고 언급했습니다 sort()
.
나의 주장은 다음과 c
같은 내용 c = n(n-1)/2
을 따른다 : 정렬 알고리즘은 특정한 수 의 비교 가 필요하다 ( 예 : Bubblesort). 우리의 무작위 비교 함수는 각 비교의 결과를 똑같이 가능하게 만듭니다. 즉 2^c
똑같이 가능한 결과가 있습니다. 이제 각 결과는 n!
배열 항목 의 순열 중 하나와 일치해야 하므로 일반적인 경우 고른 분포가 불가능합니다. (필요한 실제 비교 수는 입력 배열에 따라 다르지만 어설 션은 여전히 유지되어야하므로 이는 단순화 된 것입니다.)
Jon이 지적한 것처럼 sort()
난수 생성기가 유한 수의 의사 난수 값을 n!
순열에 매핑하기 때문에 이것만으로 Fisher-Yates를 선호하는 이유는 없습니다 . 그러나 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!
, 따라서 불균일 한 분포를 초래 정렬 알고리즘은 여기서 균등 permutaions에 매핑하는 경우에도. 이것이 실질적인 영향을 미친다면 저를 넘어선 것입니다.
실제 문제는 정렬 알고리즘이 순열에 균등하게 매핑되는 것이 보장되지 않는다는 것입니다. Mergesort가 대칭 적 인 것처럼 보이지만 Bubblesort 또는 Quicksort 또는 Heapsort와 같은 것에 대한 추론은 그렇지 않습니다.
결론 : sort()
Mergesort 를 사용하는 한 코너 케이스 (적어도 코너 케이스 인 경우)를 제외하고는 합리적으로 안전 해야 합니다 2^c ≤ n!
.
답변
이 무작위 정렬 결과가 얼마나 무작위인지 측정했습니다.
내 기술은 작은 배열 [1,2,3,4]을 취하고 그것의 모든 (4! = 24) 순열을 만드는 것이었다. 그런 다음 셔플 링 함수를 배열에 여러 번 적용하고 각 순열이 몇 번 생성되는지 계산합니다. 좋은 셔플 링 알고리즘은 모든 순열에 대해 결과를 균등하게 분배하지만 나쁜 것은 균일 한 결과를 생성하지 않습니다.
아래 코드를 사용하여 Firefox, Opera, Chrome, IE6 / 7 / 8에서 테스트했습니다.
놀랍게도, 무작위 정렬과 실제 셔플은 모두 똑같이 균일 한 분포를 만들었습니다. 그래서 (많은 사람들이 제안했듯이) 주요 브라우저는 병합 정렬을 사용하고있는 것 같습니다. 물론 이것은 다른 브라우저를 사용할 수 없다는 것을 의미하지는 않지만,이 임의 정렬 방법이 실제로 사용하기에 충분히 신뢰할 수 있음을 의미합니다.
편집 : 이 테스트는 실제로 무작위 또는 그 부족을 올바르게 측정하지 못했습니다. 내가 게시 한 다른 답변을 참조하십시오.
그러나 성능 측면에서 크리스토프가 제공하는 셔플 기능은 확실한 승자였습니다. 작은 4 요소 어레이의 경우에도 실제 셔플은 임의 정렬보다 약 2 배 빠릅니다!
// Cristoph가 게시 한 셔플 기능. var shuffle = 함수 (배열) { var tmp, current, top = array.length; if (top) while (-top) { 전류 = Math.floor (Math.random () * (top + 1)); tmp = 배열 [현재]; 배열 [현재] = 배열 [위]; 배열 [상단] = tmp; } 배열 반환; }; // 랜덤 정렬 함수 var rnd = function () { 반환 Math.round (Math.random ())-0.5; }; var randSort = 함수 (A) { return A.sort (rnd); }; var 순열 = function (A) { if (A.length == 1) { 반환 [A]; } 다른 { var perms = []; (var i = 0; i <A.length; i ++) { var x = A.slice (i, i + 1); var xs = A.slice (0, i) .concat (A.slice (i + 1)); var subperms = 순열 (xs); for (var j = 0; j <subperms.length; j ++) { perms.push (x.concat (subperms [j])); } } 반환 파마; } }; var test = 함수 (A, 반복, 기능) { // 초기화 순열 var 통계 = {}; var perms = 순열 (A); for (var i in perms) { 통계 [ ""+ perms [i]] = 0; } // 여러 번 섞어 통계를 수집 var start = new Date (); for (var i = 0; i <iterations; i ++) { var shuffled = func (A); 통계 [ ""+ shuffled] ++; } var end = new Date (); // 결과 형식 var arr = []; for (통계의 var i) { arr.push (i + ""+ stats [i]); } return arr.join ( "\ n") + "\ n \ n 걸린 시간 :"+ ((end-start) / 1000) + "seconds."; }; alert ( "무작위 정렬 :"+ 테스트 ([1,2,3,4], 100000, randSort)); alert ( "셔플 :"+ 테스트 ([1,2,3,4], 100000, 셔플));
답변
흥미롭게도 Microsoft 는 pick-random-browser-page에서 동일한 기술 을 사용했습니다 .
그들은 약간 다른 비교 기능을 사용했습니다.
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"));
답변
내 웹 사이트에 간단한 테스트 페이지 를 배치 하여 다른 방법으로 셔플하는 다른 브라우저를 사용하여 현재 브라우저의 바이어스를 보여줍니다. 그것은 바이어스를 사용 Math.random()-0.5
하지 않는 또 다른 ‘임의’셔플과 위에서 언급 한 Fisher-Yates 방법을 사용하는 것의 끔찍한 편견을 보여줍니다 .
일부 브라우저에서는 ‘셔플’중에 특정 요소가 전혀 바뀌지 않을 확률이 50 % 나 높다는 것을 알 수 있습니다!
참고 : 코드를 다음과 같이 변경하면 Safari에서 @Christoph에 의해 Fisher-Yates 셔플을 약간 더 빠르게 구현할 수 있습니다.
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
답변
배포판에 대해 까다 롭지 않고 소스 코드를 작게 만들고 싶을 때 좋습니다.
JavaScript (소스가 지속적으로 전송되는 곳)에서 작 으면 대역폭 비용이 달라집니다.
답변
확실히 해킹입니다. 실제로 무한 루프 알고리즘은 불가능합니다. 객체를 정렬하는 경우 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를 제거하십시오)
그래도 해킹. 잘하고 싶다면 열심히해야합니다 🙂