Sayıları azalan sırada ama başlangıçta `0`larla sıralama


36

JavaScript'te bir süredir anlamaya çalıştığım bir zorluk var.

Şu diziyi düşünün:

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

Bu sonucu çıkarmak zorundayım:

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

İndeks değerini ayarlayarak sıfırları öne konumlandırmak için bu mantık satırını takip ediyorum:

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

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

    return y - x;
});

Ama bu sonuca takılı kaldım:

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

Herkes bunu çözmek için herhangi bir ipucu var mı?


6
Negatif sayı olmadığı garanti ediliyor mu?
Michael - Clay Shirky

2
Son satır değiştirilirse düzeltilmez return x - y;mi?
Mooing Duck

2
JavaScript'te sıfırları saymak ve kaldırmak, kalan öğeleri normal olarak sıralamak etkili olur mu? (özel bir karşılaştırıcı olmadan, umarım JS motoru yerleşik bir sayı sıralama işlevi kullanabilir). Ardından, sonuca doğru sayıda sıfır ekleyin. Çok sayıda sıfır varsa, sıralamadan önce bunları kaldırmak daha küçük bir sorun yaratır. Ya da sıfırları bir geçişle dizinin başlangıcına değiştirin, sonra dizinin kuyruğunu sıralayın?
Peter Cordes

2
Bu hiç geçiyor mu return y - x;? Javascript'te bile, ne ===0de ne olacak bir şey düşünemiyorum !==0.
George T

Yanıtlar:


35

bVe a(azalan sıralama için) deltalarına göre sıralayabilir ve Number.MAX_VALUEsıfır gibi yanlış değerler için alabilirsiniz .

Bu:

Number.MAX_VALUE - Number.MAX_VALUE

sıfıra eşittir.

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
NaNHer ikisi de ave bsıfırsa karşılaştırma işleviniz geri döner . Bu istenmeyen bir davranış olabilir.
dan04

20
Sonuçları Array.prototype.sortuygulanması tanımlı karşılaştırıcı hiç döndüreceği olan NaNbu karşılaştırıcı kötü bir fikir yüzden. Akıllı olmaya çalışır ve yanlış anlar.
user2357112 Monica

3
Dizinin içindeki bir öğe Infinity ise ne olur? Bir Infinity numarası 0 ile karşılaştırılırken karşılaştırma fonksiyonu 0 değerini döndürür.
Marco

4
hepinize teşekkür ederim. kendisi tarafından çıkarılabilir en büyük sayı almak ve bu sayı op dizisinde kullanılmaz umuyoruz.
Nina Scholz

4
Kesinlikle okunamaz. Serin, ama okunamaz.
Salman A

24

Mdn belgelerinin dediği gibi:

A ve b karşılaştırılan iki öğeyse, o zaman:

Eğer compareFunction(a, b)daha küçük döndürürse, daha düşük bir dizine 0sıralayın (yani önce bir gelir).ab

Eğer compareFunction(a, b)getiri 0, bir ayrılıp birbirlerine göre b değişmeden, ancak tüm farklı unsurları açısından sıralanmış. Not: ECMAscript standardı bu davranışı garanti etmez, bu nedenle tüm tarayıcılar (örneğin, en az 2003'e dayanan Mozilla sürümleri) buna saygı göstermez.

Eğer compareFunction(a, b) sıralama 0'dan büyük getiriler, bbir dizin için daha düşük (yani bönce gelir).

compareFunction(a, b)elemanların belirli bir çift verilen her zaman aynı değeri gerekir ave biki bağımsız değişken olarak. Tutarsız sonuçlar döndürülürse, sıralama düzeni tanımsızdır.

Yani, karşılaştırma işlevi aşağıdaki forma sahiptir:

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
Negatif sayılar da dahil olmak üzere tüm girdiler için gerçekten tutarlı ( standartta tanımlandığı gibi) bir karşılaştırma işleviyle şimdiye kadar tek çözüm gibi görünen şeyi vermek için +1 .
Ilmari Karonen

3
@IlmariKaronen: Maalesef, karşılaştırma negatif sayılar için tutarlıdır , ancak negatif sayılar için yanlış karşılaştırmadır . Negatifler bu karşılaştırıcı ile 0'dan önce sıralanır ve artan sırada sıralanır.
user2357112 Monica

2
Bununla birlikte, OP negatif sayılar için istenen davranışı belirtmemiştir ve bu prototip ile negatif sayıların davranışını OP'nin sahip olduğu her şeye uyacak şekilde ayarlamak önemsizdir. Bu cevapta iyi olan şey, deyimsel olması ve işi yapmak için standart bir karşılaştırma işlevi kullanmasıdır.
J ...

13

Verimliliği önemsiyorsanız, öncelikle sıfırları filtrelemek en hızlı yöntemdir . Bu sortözel durumu ele almak için karşılaştırma geri aramanıza fazladan iş ekleyerek, onlara bakmakla bile zaman kaybetmek istemezsiniz .

Özellikle önemli sayıda sıfır bekliyorsanız, bunları filtrelemek için verilerin üzerinden bir geçiş, her sıfıra birden çok kez bakacak daha büyük bir O (N log N) türünden çok daha iyi olmalıdır.

Şunları yapabilirsiniz verimli bitirdiniz sonra sıfır doğru sayıda getirebilirsiniz.

Ortaya çıkan kodu okumak da kolaydır. Ben verimli ve sayısal sıralama kolaylaştırır çünkü TypedArray kullandım . Ancak bu tekniği (a,b)=>a-bfor için standart deyimi kullanarak normal Array ile kullanabilirsiniz .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()olup olmadığını bilmiyorum ve sonra .reverseazalan düzende sıralamak için özel bir karşılaştırma işlevi kullanarak daha hızlı. Ya da bir yineleyici ile anında kopyalayıp geri çevirebilirsek.


Ayrıca dikkate değer: sadece tam uzunlukta bir TypedArray kullanın .

Kullanmak yerine, .filterüzerine gelin ve sıfırları dizinin önüne kaydırın . Bu, verilerinizin üzerinden bir kez geçer.

Ardından .subarray(), aynı temel ArrayBuffer öğesinin sıfır olmayan öğelerinin yeni bir TypedArray görünümünü elde etmek için kullanın . Sıralamayı sıfırdan farklı elemanlara bakacak şekilde sıfır başlangıcı ve sıralı kuyruğu ile tam dizi bırakacak sıralama.

Array veya TypedArray yöntemlerinde bir bölüm işlevi görmedim, ancak JavaScript'i zar zor biliyorum. İyi JIT ile, bir döngü yerleşik bir yöntemden çok daha kötü olmamalıdır. (Özellikle bu yöntem bir geri çağırma içeriyorsa .filterve büzülmek reallociçin kaputun altında kullanılmadıkça, gerçekten filtrelenmeden önce ne kadar bellek ayıracağını bulmalıdır).

Bir TypedArray dönüştürmeden .filter() önce düzenli Array kullandım . Girişiniz zaten bir TypedArray ise, bu sorunla karşılaşmazsınız ve bu strateji daha da cazip hale gelir.


Bu muhtemelen daha basit ve / veya daha deyimsel veya en azından daha kompakt olabilir. JSK, çok sayıda kopyalama veya sıfır başlatma işlemini ortadan kaldırmayı / optimize etmeyi başarıyorsa veya TypedArray yöntemleri yerine döngüler kullanılmasına yardımcı olursa, örneğin ters + .set yerine geriye doğru kopyalamak için IDK. Hız testi yapmak için uğraşmadım; eğer birisi bunu yapmak isterse ilgilenirim.
Peter Cordes

@ Salman'ın testine göre, bu cevap ufacık diziler (% 10'u 0 olan ~ 1000 element) için bok gibi çalışır. Muhtemelen yeni dizilerin tüm yaratılması acıyor. Ya da belki TypedArray ve düzenli dikkatsiz karıştırma? Cevabımın 2. bölümünde önerildiği gibi, bir TypedArray içindeki sıfırdan farklı ve sıfırdan farklı + alt dizi sıralamasında yerinde bölüm çok daha iyi olabilir.
Peter Cordes

8

Karşılaştırma işlevinizin durumunu şu şekilde değiştirin -

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
Girişlerden herhangi biri negatifse bu tutarlı bir karşılaştırıcı değildir; Karşılaştırmanıza göre, 0'dan önce sıralama 1'den önce 0, 0'dan önce sıralama -1
Ilmari Karonen

Negatif girdilerle güncellendi
Harunur Rashid

@SalmanA, Her ikisi de sıfırsa, durum !ahala doğrudur. Geri dönecek-1
Harunur Rashid

Ben büyük bir anlaşma olduğunu düşünmüyorum bir ve b her ikisi de sıfır @SalmanA
Harunur Rashid

1
Kesin olmak gerekirse, cevabı düzenledim. Sadece için bir koşul ekledia=b=0
Harunur Rashid

6

Burada kod golf oynamıyor:

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

Zaten varsa, kendi sayısal sıralamanızı yazmayın. Ne yapmak istediğiniz tam olarak başlıkta söylediklerinizdir; sayıları başlangıçtaki sıfırlar dışında azalan sırada sıralayın.

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

İhtiyacınız olmayan herhangi bir kod yazmayın; yanlış anlayabilirsin.

Dizinin işlemesini istediğiniz sayı türüne göre TypedArray öğesini seçin . Float64, tüm normal JS numaralarını işlediğinden iyi bir varsayılan değerdir.


@IlmariKaronen Daha iyi mi?
JollyJoker

İki kez filtrelemenize gerek yoktur; cevabımın gösterdiği gibi, bir kez filtreleyebilir ve bir numara almak için uzunlukları çıkarabilirsiniz Array(n).fill(0).
Peter Cordes

@PeterCordes Muhtemelen daha basit olarak adlandırılabilir olsa da, kodun okunması daha az kolay olacak kadar uzun sürdüğünü düşünüyorum
JollyJoker

Evet, verimlilik için bir tmp var. Cevabım birden fazla satır kullanıyor çünkü C ve C ++ ve montaj diline alışkınım ve daha ayrı satırlara sahip olmak, derleyici tarafından oluşturulan asm'ı optimize ederken / profil oluştururken sorumlu ifadeye kadar izlemeyi kolaylaştırıyor. Ayrıca normalde JS yapmıyorum, bu yüzden hepsini birkaç satıra sıkıştırmaya gerek duymadım. Ama yapabilirsin let f64arr = new Float64Array(arr.filter(n => n != 0)), o zaman [ ...Array(arr.length - f64arr.length).fill(0),... 1 ekstra çizgi ekler ve son çizgiyi basitleştirir.
Peter Cordes

4

Bunu şu şekilde yapabilirsiniz:

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)

ya da bunu yapabilirsiniz:

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
İlk çözümünüz, Harunur Rashid'in cevabı ile negatif girdilerle aynı tutarlılık sorununa sahip. İkincisi ise ise tutarlı, ancak karşılıklı sıralama düzeni bırakarak sıfıra eşit olarak muamele tüm negatif sayılar tanımsız.
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);

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.