Skip to content

Commit ae0cd69

Browse files
committed
add polyfill of stable sort
1 parent 8cf605d commit ae0cd69

File tree

10 files changed

+339
-37
lines changed

10 files changed

+339
-37
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
## Changelog
22
##### Unreleased
3+
- Added polyfill of stable sort in `{ Array, %TypedArray% }.prototype.sort`, [#769](https://github.com/zloirock/core-js/issues/769)
34
- `.at` marked as supported from V8 9.2
45

56
##### 3.13.1 - 2021.05.29

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ class Array {
541541
slice(start?: number, end?: number): Array<mixed>; // with adding support of @@species
542542
splice(start?: number, deleteCount?: number, ...items: Array<mixed>): Array<mixed>; // with adding support of @@species
543543
some(callbackfn: (value: any, index: number, target: any) => boolean, thisArg?: any): boolean;
544-
sort(comparefn?: (a: any, b: any) => number): this;
544+
sort(comparefn?: (a: any, b: any) => number): this; // with modern behavior like stable sort
545545
values(): Iterator<value>;
546546
@@iterator(): Iterator<value>;
547547
@@unscopables: { [newMethodNames: string]: true };
@@ -1510,7 +1510,7 @@ class %TypedArray% {
15101510
set(array: ArrayLike, offset?: number): void;
15111511
slice(start?: number, end?: number): %TypedArray%;
15121512
some(callbackfn: (value: number, index: number, target: %TypedArray%) => boolean, thisArg?: any): boolean;
1513-
sort(comparefn?: (a: number, b: number) => number): this;
1513+
sort(comparefn?: (a: number, b: number) => number): this; // with modern behavior like stable sort
15141514
subarray(begin?: number, end?: number): %TypedArray%;
15151515
toString(): string;
15161516
toLocaleString(): string;

packages/core-js-compat/src/data.js

+3-5
Original file line numberDiff line numberDiff line change
@@ -236,9 +236,8 @@ const data = {
236236
safari: '8.0',
237237
},
238238
'es.array.sort': {
239-
chrome: '63',
239+
chrome: '70',
240240
firefox: '4',
241-
ie: '9',
242241
safari: '12.0',
243242
},
244243
'es.array.species': {
@@ -1229,9 +1228,8 @@ const data = {
12291228
safari: '10.0',
12301229
},
12311230
'es.typed-array.sort': {
1232-
chrome: '45',
1233-
edge: '13',
1234-
firefox: '46',
1231+
chrome: '74',
1232+
firefox: '67',
12351233
safari: '10.0',
12361234
},
12371235
'es.typed-array.subarray': {
+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
'use strict';
2+
var aFunction = require('../internals/a-function');
3+
var toObject = require('../internals/to-object');
4+
var toLength = require('../internals/to-length');
5+
var fails = require('../internals/fails');
6+
var arrayMethodIsStrict = require('../internals/array-method-is-strict');
7+
8+
var test = [];
9+
var nativeSort = test.sort;
10+
var floor = Math.floor;
11+
12+
// IE8-
13+
var FAILS_ON_UNDEFINED = fails(function () {
14+
test.sort(undefined);
15+
});
16+
// V8 bug
17+
var FAILS_ON_NULL = fails(function () {
18+
test.sort(null);
19+
});
20+
// Old WebKit
21+
var STRICT_METHOD = arrayMethodIsStrict('sort');
22+
23+
var STABLE_SORT = !fails(function () {
24+
var result = '';
25+
var code, chr, value, index;
26+
27+
// generate an array with more 512 elements (Chakra and old V8 fails only in this case)
28+
for (code = 65; code < 76; code++) {
29+
chr = String.fromCharCode(code);
30+
switch (code) {
31+
case 66: case 69: case 70: case 72: value = 3; break;
32+
case 68: case 71: value = 4; break;
33+
default: value = 2;
34+
}
35+
36+
for (index = 0; index < 47; index++) {
37+
test.push({ k: chr + index, v: value });
38+
}
39+
}
40+
41+
test.sort(function (a, b) { return b.v - a.v; });
42+
43+
for (index = 0; index < test.length; index++) {
44+
chr = test[index].k.charAt(0);
45+
if (result.charAt(result.length - 1) !== chr) result += chr;
46+
}
47+
48+
return result !== 'DGBEFHACIJK';
49+
});
50+
51+
var FORCED = FAILS_ON_UNDEFINED || !FAILS_ON_NULL || !STRICT_METHOD || !STABLE_SORT;
52+
53+
var mergeSort = function (array, comparefn) {
54+
var length = array.length;
55+
var middle = floor(length / 2);
56+
if (length < 2) return array;
57+
return merge(
58+
mergeSort(array.slice(0, middle), comparefn),
59+
mergeSort(array.slice(middle), comparefn),
60+
comparefn
61+
);
62+
};
63+
64+
var merge = function (left, right, comparefn) {
65+
var llength = left.length;
66+
var rlength = right.length;
67+
var lindex = 0;
68+
var rindex = 0;
69+
var result = [];
70+
71+
while (lindex < llength || rindex < rlength) {
72+
if (lindex < llength && rindex < rlength) {
73+
result.push(sortCompare(left[lindex], right[rindex], comparefn) <= 0 ? left[lindex++] : right[rindex++]);
74+
} else {
75+
result.push(lindex < llength ? left[lindex++] : right[rindex++]);
76+
}
77+
} return result;
78+
};
79+
80+
var sortCompare = function (x, y, comparefn) {
81+
if (x === undefined && y === undefined) return 0;
82+
if (x === undefined) return 1;
83+
if (y === undefined) return -1;
84+
if (comparefn !== undefined) {
85+
return +comparefn(x, y) || 0;
86+
} return String(x) > String(y) ? 1 : -1;
87+
};
88+
89+
// `Array.prototype.sort` method
90+
// https://tc39.es/ecma262/#sec-array.prototype.sort
91+
module.exports = FORCED ? function sort(comparefn) {
92+
if (comparefn !== undefined) aFunction(comparefn);
93+
94+
var array = toObject(this);
95+
96+
if (STABLE_SORT) return comparefn === undefined ? nativeSort.call(array) : nativeSort.call(array, comparefn);
97+
98+
var items = [];
99+
var arrayLength = toLength(array.length);
100+
var itemsLength, index;
101+
102+
for (index = 0; index < arrayLength; index++) {
103+
if (index in array) items.push(array[index]);
104+
}
105+
106+
items = mergeSort(items, comparefn);
107+
itemsLength = items.length;
108+
index = 0;
109+
110+
while (index < itemsLength) array[index] = items[index++];
111+
while (index < arrayLength) delete array[index++];
112+
113+
return array;
114+
} : nativeSort;
+3-26
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,9 @@
11
'use strict';
22
var $ = require('../internals/export');
3-
var aFunction = require('../internals/a-function');
4-
var toObject = require('../internals/to-object');
5-
var fails = require('../internals/fails');
6-
var arrayMethodIsStrict = require('../internals/array-method-is-strict');
7-
8-
var test = [];
9-
var nativeSort = test.sort;
10-
11-
// IE8-
12-
var FAILS_ON_UNDEFINED = fails(function () {
13-
test.sort(undefined);
14-
});
15-
// V8 bug
16-
var FAILS_ON_NULL = fails(function () {
17-
test.sort(null);
18-
});
19-
// Old WebKit
20-
var STRICT_METHOD = arrayMethodIsStrict('sort');
21-
22-
var FORCED = FAILS_ON_UNDEFINED || !FAILS_ON_NULL || !STRICT_METHOD;
3+
var sort = require('../internals/array-sort');
234

245
// `Array.prototype.sort` method
256
// https://tc39.es/ecma262/#sec-array.prototype.sort
26-
$({ target: 'Array', proto: true, forced: FORCED }, {
27-
sort: function sort(comparefn) {
28-
return comparefn === undefined
29-
? nativeSort.call(toObject(this))
30-
: nativeSort.call(toObject(this), aFunction(comparefn));
31-
}
7+
$({ target: 'Array', proto: true, forced: [].sort !== sort }, {
8+
sort: sort
329
});
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,34 @@
11
'use strict';
22
var ArrayBufferViewCore = require('../internals/array-buffer-view-core');
3+
var fails = require('../internals/fails');
4+
var $sort = require('../internals/array-sort');
35

46
var aTypedArray = ArrayBufferViewCore.aTypedArray;
57
var exportTypedArrayMethod = ArrayBufferViewCore.exportTypedArrayMethod;
6-
var $sort = [].sort;
8+
9+
var STABLE_SORT = !fails(function () {
10+
// eslint-disable-next-line es/no-typed-arrays -- required for testing
11+
var array = new Uint16Array(516);
12+
var expected = Array(516);
13+
var index, mod;
14+
15+
for (index = 0; index < 516; index++) {
16+
mod = index % 4;
17+
array[index] = 515 - index;
18+
expected[index] = index - 2 * mod + 3;
19+
}
20+
21+
array.sort(function (a, b) {
22+
return (a / 4 | 0) - (b / 4 | 0);
23+
});
24+
25+
for (index = 0; index < 516; index++) {
26+
if (array[index] !== expected[index]) return true;
27+
}
28+
});
729

830
// `%TypedArray%.prototype.sort` method
931
// https://tc39.es/ecma262/#sec-%typedarray%.prototype.sort
1032
exportTypedArrayMethod('sort', function sort(comparefn) {
1133
return $sort.call(aTypedArray(this), comparefn);
12-
});
34+
}, !STABLE_SORT);

tests/compat/tests.js

+45-2
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,33 @@ GLOBAL.tests = {
382382
[1, 2, 3].sort(null);
383383
} catch (error2) {
384384
[1, 2, 3].sort(undefined);
385-
return true;
385+
386+
// stable sort
387+
var array = [];
388+
var result = '';
389+
var code, chr, value, index;
390+
391+
// generate an array with more 512 elements (Chakra and old V8 fails only in this case)
392+
for (code = 65; code < 76; code++) {
393+
chr = String.fromCharCode(code);
394+
switch (code) {
395+
case 66: case 69: case 70: case 72: value = 3; break;
396+
case 68: case 71: value = 4; break;
397+
default: value = 2;
398+
}
399+
for (index = 0; index < 47; index++) {
400+
array.push({ k: chr + index, v: value });
401+
}
402+
}
403+
404+
array.sort(function (a, b) { return b.v - a.v; });
405+
406+
for (index = 0; index < array.length; index++) {
407+
chr = array[index].k.charAt(0);
408+
if (result.charAt(result.length - 1) !== chr) result += chr;
409+
}
410+
411+
return result === 'DGBEFHACIJK';
386412
}
387413
}
388414
},
@@ -1039,7 +1065,24 @@ GLOBAL.tests = {
10391065
return Int8Array.prototype.some;
10401066
}],
10411067
'es.typed-array.sort': [ARRAY_BUFFER_VIEWS_SUPPORT, function () {
1042-
return Int8Array.prototype.sort;
1068+
// stable sort
1069+
var array = new Uint16Array(516);
1070+
var expected = Array(516);
1071+
var index, mod;
1072+
1073+
for (index = 0; index < 516; index++) {
1074+
mod = index % 4;
1075+
array[index] = 515 - index;
1076+
expected[index] = index - 2 * mod + 3;
1077+
}
1078+
1079+
array.sort(function (a, b) {
1080+
return (a / 4 | 0) - (b / 4 | 0);
1081+
});
1082+
1083+
for (index = 0; index < 516; index++) {
1084+
if (array[index] !== expected[index]) return;
1085+
} return true;
10431086
}],
10441087
'es.typed-array.subarray': [ARRAY_BUFFER_VIEWS_SUPPORT, function () {
10451088
return Int8Array.prototype.subarray;

tests/pure/es.array.sort.js

+42
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,48 @@ QUnit.test('Array#sort', assert => {
77
assert.notThrows(() => sort([1, 2, 3], undefined), 'works with undefined');
88
assert.throws(() => sort([1, 2, 3], null), 'throws on null');
99
assert.throws(() => sort([1, 2, 3], {}), 'throws on {}');
10+
11+
const expected = Array(516);
12+
let array = Array(516);
13+
let index, mod, code, chr, value;
14+
15+
for (index = 0; index < 516; index++) {
16+
mod = index % 4;
17+
array[index] = 515 - index;
18+
expected[index] = index - 2 * mod + 3;
19+
}
20+
21+
sort(array, (a, b) => (a / 4 | 0) - (b / 4 | 0));
22+
23+
assert.same(String(array), String(expected), 'stable #1');
24+
25+
let result = '';
26+
array = [];
27+
28+
// generate an array with more 512 elements (Chakra and old V8 fails only in this case)
29+
for (code = 65; code < 76; code++) {
30+
chr = String.fromCharCode(code);
31+
32+
switch (code) {
33+
case 66: case 69: case 70: case 72: value = 3; break;
34+
case 68: case 71: value = 4; break;
35+
default: value = 2;
36+
}
37+
38+
for (index = 0; index < 47; index++) {
39+
array.push({ k: chr + index, v: value });
40+
}
41+
}
42+
43+
sort(array, (a, b) => b.v - a.v);
44+
45+
for (index = 0; index < array.length; index++) {
46+
chr = array[index].k.charAt(0);
47+
if (result.charAt(result.length - 1) !== chr) result += chr;
48+
}
49+
50+
assert.same(result, 'DGBEFHACIJK', 'stable #2');
51+
1052
if (STRICT) {
1153
assert.throws(() => sort(null), TypeError, 'ToObject(this)');
1254
assert.throws(() => sort(undefined), TypeError, 'ToObject(this)');

tests/tests/es.array.sort.js

+42
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,48 @@ QUnit.test('Array#sort', assert => {
77
assert.name(sort, 'sort');
88
assert.looksNative(sort);
99
assert.nonEnumerable(Array.prototype, 'sort');
10+
11+
const expected = Array(516);
12+
let array = Array(516);
13+
let index, mod, code, chr, value;
14+
15+
for (index = 0; index < 516; index++) {
16+
mod = index % 4;
17+
array[index] = 515 - index;
18+
expected[index] = index - 2 * mod + 3;
19+
}
20+
21+
array.sort((a, b) => (a / 4 | 0) - (b / 4 | 0));
22+
23+
assert.same(String(array), String(expected), 'stable #1');
24+
25+
let result = '';
26+
array = [];
27+
28+
// generate an array with more 512 elements (Chakra and old V8 fails only in this case)
29+
for (code = 65; code < 76; code++) {
30+
chr = String.fromCharCode(code);
31+
32+
switch (code) {
33+
case 66: case 69: case 70: case 72: value = 3; break;
34+
case 68: case 71: value = 4; break;
35+
default: value = 2;
36+
}
37+
38+
for (index = 0; index < 47; index++) {
39+
array.push({ k: chr + index, v: value });
40+
}
41+
}
42+
43+
array.sort((a, b) => b.v - a.v);
44+
45+
for (index = 0; index < array.length; index++) {
46+
chr = array[index].k.charAt(0);
47+
if (result.charAt(result.length - 1) !== chr) result += chr;
48+
}
49+
50+
assert.same(result, 'DGBEFHACIJK', 'stable #2');
51+
1052
assert.notThrows(() => [1, 2, 3].sort(undefined).length === 3, 'works with undefined');
1153
assert.throws(() => [1, 2, 3].sort(null), 'throws on null');
1254
assert.throws(() => [1, 2, 3].sort({}), 'throws on {}');

0 commit comments

Comments
 (0)