[javascript] 자바 스크립트에서 빠르고 안정적인 정렬 알고리즘 구현
특정 키와 지정된 순서 (asc / desc)를 기준으로 약 200-300 개의 개체 배열을 정렬하려고합니다. 결과 순서는 일관성 있고 안정적이어야합니다.
사용하기에 가장 좋은 알고리즘은 무엇이며 자바 스크립트로 구현 된 예를 제공 할 수 있습니까?
감사!
답변
비 안정 정렬 기능에서 안정적인 정렬을 얻을 수 있습니다.
정렬하기 전에 모든 요소의 위치를 얻습니다. 정렬 조건에서 두 요소가 같으면 위치를 기준으로 정렬합니다.
타다! 당신은 안정적인 종류를 가지고 있습니다.
이 기술과 구현 방법에 대해 더 알고 싶다면 내 블로그에 기사를 작성했습니다. http://blog.vjeux.com/2010/javascript/javascript-sorting-table.html
답변
안정적인 것을 찾고 있기 때문에 병합 정렬이 수행되어야합니다.
http://www.stoimen.com/blog/2010/07/02/friday-algorithms-javascript-merge-sort/
코드는 위 웹 사이트에서 찾을 수 있습니다.
function mergeSort(arr)
{
if (arr.length < 2)
return arr;
var middle = parseInt(arr.length / 2);
var left = arr.slice(0, middle);
var right = arr.slice(middle, arr.length);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right)
{
var result = [];
while (left.length && right.length) {
if (left[0] <= right[0]) {
result.push(left.shift());
} else {
result.push(right.shift());
}
}
while (left.length)
result.push(left.shift());
while (right.length)
result.push(right.shift());
return result;
}
편집하다:
이 게시물 에 따르면 일부 구현에서는 Array.Sort가 병합 정렬을 사용하는 것처럼 보입니다.
답변
화살표 함수 및 구조 분해와 같은 ES2017 기능을 사용하는 동일한 것의 약간 더 짧은 버전 :
함수
var stableSort = (arr, compare) => arr
.map((item, index) => ({item, index}))
.sort((a, b) => compare(a.item, b.item) || a.index - b.index)
.map(({item}) => item)
입력 배열을 받아들이고 함수를 비교합니다.
stableSort([5,6,3,2,1], (a, b) => a - b)
또한 내장 Array.sort () 함수 와 같이 내부 정렬을 만드는 대신 새 배열을 반환 합니다.
테스트
다음 input
배열을 사용하면 처음에는 다음과 같이 정렬됩니다 weight
.
// sorted by weight
var input = [
{ height: 100, weight: 80 },
{ height: 90, weight: 90 },
{ height: 70, weight: 95 },
{ height: 100, weight: 100 },
{ height: 80, weight: 110 },
{ height: 110, weight: 115 },
{ height: 100, weight: 120 },
{ height: 70, weight: 125 },
{ height: 70, weight: 130 },
{ height: 100, weight: 135 },
{ height: 75, weight: 140 },
{ height: 70, weight: 140 }
]
그런 다음 다음을 height
사용하여 정렬하십시오 stableSort
.
stableSort(input, (a, b) => a.height - b.height)
결과 :
// Items with the same height are still sorted by weight
// which means they preserved their relative order.
var stable = [
{ height: 70, weight: 95 },
{ height: 70, weight: 125 },
{ height: 70, weight: 130 },
{ height: 70, weight: 140 },
{ height: 75, weight: 140 },
{ height: 80, weight: 110 },
{ height: 90, weight: 90 },
{ height: 100, weight: 80 },
{ height: 100, weight: 100 },
{ height: 100, weight: 120 },
{ height: 100, weight: 135 },
{ height: 110, weight: 115 }
]
그러나 input
내장 Array.sort()
(Chrome / NodeJS에서)을 사용하여 동일한 배열 정렬 :
input.sort((a, b) => a.height - b.height)
보고:
var unstable = [
{ height: 70, weight: 140 },
{ height: 70, weight: 95 },
{ height: 70, weight: 125 },
{ height: 70, weight: 130 },
{ height: 75, weight: 140 },
{ height: 80, weight: 110 },
{ height: 90, weight: 90 },
{ height: 100, weight: 100 },
{ height: 100, weight: 80 },
{ height: 100, weight: 135 },
{ height: 100, weight: 120 },
{ height: 110, weight: 115 }
]
자원
최신 정보
Array.prototype.sort
이제 V8 v7.0 / Chrome 70에서 안정적입니다!이전에 V8은 요소가 10 개 이상인 배열에 대해 불안정한 QuickSort를 사용했습니다. 이제 안정적인 TimSort 알고리즘을 사용합니다.
답변
이 질문에 대한 답변은 얼마 동안 이루어졌지만 클립 보드에 Array 및 jQuery에 대한 안정적인 병합 정렬 구현이 있으므로 향후 일부 검색자가 유용 할 수 있기를 바라며 공유 할 것입니다.
일반 Array.sort
구현 과 마찬가지로 고유 한 비교 기능을 지정할 수 있습니다 .
이행
// Add stable merge sort to Array and jQuery prototypes
// Note: We wrap it in a closure so it doesn't pollute the global
// namespace, but we don't put it in $(document).ready, since it's
// not dependent on the DOM
(function() {
// expose to Array and jQuery
Array.prototype.mergeSort = jQuery.fn.mergeSort = mergeSort;
function mergeSort(compare) {
var length = this.length,
middle = Math.floor(length / 2);
if (!compare) {
compare = function(left, right) {
if (left < right)
return -1;
if (left == right)
return 0;
else
return 1;
};
}
if (length < 2)
return this;
return merge(
this.slice(0, middle).mergeSort(compare),
this.slice(middle, length).mergeSort(compare),
compare
);
}
function merge(left, right, compare) {
var result = [];
while (left.length > 0 || right.length > 0) {
if (left.length > 0 && right.length > 0) {
if (compare(left[0], right[0]) <= 0) {
result.push(left[0]);
left = left.slice(1);
}
else {
result.push(right[0]);
right = right.slice(1);
}
}
else if (left.length > 0) {
result.push(left[0]);
left = left.slice(1);
}
else if (right.length > 0) {
result.push(right[0]);
right = right.slice(1);
}
}
return result;
}
})();
사용 예
var sorted = [
'Finger',
'Sandwich',
'sandwich',
'5 pork rinds',
'a guy named Steve',
'some noodles',
'mops and brooms',
'Potato Chip Brand® chips'
].mergeSort(function(left, right) {
lval = left.toLowerCase();
rval = right.toLowerCase();
console.log(lval, rval);
if (lval < rval)
return -1;
else if (lval == rval)
return 0;
else
return 1;
});
sorted == ["5 pork rinds", "a guy named Steve", "Finger", "mops and brooms", "Potato Chip Brand® chips", "Sandwich", "sandwich", "some noodles"];
답변
이 답변에서 만든 어설 션을 기반으로 기본 구현에 관계없이 다음 polyfill을 사용하여 안정적인 정렬을 구현할 수 있습니다 .
// ECMAScript 5 polyfill
Object.defineProperty(Array.prototype, 'stableSort', {
configurable: true,
writable: true,
value: function stableSort (compareFunction) {
'use strict'
var length = this.length
var entries = Array(length)
var index
// wrap values with initial indices
for (index = 0; index < length; index++) {
entries[index] = [index, this[index]]
}
// sort with fallback based on initial indices
entries.sort(function (a, b) {
var comparison = Number(this(a[1], b[1]))
return comparison || a[0] - b[0]
}.bind(compareFunction))
// re-map original array to stable sorted values
for (index = 0; index < length; index++) {
this[index] = entries[index][1]
}
return this
}
})
// usage
const array = Array(500000).fill().map(() => Number(Math.random().toFixed(4)))
const alwaysEqual = () => 0
const isUnmoved = (value, index) => value === array[index]
// not guaranteed to be stable
console.log('sort() stable?', array
.slice()
.sort(alwaysEqual)
.every(isUnmoved)
)
// guaranteed to be stable
console.log('stableSort() stable?', array
.slice()
.stableSort(alwaysEqual)
.every(isUnmoved)
)
// performance using realistic scenario with unsorted big data
function time(arrayCopy, algorithm, compare) {
var start
var stop
start = performance.now()
algorithm.call(arrayCopy, compare)
stop = performance.now()
return stop - start
}
const ascending = (a, b) => a - b
const msSort = time(array.slice(), Array.prototype.sort, ascending)
const msStableSort = time(array.slice(), Array.prototype.stableSort, ascending)
console.log('sort()', msSort.toFixed(3), 'ms')
console.log('stableSort()', msStableSort.toFixed(3), 'ms')
console.log('sort() / stableSort()', (100 * msSort / msStableSort).toFixed(3) + '%')
위에서 구현 된 성능 테스트를 실행하면 Chrome 버전 59-61 stableSort()
에서 속도의 약 57 %로 실행되는 것으로 보입니다 sort()
.
사용 .bind(compareFunction)
내에 싸여 익명 함수 stableSort()
에 불필요한 범위의 기준 피함으로써 약 38 %의 상대 성능이 승압 compareFunction
대신 문맥에 할당하여 각 호에있다.
최신 정보
삼항 연산자를 논리적 단락으로 변경하여 평균적으로 더 나은 성능을 발휘하는 경향이 있습니다 (효율성에서 2-3 % 차이를 만드는 것으로 나타남).
답변
다음은 제공된 비교 함수를 적용하여 제공된 배열을 정렬하고 비교 함수가 0을 반환 할 때 원래 인덱스 비교를 반환합니다.
function stableSort(arr, compare) {
var original = arr.slice(0);
arr.sort(function(a, b){
var result = compare(a, b);
return result === 0 ? original.indexOf(a) - original.indexOf(b) : result;
});
return arr;
}
아래 예제는 동일한 성의 순서를 유지하면서 성별로 이름 배열을 정렬합니다.
var names = [
{ surname: "Williams", firstname: "Mary" },
{ surname: "Doe", firstname: "Mary" },
{ surname: "Johnson", firstname: "Alan" },
{ surname: "Doe", firstname: "John" },
{ surname: "White", firstname: "John" },
{ surname: "Doe", firstname: "Sam" }
]
function stableSort(arr, compare) {
var original = arr.slice(0);
arr.sort(function(a, b){
var result = compare(a, b);
return result === 0 ? original.indexOf(a) - original.indexOf(b) : result;
});
return arr;
}
stableSort(names, function(a, b) {
return a.surname > b.surname ? 1 : a.surname < b.surname ? -1 : 0;
})
names.forEach(function(name) {
console.log(name.surname + ', ' + name.firstname);
});
답변
다음은 안정적인 구현입니다. 기본 정렬을 사용하여 작동하지만 요소가 동일한 것으로 비교되는 경우 원래 인덱스 위치를 사용하여 연결을 끊습니다.
function stableSort(arr, cmpFunc) {
//wrap the arr elements in wrapper objects, so we can associate them with their origional starting index position
var arrOfWrapper = arr.map(function(elem, idx){
return {elem: elem, idx: idx};
});
//sort the wrappers, breaking sorting ties by using their elements orig index position
arrOfWrapper.sort(function(wrapperA, wrapperB){
var cmpDiff = cmpFunc(wrapperA.elem, wrapperB.elem);
return cmpDiff === 0
? wrapperA.idx - wrapperB.idx
: cmpDiff;
});
//unwrap and return the elements
return arrOfWrapper.map(function(wrapper){
return wrapper.elem;
});
}
철저하지 않은 시험
var res = stableSort([{a:1, b:4}, {a:1, b:5}], function(a, b){
return a.a - b.a;
});
console.log(res);
이것에 대한 또 다른 대답이 암시되었지만 코드를 게시하지 않았습니다.
그러나 내 벤치 마크 에 따르면 빠르지 않습니다 . 사용자 정의 비교기 기능을 허용 하도록 병합 정렬 impl 을 수정했으며 훨씬 더 빠릅니다.