Menghasilkan kunci sortir saat menyusun ulang item


11

Kami memiliki sejumlah item yang dapat diatur oleh pengguna akhir menjadi pesanan yang diinginkan. Set item tidak teratur, tetapi setiap item berisi kunci pengurutan yang dapat dimodifikasi.

Kami sedang mencari algoritme yang memungkinkan pembuatan kunci sortir baru untuk item yang ditambahkan atau dipindahkan menjadi item pertama, item terakhir, atau antara dua item. Kami berharap hanya perlu mengubah kunci sortir dari item yang dipindahkan.

Contoh algoritma adalah memiliki setiap kunci sortir menjadi angka floating point, dan ketika menempatkan item di antara dua item, atur kunci sortir menjadi rata-rata. Menempatkan item pertama atau terakhir akan mengambil nilai terluar + - 1.

Masalahnya di sini adalah bahwa presisi floating point dapat menyebabkan penyortiran gagal. Menggunakan dua bilangan bulat untuk merepresentasikan angka fraksional dapat membuat angka tersebut menjadi sangat besar sehingga mereka tidak dapat secara akurat diwakili dalam tipe numerik reguler (misalnya ketika mentransfer sebagai JSON). Kami tidak ingin menggunakan BigInts.

Apakah ada algoritma yang cocok untuk ini yang akan berfungsi, misalnya, menggunakan string, yang tidak akan terpengaruh oleh kekurangan ini?

Kami tidak mencari untuk mendukung sejumlah besar gerakan, tetapi algoritma yang dijelaskan di atas dapat gagal pada angka floating point presisi ganda setelah sekitar 50 gerakan.


String adalah pilihan yang jelas, karena Anda bisa terus menambahkan karakter ke ujungnya agar terbagi dua. Yang mengatakan, saya merasa ada cara yang lebih baik untuk mendekati ini.
Robert Harvey

Dari atas kepala saya, saya tidak melihat cara membuatnya bekerja menggunakan string baik tanpa mengubah kunci item lain.
Sampo

3
Masalah yang Anda gambarkan disebut Masalah Pemeliharaan Pesanan
Nathan Merrill

1
Mengapa Anda khawatir tidak mengubah item lain dalam daftar?
APK

1
Cara Anda membuatnya bekerja dengan string adalah seperti ini: A, B, C- A, AA, B, C- A, AA, AB, B, C- A, AA, AAA, AAB, AAC, AB, AC, B, C. Tentu saja, Anda mungkin ingin melonggarkan surat Anda lebih banyak sehingga string tidak tumbuh begitu cepat, tetapi itu bisa dilakukan.
Robert Harvey

Jawaban:


4

Sebagai ringkasan dari semua komentar dan jawaban:

TL; DR - Menggunakan angka floating point presisi ganda dengan algoritma yang diusulkan semula harus cukup untuk kebutuhan yang paling praktis (setidaknya dipesan secara manual). Mempertahankan daftar unsur yang terpisah harus dipertimbangkan juga. Solusi kunci semacam lainnya agak rumit.

Dua operasi bermasalah adalah menyisipkan item di awal / akhir berulang-ulang, dan berulang kali memasukkan atau memindahkan item ke tempat yang sama (misalnya dengan tiga elemen berulang kali memindahkan elemen ketiga antara dua yang pertama, atau berulang kali menambahkan elemen baru sebagai yang kedua elemen).

Dari sudut pandang teoretis (yaitu memungkinkan pengurutan ulang tanpa batas), satu-satunya solusi yang dapat saya pikirkan adalah menggunakan dua bilangan bulat tak terbatas sebagai a / b fraksional. Ini memungkinkan ketelitian tak terbatas untuk insersi tengah, tetapi jumlahnya bisa menjadi semakin besar.

String mungkin dapat mendukung sejumlah besar pembaruan (meskipun saya masih mengalami kesulitan mencari tahu algoritma untuk kedua operasi), tetapi tidak terbatas, karena Anda tidak dapat menambahkan banyak tak terhingga di posisi pertama (setidaknya menggunakan pengurutan string biasa) perbandingan).

Integer akan membutuhkan pemilihan jarak awal untuk tombol sortir, yang membatasi berapa banyak insert tengah yang dapat Anda lakukan. Jika Anda awalnya menyortir tombol 1024 terpisah, Anda hanya dapat melakukan 10 insersi kasus terburuk sebelum Anda memiliki angka yang berdekatan. Memilih batas jarak awal yang lebih besar berapa banyak sisipan pertama / terakhir yang dapat Anda lakukan. Menggunakan integer 64-bit, Anda dibatasi pada ~ 63 operasi dengan cara apa pun, yang harus Anda pisahkan antara insert tengah dan insert pertama / terakhir a priori.

Menggunakan nilai floating point menghilangkan kebutuhan untuk memilih spasi apriori. Algoritma ini sederhana:

  1. Elemen pertama yang disisipkan memiliki kunci urut 0,0
  2. Suatu elemen yang disisipkan (atau dipindahkan) pertama atau terakhir memiliki kunci sortir dari elemen pertama - 1.0 atau elemen terakhir + 1.0, masing-masing.
  3. Elemen yang disisipkan (atau dipindahkan) antara dua elemen memiliki kunci sortir yang sama dengan rata-rata keduanya.

Menggunakan float presisi ganda memungkinkan 52 insersi mid-case terburuk dan efektif insersi pertama / terakhir infinite (sekitar 1e15). Dalam praktiknya saat memindahkan item di sekitar algoritme akan mengoreksi dirinya sendiri, karena setiap kali Anda memindahkan item pertama atau terakhir itu akan memperluas rentang yang dapat digunakan.

Pelampung presisi ganda juga memiliki keuntungan karena didukung oleh semua platform dan mudah disimpan dan diangkut oleh hampir semua format dan perpustakaan transportasi. Inilah yang akhirnya kami gunakan.


1

Saya menulis solusi dalam TypeScript berdasarkan ringkasan @ Sampo. Kode dapat ditemukan di bawah.

Beberapa wawasan diperoleh sepanjang jalan.

  • Hanya penyisipan di tengah antara dua kunci sortir yang ada perlu menghasilkan kunci sortir baru, bertukar (mis. Mengatur ulang) tidak menyebabkan perpecahan (yaitu titik tengah baru). Jika Anda memindahkan dua item dan Anda hanya menyentuh salah satunya, Anda kehilangan informasi tentang dua elemen yang berubah posisi dalam daftar. Bahkan jika itu adalah persyaratan untuk memulai, perhatikan bahwa itu adalah ide yang bagus

  • Setiap 1074: perpecahan titik tengah kita perlu menormalkan kisaran titik mengambang. Kami mendeteksi ini dengan hanya memeriksa apakah titik tengah baru memenuhi invarian

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

  • Penskalaan tidak masalah, karena tombol sortir dinormalisasi, normalisasi masih terjadi setiap 1074titik tengah terpecah. Situasi tidak akan membaik jika kami membagikan angka lebih banyak untuk memulai.

  • Sortir kunci normalisasi sangat jarang. Anda akan mengamortisasi biaya ini ke titik di mana normalisasi tidak terlihat. Padahal, saya akan berhati-hati dengan pendekatan ini jika Anda memiliki lebih dari 1000 elemen.


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

Pernah ke sana, melakukan itu, mungkin harus melakukannya lagi. Gunakan string sebagai kunci pengurutan, maka Anda selalu dapat menemukan kunci yang berada di antara dua kunci yang diberikan. Jika string terlalu panjang untuk selera Anda, Anda harus memodifikasi beberapa atau semua tombol sortir.


1
Anda tidak selalu dapat menemukan kunci yang ada sebelum kunci string lain.
Sampo

-1

Gunakan bilangan bulat, dan atur kunci pengurutan untuk daftar awal menjadi 500 * nomor item. Saat menyisipkan di antara item, Anda dapat menggunakan rata-rata. Ini akan memungkinkan banyak penyisipan untuk memulai


2
Ini sebenarnya lebih buruk daripada menggunakan pelampung. Jarak awal 500 hanya memungkinkan penyisipan titik tengah 8-9 (2 ^ 9 = 512), sedangkan pelampung ganda memungkinkan sekitar 50, tanpa masalah pada awalnya memilih spasi.
Sampo

Gunakan celah 500 dan mengapung!
Rob Mulder

Saat menggunakan float, celah tidak ada bedanya, karena faktor pembatas untuk insersi-tengah adalah jumlah bit dalam signifikan. Itu sebabnya saya mengusulkan gap default 1.0 ketika menggunakan floats.
Sampo
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.