When I was writing in C++
about 10 years ago, I remember using the std:set
structure to immediately insert data into an array in an orderly manner. In JavaScript, a similar Set
has been added, but unfortunately, a comparator cannot be inserted into it. You will have to roll up your sleeves and write everything yourself.
export function pushSortable<T>(
list: T[],
item: T,
comparator: (a: T, b: T) => -1 | 0 | 1
) {
// find the position of the first element that is greater than the inserted one
const index = list.findIndex(list, item, (a, b) => comparator(a, b) === -1);
// if we do not find it, then add it to the end
if (index < 0) {
list.push(item);
return;
}
// последовательно сдвигаем элементы на 1 позицию влево
let tmp = item;
for (let i = index, iLen = list.length; i <= iLen; i++) {
tmp = list[i];
list[i] = item;
item = tmp;
}
}
This could be the end of it. But analyzing the complexity of such a solution, it turns out that findIndex is O(N)
, and shifting elements is O(N)
. And if the shifting of elements cannot be accelerated, then the search can be replaced with a binary one and the complexity can be improved to O(log N)
.
export function binarySearch<T, V>(
list: T[],
target: V,
cmp: (num: T, target: V) => 0 | -1 | 1
): number {
let start = 0,
end = list.length - 1,
i = -1;
while (start <= end) {
i = start + ~~((end - start) / 2);
const cmpResult = cmp(list[i], target);
if (cmpResult === 0) {
return i;
}
if (cmpResult === -1) {
start = i + 1;
} else {
end = i - 1;
}
}
return -1;
}
The problem with this implementation is that it perfectly finds an element in an array only if it exists, and we are just going to add it and are looking for a suitable place for it. To do this, we will slightly expand the comparator.
export function binarySearch<T, V>(
list: T[],
target: V,
cmp: (num: T, target: V, i: number, list: T[]) => 0 | -1 | 1
): number {
// <- update
let start = 0,
end = list.length - 1,
i = -1;
while (start <= end) {
i = start + ~~((end - start) / 2);
const cmpResult = cmp(list[i], target, i, list); // <- update
if (cmpResult === 0) {
return i;
}
if (cmpResult === -1) {
start = i + 1;
} else {
end = i - 1;
}
}
return -1;
}
Now we can implement our faster findIndex
. The idea is that when comparing, we check the previous element and if the desired element is found or is simultaneously greater than the previous and less than the next, then we get the desired index and O(log N)
function findIndexBinary<T>(
list: T[],
item: T,
cmp: (a: T, b: T) => -1 | 0 | 1
) {
return binarySearch(list, item, (a, t, index, lst) => {
const right = cmp(a, t);
if (index > 0) {
const left = cmp(lst[index - 1], t);
if (right !== left) {
return 0;
}
} else if (right === 1) {
// index === 0
return 0;
}
return right;
});
}
Final version
export function pushSortable<T>(
list: T[],
item: T,
comparator: (a: T, b: T) => -1 | 0 | 1
) {
const index = findIndexBinary(list, item, comparator);
if (index < 0) {
list.push(item);
return;
}
let tmp = item;
for (let i = index, iLen = list.length; i <= iLen; i++) {
tmp = list[i];
list[i] = item;
item = tmp;
}
}