Sortowanie liczb w kolejności malejącej, ale z początkiem „0”

36

Mam wyzwanie w JavaScript, które próbuję już wymyślić.

Rozważ tę tablicę:

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

Muszę przedstawić ten wynik:

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

Podążam tą linią logiczną, aby ustawić zera na wierzchu, dostosowując wartość indeksu:

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

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

    return y - x;
});

Ale utknąłem na tym wyniku:

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

Czy ktoś ma jakieś wskazówki, jak to rozwiązać?

lianbwl
źródło
6
Czy to gwarancja, że ​​nie ma liczb ujemnych?
Michael - Where's Clay Shirky
2
Czy to nie naprawia, jeśli ostatnia linia jest przełączona na return x - y;?
Kaczka Mooing
2
Czy w JavaScript byłoby zliczanie i usuwanie zer, sortowanie pozostałych elementów normalnie? (bez niestandardowego komparatora, więc mam nadzieję, że silnik JS może korzystać z wbudowanej funkcji sortowania liczb). Następnie wstaw odpowiednią liczbę zer do wyniku. Jeśli jest wiele zer, usunięcie ich przed sortowaniem stanowi mniejszy problem. Lub zamień zera na początek tablicy za pomocą jednego przejścia, a następnie posortuj ogon tablicy?
Peter Cordes,
2
Czy to się kiedykolwiek zdarza return y - x;? Nawet w javascript nie mogę wymyślić niczego, co nie byłoby ani ===0ani !==0.
George T
2
JSPerf dla odpowiedzi: jsperf.com/stackoverflow-question-58933996-v2
Salman A

Odpowiedzi:

35

Możesz sortować według delty bi a(dla sortowania malejącego) i brać Number.MAX_VALUE, dla wartości falsy takich jak zero.

To:

Number.MAX_VALUE - Number.MAX_VALUE

jest równy zero.

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);

Nina Scholz
źródło
13
Twoja funkcja porównanie powróci NaN, gdy oba ai bsą równe zeru. Może to być niepożądane zachowanie.
dan04,
20
Wyniki Array.prototype.sortsą zdefiniowane w implementacji, jeśli komparator kiedykolwiek zwróci NaN, więc ten komparator jest złym pomysłem. Stara się być sprytny i robi to źle.
użytkownik2357112 obsługuje Monikę
3
Co jeśli element w tablicy to Infinity? Funkcja porównania zwraca 0 podczas porównywania liczby nieskończoności z 0.
Marco
4
Dziękuję wam wszystkim. Biorę największą liczbę, którą można odjąć od siebie i mam nadzieję, że ta liczba nie jest używana w tablicy op.
Nina Scholz,
4
Absolutnie nieczytelne. Fajnie, ale nieczytelnie.
Salman A,
24

Jak mdn docs mówi:

Jeśli a i b to dwa porównywane elementy, to:

Jeśli compareFunction(a, b)zwraca mniej niż 0, posortuj awedług indeksu niższego niż b(tzn. Pierwszy jest).

Jeśli compareFunction(a, b)zwraca 0, pozostaw aib niezmienione względem siebie, ale posortowane względem wszystkich różnych elementów. Uwaga: standard ECMAscript nie gwarantuje takiego zachowania, dlatego nie wszystkie przeglądarki (np. Wersje Mozilli z co najmniej 2003 r.) Przestrzegają tego.

Jeśli compareFunction(a, b) zwraca więcej niż 0, posortuj bwedług indeksu niższego niż (tzn. Jest bpierwszy).

compareFunction(a, b)musi zawsze zwracać tę samą wartość, gdy podano określoną parę elementów ai bjako dwa argumenty. Jeśli zwracane są niespójne wyniki, kolejność sortowania jest niezdefiniowana.

Tak więc funkcja porównania ma następującą postać:

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);

StepUp
źródło
3
+1 za podanie, jak się wydaje, jedynego jak dotąd rozwiązania z funkcją porównania, która jest faktycznie spójna (jak zdefiniowano w standardzie ) dla wszystkich danych wejściowych, w tym liczb ujemnych.
Ilmari Karonen,
3
@IlmariKaronen: Niestety, porównanie jest spójna dla liczb ujemnych, ale jest to błędne porównanie liczb ujemnych. Negatywy sortują przed 0 za pomocą tego komparatora i sortują w kolejności rosnącej.
użytkownik2357112 obsługuje Monikę
2
@ user2357112supportsMonica Niemniej jednak OP nie określił pożądanego zachowania dla liczb ujemnych, a przy tym prototypie dostosowanie bodźców zachowania liczb ujemnych jest trywialne, aby spełnić wszelkie wymagania OP. Zaletą tej odpowiedzi jest to, że jest idiomatyczna i korzysta ze standardowej funkcji porównywania do wykonania zadania.
J ...
13

Jeśli zależy Ci na wydajności, prawdopodobnie najszybciej odfiltruje zera . Nie chcesz sorttracić czasu, nawet na nie patrząc, nie mówiąc już o dodaniu dodatkowej pracy do wywołania zwrotnego porównania w celu obsługi tego specjalnego przypadku.

Zwłaszcza jeśli spodziewasz się znacznej liczby zer, jedno przejście przez dane w celu ich odfiltrowania powinno być znacznie lepsze niż wykonanie większego sortowania O (N log N), który będzie sprawdzał każde zera wiele razy.

Po zakończeniu możesz skutecznie wstawić poprawną liczbę zer.

Równie łatwo jest odczytać wynikowy kod. Użyłem TypedArray, ponieważ jest wydajny i ułatwia sortowanie numeryczne . Ale możesz użyć tej techniki ze zwykłym Array, używając standardowego idiomu (a,b)=>a-bfor .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);
*/

Nie wiem, czy TypedArray, .sort()a następnie .reversejest szybszy niż używanie niestandardowej funkcji porównywania do sortowania w kolejności malejącej. Lub jeśli możemy kopiować i cofać w locie za pomocą iteratora.


Warto również rozważyć: użyj tylko jednego Tablicy Typów o pełnej długości .

Zamiast tego użyj .filterpętli i zamień zera na przód tablicy podczas pracy. To zajmuje jedno przejście przez twoje dane.

Następnie użyj .subarray() aby uzyskać nowy widok TypedArray niezerowych elementów tego samego bazowego ArrayBuffer. Sortowanie, które pozostawia pełną tablicę z zerowym początkiem i posortowanym ogonem, przy czym sortowanie obejmuje tylko niezerowe elementy.

Nie widziałem funkcji partycji w metodach Array ani TypedArray, ale ledwo znam JavaScript. Przy dobrym JIT pętla nie powinna być o wiele gorsza niż wbudowana metoda. (Zwłaszcza gdy ta metoda wymaga wywołania zwrotnego.filter , i jeśli nie używa się reallocpod maską do zmniejszenia, musi dowiedzieć się, ile pamięci do przydzielenia, zanim faktycznie się odfiltruje).

Użyłem tablicy regularnej .filter() przed konwersją na tablicę TypedArray. Jeśli Twój wkład jest już TypedArray, nie masz tego problemu, a ta strategia staje się jeszcze bardziej atrakcyjna.

Peter Cordes
źródło
Może to być prawdopodobnie prostsze i / lub bardziej idiomatyczne lub co najmniej bardziej kompaktowe. IDK, jeśli silnikom JS uda się wyeliminować / zoptymalizować wiele kopiowania lub inicjalizacji zera, lub jeśli pomogłoby to w użyciu pętli zamiast metod TypedArray, np. Do kopiowania wstecz zamiast do tyłu + .set. Nie udało mi się go przetestować; jeśli ktoś chciałby to zrobić, byłbym zainteresowany.
Peter Cordes,
Według testów @ Salmana odpowiedź ta działa jak bzdura dla małych tablic (z ~ 1000 elementów, z czego 10% to 0). Przypuszczalnie całe tworzenie nowych tablic boli. A może niedbałe miksowanie TypedArray i regularnego? Partycjonowanie na miejscu wewnątrz TypedArray na sortowanie podtablicowe z zerową i niezerową + może być znacznie lepsze, jak zasugerowano w drugiej części mojej odpowiedzi.
Peter Cordes
8

Po prostu zmodyfikuj stan swojej funkcji porównania w ten sposób -

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);

Harunur Rashid
źródło
6
Nie jest to spójny komparator, jeśli którekolwiek z danych wejściowych może być ujemne; według twojego komparatora, 0 sortuje przed 1, który sortuje przed -1, który sortuje przed 0.
Ilmari Karonen
Zaktualizowano z ujemnymi
danymi
@ SalmanA, jeśli oba są równe zero, warunek !ajest nadal spełniony. Powróci-1
Harunur Rashid
Nie sądzę, że duża sprawa to a b oba są zerowe @SalmanA
Harunur Rashid
1
Mówiąc ściślej, zredagowałem odpowiedź. Właśnie dodałem warunek dlaa=b=0
Harunur Rashid
6

Nie grasz tutaj w golfa kodowego:

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());

Salman A.
źródło
5

Nie pisz własnego sortowania numerycznego, jeśli już istnieje. To, co chcesz zrobić, to dokładnie to, co mówisz w tytule; sortuj liczby w kolejności malejącej, z wyjątkiem zer na początku.

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]));

Nie pisz kodu, którego nie potrzebujesz; możesz to źle pomylić.

Wybierz TypedArray w oparciu o typ liczb, które ma obsługiwać tablica. Float64 jest dobrym domyślnym, ponieważ obsługuje wszystkie normalne liczby JS.

JollyJoker
źródło
@IlmariKaronen Better?
JollyJoker,
Nie musisz filtrować dwa razy; jak pokazuje moja odpowiedź, możesz raz przefiltrować i odjąć długości, aby uzyskać liczbę Array(n).fill(0).
Peter Cordes,
@PeterCordes Chociaż można by to nazwać prostszym, myślę, że kod staje się wystarczająco długi, aby był mniej czytelny
JollyJoker
Tak, wydajność wymagałaby 1 dodatkowej linii dla tmp var. Moja odpowiedź korzysta z wielu dodatkowych wierszy, ponieważ jestem przyzwyczajony do C i C ++ oraz języka asemblera, a posiadanie większej liczby osobnych wierszy ułatwia śledzenie generowanego przez kompilator asm z powrotem do instrukcji odpowiedzialnej za to podczas optymalizacji / profilowania. Poza tym zwykle nie robię JS, więc nie czułem potrzeby wrzucania wszystkiego na kilka linii. Ale można zrobić let f64arr = new Float64Array(arr.filter(n => n != 0)), to [ ...Array(arr.length - f64arr.length).fill(0),... tak to dodaje 1 dodatkową linię i upraszcza ostatnią linię.
Peter Cordes,
4

Możesz to zrobić w następujący sposób:

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)

lub możesz to zrobić:

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)

NuOne
źródło
4
Twoje pierwsze rozwiązanie ma ten sam problem ze spójnością z negatywnymi danymi wejściowymi, co odpowiedź Harunura Rashida. Drugi jest spójny, ale traktuje wszystkie liczby ujemne jako równe zeru, pozostawiając ich niezdefiniowany porządek sortowania.
Ilmari Karonen,
-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);

Max
źródło