Öğeleri yeniden sıralarken sıralama anahtarları oluşturma


11

Son kullanıcının istediği bir siparişte organize edebileceği birkaç öğemiz var. Öğe kümesi sıralanmamış, ancak her öğe değiştirilebilen bir sıralama anahtarı içeriyor.

Eklenen veya taşınan bir öğe için ilk öğe, son öğe veya herhangi iki öğe arasında yeni bir sıralama anahtarı oluşturulmasına izin verecek bir algoritma arıyoruz. Yalnızca taşınan öğenin sıralama anahtarını değiştirmek zorundayız.

Örnek bir algoritma, her bir sıralama anahtarının bir kayan nokta sayısı olması ve bir öğeyi iki öğe arasına yerleştirirken, sıralama anahtarını ortalama olarak ayarlamanızdır. Bir öğeyi ilk veya son yerleştirmek en dıştaki değeri + - 1 alır.

Buradaki sorun, kayan nokta hassasiyetinin sıralamanın başarısız olmasına neden olabilmesidir. Kesirli bir sayıyı temsil etmek için iki tamsayı kullanmak, sayıların o kadar büyük olmasını sağlayabilir ki, bunlar normal sayısal türlerde (örn. JSON olarak aktarılırken) doğru bir şekilde temsil edilemez. BigInts kullanmak istemezdik.

Bunun için, örneğin bu eksikliklerden etkilenmeyecek olan dizeleri kullanarak işe yarayacak uygun bir algoritma var mı?

Çok sayıda hamleyi desteklemiyoruz, ancak yukarıda açıklanan algoritma, yaklaşık 50 hamleden sonra çift kesinlikli kayar nokta sayısı üzerinde başarısız olabilir.


Dizeler bariz bir seçimdir, çünkü çatallanmak için sonuna karakter eklemeye devam edebilirsiniz. Bununla birlikte, buna yaklaşmanın daha iyi bir yolu olduğunu hissediyorum.
Robert Harvey

Başımın üstünden, diğer öğelerin tuşlarını değiştirmeden dizeleri kullanarak nasıl çalışacağını göremiyorum.
Sampo

3
Tanımladığınız soruna Sipariş Bakım Sorunu
Nathan Merrill

1
Neden listedeki diğer öğeleri değiştirmemekle ilgileniyorsunuz?
GER

1
Dizelerle nasıl çalıştıracağınız şöyledir: A, B, C- A, AA, B, C- A, AA, AB, B, C- A, AA, AAA, AAB, AAC, AB, AC, B, C. Tabii ki, muhtemelen harflerinizi daha fazla boşluk bırakmak istersiniz, böylece dizeler çok hızlı büyümez, ancak yapılabilir.
Robert Harvey

Yanıtlar:


4

Tüm yorum ve cevapların bir özeti olarak:

TL; DR - Başlangıçta önerilen algoritma ile çift kesinlikli kayar nokta sayılarının kullanılması, çoğu pratik (en azından elle sipariş edilen) ihtiyaçlar için yeterli olmalıdır. Öğelerin ayrı sıralı bir listesini tutmak da dikkate alınmalıdır. Diğer çeşit anahtar çözümler biraz hantaldır.

İki sorunlu işlem, başlangıç ​​/ bitiş öğelerini tekrar tekrar eklemek ve öğeleri aynı noktaya tekrar tekrar eklemek veya taşımaktır (örneğin, üç öğeyi üçüncü öğe arasında tekrar tekrar hareket ettirmek veya ikinci öğe olarak tekrar tekrar yeni öğeler eklemek) elemanı) sağlanabilir.

Teorik bir bakış açısından (yani sonsuz yeniden sıralamaya izin vermek), düşünebildiğim tek çözüm, iki sınırsız boyut tamsayısını kesirli a / b olarak kullanmaktır. Bu, orta kesici uçlar için sonsuz hassasiyet sağlar, ancak sayılar giderek büyüyebilir.

Dizeler çok sayıda güncellemeyi destekleyebilir (yine de her iki işlem için algoritmayı anlamada sorun yaşıyorum), ancak sonsuz değil, çünkü ilk konumda sonsuz sayıda (en azından normal dize sıralama kullanarak) ekleyemezsiniz karşılaştırma).

Tamsayılar, sıralama anahtarları için bir kaç başlangıç ​​eki yapabileceğinizi sınırlayan bir başlangıç ​​aralığı seçmeyi gerektirir. Başlangıçta 1024 tuşunu birbirinden ayırarak ayırırsanız, bitişik sayılara sahip olmadan önce yalnızca en kötü durumda 10 ara yerleştirme gerçekleştirebilirsiniz. Daha büyük bir başlangıç ​​aralığı seçmek, kaç adet ilk / son kesici uç yapabileceğinizi sınırlar. 64 bitlik bir tamsayı kullanarak, her iki yönde de ~ 63 işlemle sınırlandırılırsınız; bu işlem, orta uçlar ve ilk / son uçlar arasında bir önsel arasında bölünmeniz gerekir.

Kayan nokta değerlerini kullanmak, a priori aralığını seçme ihtiyacını ortadan kaldırır. Algoritma basit:

  1. Eklenen ilk öğenin sıralama tuşu 0,0
  2. İlk ya da son eklenen (ya da taşınan) bir eleman ilk elemanın sıralama tuşuna sahiptir - sırasıyla 1.0 ya da son eleman + 1.0.
  3. İki öğe arasına yerleştirilen (veya taşınan) bir öğe, ikisinin ortalamasına eşit bir sıralama tuşuna sahiptir.

Çift hassasiyetli bir şamandıra kullanmak, 52 en kötü durumdaki orta kesici uçlara ve etkili bir şekilde sonsuz (yaklaşık 1e15) ilk / son kesici uçlara izin verir. Uygulamada, öğeleri algoritma etrafında hareket ettirirken kendini düzeltmelidir, çünkü bir öğeyi ilk veya son hareket ettirdiğinizde kullanılabilecek aralığı genişletir.

Çift hassasiyetli şamandıralar, tüm platformlar tarafından desteklenmeleri ve neredeyse tüm taşıma formatları ve kütüphaneleri tarafından kolayca depolanmaları ve taşınmaları avantajına sahiptir. Biz bunu kullanarak sona erdi.


1

@ Sampo'nun özetine dayanan TypeScript'te bir çözüm yazdım. Kod aşağıda bulunabilir.

Yol boyunca birkaç fikir kazandı.

  • Sadece ortada ekleme mevcut iki çeşit anahtarlar arasında yaptığı takas, (yani yeniden düzenleme) Yeni bir sıralama anahtarı oluşturmanız gerekecektir değil böler (yani yeni orta noktalarından) neden olur. İki öğeyi taşırsanız ve bunlardan sadece birine dokunursanız, listede hangi iki öğenin konum değiştirdiği hakkında bilgi kaybedersiniz. Başlamak için bir gereklilik olsa bile, bunun iyi bir fikir olduğuna dikkat edin

  • Her 1074: orta nokta ayrımında kayan nokta aralığını normalleştirmemiz gerekir. Bunu, yeni orta noktanın değişmezi tatmin edip etmediğini kontrol ederek tespit ediyoruz.

    a.sortKey < m && m < b.sortKey

  • Sıralama anahtarları normalleştirildiğinden, normalleştirme hala her 1074orta nokta ayrımında gerçekleşir. Başlamak için sayıları daha geniş bir şekilde dağıtırsak durum düzelmeyecekti.

  • Sıralama anahtarı normalizasyonu inanılmaz derecede nadirdir. Bu maliyeti normalleştirmenin fark edilmeyeceği noktaya kadar amorti edeceksiniz. Yine de, 1000'den fazla elemanınız varsa, bu yaklaşıma dikkat ediyorum.


export interface HasSortKey {
  sortKey: number;
}

function normalizeList<T extends HasSortKey>(list: Array<T>) {
  const normalized = new Array<T>(list.length);
  for (let i = 0; i < list.length; i++) {
    normalized[i] = { ...list[i], sortKey: i };
  }
  return normalized;
}

function insertItem<T extends HasSortKey>(
  list: Array<T>,
  index: number,
  item: Partial<T>
): Array<T> {
  if (list.length === 0) {
    list.push({ ...item, sortKey: 0 } as T);
  } else {
    // list is non-empty

    if (index === 0) {
      list.splice(0, 0, { ...item, sortKey: list[0].sortKey - 1 } as T);
    } else if (index < list.length) {
      // midpoint, index is non-zero and less than length

      const a = list[index - 1];
      const b = list[index];

      const m = (a.sortKey + b.sortKey) / 2;

      if (!(a.sortKey < m && m < b.sortKey)) {
        return insertItem(normalizeList(list), index, item);
      }

      list.splice(index, 0, { ...item, sortKey: m } as T);
    } else if (index === list.length) {
      list.push({ ...item, sortKey: list[list.length - 1].sortKey + 1 } as T);
    }
  }
  return list;
}

export function main() {
  const normalized: Array<number> = [];

  let list: Array<{ n: number } & HasSortKey> = [];

  list = insertItem(list, 0, { n: 0 });

  for (let n = 1; n < 10 * 1000; n++) {
    const list2 = insertItem(list, 1, { n });
    if (list2 !== list) {
      normalized.push(n);
    }
    list = list2;
  }

  let m = normalized[0];

  console.log(
    normalized.slice(1).map(n => {
      const k = n - m;
      m = n;
      return k;
    })
  );
}

0

Orada bulundum, bunu yaptım, tekrar yapmak zorunda kalabilirler. Sıralama anahtarı olarak bir dize kullanın, her zaman verilen iki anahtar arasında bir anahtar bulabilirsiniz. Dizeler zevkinize göre çok uzunsa, birden fazla veya tüm sıralama tuşlarını değiştirmeniz gerekir.


1
Yine de başka bir dize anahtarından önce olan bir anahtarı bulamazsınız.
Sampo

-1

Tamsayıları kullanın ve ilk liste için sıralama tuşunu 500 * öğe numarası olarak ayarlayın. Öğeler arasına yerleştirirken ortalamayı kullanabilirsiniz. Bu, birçok eklemenin başlamasını sağlar


2
Bu aslında bir şamandıra kullanmaktan daha kötü. 500 başlangıç ​​aralığı, yalnızca 8-9 orta nokta eklemeye (2 ^ 9 = 512) izin verirken, bir çift şamandıra yaklaşık 50'ye izin verir, başlangıçta bir aralık seçme sorunu yoktur.
Sampo

500 boşluk kullanın ve yüzün!
Rob Mulder

Bir şamandıra kullanıldığında, ara yerleştirmeler için sınırlayıcı faktör anlamlıdaki bit sayısı olduğundan boşluk bir fark yaratmaz. Bu yüzden şamandıraları kullanırken 1.0 varsayılan boşluğunu önerdim.
Sampo
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.