Предложу свой велосипед на костылях.
Без сортировки!const zip = arr => arr
.reduce((agg, c) => {
const iR = agg.indexOf(c + 1);
const iL = agg.lastIndexOf(c - 1);
if (!!~iR && !!~iL) agg.splice(iL, 2); // закрыли дырку
else if (!!~iR) agg[iR] = c; // сдвинули границу
else if (!!~iL) agg[iL] = c; // то же
else { // вставляем сироту - найти позицию сразу после меньшего
let pos = 0;
while (pos < agg.length && agg[pos] < c) pos++;
agg.splice(pos, 0, c, c); // вставляем дважды
}
return agg;
}, [])
.reduce((agg, c, i, arr) => {
if (!(i&1)) agg.push(arr[i+1] === c ? c : [c, arr[i+1]].join('-'));
return agg;
}, [])
.join(', ')
;
Массив границ диапазонов, в нём всегда чётное число элементов.
Очередное число вставляем в массив: ищем, есть ли его ближайшие соседи слева и справа.
- Если есть оба, число закрывает «дырку», надо просто убрать этих двух соседей.
- Если нашёлся только один – заменяем его собой, сдвигая границу.
- Если ни одного соседа, значит, число пока сирота, вставляем его дважды,
как будто это и левая и правая граница диапазона.
Так из первого примера получается
[ 0, 5, 8, 9, 11, 11 ]
Остаётся форматирование. Смотрим только чётные элементы. Если текущий и следующий элементы равны, это «одинокое» число. Если не равны — это диапазон через дефис. И склеиваем через запятую-с-пробелом.
Учитывая сортированность собираемого массива, можно ускорить, заменив
indexOf()
и
lastIndexOf()
на самописный поиск, останавливающийся на элементе, бОльшем или меньшем искомого.