Сортування чисел у порядку зменшення, але з 0 на початку


36

У мене виклик JavaScript, який я намагаюся розібратися на деякий час.

Розглянемо цей масив:

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

Я повинен вивести цей результат:

arr = [0, 0, 0, 0, 0, 5, 4, 3, 2, 1]

Я дотримуюся цієї лінії логіки, щоб розташувати нулі попереду, коригуючи значення індексу:

arr.sort((x, y) => {
    if (x !== 0) {
        return 1;
    }

    if (x === 0) {
        return -1;
    }

    return y - x;
});

Але я застряг у цьому результаті:

arr = [0, 0, 0, 0, 0, 1, 2, 3, 4, 5]

Хтось має поради, як це вирішити?


6
Чи гарантовано немає негативних цифр?
Майкл - Де Клей

2
Чи не виправлено це, якщо переключено останній рядок return x - y;?
Mooing Duck

2
Чи буде в JavaScript ефективним підраховувати та видаляти нулі, сортувати решта елементів, як правило? (без спеціального компаратора, тому сподіваємось, що двигун JS може використовувати вбудовану функцію сортування чисел). Потім додайте до результату потрібну кількість нулів. Якщо нулів багато, видалення їх перед сортуванням становить меншу проблему. Або поміняйте нулі на початок масиву одним проходом, а потім сортуйте хвіст масиву?
Пітер Кордес

2
Це коли-небудь навіть дістанеться return y - x;? Навіть у javascript я не можу придумати нічого, що не було б ===0ні !==0.
Джордж Т

2
JSPerf для відповідей: jsperf.com/stackoverflow-question-58933996-v2
Салман

Відповіді:


35

Ви можете сортувати за дельтою bта a(за низхідним сортуванням) та брати Number.MAX_VALUEдля фальшивих значень, таких як нуль.

Це:

Number.MAX_VALUE - Number.MAX_VALUE

дорівнює нулю.

let array = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

array.sort((a, b) => (b || Number.MAX_VALUE) - (a || Number.MAX_VALUE));

console.log(...array);


13
Ваша функція порівняння повернеться, NaNякщо обоє aі bдорівнюють нулю. Це може бути небажана поведінка.
dan04

20
Результати Array.prototype.sortвизначаються впровадженням, якщо порівняльник коли-небудь повернеться NaN, тому цей компаратор є поганою ідеєю. Він намагається бути розумним, і помиляється.
user2357112 підтримує Моніку

3
Що робити, якщо елементом всередині масиву є Infinity? Функція порівняння повертає 0 при порівнянні числа Infinity з 0.
Марко

4
Дякую всім. я беру найбільше число, яке може бути вилучено самим собою, і сподіваюся, що це число не використовується в масиві ОП.
Ніна Шольц

4
Абсолютно нечитабельний. Класно, але не читати.
Салман

24

Як говорить mdn docs:

Якщо a і b - це два елементи, які порівнюються, то:

Якщо ви compareFunction(a, b)повертаєте менше 0, відсортуйте aдо індексу, нижчого за b(тобто a приходить першим).

Якщо compareFunction(a, b)повертається 0, залиште a і b незмінними відносно один одного, але відсортованими відносно всіх різних елементів. Примітка: стандарт ECMAscript не гарантує такої поведінки, тому не всі веб-переглядачі (наприклад, версії Mozilla, що починаються з принаймні 2003 року) поважають це.

Якщо compareFunction(a, b) повернення більше 0, відсортуйте bдо індексу, нижчого за (тобто bприходить першим).

compareFunction(a, b)завжди повинен повертати одне і те ж значення, коли йому задано певну пару елементів aі bяк два її аргументи. Якщо результати повертаються непослідовно, порядок сортування не визначений.

Отже, функція порівняння має таку форму:

function compare(a, b) {
  if (a is less than b by some ordering criterion) {
    return -1;
  }
  if (a is greater than b by the ordering criterion) {
    return 1;
  }
  // a must be equal to b
  return 0;
}

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

arr.sort((x, y) => {
    if (x > 0 && y > 0) {
        return y - x;
    }
    return x - y;
});

console.log(arr);


3
+1 - надання єдиного рішення поки що з функцією порівняння, яка фактично відповідає (як визначено в стандарті ) для всіх входів, включаючи від’ємні числа.
Ільмарі Каронен

3
@IlmariKaronen: На жаль, порівняння відповідає негативним числам, але це неправильне порівняння для негативних чисел. Негативи сортують до 0 за допомогою цього порівняльника та сортують у порядку зростання.
user2357112 підтримує Моніку

2
@ user2357112supportsMonica Тим не менш, ОР не вказала бажану поведінку для від'ємних чисел, і в цьому прототипі тривіально коригувати поведінку від'ємних чисел відповідно до будь-яких вимог, що пред'являються до ОП. Що добре в цій відповіді, це те, що вона ідіоматична і використовує стандартну функцію порівняння, щоб виконувати цю роботу.
J ...

13

Якщо ви піклуєтесь про ефективність, напевно, найшвидше спочатку відфільтрувати нулі . Ви не хочете sortвитрачати час, навіть не дивлячись на них, не кажучи вже про додавання додаткової роботи до зворотного виклику порівняння, щоб обробити цей особливий випадок.

Особливо, якщо ви очікуєте значної кількості нулів, один пропуск даних для їх фільтрації повинен бути набагато кращим, ніж робити великий розмір O (N log N), який буде дивитися на кожен нуль кілька разів.

Ви можете ефективно додати потрібну кількість нулів, коли закінчите.

Так само просто читати отриманий код. Я використовував TypedArray, тому що він ефективний і полегшує числове сортування . Але ви можете використовувати цю техніку за допомогою звичайного масиву, використовуючи стандартну ідіому (a,b)=>a-bдля .sort.

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

let nonzero_arr = Int32Array.from(arr.filter(n => n != 0));
let zcount = arr.length - nonzero_arr.length;
nonzero_arr.sort();      // numeric TypedArray sorts numerically, not alphabetically

// Reverse the sorted part before copying into the final array.
nonzero_arr.reverse();

 // efficient-ish TypedArray for main result
let revsorted = new Int32Array(arr.length);   // zero-filled full size
revsorted.set(nonzero_arr, zcount);           // copy after the right number of zeros

console.log(Array.from(revsorted));      // prints strangely for TypedArray, with invented "0", "1" keys

/*
   // regular Array result
let sorted = [...Array(zcount).fill(0), ...nonzero_arr]  // IDK if this is efficient
console.log(sorted);
*/

Я не знаю, чи TypedArray, .sort()а потім .reverseшвидше, ніж використання спеціальної функції порівняння для сортування у порядку зменшення. Або якщо ми можемо копіювати і повертати назад на льоту за допомогою ітератора.


Також варто врахувати: використовувати тільки один TypedArray повної довжини .

Замість використання .filterпереведіть на нього цикл і поміняйте нулі на передню частину масиву, як ви йдете. Це займе один пропуск над вашими даними.

Потім використовуйте .subarray()для отримання нового виду TypedArray ненульових елементів того ж базового ArrayBuffer. Сортування, яке залишить вам повний масив із нульовим початком та відсортованим хвостом, з сортуванням, що коли-небудь дивиться на ненульові елементи.

Я не бачив функції розділу в методах Array або TypedArray, але я ледве знаю JavaScript. При хорошому JIT цикл не повинен бути надто гіршим, ніж вбудований метод. (Особливо, коли цей метод включає в себе зворотний виклик .filter, і якщо він не використовується reallocпід кришкою для зменшення, він повинен з'ясувати, скільки пам'яті потрібно виділити до того, як вона насправді фільтрує).

.filter() Перед перетворенням в TypedArray я використовував regular-Array . Якщо ваш внесок вже є TypedArray, у вас немає цієї проблеми, і ця стратегія стає ще більш привабливою.


Можливо, це може бути простішим та / або більш ідіоматичним, або, принаймні, більш компактним. IDK, якщо двигунам JS вдасться усунути / оптимізувати велику кількість копіювальних або нульових ініціалізацій, або якщо це допоможе використовувати петлі замість методів TypedArray, наприклад, копіювати назад замість reverse + .set. Я не обійшов його, щоб перевірити його швидкість; якщо хтось хоче це зробити, мені було б цікаво.
Пітер Кордес

Згідно з тестуванням @ Салмана, ця відповідь виконує як лайно для невеликих масивів (з ~ 1000 елементів, 10% з яких 0). Імовірно, все створення нових масивів шкодить. А може, недбале змішування TypedArray та регулярного? Встановити розділ всередині TypedArray у сортування під нульовим значенням "нуль" та "не нуль +" може бути набагато краще, як це запропоновано у другому розділі моєї відповіді.
Пітер Кордес

8

Просто змініть умову вашої функції порівняння, як це -

let arr = [-1, 0, 1, 0, 2, -2, 0, 3, -3, 0, 4, -4, 0, 5, -5];
arr.sort((a, b) => {
   if(a && b) return b-a;
   if(!a && !b) return 0;
   return !a ? -1 : 1;
});

console.log(arr);


6
Це не є послідовним порівняльником, якщо будь-який із вхідних даних може бути негативним; за вашим порівнянням, 0 сортування перед 1, сортування перед -1, сортування перед 0.
Ільмарі Каронен

Оновлено негативні дані
Харунур Рашид

@SalmanA, Якщо обоє дорівнюють нулю, умова !aвсе-таки є вірною. Він повернеться-1
Харунер Рашид

Я не думаю, що велика справа - це і b - нуль @SalmanA
Харунур Рашид

1
Якщо бути точним, я відредагував відповідь. Щойно додав умовуa=b=0
Харунур Рашид

6

Тут не грають коди в гольф:

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5, -1];
arr.sort(function(a, b) {
  if (a === 0 && b !== 0) {
    // a is zero b is nonzero, a goes first
    return -1;
  } else if (a !== 0 && b === 0) {
    // a is nonzero b is zero, b goes first
    return 1;
  } else {
    // both are zero or both are nonzero, sort descending
    return b - a;
  }
});
console.log(arr.toString());


5

Не пишіть власне числове сортування, якщо воно вже існує. Те, що ви хочете зробити, - це саме те, що ви говорите в заголовку; сортувати числа у порядку зменшення, за винятком нулів на початку.

const zeroSort = arr => [...arr.filter(n => n == 0),
                         ...new Float64Array(arr.filter(n => n != 0)).sort().reverse()];

console.log(zeroSort([0, 1, 0, 2, 0, 3, 0, 4, 0, 500]));

Не пишіть жодного коду, який вам не потрібно; ви можете помилитися

Виберіть TypedArray виходячи з того, який тип номерів ви хочете обробляти масив. Float64 - це хороший стандарт за замовчуванням, оскільки він обробляє всі звичайні номери JS.


@IlmariKaronen Краще?
JollyJoker

Вам не потрібно двічі фільтрувати; як показує моя відповідь, ви можете фільтрувати один раз і віднімати довжини, щоб отримати число Array(n).fill(0).
Пітер Кордес

@PeterCordes Хоча це, можливо, можна назвати простішим, я думаю, що код стає досить довгим, щоб бути менш простим для читання
JollyJoker

Так, для ефективності потрібен 1 додатковий рядок для tmp var. У моїй відповіді використовуються кілька додаткових рядків, оскільки я звик до C і C ++ та мови збірки, а наявність більше окремих рядків полегшує відстеження створеного компілятором asm назад до заяви, відповідального за нього при оптимізації / профілюванні. Плюс зазвичай я не роблю JS, тому я не відчував ніякої необхідності набивати все це на пару ліній. Але ви могли б зробити let f64arr = new Float64Array(arr.filter(n => n != 0)), тоді [ ...Array(arr.length - f64arr.length).fill(0),... так що він додає 1 додатковий рядок і спрощує останній рядок.
Пітер Кордес

4

Ви можете зробити це так:

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

let result = arr.sort((a,b) => {
  if(a == 0 || b == 0)
    return a-b;
  return b-a;
})
console.log(result)

або ви можете це зробити:

let arr = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];

let result = arr.sort().sort((a,b) => {
  if(a > 0 && b > 0)
    return b-a
  return 0
})

console.log(result)


4
Ваше перше рішення має ту саму проблему послідовності з негативними введеннями, що і відповідь Харунура Рашида. Другий з них є послідовним, але розглядає всі від’ємні числа як нульові, залишаючи невизначеним їх взаємний порядок сортування.
Ільмарі Каронен

-2

const myArray = [0, 1, 0, 2, 0, 3, 0, 4, 0, 5];
const splitArray = myArray.reduce((output, item) => {
     if(!item) output[0].push(item);
    else output[1].push(item);
    return output;
}, [[], []]);
const result = [...splitArray[0], ...splitArray[1].reverse()]
console.log(result);

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.