-
Notifications
You must be signed in to change notification settings - Fork 5
/
30timsort.js
400 lines (361 loc) · 12.4 KB
/
30timsort.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
// TimSort: Put It Together
// - combine codes of both split and merge improvement
var MIN_GALLOP = 7;
// === [SPLIT IMPROVEMENTS] === //
// timsort: main loop
var timSort = function (array, first, last, lessThan) {
if (last - first <= 1) return array;
var state = {
array: array,
lessThan: lessThan,
lessThanEqual: function (a, b) {return !lessThan(b, a);},
runStack: [],
remain: first,
last: last,
minrun: calcMinrun(last - first),
minGallop: MIN_GALLOP,
};
while (nextRun(state)) {
while (whenMerge(state)) {
mergeTwoRuns(state);
}
}
return array;
};
// calculate minimum run size
var calcMinrun = function (n) {
// from python listsort
// e.g. 1=>1, ..., 63=>63, 64=>32, 65=>33, ..., 127=>64, 128=>32, ...
var r = 0;
while (n >= 64) {
r = r | n & 1;
n = n >> 1;
}
return n + r;
};
// cut array to monotonic chunks named (natural) "run"
var nextRun = function (state) {
if (state.remain >= state.last) return false;
if (state.last - state.remain <= 1) {
cutRun(state, state.last);
return true;
}
var last = state.remain;
var prev = state.array[last++];
var lastVal = state.array[last++];
if (state.lessThanEqual(prev, lastVal)) {
prev = lastVal;
while (last < state.last) {
var val = state.array[last];
// inc-run elems allowed prev == val
if (!state.lessThanEqual(prev, val)) break;
prev = val;
last++;
}
} else {
prev = lastVal;
while (last < state.last) {
var val = state.array[last];
// dec-run elems must prev > val, prev == val not allowed
if (!state.lessThan(val, prev)) break;
prev = val;
last++;
}
reverse(state.array, state.remain, last);
}
if (last - state.remain < state.minrun) {
// replace binary sorted minrun
var minrun = state.remain + state.minrun;
var sortStart = last;
last = (minrun > state.last) ? state.last : minrun;
binarySort(state.array, state.remain, last, state.lessThan, sortStart);
}
cutRun(state, last);
return true;
};
// cut and stack a run
var cutRun = function (state, last) {
var run = {
first: state.remain,
last: last,
length: last - state.remain,
};
state.runStack.push(run);
state.remain = last;
};
// reverse elements in array range
var reverse = function (array, first, last) {
last--;
while (first < last) {
swap(array, first++, last--);
}
};
// swap array elements
var swap = function (array, a, b) {
var tmp = array[a];
array[a] = array[b];
array[b] = tmp;
};
// loop condition when merge neighbors
var whenMerge = function (state) {
if (state.remain === state.last) {
return state.runStack.length > 1;
}
if (state.runStack.length <= 1) {
return false;
}
// similar sized runs should be merged: it introduces log(n) merge count
var curRun = state.runStack[state.runStack.length - 1];
var preRun = state.runStack[state.runStack.length - 2];
if (state.runStack.length === 2) return preRun.length <= curRun.length;
var pre2Run = state.runStack[state.runStack.length - 3];
return pre2Run.length <= preRun.length + curRun.length;
};
// merge two chunks
var mergeTwoRuns = function (state) {
if (state.runStack.length > 2 &&
state.runStack[state.runStack.length - 3].length <
state.runStack[state.runStack.length - 1].length) {
// merge last two runs if stack top run is larger than last two runs
// e.g. run length of stack state changed as [..., 5,2,6] => [..., 7,6]
var curRun = state.runStack.pop();
mergeHeadRuns(state);
state.runStack.push(curRun);
} else {
mergeHeadRuns(state);
}
};
// merge neighbor chunks and add two stacked runs to single run
var mergeHeadRuns = function (state) {
var mergerRun = state.runStack.pop();
var mergedRun = state.runStack[state.runStack.length - 1];
// assert mergedRun.last === mergerRun.first
mergeNeighbor(
state.array, mergedRun.first, mergerRun.first, mergerRun.last,
state);
mergedRun.last = mergerRun.last;
mergedRun.length += mergerRun.length;
};
// === [MERGE IMPROVEMENTS] === //
// merge neighbor chunks
var mergeNeighbor = function (array, first, connect, last, state) {
var llength = connect - first;
var rlength = last - connect;
if (llength < rlength) {
return mergeIntoLeft(array, first, connect, last, state);
} else {
return mergeIntoRight(array, first, connect, last, state);
}
};
// merge with filling to smaller side
var mergeIntoLeft = function (array, first, connect, last, state) {
// packed states of the function
var m = {};
// escape shorter buffer only
m.right = array;
m.rcur = connect; m.rlast = last;
// find merge start point which is insert point for first of larger side
m.cur = binSearch(array, first, connect, m.right[m.rcur], state.lessThan);
m.left = array.slice(m.cur, connect);
m.lcur = 0; m.llast = connect - m.cur;
// states for mode control
m.galloping = false;
m.gallopingOut = false;
m.selectLeft = true;
m.selectCount = 0;
while (m.lcur < m.llast && m.rcur < m.rlast) {
if (!m.galloping) {
mergeLeftOnePairMode(array, state, m);
} else {
mergeLeftGallopingMode(array, state, m);
}
}
// copy back to escaped side (the loop may be empty)
while (m.lcur < m.llast) array[m.cur++] = m.left[m.lcur++];
return array;
};
// one pair mode when filling to smaller side
var mergeLeftOnePairMode = function (array, state, m) {
var lval = m.left[m.lcur];
var rval = m.right[m.rcur];
if (state.lessThanEqual(lval, rval)) { // for sort stable
array[m.cur++] = lval; m.lcur++;
modeControlInOnePairMode(state, m, !m.selectLeft);
} else {
array[m.cur++] = rval; m.rcur++;
modeControlInOnePairMode(state, m, m.selectLeft);
}
};
// mode control for one pair mode
var modeControlInOnePairMode = function (state, m, selectSwitched) {
if (selectSwitched) {
m.selectLeft = !m.selectLeft;
m.selectCount = 0;
}
m.selectCount++;
if (m.selectCount >= state.minGallop) {
m.galloping = true;
m.selectCount = 0;
}
};
// galloping mode when filling to smaller side
var mergeLeftGallopingMode = function (array, state, m) {
if (state.minGallop > 0) state.minGallop--;
var lval = m.left[m.lcur];
var rval = m.right[m.rcur];
if (state.lessThanEqual(lval, rval)) {
// left(shorter) side gallop includes right side first (rightmost)
var end = gallopFirstSearch(
m.left, m.lcur + 1, m.llast, rval, state.lessThan);
modeControlInGallopingMode(state, m, end - m.lcur);
while (m.lcur < end) array[m.cur++] = m.left[m.lcur++];
} else {
// right(longer) side gallop excludes left side first (leftmost)
var end = gallopFirstSearch(
m.right, m.rcur + 1, m.rlast, lval, state.lessThanEqual);
modeControlInGallopingMode(state, m, end - m.rcur);
while (m.rcur < end) array[m.cur++] = m.right[m.rcur++];
}
};
// mode control for galloping mode
var modeControlInGallopingMode = function (state, m, gallopSize) {
if (gallopSize < MIN_GALLOP) {
if (m.gallopOut) { // exit galloping mode if gallop out at both sides
m.galloping = false;
m.gallopOut = false;
state.minGallop++;
} else {
m.gallopOut = true;
}
} else {
m.gallopOut = false;
}
};
// merge with filling to larger side
var mergeIntoRight = function (array, first, connect, last, state) {
// packed states of the function
var m = {};
// escape shorter buffer only
m.left = array
m.lcur = connect; m.lfirst = first;
// find merge start point which is insert point for first of larger side
m.cur = binSearch(
array, connect, last, m.left[m.lcur - 1], state.lessThanEqual);
m.right = array.slice(connect, m.cur);
m.rcur = m.cur - connect; m.rfirst = 0;
// states for mode control
m.galloping = false;
m.gallopingOut = false;
m.selectLeft = true;
m.selectCount = 0;
while (m.lfirst < m.lcur && m.rfirst < m.rcur) {
if (!m.galloping) {
mergeRightOnePairMode(array, state, m);
} else {
mergeRightGallopingMode(array, state, m);
}
}
// copy back to escaped side (the loop may be empty)
while (m.rfirst < m.rcur) array[--m.cur] = m.right[--m.rcur];
return array;
};
// one pair mode when filling to larger side
var mergeRightOnePairMode = function (array, state, m) {
var lval = m.left[m.lcur - 1];
var rval = m.right[m.rcur - 1];
if (state.lessThan(rval, lval)) { // (lval > rval) for sort stable
array[--m.cur] = lval; --m.lcur;
modeControlInOnePairMode(state, m, !m.selectLeft);
} else {
array[--m.cur] = rval; --m.rcur;
modeControlInOnePairMode(state, m, m.selectLeft);
}
};
// galloping mode when filling to larger side
var mergeRightGallopingMode = function (array, state, m) {
if (state.minGallop > 0) state.minGallop--;
var lval = m.left[m.lcur - 1];
var rval = m.right[m.rcur - 1];
if (state.lessThan(rval, lval)) {
// left(longer) side gallop excludes right side last (rightmost)
var begin = gallopLastSearch(
m.left, m.lfirst, m.lcur - 1, rval, state.lessThan);
modeControlInGallopingMode(state, m, m.lcur - begin);
while (begin < m.lcur) array[--m.cur] = m.left[--m.lcur];
} else {
// right(shorter) side gallop includes left side last (leftmost)
var begin = gallopLastSearch(
m.right, m.rfirst, m.rcur - 1, lval, state.lessThanEqual);
modeControlInGallopingMode(state, m, m.rcur - begin);
while (begin < m.rcur) array[--m.cur] = m.right[--m.rcur];
}
};
// binsearch for gallop mode from first element side
// search to one of regions [0,1) [1,3),[3,7),[7,15),...
var gallopFirstSearch = function (array, first, last, value, lessThan) {
var pre = 0;
var offset = 1;
while (first + offset < last) {
if (lessThan(value, array[first + offset])) break;
pre = offset;
offset = (offset << 1) + 1;
}
var searchFirst = first + pre;
var searchLast = (first + offset < last) ? first + offset : last;
return binSearch(array, searchFirst, searchLast, value, lessThan);
};
// binsearch for gallop mode from last element side
// search to one of regions(from last) [-1,-0),[-3,-1),[-7,-3),[-15,-7),...
var gallopLastSearch = function (array, first, last, value, lessThan) {
var pre = 0;
var offset = 1;
while (first < last - offset) {
if (!lessThan(value, array[last - offset])) break;
pre = offset;
offset = (offset << 1) + 1;
}
var searchFirst = (first < last - offset) ? last - offset : first;
var searchLast = last - pre;
return binSearch(array, searchFirst, searchLast, value, lessThan);
};
// binary search
var binSearch = function (array, first, last, value, lessThan) {
while (first < last) {
var mid = last + ((first - last) >> 1);
if (lessThan(value, array[mid])) {
last = mid;
} else {
first = mid + 1;
}
}
return first;
};
// binary sort: insertion sort with binary search
var binarySort = function (array, first, last, lessThan, sortStart) {
sortStart = sortStart || first + 1;
for (var i = sortStart; i < last; i += 1) {
var point = binSearch(array, first, i, array[i], lessThan);
cyclicRShift(array, point, i + 1);
}
return array;
};
// 1 right cyclic shift of array range
var cyclicRShift = function (array, first, last) {
if (last - first <= 1) return array;
var mostRight = array[last - 1];
// C: memmove(first, first+1, last-first-1)
for (var cur = last - 1; cur > first; cur -= 1) {
array[cur] = array[cur - 1];
}
array[first] = mostRight;
return array;
};
var builtinLessThan = function (a, b) {
return a < b;
};
// export interface for runner.js
var sort = this.sort = function (array) {
var lessThan = arguments[1] || builtinLessThan;
timSort(array, 0, array.length, lessThan);
return array;
};