Runtime: Tambahkan Antrian Prioritas<t>ke Koleksi</t>

Dibuat pada 30 Jan 2015  ·  318Komentar  ·  Sumber: dotnet/runtime

Lihat Proposal TERBARU di repo corefxlab.

Opsi Proposal Kedua

Proposal dari https://github.com/dotnet/corefx/issues/574#issuecomment -307971397

Asumsi

Elemen dalam antrian prioritas bersifat unik. Jika tidak, kami harus memperkenalkan 'pegangan' item untuk mengaktifkan pembaruan/penghapusannya. Atau perbarui/hapus semantik harus berlaku untuk pertama/semua, yang aneh.

Dimodelkan setelah Queue<T> ( tautan MSDN )

API

```c#
Antrian Prioritas kelas publik
: IE dapat dihitung,
IEnumerable<(Elemen elemen, prioritas TPrioritas)>,
IReadOnlyCollection<(elemen TElement, prioritas TPriority)>
// Koleksi tidak disertakan dengan sengaja
{
Antrian Prioritas publik();
Antrian Prioritas publik(IComparerpembanding);

public IComparer<TPriority> Comparer { get; }
public int Count { get; }
public bool IsEmpty { get; }

public bool Contains(TElement element);

// Peek & Dequeue
public (TElement element, TPriority priority) Peek(); // Throws if empty
public (TElement element, TPriority priority) Dequeue(); // Throws if empty
public bool TryPeek(out TElement element, out TPriority priority); // Returns false if empty
public bool TryDequeue(out TElement element, out TPriority priority); // Returns false if empty

// Enqueue & Update
public void Enqueue(TElement element, TPriority priority); // Throws if it is duplicate
public void Update(TElement element, TPriority priority); // Throws if element does not exist
public void EnqueueOrUpdate(TElement element, TPriority priority);
public bool TryEnqueue(TElement element, TPriority priority); // Returns false if it is duplicate (does NOT update it)
public bool TryUpdate(TElement element, TPriority priority); // Returns false if element does not exist (does NOT add it)

public void Remove(TElement element); // Throws if element does not exist
public bool TryRemove(TElement element); // Returns false if element does not exist

public void Clear();

public IEnumerator<(TElement element, TPriority priority)> GetEnumerator();
IEnumerator IEnumerable.GetEnumerator();

//
// Bagian pemilih
//
Antrian Prioritas publik(FuncprioritasPemilih);
Antrian Prioritas publik(FuncprioritySelector, IComparerpembanding);

public Func<TElement, TPriority> PrioritySelector { get; }

public void Enqueue(TElement element);
public void Update(TElement element);

}
````

Pertanyaan-pertanyaan terbuka:

  1. Nama kelas PriorityQueue vs. Heap
  2. Perkenalkan IHeap dan kelebihan konstruktor? (Haruskah kita menunggu nanti?)
  3. Perkenalkan IPriorityQueue ? (Haruskah kita menunggu nanti - contoh IDictionary )
  4. Gunakan pemilih (prioritas yang disimpan di dalam nilai) atau tidak (perbedaan 5 API)
  5. Gunakan tupel (TElement element, TPriority priority) vs. KeyValuePair<TPriority, TElement>

    • Haruskah Peek dan Dequeue lebih suka memiliki argumen out daripada Tuple?

  6. Apakah melempar Peek dan Dequeue berguna sama sekali?

Usulan Asli

Masalah https://github.com/dotnet/corefx/issues/163 meminta penambahan antrian prioritas ke struktur data kumpulan .NET inti.

Postingan ini, meskipun merupakan duplikat, dimaksudkan untuk bertindak sebagai penyerahan resmi ke Proses Peninjauan API corefx. Konten masalah adalah _speclet_ untuk System.Collections.Generic.PriorityQueue baruTipe.

Saya akan berkontribusi PR, jika disetujui.

Alasan dan Penggunaan

Perpustakaan Kelas Dasar .NET (BCL) saat ini tidak memiliki dukungan untuk koleksi produsen-konsumen yang dipesan. Persyaratan umum dari banyak aplikasi perangkat lunak adalah kemampuan menghasilkan daftar item dari waktu ke waktu dan memprosesnya dalam urutan yang berbeda dari urutan penerimaannya.

Ada tiga struktur data umum dalam hierarki System.Collections dari ruang nama yang mendukung kumpulan item yang diurutkan; System.Collections.Generic.SortedList, System.Collections.Generic.SortedSet, dan System.Collections.Generic.SortedDictionary.

Dari jumlah tersebut, SortedSet dan SortedDictionary tidak sesuai untuk pola produsen-konsumen yang menghasilkan nilai duplikat. Kompleksitas SortedList adalah (n) kasus terburuk untuk Tambah dan Hapus.

Struktur data yang jauh lebih hemat memori dan waktu untuk koleksi yang dipesan dengan pola penggunaan produsen-konsumen adalah antrian prioritas. Selain ketika pengubahan ukuran kapasitas diperlukan, kinerja penyisipan case (enqueue) dan penghapusan top (dequeue) yang lebih buruk adalah (log n) - jauh lebih baik daripada opsi yang ada di BCL.

Antrian prioritas memiliki tingkat penerapan yang luas di berbagai kelas aplikasi. Halaman Wikipedia tentang Antrian Prioritas menawarkan daftar banyak kasus penggunaan yang dipahami dengan baik. Sementara implementasi yang sangat terspesialisasi mungkin masih memerlukan implementasi antrian prioritas khusus, implementasi standar akan mencakup berbagai skenario penggunaan. Antrian prioritas sangat berguna dalam menjadwalkan output dari banyak produsen, yang merupakan pola penting dalam perangkat lunak yang sangat paralel.

Perlu dicatat bahwa baik pustaka standar C++ dan Java menawarkan fungsionalitas antrian prioritas sebagai bagian dari API dasar mereka.

API yang Diusulkan

``` C#
namespace System.Collections.Generic
{
///


/// Mewakili kumpulan objek yang dihapus dalam urutan yang diurutkan.
///

///Menentukan jenis elemen dalam antrian.
[DebuggerDisplay("Jumlah = {hitung}")]
[DebuggerTypeProxy(typeof(System_PriorityQueueDebugView<>))]
Antrian Prioritas kelas publik: IE dapat dihitung, ICollection, IEnumerable, IReadOnlyCollection
{
///
/// Menginisialisasi instance baru dari kelas
/// yang menggunakan pembanding default.
///

Antrian Prioritas publik();

    /// <summary>
    /// Initializes a new instance of the <see cref="PriorityQueue{T}"/> class 
    /// that has the specified initial capacity.
    /// </summary>
    /// <param name="capacity">The initial number of elements that the <see cref="PriorityQueue{T}"/> can contain.</param>
    /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="capacity"/> is less than zero.</exception>
    public PriorityQueue(int capacity);

    /// <summary>
    /// Initializes a new instance of the <see cref="PriorityQueue{T}"/> class 
    /// that uses a specified comparer.
    /// </summary>
    /// <param name="comparer">The <see cref="T:System.Collections.Generic.IComparer{T}"/> to use when comparing elements.</param>
    /// <exception cref="T:System.ArgumentNullException"><paramref name="comparer"/> is null.</exception>
    public PriorityQueue(IComparer<T> comparer);

    /// <summary>
    /// Initializes a new instance of the <see cref="PriorityQueue{T}"/> class 
    /// that contains elements copied from the specified collection and uses a default comparer.
    /// </summary>
    /// <param name="collection">The collection whose elements are copied to the new <see cref="PriorityQueue{T}"/>.</param>
    /// <exception cref="T:System.ArgumentNullException"><paramref name="collection"/> is null.</exception>
    public PriorityQueue(IEnumerable<T> collection);

    /// <summary>
    /// Initializes a new instance of the <see cref="PriorityQueue{T}"/> class 
    /// that contains elements copied from the specified collection and uses a specified comparer.
    /// </summary>
    /// <param name="collection">The collection whose elements are copied to the new <see cref="PriorityQueue{T}"/>.</param>
    /// <param name="comparer">The <see cref="T:System.Collections.Generic.IComparer{T}"/> to use when comparing elements.</param>
    /// <exception cref="T:System.ArgumentNullException">
    /// <paramref name="collection"/> is null. -or-
    /// <paramref name="comparer"/> is null.
    /// </exception>
    public PriorityQueue(IEnumerable<T> collection, IComparer<T> comparer);

    /// <summary>
    /// Initializes a new instance of the <see cref="PriorityQueue{T}"/> class that is empty,
    /// has the specified initial capacity, and uses a specified comparer.
    /// </summary>
    /// <param name="capacity">The initial number of elements that the <see cref="PriorityQueue{T}"/> can contain.</param>
    /// <param name="comparer">The <see cref="T:System.Collections.Generic.IComparer{T}"/> to use when comparing elements.</param>
    /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="capacity"/> is less than zero.</exception>
    /// <exception cref="T:System.ArgumentNullException"><paramref name="comparer"/> is null.</exception>
    public PriorityQueue(int capacity, IComparer<T> comparer);

    /// <summary>
    /// Gets the <see cref="IComparer{T}"/> for the <see cref="PriorityQueue{T}"/>. 
    /// </summary>
    /// <value>
    /// The <see cref="T:System.Collections.Generic.IComparer{T}"/> that is used when
    /// comparing elements in the <see cref="PriorityQueue{T}"/>. 
    /// </value>
    public IComparer<T> Comparer 
    { 
        get;
    }

    /// <summary>
    /// Gets the number of elements contained in the <see cref="PriorityQueue{T}"/>.
    /// </summary>
    /// <value>The number of elements contained in the <see cref="PriorityQueue{T}"/>.</value>
    public int Count 
    { 
        get;
    }

    /// <summary>
    /// Adds an object to the into the <see cref="PriorityQueue{T}"/> by its priority.
    /// </summary>
    /// <param name="item">
    /// The object to add to the <see cref="PriorityQueue{T}"/>. 
    /// The value can be null for reference types.
    /// </param>
    public void Enqueue(T item);

    /// <summary>
    /// Removes and returns the object with the lowest priority in the <see cref="PriorityQueue{T}"/>.
    /// </summary>
    /// <returns>The object with the lowest priority that is removed from the <see cref="PriorityQueue{T}"/>.</returns>
    /// <exception cref="InvalidOperationException">The <see cref="PriorityQueue{T}"/> is empty.</exception>
    public T Dequeue();

    /// <summary>
    /// Returns the object with the lowest priority in the <see cref="PriorityQueue{T}"/>.
    /// </summary>
    /// <exception cref="InvalidOperationException">The <see cref="PriorityQueue{T}"/> is empty.</exception>
    public T Peek();

    /// <summary>
    /// Removes all elements from the <see cref="PriorityQueue{T}"/>.
    /// </summary>
    public void Clear();

    /// <summary>
    /// Determines whether an element is in the <see cref="PriorityQueue{T}"/>.
    /// </summary>
    /// <param name="item">
    /// The object to add to the end of the <see cref="PriorityQueue{T}"/>. 
    /// The value can be null for reference types.
    /// </param>
    /// <returns>
    /// true if item is found in the <see cref="PriorityQueue{T}"/>;  otherwise, false.
    /// </returns>
    public bool Contains(T item);

    /// <summary>
    /// Copies the elements of the <see cref="PriorityQueue{T}"/> to an  <see cref="T:System.Array"/>, 
    /// starting at a particular <see cref="T:System.Array"/> index.
    /// </summary>
    /// <param name="array">
    /// The one-dimensional <see cref="T:System.Array">Array</see> that is the
    /// destination of the elements copied from the <see cref="PriorityQueue{T}"/>. 
    /// The <see cref="T:System.Array">Array</see> must have zero-based indexing.
    /// </param>
    /// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param>
    /// <exception cref="T:System.ArgumentNullException"><paramref name="array"/> is null.</exception>
    /// <exception cref="T:System.ArgumentOutOfRangeException">
    /// <paramref name="arrayIndex"/> is less than zero. -or- 
    /// <paramref name="arrayIndex"/> is equal to or greater than the length of the <paramref name="array"/>
    /// </exception>
    /// <exception cref="ArgumentException">
    /// The number of elements in the source <see cref="T:System.Collections.ICollection"/> is
    /// greater than the available space from <paramref name="index"/> to the end of the destination
    /// <paramref name="array"/>.
    /// </exception>
    public void CopyTo(T[] array, int arrayIndex);

    /// <summary>
    /// Copies the elements of the <see cref="T:System.Collections.ICollection"/> to an 
    /// <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index.
    /// </summary>
    /// <param name="array">
    /// The one-dimensional <see cref="T:System.Array">Array</see> that is the
    /// destination of the elements copied from the <see cref="PriorityQueue{T}"/>. 
    /// The <see cref="T:System.Array">Array</see> must have zero-based indexing.
    /// </param>
    /// <param name="index">The zero-based index in <paramref name="array"/> at which copying begins.</param>
    /// <exception cref="T:System.ArgumentNullException"><paramref name="array"/> is null.</exception>
    /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is less than zero.</exception>
    /// <exception cref="ArgumentException">
    /// <paramref name="array"/> is multidimensional. -or-
    /// <paramref name="array"/> does not have zero-based indexing. -or-
    /// <paramref name="index"/> is equal to or greater than the length of the <paramref name="array"/> -or- 
    /// The number of elements in the source <see cref="T:System.Collections.ICollection"/> is
    /// greater than the available space from <paramref name="index"/> to the end of the destination
    /// <paramref name="array"/>. -or- 
    /// The type of the source <see cref="T:System.Collections.ICollection"/> cannot be cast automatically 
    /// to the type of the destination <paramref name="array"/>.
    /// </exception>
    void ICollection.CopyTo(Array array, int index);

    /// <summary>
    /// Copies the elements stored in the <see cref="PriorityQueue{T}"/> to a new array.
    /// </summary>
    /// <returns>
    /// A new array containing a snapshot of elements copied from the <see cref="PriorityQueue{T}"/>.
    /// </returns>
    public T[] ToArray();

    /// <summary>
    /// Returns an enumerator that iterates through the <see cref="PriorityQueue{T}"/>
    /// </summary>
    /// <returns>An enumerator for the contents of the <see cref="PriorityQueue{T}"/>.</returns>
    public Enumerator GetEnumerator();

    /// <summary>
    /// Returns an enumerator that iterates through the <see cref="PriorityQueue{T}"/>
    /// </summary>
    /// <returns>An enumerator for the contents of the <see cref="PriorityQueue{T}"/>.</returns>
    IEnumerator<T> IEnumerable<T>.GetEnumerator();

    /// <summary>
    /// Returns an enumerator that iterates through the <see cref="PriorityQueue{T}"/>.
    /// </summary>
    /// <returns>An <see cref="T:System.Collections.IEnumerator"/> that can be used to iterate through the collection.</returns>
    IEnumerator IEnumerable.GetEnumerator();

    /// <summary>
    /// Sets the capacity to the actual number of elements in the <see cref="PriorityQueue{T}"/>, 
    /// if that number is less than than a threshold value.
    /// </summary>
    public void TrimExcess();

    /// <summary>
    /// Gets a value that indicates whether access to the <see cref="ICollection"/> is 
    /// synchronized with the SyncRoot.
    /// </summary>
    /// <value>true if access to the <see cref="T:System.Collections.ICollection"/> is synchronized
    /// with the SyncRoot; otherwise, false. For <see cref="PriorityQueue{T}"/>, this property always
    /// returns false.</value>
    bool ICollection.IsSynchronized
    {
        get;
    }

    /// <summary>
    /// Gets an object that can be used to synchronize access to the 
    /// <see cref="T:System.Collections.ICollection"/>.
    /// </summary>
    /// <value>
    /// An object that can be used to synchronize access to the 
    /// <see cref="T:System.Collections.ICollection"/>.
    /// </value>
    object ICollection.SyncRoot
    {
        get;
    }

    public struct Enumerator : IEnumerator<T>
    {
        public T Current { get; }
        object IEnumerator.Current { get; }
        public bool MoveNext();
        public void Reset();
        public void Dispose();
    }
}

}
```

rincian

  • Struktur data implementasi akan menjadi tumpukan biner. Item dengan nilai perbandingan yang lebih besar akan dikembalikan terlebih dahulu. (urutan menurun)
  • Kompleksitas waktu:

| Operasi | Kompleksitas | Catatan |
| --- | --- | --- |
| Bangun | (1) | |
| Membangun Menggunakan IEnumerable | (n) | |
| Antrean | (log n) | |
| Dequeue | (log n) | |
| Mengintip | (1) | |
| Hitung | (1) | |
| Hapus | (N) | |
| Berisi | (N) | |
| SalinKe | (N) | Menggunakan Array.Copy, kompleksitas sebenarnya mungkin lebih rendah |
| ToArray | (N) | Menggunakan Array.Copy, kompleksitas sebenarnya mungkin lebih rendah |
| GetEnumerator | (1) | |
| Enumerator.PindahkanBerikutnya | (1) | |

  • Kelebihan konstruktor tambahan yang menggunakan System.Comparisondelegasi sengaja dihilangkan demi area permukaan API yang disederhanakan. Penelepon dapat menggunakan Pembanding.Buat untuk mengonversi fungsi atau ekspresi Lambda ke IComparerantarmuka jika perlu. Ini memang membutuhkan penelepon untuk mendapatkan alokasi tumpukan satu kali.
  • Meskipun System.Collections.Generic belum menjadi bagian dari corefx, saya mengusulkan agar kelas ini ditambahkan ke corefxlab untuk sementara. Itu dapat dipindahkan ke repositori corefx utama setelah System.Collections.Generic ditambahkan dan ada konsensus bahwa statusnya harus ditingkatkan dari eksperimental ke API resmi.
  • Properti IsEmpty tidak disertakan, karena tidak ada penalti kinerja tambahan yang memanggil Count. Sebagian besar struktur data koleksi tidak menyertakan IsEmpty.
  • Properti IsSynchronized dan SyncRoot dari ICollection diimplementasikan secara eksplisit karena sudah usang secara efektif. Ini juga mengikuti pola yang digunakan untuk struktur data System.Collection.Generic lainnya.
  • Dequeue dan Peek melempar InvalidOperationException saat antrian kosong agar sesuai dengan perilaku System.Collections.Queue yang ditetapkan.
  • SayaProduserConsumerCollectiontidak diimplementasikan karena dokumentasinya menyatakan bahwa itu hanya ditujukan untuk koleksi thread-safe.

Pertanyaan-pertanyaan terbuka

  • Apakah menghindari alokasi tumpukan tambahan selama panggilan ke GetEnumerator saat menggunakan foreach merupakan alasan yang cukup kuat untuk menyertakan struktur enumerator publik bersarang?
  • Haruskah CopyTo, ToArray, dan GetEnumerator mengembalikan hasil dalam urutan yang diprioritaskan (diurutkan), atau urutan internal yang digunakan oleh struktur data? Asumsi saya adalah bahwa pesanan internal harus dikembalikan, karena tidak dikenakan penalti kinerja tambahan. Namun, ini adalah masalah kegunaan potensial jika pengembang menganggap kelas sebagai "antrian yang diurutkan" daripada antrian prioritas.
  • Apakah menambahkan tipe bernama PriorityQueue ke System.Collections.Generic menyebabkan perubahan yang berpotensi merusak? Namespace banyak digunakan, dan dapat menyebabkan masalah kompatibilitas sumber untuk proyek yang menyertakan jenis antrian prioritasnya sendiri.
  • Haruskah item diurutkan dalam urutan menaik atau menurun, berdasarkan output IComparer? (asumsi saya adalah urutan menaik, agar sesuai dengan konvensi penyortiran normal IComparer).
  • Haruskah koleksinya 'stabil'? Dengan kata lain, haruskah dua item dengan IComparison yang samahasil didequeued dalam urutan yang sama persis dengan enqueuednya? (Asumsi saya adalah ini tidak diperlukan)

    Pembaruan

  • Memperbaiki kompleksitas 'Construct Using IEnumerable' menjadi (n). Terima kasih @svick.

  • Menambahkan pertanyaan opsi lain mengenai apakah antrian prioritas harus diurutkan dalam urutan menaik atau menurun dibandingkan dengan IComparer.
  • Menghapus NotSupportedException dari properti SyncRoot eksplisit untuk mencocokkan perilaku tipe System.Collection.Generic lain alih-alih menggunakan pola yang lebih baru.
  • Membuat metode GetEnumerator publik mengembalikan struct Enumerator bersarang alih-alih IEnumerable, mirip dengan tipe System.Collections.Generic yang ada. Ini adalah optimasi untuk menghindari alokasi heap (GC) saat menggunakan foreach loop.
  • Atribut ComVisible yang dihapus.
  • Mengubah kompleksitas Clear menjadi (n). Terima kasih @mbeidler.
api-needs-work area-System.Collections wishlist

Komentar yang paling membantu

struktur data heap adalah KEHARUSAN untuk melakukan leetcode
lebih banyak leetcode, lebih banyak wawancara kode c# yang berarti lebih banyak pengembang c#.
lebih banyak pengembang berarti ekosistem yang lebih baik.
ekosistem yang lebih baik berarti jika kita masih bisa memprogram di c# besok.

Singkatnya: ini bukan hanya fitur, tetapi juga masa depan. itulah mengapa masalah diberi label 'masa depan'.

Semua 318 komentar

| Operasi | Kompleksitas |
| --- | --- |
| Membangun Menggunakan IEnumerable | (log n) |

Saya pikir ini seharusnya (n). Anda harus setidaknya mengulangi input.

+1

Haruskah struktur enumerator publik bersarang digunakan untuk menghindari alokasi tumpukan tambahan selama panggilan ke GetEnumerator dan saat menggunakan foreach? Asumsi saya tidak, karena menghitung melalui antrian adalah operasi yang tidak biasa.

Saya akan condong menggunakan struct enumerator agar konsisten dengan Queue<T> yang menggunakan struct enumerator. Juga, jika enumerator struct tidak digunakan sekarang, kami tidak akan dapat mengubah PriorityQueue<T> untuk menggunakannya di masa mendatang.

Juga mungkin metode untuk sisipan batch? Bisakah selalu mengurutkan dan melanjutkan dari titik penyisipan sebelumnya daripada memulai dari awal jika itu akan membantu?:

    public void Enqueue(List<T> items) {
         items.Sort(_comparer);
         ... insertions ...
    }

Saya telah melemparkan salinan implementasi awal di sini . Cakupan tes masih jauh dari lengkap, tetapi jika ada yang penasaran, silakan lihat dan beri tahu saya pendapat Anda. Saya sudah mencoba mengikuti konvensi pengkodean yang ada dari kelas System.Collections sebanyak mungkin.

Dingin. Beberapa umpan balik awal:

  • Queue<T>.Enumerator mengimplementasikan IEnumerator.Reset secara eksplisit. Haruskah PriorityQueue<T>.Enumerator melakukan hal yang sama?
  • Queue<T>.Enumerator menggunakan _index == -2 untuk menunjukkan enumerator telah dibuang. PriorityQueue<T>.Enumerator memiliki komentar yang sama tetapi memiliki bidang tambahan _disposed . Pertimbangkan untuk menyingkirkan bidang _disposed dan gunakan _index == -2 untuk menunjukkan bahwa itu telah dibuang untuk membuat struct lebih kecil dan konsisten dengan Queue<T>
  • Saya pikir bidang _emptyArray statis dapat dihapus dan penggunaan diganti dengan Array.Empty<T>() sebagai gantinya.

Juga...

  • Koleksi lain yang menggunakan pembanding (misalnya Dictionary<TKey, TValue> , HashSet<T> , SortedList<TKey, TValue> , SortedDictionary<TKey, TValue> , SortedSet<T> , dll.) memungkinkan null menjadi diteruskan untuk pembanding, dalam hal ini Comparer<T>.Default digunakan.

Juga...

  • ToArray dapat dioptimalkan dengan memeriksa _size == 0 sebelum mengalokasikan array baru, dalam hal ini kembalikan Array.Empty<T>() .

@justinvp Umpan balik yang bagus, terima kasih!

  • Saya menerapkan Enumerator.Setel ulang secara eksplisit karena merupakan inti, fungsionalitas enumerator yang tidak digunakan lagi. Apakah terekspos atau tidak tampaknya tidak konsisten di seluruh jenis koleksi, dan hanya sedikit yang menggunakan varian eksplisit.
  • Menghapus bidang _disposed demi _index, terima kasih! Melemparkan itu pada menit terakhir malam itu dan melewatkan yang sudah jelas. Memutuskan untuk menyimpan ObjectDisposedException untuk kebenaran dengan tipe koleksi yang lebih baru, meskipun tipe System.Collections.Generic lama tidak menggunakannya.
  • Array.Kosongadalah fitur F#, jadi sayangnya tidak dapat menggunakannya di sini!
  • Memodifikasi parameter pembanding untuk menerima nol, penemuan bagus!
  • Optimalisasi ToArray itu rumit. _Secara teknis_ array berbicara bisa berubah dalam C#, bahkan ketika panjangnya nol. Pada kenyataannya Anda benar, alokasi tidak diperlukan dan dapat dioptimalkan. Saya condong ke implementasi yang lebih hati-hati, jika ada efek samping yang tidak saya pikirkan. Secara semantik penelepon masih akan mengharapkan alokasi itu, dan itu kecil.

@ebickle

Array.Empty adalah fitur F#, jadi sayangnya tidak dapat menggunakannya di sini!

Tidak lagi: https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/Array.cs#L1060 -L1069

Saya telah memigrasikan kode ke ebickle/corefx di cabang issue-574.

Menerapkan Array.Empty() ubah dan hubungkan semuanya ke pipa build biasa. Satu kludge sementara kecil yang harus saya perkenalkan adalah membuat proyek System.Collections bergantung pada paket nuget System.Collections, sebagai Pembandingbelum open source.

Akan diperbaiki setelah masalah dotnet/corefx#966 selesai.

Salah satu area utama yang saya cari umpan baliknya adalah bagaimana ToArray, CopyTo, dan enumerator harus ditangani. Saat ini mereka dioptimalkan untuk kinerja, yang berarti larik target adalah tumpukan dan tidak diurutkan berdasarkan pembanding.

Ada tiga opsi:
1) Biarkan apa adanya, dan dokumentasikan bahwa array yang dikembalikan tidak diurutkan. (ini adalah antrian prioritas, bukan antrian yang diurutkan)
2) Ubah metode untuk mengurutkan item/array yang dikembalikan. Mereka tidak akan lagi menjadi operasi O(n).
3) Hapus metode dan dukungan enumerable sama sekali. Ini adalah opsi "murni" tetapi menghilangkan kemampuan untuk mengambil item antrean yang tersisa dengan cepat saat antrean prioritas tidak lagi diperlukan.

Hal lain yang saya ingin umpan balik adalah apakah antrian harus stabil untuk dua item dengan prioritas yang sama (bandingkan hasil 0). Umumnya antrian prioritas tidak menjamin bahwa dua item dengan prioritas yang sama akan di-dequeued sesuai urutan antriannya, tetapi saya perhatikan bahwa implementasi antrian prioritas internal yang digunakan di System.Reactive.Core mengambil beberapa overhead tambahan untuk memastikan properti ini. Preferensi saya adalah tidak melakukan ini, tetapi saya tidak sepenuhnya yakin opsi mana yang lebih baik dalam hal harapan pengembang.

Saya mengalami PR ini karena saya juga tertarik untuk menambahkan antrian prioritas ke .NET. Senang melihat seseorang pergi ke upaya membuat proposal ini :). Setelah meninjau kode, saya perhatikan yang berikut:

  • Ketika IComparer pemesanan tidak konsisten dengan Equals , perilaku implementasi Contains (yang menggunakan IComparer ) mungkin mengejutkan bagi beberapa pengguna, karena itu pada dasarnya _berisi item dengan prioritas yang sama_.
  • Saya tidak melihat kode untuk mengecilkan array di Dequeue . Menyusut susunan tumpukan hingga setengahnya saat seperempat penuh adalah tipikal.
  • Haruskah metode Enqueue menerima argumen null ?
  • Saya pikir kompleksitas Clear seharusnya (n), karena itu adalah kompleksitas System.Array.Clear , yang digunakannya. https://msdn.microsoft.com/en-us/library/system.array.clear%28v=vs.110%29.aspx

Saya tidak melihat kode untuk mengecilkan array di Dequeue . Menyusut susunan tumpukan hingga setengahnya saat seperempat penuh adalah tipikal.

Queue<T> dan Stack<T> juga tidak mengecilkan array mereka (berdasarkan Sumber Referensi , mereka belum ada di CoreFX).

@mbeidler Saya telah mempertimbangkan untuk menambahkan beberapa bentuk penyusutan array otomatis di dequeue, tetapi seperti yang ditunjukkan @svick, itu tidak ada dalam implementasi referensi dari struktur data yang serupa. Saya ingin tahu dari siapa pun di tim .NET Core/BCL apakah ada alasan khusus mereka memilih gaya implementasi itu.

Pembaruan: Saya memeriksa Daftar, Antre, Queue, dan ArrayList - tidak ada yang mengecilkan ukuran array internal pada penghapusan/dequeue.

Enqueue harus mendukung null, dan didokumentasikan sebagai mengizinkannya. Apakah Anda mengalami bug? Saya tidak dapat mengingat seberapa kuat area pengujian unit di area tersebut.

Menarik, saya perhatikan di sumber referensi yang ditautkan oleh @svick bahwa Queue<T> memiliki konstanta pribadi yang tidak digunakan bernama _ShrinkThreshold . Mungkin perilaku itu ada di versi sebelumnya.

Mengenai penggunaan IComparer alih-alih Equals dalam implementasi Contains , saya menulis unit test berikut, yang saat ini akan gagal: https://Gist.github. com/mbeidler/9e9f566ba7356302c57e

@mbeidler Poin bagus. Menurut MSDN, IComparer/ ISebandinghanya menjamin bahwa nilai nol memiliki urutan yang sama.

Namun, sepertinya masalah yang sama ada di kelas koleksi lainnya. Jika saya memodifikasi kode untuk beroperasi pada Daftar Terurut, kasus uji masih gagal di BerisiKey. Implementasi SortedList.ContainsKey memanggil Array.BinarySearch, yang bergantung pada IComparer untuk memeriksa kesetaraan. Hal yang sama berlaku untuk SortedSet.

Bisa dibilang itu adalah bug di kelas koleksi yang ada juga. Saya akan menggali seluruh kelas koleksi dan melihat apakah ada struktur data lain yang menerima IComparer tetapi menguji kesetaraan secara terpisah. Anda benar, untuk antrian prioritas Anda akan mengharapkan perilaku pemesanan khusus yang sepenuhnya independen dari kesetaraan.

Melakukan perbaikan dan test case ke cabang fork saya. Implementasi baru dari Berisi didasarkan langsung pada perilaku Daftar.Mengandung. Sejak Daftartidak menerima IEqualityComparer, perilakunya secara fungsional setara.

Ketika saya mendapatkan beberapa waktu kemudian hari ini, saya kemungkinan akan mengirimkan laporan bug untuk koleksi bawaan lainnya. Mungkin tidak dapat diperbaiki karena perilaku regresi, tetapi setidaknya dokumentasi perlu diperbarui.

Saya pikir masuk akal jika ContainsKey menggunakan implementasi IComparer<TKey> , karena itulah yang menentukan kuncinya. Namun, saya pikir akan lebih logis jika ContainsValue menggunakan Equals daripada IComparable<TValue> dalam pencarian liniernya; meskipun dalam hal ini cakupannya berkurang secara signifikan karena pengurutan alami tipe jauh lebih kecil kemungkinannya untuk tidak konsisten dengan yang setara.

Tampaknya dalam dokumentasi MSDN untuk SortedList<TKey, TValue> , bagian komentar untuk ContainsValue menunjukkan bahwa pengurutan TValue digunakan sebagai pengganti kesetaraan.

@terrajobst Bagaimana perasaan Anda tentang proposal API sejauh ini? Apakah Anda merasa ini cocok untuk CoreFX?

:+1:

Terima kasih telah mengajukan ini. Saya yakin kami memiliki cukup data untuk membuat tinjauan formal atas proposal ini, oleh karena itu saya memberi label sebagai 'siap untuk tinjauan api'

Karena Dequeue dan Peek melempar metode, pemanggil perlu memeriksa jumlah sebelum setiap panggilan. Apakah masuk akal untuk sebagai gantinya (atau sebagai tambahan) memberikan TryDequeue dan TryPeek mengikuti pola koleksi bersamaan? Ada masalah terbuka untuk menambahkan metode non-melempar ke koleksi generik yang ada sehingga menambahkan koleksi baru yang tidak memiliki metode ini tampaknya kontraproduktif.

@andrewgmorris terkait https://github.com/dotnet/corefx/issues/4316 "Tambahkan TryDequeue ke Antrian"

Kami memiliki tinjauan dasar tentang ini dan kami setuju bahwa kami menginginkan ProrityQueue dalam kerangka kerja. Namun, kita perlu meminta seseorang untuk membantu mendorong desain dan implementasinya. Siapa pun yang mengambil masalah ini dapat bekerja @terrajobst untuk menyelesaikan API.

Jadi pekerjaan apa yang tersisa di API?

Ini hilang dari proposal API di atas: PriorityQueue<T> harus mengimplementasikan IReadOnlyCollection<T> untuk mencocokkan Queue<T> ( Queue<T> sekarang mengimplementasikan IReadOnlyCollection<T> pada .NET 4.6).

Saya tidak tahu bahwa antrian prioritas berbasis array adalah yang terbaik. Alokasi memori di .NET sangat cepat. Kami tidak memiliki masalah pencarian-kecil-blok yang sama dengan malloc lama. Anda dipersilakan untuk menggunakan kode antrian prioritas saya dari sini: https://github.com/BrannonKing/Kts.Astar/tree/master/Kts.AStar

@ebickle Satu nit kecil di _speclet_. Ia mengatakan:

/// Adds an object to the end of the <see cref="PriorityQueue{T}"/>. ... public void Enqueue(T item);

Bukankah seharusnya dikatakan, /// Inserts object into the <see cref="PriorityQueue{T}"> by its priority.

@SunnyWar Memperbaiki dokumentasi metode Enqueue, terima kasih!

Beberapa waktu yang lalu, saya membuat struktur data dengan kompleksitas yang mirip dengan antrian prioritas berdasarkan struktur data Lewati Daftar yang telah saya putuskan pada saat ini untuk dibagikan: https://Gist.github.com/bbarry/5e0f3cc1ac7f7521fe6ea25947f48ace

https://en.wikipedia.org/wiki/Skip_list

Daftar lewati cocok dengan kompleksitas antrian prioritas di atas dalam kasus rata-rata, kecuali yang Berisi adalah kasus rata-rata O(log(n)) . Selain itu, akses ke elemen pertama atau terakhir adalah operasi waktu yang konstan dan iterasi dalam urutan maju dan mundur sesuai dengan kompleksitas urutan maju dari PQ.

Jelas ada kerugian untuk struktur seperti itu dalam bentuk biaya yang lebih tinggi pada memori, dan itu berpindah ke sisipan dan penghapusan kasus terburuk O(n) , sehingga memiliki trade off ...

Apakah ini sudah diterapkan di suatu tempat? Kapan rilis yang diharapkan?
Juga bagaimana memperbarui prioritas item yang ada?

@Priya91 @ianhays apakah ini siap untuk ditandai siap untuk ditinjau?

Ini hilang dari proposal API di atas: PriorityQueueharus mengimplementasikan IReadOnlyCollectionuntuk mencocokkan Antrian(Antresekarang mengimplementasikan IReadOnlyCollectionpada .NET 4.6).

Saya setuju dengan @justinvp di sini.

@Priya91 @ianhays apakah ini siap untuk ditandai siap untuk ditinjau?

Saya akan mengatakan demikian. Ini telah duduk untuk sementara waktu; mari kita bergerak di atasnya.

@justinvp @ianhays Saya telah memperbarui spesifikasi untuk mengimplementasikan IReadOnlyCollection. Terima kasih!

Saya memiliki implementasi penuh dari kelas dan itu terkait PriorityQueueDebugView yang menggunakan implementasi berbasis array. Tes unit belum mencapai cakupan 100%, tetapi jika ada minat, saya dapat melakukan sedikit pekerjaan dan membersihkan garpu saya.

@NKnusperer membuat poin bagus tentang memperbarui prioritas item yang ada. Saya akan mengabaikannya untuk saat ini tetapi itu adalah sesuatu yang perlu dipertimbangkan selama tinjauan spesifikasi.

Ada 2 implementasi dalam kerangka kerja penuh, yang merupakan alternatif yang memungkinkan.
https://referencesource.microsoft.com/#q =priorityqueue

Untuk referensi di sini adalah pertanyaan tentang Java PriorityQueue di stackoverflow http://stackoverflow.com/questions/683041/Java-how-do-i-use-a-priorityqueue . Sangat menarik bahwa prioritas ditangani oleh pembanding, bukan hanya objek pembungkus prioritas int sederhana. Misalnya tidak mudah untuk mengubah prioritas item dalam antrian.

tinjauan API:
Kami setuju bahwa jenis ini berguna di CoreFX, karena kami berharap CoreFX menggunakannya.

Untuk tinjauan akhir bentuk API, kami ingin melihat kode contoh: PriorityQueue<Thread> dan PriorityQueue<MyClass> .

  1. Bagaimana kita mempertahankan prioritas? Saat ini hanya tersirat oleh T .
  2. Apakah kami ingin memungkinkan untuk melewati prioritas saat Anda menambahkan entri? (yang tampaknya cukup berguna)

Catatan:

  • Kami berharap bahwa prioritas tidak bermutasi dengan sendirinya - kami akan membutuhkan salah satu API untuk itu, atau kami mengharapkan Remove dan Add pada antrian.
  • Mengingat bahwa kami tidak memiliki kode pelanggan yang jelas di sini (hanya keinginan umum bahwa kami menginginkan jenisnya), sulit untuk memutuskan apa yang harus dioptimalkan - kinerja, vs. kegunaan vs. sesuatu yang lain?

Ini akan menjadi tipe yang sangat berguna untuk dimiliki di CoreFX. Ada yang minat ambil yang ini?

Saya tidak suka ide memperbaiki antrian prioritas ke tumpukan biner. Lihat halaman wiki AlgoKit saya untuk detailnya. Pikiran cepat:

  • Jika kita memperbaiki implementasi ke jenis tumpukan tertentu, buatlah menjadi tumpukan 4-ary.
  • Kita seharusnya tidak memperbaiki implementasi heap, karena dalam beberapa skenario beberapa jenis heap lebih berkinerja daripada yang lain. Jadi, mengingat kami memperbaiki implementasi ke jenis tumpukan tertentu dan pelanggan membutuhkan yang berbeda untuk kinerja yang lebih baik, dia perlu mengimplementasikan seluruh antrian prioritas dari bawah ke atas. Itu salah. Kita harus lebih fleksibel di sini dan membiarkan dia menggunakan kembali beberapa kode CoreFX (setidaknya beberapa antarmuka).

Tahun lalu saya menerapkan beberapa jenis tumpukan . Secara khusus, ada antarmuka IHeap yang dapat digunakan sebagai antrian prioritas.

  • Mengapa tidak menyebutnya tumpukan saja...? Anda tahu cara waras untuk mengimplementasikan antrian prioritas selain menggunakan heap?

Saya siap untuk memperkenalkan antarmuka IHeap dan beberapa implementasi yang paling berkinerja (setidaknya yang berbasis array). API dan implementasinya ada di repositori yang saya tautkan di atas.

Jadi tidak ada _antrian prioritas_. tumpukan .

Bagaimana menurutmu?

@karelz @safern @danmosemsft

@pgolebiowski Jangan lupa kami merancang API yang mudah digunakan dan berkinerja cantik. Jika Anda menginginkan PriorityQueue (yang merupakan istilah ilmu komputer yang mapan), Anda harus dapat menemukannya di dokumen / melalui pencarian internet.
Jika implementasi yang mendasarinya adalah heap (yang secara pribadi saya heran mengapa), atau yang lainnya, itu tidak terlalu menjadi masalah. Dengan asumsi Anda dapat menunjukkan implementasi yang terukur lebih baik daripada alternatif yang lebih sederhana (kompleksitas kode juga merupakan metrik yang agak penting).

Jadi, jika Anda masih percaya implementasi berbasis heap (tanpa IHeap API) lebih baik daripada beberapa implementasi berbasis daftar atau daftar-array-chunks sederhana, tolong jelaskan alasannya (idealnya dalam beberapa kalimat/paragraf ), sehingga kita bisa mendapatkan kesepakatan tentang pendekatan implementasi (dan menghindari membuang-buang waktu di pihak Anda pada implementasi yang kompleks yang mungkin ditolak pada waktu tinjauan PR).

Jatuhkan ICollection , IEnumerable ? Hanya memiliki versi generik (meskipun generik IEnumerable<T> akan membawa IEnumerable )

@pgolebiowski bagaimana penerapannya tidak mengubah api eksternal. PriorityQueue mendefinisikan perilaku/kontrak; sedangkan Heap adalah implementasi khusus.

Antrian prioritas vs tumpukan

Jika implementasi yang mendasarinya adalah heap (yang secara pribadi saya heran mengapa), atau yang lainnya, itu tidak terlalu menjadi masalah. Dengan asumsi Anda dapat menunjukkan implementasi yang terukur lebih baik daripada alternatif yang lebih sederhana (kompleksitas kode juga merupakan metrik yang agak penting).

Jadi, jika Anda masih percaya implementasi berbasis heap lebih baik daripada beberapa implementasi berbasis daftar atau daftar-array-chunks sederhana, tolong jelaskan alasannya (idealnya dalam beberapa kalimat/paragraf), sehingga kita bisa mendapatkan kesepakatan tentang pendekatan implementasi [...]

OKE. Antrian prioritas adalah struktur data abstrak yang kemudian dapat diimplementasikan dalam beberapa cara. Tentu saja Anda dapat mengimplementasikannya dengan struktur data yang berbeda dari heap. Tapi tidak ada cara yang lebih efisien. Hasil dari:

  • antrian prioritas dan tumpukan sering digunakan sebagai sinonim (lihat dokumen Python di bawah ini sebagai contoh paling jelas)
  • setiap kali Anda memiliki dukungan "antrian prioritas" di beberapa perpustakaan, ia menggunakan tumpukan (lihat semua contoh di bawah)

Untuk mendukung kata-kata saya, mari kita mulai dengan dukungan teoretis. Pengantar Algoritma , Cormen:

[...] antrian prioritas datang dalam dua bentuk: antrian prioritas-maks dan antrian prioritas-min. Kami akan fokus di sini pada bagaimana mengimplementasikan antrian prioritas-maks, yang pada gilirannya didasarkan pada tumpukan-maks.

Menyatakan dengan jelas bahwa antrian prioritas adalah tumpukan. Ini adalah jalan pintas tentu saja, tetapi Anda mendapatkan idenya. Sekarang, yang lebih penting, mari kita lihat bagaimana bahasa lain dan pustaka standar mereka menambahkan dukungan untuk operasi yang sedang kita diskusikan:

  • Java: PriorityQueue<T> — antrian prioritas diimplementasikan dengan heap.
  • Karat: BinaryHeap — menumpuk secara eksplisit di API. Dikatakan dalam dokumen bahwa itu adalah antrian prioritas yang diimplementasikan dengan tumpukan biner. Tetapi API sangat jelas tentang strukturnya — tumpukan.
  • Swift: CFBinaryHeap — sekali lagi, secara eksplisit mengatakan apa itu struktur data, menghindari penggunaan istilah abstrak "antrian prioritas". Dokumen yang menjelaskan kelas: Tumpukan biner dapat berguna sebagai antrian prioritas. Saya suka pendekatannya.
  • C++: priority_queue — sekali lagi, diimplementasikan secara kanonik dengan tumpukan biner yang dibangun di atas array.
  • Python: heapq — heap secara eksplisit diekspos di API. Antrian prioritas hanya disebutkan dalam dokumen: Modul ini menyediakan implementasi dari algoritma antrian tumpukan, juga dikenal sebagai algoritma antrian prioritas.
  • Buka: heap package — bahkan ada antarmuka tumpukan. Tidak ada antrean prioritas eksplisit, tetapi sekali lagi hanya di dokumen: Heap adalah cara umum untuk mengimplementasikan antrean prioritas.

Saya sangat percaya kita harus menggunakan cara Rust / Swift / Python / Go dan mengekspos heap secara eksplisit sambil dengan jelas menyatakan dalam dokumen bahwa itu dapat digunakan sebagai antrian prioritas. Saya sangat percaya pendekatan ini sangat bersih dan sederhana.

  • Kami memahami dengan jelas tentang struktur data dan membiarkan API ditingkatkan di masa mendatang — jika seseorang menemukan cara baru yang revolusioner dalam menerapkan antrean prioritas yang lebih baik dalam beberapa aspek (dan tumpukan pilihan vs jenis baru akan bergantung pada skenario), API kita masih bisa utuh. Kita cukup menambahkan tipe revolusioner baru yang meningkatkan perpustakaan — dan kelas heap akan tetap ada di sana.
  • Bayangkan seorang pengguna bertanya-tanya apakah struktur data yang kita pilih stabil. Ketika solusi yang kita ikuti adalah antrian prioritas , ini tidak jelas. Istilahnya abstrak dan apa saja bisa di bawahnya. Jadi beberapa waktu hilang oleh pengguna untuk mencari dokumen dan mengetahui bahwa ia menggunakan tumpukan internal dan karena itu tidak stabil. Ini bisa dihindari dengan hanya secara eksplisit menyatakan melalui API bahwa ini adalah heap.

Saya harap Anda semua menyukai pendekatan dengan mengekspos struktur data heap dengan jelas — dan membuat referensi antrian prioritas di dokumen saja.

API & implementasi

Kita perlu menyepakati masalah di atas, tetapi izinkan saya memulai topik lain — bagaimana menerapkan ini . Saya dapat melihat dua solusi di sini:

  • Kami hanya menyediakan kelas ArrayHeap . Melihat namanya, Anda dapat langsung mengetahui jenis tumpukan yang Anda hadapi (sekali lagi, ada lusinan jenis tumpukan). Anda melihatnya berbasis array. Anda segera tahu binatang yang Anda hadapi.
  • Kemungkinan lain yang lebih saya sukai adalah menyediakan antarmuka IHeap dan menyediakan satu atau lebih implementasi. Pelanggan dapat menulis kode yang bergantung pada antarmuka — ini akan memungkinkan penyediaan implementasi algoritme kompleks yang sangat jelas dan mudah dibaca. Bayangkan menulis kelas DijkstraAlgorithm . Itu hanya bisa bergantung pada antarmuka IHeap (satu konstruktor parametrized) atau cukup gunakan ArrayHeap (konstruktor default). Bersih, sederhana, eksplisit, tidak ada ambiguitas yang terlibat karena menggunakan istilah "antrian prioritas". Dan antarmuka luar biasa yang sangat masuk akal secara teoritis.

Dalam pendekatan di atas, ArrayHeap mewakili pohon d-ary lengkap yang diurutkan secara implisit, disimpan sebagai larik. Ini dapat digunakan untuk membuat misalnya BinaryHeap atau QuaternaryHeap .

Intinya

Untuk diskusi lebih lanjut, saya sangat menganjurkan Anda untuk melihat makalah ini . Anda akan tahu bahwa:

  • Tumpukan 4-ary (kuartener) lebih cepat daripada tumpukan 2-ary (biner). Ada banyak tes yang dilakukan dalam makalah ini — Anda akan tertarik untuk membandingkan kinerja implicit_4 dan implicit_2 (terkadang implicit_simple_4 dan implicit_simple_2 ).

  • Pilihan implementasi yang optimal sangat bergantung pada input. Dari tumpukan d-ary implisit, tumpukan pasangan, tumpukan Fibonacci, tumpukan binomial, tumpukan d-ary eksplisit, tumpukan pasangan peringkat, tumpukan gempa, tumpukan pelanggaran, tumpukan lemah peringkat-santai, dan tumpukan Fibonacci ketat, jenis berikut tampaknya mencakup hampir semua kebutuhan untuk berbagai skenario:

    • Tumpukan d-ary implisit (ArrayHeap), <- pasti kita membutuhkan ini
    • Memasangkan tumpukan, <- jika kita menggunakan pendekatan IHeap yang bagus dan bersih, ada baiknya menambahkan ini, karena dalam banyak kasus ini sangat cepat dan lebih cepat daripada solusi berbasis array .

    Untuk keduanya, upaya pemrograman sangat rendah. Lihat implementasi saya.


@karelz @benaadams

Ini akan menjadi tipe yang sangat berguna untuk dimiliki di CoreFX. Ada yang minat ambil yang ini?

@safern Saya akan sangat senang untuk mengambil yang ini mulai sekarang.

Oke, itu masalah antara keyboard dan kursi saya - PriorityQueue tentu saja didasarkan pada Heap -- Saya hanya memikirkan Queue tidak masuk akal dan lupa bahwa heap 'diurutkan' -- gangguan proses berpikir yang sangat memalukan bagi orang seperti saya, yang menyukai logika, algoritme, mesin Turing, dll., saya minta maaf. (BTW: Segera setelah saya membaca beberapa kalimat di tautan Java docs Anda, perbedaannya segera diklik)

Dari perspektif itu masuk akal untuk membangun API di atas Heap . Kita seharusnya tidak membuat kelas itu menjadi publik - itu akan membutuhkan tinjauan API sendiri dan diskusi sendiri jika itu adalah sesuatu yang kita butuhkan di CoreFX. Kami tidak ingin API permukaan merayap karena implementasi, tetapi mungkin hal yang benar untuk dilakukan -- maka diskusi diperlukan.
Dari perspektif itu saya rasa kita belum perlu membuat IHeap dulu. Ini mungkin keputusan yang baik nanti.
Jika ada penelitian bahwa tumpukan tertentu (misalnya 4-ary seperti yang Anda sebutkan di atas) adalah yang terbaik untuk input acak umum , maka kita harus memilihnya. Mari kita tunggu @safern @ianhays @stephentoub untuk konfirmasi/suara opini.

Parametrisasi tumpukan yang mendasari dengan beberapa opsi yang diterapkan adalah sesuatu yang IMO tidak termasuk dalam CoreFX (saya mungkin salah di sini, sekali lagi - mari kita lihat apa yang dipikirkan orang lain).
Alasan saya adalah bahwa IMO kami akan segera mengirimkan trilyunan koleksi khusus yang akan sangat sulit bagi orang (pengembang rata-rata tanpa latar belakang yang kuat dalam nuansa algoritme) untuk dipilih. Perpustakaan seperti itu, bagaimanapun, akan menjadi paket NuGet yang hebat, untuk para ahli di bidangnya - yang dimiliki oleh Anda/komunitas. Di masa depan, kami dapat mempertimbangkan untuk menambahkannya ke PowerCollections (kami secara aktif mendiskusikan selama 4 bulan terakhir di mana di GitHub untuk menempatkan perpustakaan ini dan apakah kami harus memilikinya, atau jika kami harus mendorong komunitas untuk memilikinya - ada perbedaan pendapat tentang masalah ini , saya berharap kami akan menyelesaikan nasibnya pasca 2.0)

Menugaskan kepada Anda saat Anda ingin mengerjakannya ...

@pgolebiowski undangan kolaborator terkirim - ketika Anda menerima ping saya, saya akan dapat menetapkannya kepada Anda saat itu (batasan GitHub).

@benaadams saya akan menyimpan ICollection (preferensi ringan). Untuk konsistensi dengan ds lain di CoreFX. IMO tidak layak untuk memiliki satu binatang aneh di sini ... jika kita menambahkan beberapa yang baru (misalnya PowerCollections bahkan ke repo lain), kita tidak boleh memasukkan yang non-generik ... pikiran?

Oke, itu masalah antara keyboard dan kursi saya.

Haha Jangan khawatir.

masuk akal untuk membangun API di atas Heap. Kami seharusnya tidak membuat kelas itu publik [...] Kami tidak ingin API permukaan merayap karena implementasi, tetapi mungkin hal yang benar untuk dilakukan -- maka diskusi diperlukan. [...] Saya tidak berpikir kita perlu membuat IHeap dulu. Ini mungkin keputusan yang baik nanti.

Jika keputusan grup adalah untuk pergi dengan PriorityQueue , saya hanya akan membantu dengan desain dan mengimplementasikan ini. Namun, harap pertimbangkan fakta bahwa jika kita menambahkan PriorityQueue sekarang, akan menjadi berantakan di API nanti untuk menambahkan Heap -- karena keduanya berperilaku pada dasarnya sama. Ini akan menjadi semacam redundansi IMO. Ini akan menjadi bau desain bagi saya. Saya tidak akan menambahkan antrian prioritas. Itu tidak membantu.

Juga, satu pemikiran lagi. Sebenarnya, struktur data tumpukan pasangan bisa cukup sering berguna. Tumpukan berbasis array sangat buruk dalam menggabungkannya. Operasi ini pada dasarnya linier . Saat Anda memiliki banyak tumpukan penggabungan, Anda mematikan kinerjanya. Namun, jika Anda menggunakan tumpukan pasangan alih-alih tumpukan array -- operasi penggabungan adalah konstan (diamortisasi). Ini adalah argumen lain mengapa saya ingin menyediakan antarmuka yang bagus dan dua implementasi. Satu untuk masukan umum, kedua untuk beberapa skenario khusus, terutama ketika melibatkan penggabungan tumpukan.

Voting untuk IHeap + ArrayHeap + PairingHeap ! (seperti di Rust / Swift / Python / Go)

Jika tumpukan pasangan terlalu banyak -- OK. Tapi setidaknya mari kita pergi dengan IHeap + ArrayHeap . Tidakkah kalian merasa bahwa pergi dengan kelas PriorityQueue mengunci kemungkinan di masa depan dan membuat API menjadi kurang jelas?

Tetapi seperti yang saya katakan -- jika Anda semua memilih kelas PriorityQueue atas solusi yang diusulkan -- OK.

undangan kolaborator terkirim - ketika Anda menerima ping saya, saya akan dapat menetapkannya kepada Anda saat itu (batasan GitHub).

@karelz ping :)

harap pertimbangkan fakta bahwa jika kita menambahkan PriorityQueue sekarang, nanti akan berantakan di API untuk menambahkan Heap -- karena keduanya berperilaku pada dasarnya sama. Ini akan menjadi semacam redundansi IMO. Ini akan menjadi bau desain bagi saya. Saya tidak akan menambahkan antrian prioritas. Itu tidak membantu.

Bisakah Anda menjelaskan lebih detail tentang mengapa itu akan berantakan nanti? Apa kekhawatiran Anda?
PriorityQueue adalah konsep yang digunakan orang. Memiliki tipe bernama seperti itu berguna, bukan?
Saya pikir operasi logis (setidaknya nama mereka) pada Heap mungkin berbeda. Jika mereka sama, kita dapat memiliki 2 implementasi berbeda dari kode yang sama dalam kasus terburuk (tidak ideal, tetapi bukan akhir dunia). Atau kita bisa memasukkan kelas Heap sebagai induk dari PriorityQueue , bukan? (dengan asumsi itu diizinkan dari perspektif tinjauan API - saat ini saya tidak melihat alasan untuk tidak, tetapi saya tidak memiliki pengalaman bertahun-tahun dengan ulasan API, jadi akan menunggu orang lain untuk mengonfirmasi)

Mari kita lihat bagaimana voting & diskusi desain selanjutnya ... Saya perlahan-lahan melakukan pemanasan dengan gagasan IHeap + ArrayHeap , tetapi belum sepenuhnya yakin ...

jika kami menambahkan beberapa yang baru ... kami tidak boleh memasukkan yang non-generik

Kain merah untuk banteng. Adakah yang punya koleksi lain untuk ditambahkan sehingga kami dapat menjatuhkan ICollection ?

Buffer Melingkar/Cincin; Umum dan Bersamaan?

@karelz Solusi untuk masalah penamaan bisa berupa IPriorityQueue seperti yang dilakukan DataFlow untuk pola produksi/konsumen. Banyak cara untuk mengimplementasikan antrian prioritas dan jika Anda tidak peduli dengan itu gunakan antarmuka. Peduli tentang implementasi atau membuat instance menggunakan kelas implementasi.

Bisakah Anda menjelaskan lebih detail tentang mengapa itu akan berantakan nanti? Apa kekhawatiran Anda?
PriorityQueue adalah konsep yang digunakan orang. Memiliki tipe bernama seperti itu berguna, bukan? […] Saya perlahan-lahan mulai memahami gagasan IHeap + ArrayHeap , tetapi belum sepenuhnya yakin ...

@karelz Dari pengalaman saya, saya merasa sangat penting untuk memiliki abstraksi ( IPriorityQueue atau IHeap ). Berkat pendekatan seperti itu, pengembang dapat menulis kode yang dipisahkan. Karena ditulis dengan antarmuka (bukan implementasi khusus), ada lebih banyak fleksibilitas dan semangat IoC. Sangat mudah untuk menulis tes unit untuk kode semacam itu (memiliki Injeksi Ketergantungan seseorang dapat menyuntikkan IPriorityQueue atau IHeap tiruan mereka sendiri dan melihat metode apa yang dipanggil pada waktu apa dan dengan argumen apa). Abstraksi itu bagus.

Memang benar bahwa istilah "antrian prioritas" digunakan secara umum. Masalahnya adalah hanya ada satu cara untuk mengimplementasikan antrian prioritas secara efektif — dengan heap. Banyak jenis tumpukan. Jadi kita bisa memiliki:

class ArrayHeap : IPriorityQueue {}
class PairingHeap : IPriorityQueue {}
class FibonacciHeap : IPriorityQueue {}
class BinomialHeap : IPriorityQueue {}

atau

class ArrayHeap : IHeap {}
class PairingHeap : IHeap {}
class FibonacciHeap : IHeap {}
class BinomialHeap : IHeap {}

Bagi saya, pendekatan kedua terlihat lebih baik. Bagaimana perasaan Anda tentang hal itu?

Mengenai kelas PriorityQueue — Saya pikir itu akan terlalu kabur dan saya benar-benar menentang kelas seperti itu. Antarmuka yang tidak jelas bagus, tetapi bukan implementasi. Jika itu tumpukan biner, sebut saja BinaryHeap . Jika itu sesuatu yang lain, beri nama yang sesuai.

Saya siap menamai kelas dengan jelas karena masalah dengan kelas seperti SortedDictionary . Ada banyak kebingungan di antara pengembang untuk apa singkatannya dan bagaimana perbedaannya dari SortedList . Jika hanya disebut BinarySearchTree , hidup akan lebih sederhana dan kita bisa pulang dan melihat anak-anak kita lebih awal.

Mari kita beri nama heap a heap.

@benaadams masing-masing harus membuatnya menjadi CoreFX (yaitu itu harus berharga untuk kode CoreFX itu sendiri, atau harus sebagai dasar dan banyak digunakan seperti yang sudah kita miliki) -- kami membahas hal-hal itu akhir-akhir ini cukup banyak . Masih belum 100% konsensus, tetapi tidak ada yang ingin menambahkan lebih banyak koleksi ke CoreFX.

Hasil yang paling mungkin (klaim bias, karena saya suka solusinya) adalah bahwa kami akan membuat repo lain dan melengkapinya dengan PowerCollections dan kemudian membiarkan komunitas memperluasnya dengan beberapa panduan/pengawasan dasar dari tim kami.
BTW: @terrajobst menganggap itu tidak layak ("kami memiliki hal-hal yang lebih baik dan lebih berdampak dalam ekosistem untuk dilakukan") dan kami harus mendorong komunitas untuk sepenuhnya menjalankannya (termasuk memulai dengan PowerCollections yang ada) dan tidak menjadikannya salah satu dari kami repo - beberapa diskusi & keputusan di depan kami.
// Saya rasa ada kesempatan bagi komunitas untuk menggunakan solusi ini sebelum kita memutuskan ;-). Itu akan membuat diskusi (dan preferensi saya) bisu ;-)

@pgolebiowski Anda perlahan meyakinkan saya bahwa memiliki Heap lebih baik daripada PriorityQueue -- kami hanya membutuhkan panduan & dokumen yang kuat "Begini caranya PriorityQueue - gunakan Heap " ... itu bisa berhasil.

Namun, saya sangat ragu untuk memasukkan lebih dari 1 implementasi heap ke dalam CoreFX. 98%+ pengembang C# 'normal' di luar sana tidak peduli. Mereka bahkan tidak ingin berpikir mana yang terbaik, mereka hanya membutuhkan sesuatu yang menyelesaikan pekerjaan. Tidak setiap bagian dari setiap SW dirancang dengan mempertimbangkan kinerja tinggi, memang seharusnya demikian. Pikirkan tentang semua alat, aplikasi UI, dll. Kecuali Anda merancang sistem penskalaan berkinerja tinggi di mana ds ini berada di jalur kritis, Anda TIDAK perlu peduli.

Dengan cara yang sama, saya tidak peduli bagaimana SortedDictionary atau ArrayList atau ds lainnya diimplementasikan -- mereka melakukan pekerjaan mereka dengan baik. Saya (seperti banyak orang lain) mengerti bahwa jika saya membutuhkan kinerja tinggi dari ds ini untuk skenario saya , saya perlu mengukur kinerja dan/atau memeriksa implementasi dan harus memutuskan apakah itu cukup baik untuk skenario saya , atau jika saya perlu luncurkan implementasi khusus saya sendiri untuk mendapatkan kinerja terbaik, sesuai dengan kebutuhan saya.

Kita harus mengoptimalkan kegunaan untuk kasus penggunaan 98%, bukan untuk 2%. Jika kami meluncurkan terlalu banyak opsi (implementasi) dan memaksa semua orang untuk memutuskan, kami hanya menyebabkan kebingungan yang tidak perlu untuk 98% kasus penggunaan. Saya tidak berpikir itu layak ...
Ekosistem IMO .NET memiliki nilai besar dalam menawarkan pilihan tunggal dari banyak API (bukan hanya koleksi) dengan karakteristik kinerja yang sangat layak, berguna untuk sebagian besar kasus penggunaan. Dan menawarkan ekosistem yang memungkinkan ekstensi berkinerja tinggi bagi mereka yang membutuhkannya dan bersedia menggali lebih dalam dan belajar lebih banyak / membuat pilihan dan pengorbanan yang terdidik.

Yang mengatakan, memiliki antarmuka IHeap (seperti IDictionary dan IReadOnlyDictionary ), mungkin masuk akal - saya harus memikirkannya sedikit lebih banyak / bertanya kepada pakar tinjauan API di ruang angkasa ...

Kami sudah (sampai tingkat tertentu) memiliki apa yang @pgolebiowski bicarakan dengan ISet<T> dan HashSet<T> . Saya katakan cerminkan saja. Jadi API di atas diubah menjadi antarmuka ( IPriorityQueue<T> ) dan kemudian kami memiliki implementasi ( HeapPriorityQueue<T> ) yang secara internal menggunakan heap yang mungkin atau mungkin tidak diekspos secara publik sebagai kelasnya sendiri.

Haruskah itu ( PriorityQueue<T> ) juga mengimplementasikan IList<T> ?

@karelz masalah saya dengan ICollection adalah SyncRoot dan IsSynchronized ; baik mereka diimplementasikan yang berarti ada alokasi tambahan untuk objek kunci; atau mereka melempar, ketika tidak ada gunanya memilikinya.

@benaadams Ini akan menyesatkan. Karena 99,99% implementasi antrian prioritas adalah tumpukan berdasarkan array (dan seperti yang saya lihat kita juga akan melakukannya di sini), apakah itu berarti mengekspos akses ke struktur internal array?

Katakanlah kita memiliki heap dengan elemen 4, 8, 10, 13, 30, 45. Mengingat urutannya, mereka akan diakses oleh indeks 0, 1, 2, 3, 4, 5. Namun, struktur internal heap adalah [4, 8, 30, 10, 13, 45] (dalam biner, dalam kuarter akan berbeda).

  • Mengembalikan nomor internal pada indeks i tidak benar-benar masuk akal dari sudut pandang pengguna, karena hampir sewenang-wenang.
  • Mengembalikan angka secara berurutan (berdasarkan prioritas) terlalu mahal -- ini linier.
  • Mengembalikan salah satu dari ini tidak masuk akal dalam implementasi lain. Seringkali itu hanya memunculkan elemen i , mendapatkan elemen i -th, dan kemudian mendorongnya sekali lagi.

IList<T> biasanya merupakan solusi yang kurang ajar untuk: Saya ingin fleksibel dengan koleksi yang diterima api saya, dan saya ingin menghitungnya tetapi tidak ingin mengalokasikan melalui IEnumerable<T>

Baru sadar tidak ada antarmuka generik untuk

ICollection<T>
{
    int Count
    CopyTo(T[], int index)
}

Jadi tidak apa-apa tentang itu (Meskipun semacam IReadOnlyCollection)

Tetapi Reset pada Enumerator harus diterapkan secara eksplisit karena buruk dan harus dibuang begitu saja.

Jadi perubahan yang saya sarankan

public bool TryDequeue(out T item);     // Add
public bool TryPeek(out T item);        // Add

public struct Enumerator : IEnumerator<T>
{
    public T Current { get; }
    object IEnumerator.Current { get; }
    public bool MoveNext();
    void IEnumerator.Reset();             // Explicit
    public void Dispose();
}

Karena Anda sedang mendiskusikan metodenya... Kami belum menyetujui hal IHeap vs IPriorityQueue -- ini sedikit memengaruhi nama metode dan logika. Namun, bagaimanapun, saya menemukan proposal API saat ini tidak memiliki yang berikut:

  • Kemampuan untuk menghapus elemen tertentu dari antrian
  • Kemampuan untuk memperbarui prioritas elemen
  • Kemampuan untuk menggabungkan struktur ini

Operasi ini sangat penting, terutama kemungkinan memperbarui elemen. Tanpa ini, banyak algoritma tidak dapat diimplementasikan. Kita perlu memperkenalkan tipe pegangan. Di API di sini , pegangannya adalah IHeapNode . Yang merupakan argumen lain untuk menggunakan cara IHeap karena jika tidak, kita harus memperkenalkan tipe PriorityQueueHandle yang akan selalu menjadi simpul tumpukan... Ditambah lagi, itu hanya tidak jelas apa artinya... Padahal, simpul tumpukan -- semua orang tahu hal itu dan bisa membayangkan hal yang mereka hadapi.

Sebenarnya, secara singkat, untuk proposal API, silakan lihat direktori ini . Kita mungkin hanya membutuhkan sebagian saja. Namun demikian -- itu hanya berisi apa yang kita butuhkan IMO, jadi mungkin layak untuk melihatnya sebagai titik awal.

Apa pendapat Anda, teman-teman?

IHeapNode tidak jauh berbeda dengan tipe clr KeyValuePair ?

Namun itu kemudian memisahkan prioritas dan ketik jadi sekarang PriorityQueue<TKey, TValue> dengan IComparer<TKey> comparer ?

KeyValuePair bukan hanya sebuah struct tetapi propertinya hanya-baca. Ini pada dasarnya sama dengan membuat objek baru setiap kali struktur diperbarui.

Menggunakan kunci saja tidak berfungsi dengan kunci yang sama -- informasi lebih lanjut diperlukan untuk mengetahui elemen mana yang akan diperbarui/dihapus.

Dari IHeapNode dan ArrayHeapNode :

    /// <summary>
    /// Represents a heap node. It is a wrapper around a key and a value.
    /// </summary>
    public interface IHeapNode<out TKey, out TValue>
    {
        /// <summary>
        /// Gets the key for the value.
        /// </summary>
        TKey Key { get; }

        /// <summary>
        /// Gets the value contained in the node.
        /// </summary>
        TValue Value { get; }
    }

    /// <summary>
    /// Represents a node of an <see cref="ArrayHeap{TKey,TValue}"/>.
    /// </summary>
    public class ArrayHeapNode<TKey, TValue> : IHeapNode<TKey, TValue>
    {
        /// <inheritdoc cref="IHeapNode{TKey,TValue}.Key"/>
        public TKey Key { get; internal set; }

        /// <inheritdoc cref="IHeapNode{TKey,TValue}.Value"/>
        public TValue Value { get; }

        /// <summary>
        /// The index of the node within an <see cref="ArrayHeap{TKey,TValue}"/>.
        /// </summary>
        public int Index { get; internal set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="ArrayHeapNode{TKey,TValue}"/> class,
        /// containing the specified value.
        /// </summary>
        public ArrayHeapNode(TKey key, TValue value, int index)
        {
            this.Key = key;
            this.Value = value;
            this.Index = index;
        }
    }

Dan metode pembaruan dalam ArrayHeap :

        /// <inheritdoc cref="IHeap{TKey,TValue}.Update"/>
        public override void Update(ArrayHeapNode<TKey, TValue> node, TKey key)
        {
            if (node == null)
                throw new ArgumentNullException(nameof(node));

            var relation = this.Comparer.Compare(key, node.Key);
            node.Key = key;

            if (relation < 0)
                this.MoveUp(node);
            else
                this.MoveDown(node);
        }

Tapi sekarang setiap elemen di Heap adalah objek tambahan; bagaimana jika saya menginginkan perilaku PriorityQueue tetapi saya tidak menginginkan alokasi tambahan ini?

Maka Anda tidak akan dapat memperbarui/menghapus elemen. Ini akan menjadi implementasi telanjang yang tidak memungkinkan Anda untuk mengimplementasikan algoritma apa pun yang bergantung pada operasi DecreaseKey (yang sangat umum). Misalnya algoritma Dijkstra, algoritma Prim. Atau jika Anda sedang menulis semacam penjadwal dan beberapa proses (atau apa pun) telah mengubah prioritasnya, Anda tidak dapat mengatasinya.

Juga, dalam setiap implementasi heap lainnya -- elemen hanyalah node, secara eksplisit. Ini sangat alami dalam kasus lain, ini sedikit buatan, tetapi perlu untuk memperbarui / menghapus.

Di Jawa antrian prioritas tidak memiliki opsi untuk memperbarui elemen. Hasil:

Ketika saya menerapkan tumpukan untuk proyek AlgoKit dan menemukan semua masalah desain ini, saya pikir inilah alasan mengapa penulis .NET memutuskan untuk tidak menambahkan struktur data dasar seperti tumpukan (atau antrian prioritas). Karena tidak ada desain yang bagus. Masing-masing memiliki kekurangannya.

Singkatnya -- jika kita ingin menambahkan struktur data yang mendukung pengurutan elemen yang efisien berdasarkan prioritasnya, sebaiknya kita melakukannya dengan cara yang benar dan menambahkan fungsionalitas seperti memperbarui/menghapus elemen darinya.

Jika Anda merasa tidak enak karena membungkus elemen dalam array dengan objek lain -- ini bukan satu-satunya masalah. Yang lain adalah penggabungan. Dengan tumpukan berbasis array, ini sama sekali tidak efisien. Namun... jika kita menggunakan struktur data heap berpasangan ( The pairing heap: Sebuah bentuk baru dari heap yang dapat menyesuaikan sendiri ), maka:

  • pegangan ke elemen adalah simpul yang luar biasa -- ini harus tetap dialokasikan, jadi tidak ada hal-hal yang berantakan
  • penggabungan adalah konstan (vs linier dalam solusi berbasis array)

Sebenarnya, kita bisa menyelesaikan ini dengan cara berikut:

  • Tambahkan antarmuka IHeap yang mendukung semua metode
  • Tambahkan ArrayHeap dan PairingHeap dengan semua pegangan itu, perbarui, hapus, gabungkan
  • Tambahkan PriorityQueue yang hanya membungkus ArrayHeap , menyederhanakan API

PriorityQueue akan berada di System.Collections.Generic dan semua tumpukan di System.Collections.Specialized .

Bekerja?

Agak tidak mungkin kami akan mendapatkan tiga struktur data baru melalui tinjauan API. Dalam kebanyakan kasus, jumlah API yang lebih kecil lebih baik. Kami selalu dapat menambahkan lebih banyak nanti jika ternyata tidak mencukupi, tetapi kami tidak dapat menghapus API.

Itulah salah satu alasan saya bukan penggemar kelas HeapNode. Imo hal semacam itu harus hanya internal dan API harus mengekspos tipe yang sudah ada jika memungkinkan - dalam hal ini KVP mungkin.

@ianhays Jika ini disimpan hanya untuk internal, pengguna tidak akan dapat memperbarui prioritas elemen dalam struktur data. Ini akan sangat tidak berguna dan kita akan berakhir dengan semua masalah Java -- orang-orang mengimplementasikan kembali struktur data yang sudah ada di perpustakaan asli... Kedengarannya buruk bagi saya. Jauh lebih buruk daripada memiliki kelas sederhana yang mewakili sebuah simpul.

BTW: daftar tertaut memiliki kelas simpul sehingga pengguna dapat menggunakan fungsionalitas yang tepat. Ini cukup banyak mencerminkan.

pengguna tidak akan dapat memperbarui prioritas elemen dalam struktur data.

Itu belum tentu benar. Prioritas dapat diekspos dengan cara yang tidak memerlukan struktur data tambahan sehingga alih-alih

        public override void Update(ArrayHeapNode<TKey, TValue> node, TKey key)
        {

        }

Anda akan memiliki misalnya

        public override void Update(TKey item, TValue priority)
        {

        }

Saya juga belum yakin untuk mengekspos prioritas sebagai parameter tipe. Imo sebagian besar orang akan menggunakan tipe prioritas default dan TValue akan menjadi kekhususan yang tidak perlu.

void Update(TKey item, TValue priority)

Pertama -- apa itu TKey dan TValue dalam kode Anda? Bagaimana cara kerjanya? Konvensi dalam ilmu komputer adalah bahwa:

  • kunci = prioritas
  • value = apa pun yang ingin Anda simpan dalam elemen

Kedua:

Imo sebagian besar orang akan menggunakan tipe prioritas default dan TValue akan menjadi kekhususan yang tidak perlu.

Harap tentukan "jenis prioritas default". Saya dapat merasakan bahwa Anda hanya ingin memiliki PriorityQueue<T> , benarkah? Jika demikian, pertimbangkan fakta bahwa pengguna kemungkinan harus membuat kelas baru, membungkus prioritas dan nilainya, ditambah mengimplementasikan sesuatu seperti IComparable atau menyediakan pembanding khusus. Sangat miskin.

Pertama -- apa itu TKey dan TValue dalam kode Anda?

Item dan prioritas item. Anda dapat mengubahnya menjadi prioritas dan item yang terkait dengan prioritas itu, tetapi kemudian Anda memiliki koleksi di mana TKey tidak harus unik (yaitu memungkinkan prioritas duplikat). Saya tidak menentang itu, tetapi bagi saya TKey biasanya menyiratkan keunikan.

Intinya adalah mengekspos kelas Node bukanlah persyaratan untuk mengekspos metode Pembaruan.

Harap tentukan "jenis prioritas default". Saya dapat merasakan bahwa Anda hanya ingin memiliki PriorityQueue, Apakah itu benar?

Ya. Meskipun membaca utas ini lagi, saya tidak sepenuhnya yakin itu masalahnya.

Jika demikian, pertimbangkan fakta bahwa pengguna kemungkinan harus membuat kelas baru, pembungkus di sekitar prioritas dan nilainya, ditambah mengimplementasikan sesuatu seperti IComparable atau menyediakan pembanding khusus. Sangat miskin.

Kamu tidak salah. Saya membayangkan cukup banyak pengguna harus membuat jenis pembungkus dengan logika pembanding kustom. Saya membayangkan ada juga cukup banyak pengguna yang sudah memiliki tipe sebanding yang ingin mereka masukkan ke dalam antrian prioritas atau tipe dengan pembanding yang ditentukan. Pertanyaannya adalah kubu mana yang lebih besar dari keduanya.

Saya pikir jenisnya harus disebut PriorityQueue<T> , bukan Heap<T> , ArrayHeap<T> atau bahkan QuaternaryHeap<T> agar tetap konsisten dengan .Net lainnya:

  • Kami memiliki List<T> , bukan ArrayList<T> .
  • Kami memiliki Dictionary<K, V> , bukan HashTable<K, V> .
  • Kami memiliki ImmutableList<T> , bukan ImmutableAVLTreeList<T> .
  • Kami memiliki Array.Sort() , bukan Array.IntroSort() .

Pengguna tipe seperti itu biasanya tidak peduli bagaimana penerapannya, itu tentu bukan hal yang paling menonjol tentang tipe tersebut.

Jika demikian, pertimbangkan fakta bahwa pengguna kemungkinan harus membuat kelas baru, pembungkus di sekitar prioritas dan nilainya, ditambah mengimplementasikan sesuatu seperti IComparable atau menyediakan pembanding khusus.

Kamu tidak salah. Saya membayangkan cukup banyak pengguna harus membuat jenis pembungkus dengan logika pembanding khusus.

Dalam api ringkasan, perbandingan disediakan oleh pembanding yang disediakan IComparer<T> comparer , tidak diperlukan pembungkus. Seringkali prioritas akan menjadi bagian dari tipe misalnya penjadwal waktu akan memiliki waktu eksekusi sebagai properti dari tipe tersebut.

Menggunakan KeyValuePair dengan IComparer kustom tidak menambahkan alokasi tambahan; meskipun itu menambahkan satu tipuan tambahan untuk pembaruan karena nilai perlu dibandingkan daripada item.

Pembaruan akan bermasalah untuk item struct kecuali item diperoleh melalui pengembalian referensi dan kemudian diperbarui dan diteruskan kembali ke metode pembaruan oleh referensi dan referensi dibandingkan. Tapi itu agak mengerikan.

@svick

Saya pikir jenisnya harus disebut PriorityQueue<T> , bukan Heap<T> , ArrayHeap<T> atau bahkan QuaternaryHeap<T> agar tetap konsisten dengan .Net lainnya.

Saya kehilangan kepercayaan saya pada kemanusiaan.

  • Memanggil struktur data PriorityQueue konsisten dengan sisa .NET dan menyebutnya Heap bukan? Ada yang salah dengan tumpukan? Hal yang sama harus berlaku untuk tumpukan dan antrian.
  • ArrayHeap -> kelas dasar untuk QuaternaryHeap . Perbedaan besar.
  • Pembahasannya bukan soal ngambil nama keren. Ada banyak hal yang datang sebagai konsekuensi dari mengikuti jalur desain tertentu. Tolong baca ulang threadnya.
  • Tabel hash , ArrayList . Mereka tampaknya ada. BTW, "daftar" adalah pilihan penamaan IMO yang sangat buruk, karena pikiran pertama pengguna adalah bahwa List adalah daftar, tetapi itu bukan daftar. 😜
  • Ini bukan tentang bersenang-senang dan mengatakan bagaimana suatu hal diimplementasikan. Ini tentang memberi nama yang bermakna yang menunjukkan kepada pengguna dengan segera apa yang mereka hadapi.

Ingin tetap konsisten dengan .NET lainnya dan mengalami masalah seperti ini?

Dan apa yang saya lihat di sana... Jon Skeet mengatakan bahwa SortedDictionary harus disebut SortedTree karena itu mencerminkan implementasi lebih dekat.

@pgolebiowski

Ada yang salah dengan tumpukan?

Ya, ini menjelaskan implementasi, bukan penggunaan.

Hal yang sama harus berlaku untuk tumpukan dan antrian.

Tidak ada yang benar-benar menjelaskan implementasinya, misalnya namanya tidak memberi tahu Anda bahwa mereka berbasis array.

ArrayHeap -> kelas dasar untuk QuaternaryHeap . Perbedaan besar.

Ya, saya mengerti desain Anda. Apa yang saya katakan adalah semua ini tidak boleh diekspos ke pengguna.

Ada banyak hal yang datang sebagai konsekuensi dari mengikuti jalur desain tertentu. Tolong baca ulang threadnya.

saya sudah baca threadnya. Saya tidak berpikir bahwa desain itu milik BCL. Saya pikir itu harus berisi sesuatu yang mudah digunakan dan dipahami, bukan sesuatu yang membuat orang bertanya-tanya apa itu "tumpukan kuartener" atau apakah mereka harus menggunakannya.

Jika implementasi default tidak cukup baik untuk mereka, di situlah perpustakaan lain (seperti milik Anda) masuk.

Hashtable, ArrayList. Mereka tampaknya ada.

Ya, itu adalah kelas .Net Framework 1.0 yang tidak digunakan lagi oleh siapa pun. Sejauh yang saya tahu, nama mereka disalin dari Java dan desainer .Net Framework 2.0 memutuskan untuk tidak mengikuti konvensi itu. Menurut saya, itu adalah keputusan yang tepat.

BTW, "daftar" adalah pilihan penamaan IMO yang sangat buruk, karena pikiran pertama pengguna adalah bahwa List adalah daftar, tetapi itu bukan daftar.

Dia. Ini bukan daftar tertaut, tapi itu bukan hal yang sama. Dan saya suka tidak harus menulis ArrayList atau ResizeArray (nama F# untuk List<T> ) di mana-mana.

Ini tentang memberi nama yang bermakna yang menunjukkan kepada pengguna dengan segera apa yang mereka hadapi.

Kebanyakan orang tidak akan tahu apa yang mereka hadapi jika mereka melihat QuaternaryHeap . Di sisi lain, jika mereka melihat PriorityQueue , seharusnya jelas apa yang mereka hadapi, bahkan jika mereka tidak memiliki latar belakang CS. Mereka tidak akan tahu apa implementasinya, tetapi untuk itulah dokumentasi.

@ianhays

Pertama -- apa itu TKey dan TValue dalam kode Anda?

Item dan prioritas item. Anda dapat mengubahnya menjadi prioritas dan item yang terkait dengan prioritas itu, tetapi kemudian Anda memiliki koleksi di mana TKey tidak harus unik (yaitu memungkinkan prioritas duplikat). Saya tidak menentang itu, tetapi bagi saya TKey biasanya menyiratkan keunikan.

Kunci -- benda yang digunakan untuk membuat prioritas. Kunci tidak harus unik. Kita tidak bisa membuat asumsi seperti itu.

Intinya adalah mengekspos kelas Node bukanlah persyaratan untuk mengekspos metode Pembaruan.

Saya percaya itu adalah: Dukungan Penurunan-kunci/Peningkatan-kunci di perpustakaan asli . Juga pada topik ini, jadi ada kelas pembantu yang terlibat untuk menangani struktur data:

Saya membayangkan cukup banyak pengguna harus membuat jenis pembungkus dengan logika pembanding khusus. Saya membayangkan ada juga cukup banyak pengguna yang sudah memiliki tipe sebanding yang ingin mereka masukkan ke dalam antrian prioritas atau tipe dengan pembanding yang ditentukan. Pertanyaannya adalah kubu mana yang lebih besar dari keduanya.

Saya tidak tahu tentang yang lain, tetapi saya menemukan membuat pembungkus dengan logika pembanding khusus setiap kali saya ingin menggunakan antrian prioritas menjadi sangat mengerikan ...

Pikiran lain -- katakanlah saya membuat pembungkus:

class Wrapper
{
    public int Priority { get; set; }
    public string SomeStuff ...
}

Saya memberi makan PriorityQueue<T> dengan tipe ini. Jadi itu memprioritaskan elemen-elemennya berdasarkan itu. Katakanlah itu telah mendefinisikan beberapa pemilih kunci. Desain seperti itu dapat merusak pekerjaan internal tumpukan. Anda dapat mengubah prioritas kapan saja karena Anda adalah pemilik elemen. Tidak ada mekanisme notifikasi untuk heap yang akan diperbarui. Anda akan bertanggung jawab untuk menangani semua itu dan menyebutnya. Cukup berbahaya dan tidak langsung bagi saya.

Dalam hal pegangan yang saya usulkan -- jenisnya tidak dapat diubah dari sudut pandang pengguna. Dan tidak ada masalah dengan keunikan.

IMO Saya suka ide memiliki antarmuka IHeap dan kemudian kelas API mengimplementasikan antarmuka itu. Saya dengan @ianhays ' dalam hal ini bahwa kami tidak akan mengekspos 3 api baru untuk memberikan jenis tumpukan yang berbeda kepada pelanggan karena perlu ada proses Tinjauan API dan kami ingin fokus pada PriorityQueue tidak peduli apa namanya.

Kedua, saya pikir tidak perlu memiliki kelas internal/publik Node untuk menyimpan nilai dalam antrian. Tidak perlu alokasi tambahan, kita dapat mengikuti sesuatu seperti yang dilakukan IDictionary dan ketika menghasilkan nilai untuk Enumerable yang membuat KeyValuePairobjek jika kita memilih opsi untuk memiliki prioritas untuk setiap item yang kita simpan (yang menurut saya bukan cara terbaik untuk pergi, saya tidak sepenuhnya yakin untuk menyimpan prioritas per item). Pendekatan terbaik yang saya inginkan adalah memiliki PriorityQueue<T> (nama ini hanya untuk memberikan satu). Dengan pendekatan ini kita dapat memiliki konstruktor yang mengikuti apa yang dilakukan seluruh BCL dengan IComparer<T> dan melakukan perbandingan dengan pembanding itu untuk memberikan prioritas. Tidak perlu mengekspos API yang melewati prioritas sebagai parameter.

Saya pikir memiliki beberapa API menerima prioritas akan membuatnya "kurang dapat digunakan" atau lebih rumit untuk pelanggan biasa yang ingin menjadi prioritas default, maka kami membuatnya lebih rumit untuk memahami penggunaannya, ketika memberi mereka opsi untuk memiliki IComparer<T> kustom akan menjadi yang paling masuk akal dan akan mengikuti pedoman yang kami miliki di seluruh BCL.

Nama adalah apa yang mereka lakukan, bukan bagaimana mereka melakukannya.

Mereka dinamai konsep abstrak dan apa yang mereka capai untuk pengguna, bukan implementasinya dan bagaimana mereka mencapainya. (Yang juga berarti implementasinya dapat ditingkatkan untuk menggunakan implementasi yang berbeda jika terbukti lebih baik)

Hal yang sama harus berlaku untuk tumpukan dan antrian.

Stack adalah larik pengubahan ukuran tak terbatas dan Queue diimplementasikan sebagai buffer melingkar pengubahan ukuran tak terbatas. Mereka diberi nama setelah konsep abstrak mereka. Stack (tipe data abstrak) : Last-In-First-Out (LIFO), Antrian (tipe data abstrak) : First-In-First-Out (FIFO)

Hashtable, ArrayList. Mereka tampaknya ada.

KamusMasuk

Ya tapi mari kita berpura-pura tidak; mereka adalah artefak dari waktu yang kurang beradab. Mereka tidak memiliki keamanan tipe dan kotak jika Anda menggunakan primitif atau struct; jadi alokasikan pada setiap Add.

Anda dapat menggunakan Penganalisis Kompatibilitas Platform @ terrajobst dan itu akan memberi tahu Anda: "Tolong jangan"

Daftar adalah daftar, tapi itu bukan daftar

Itu benar-benar Daftar (tipe data abstrak) juga dikenal sebagai Urutan.

Equally Priority Queue adalah tipe abstrak yang memberi tahu Anda apa yang dicapainya saat digunakan; bukan bagaimana melakukannya dalam implementasi (karena ada banyak implementasi yang berbeda)

Banyak diskusi yang bagus!

Spesifikasi asli dirancang dengan beberapa prinsip inti dalam pikiran:

  • Tujuan umum - Menutupi sebagian besar kasus penggunaan dengan keseimbangan antara konsumsi CPU dan Memori.
  • Sejajarkan, sebisa mungkin, dengan pola dan konvensi yang ada di System.Collections.Generic.
  • Izinkan beberapa entri dari elemen yang sama.
  • Memanfaatkan antarmuka dan kelas perbandingan BCL yang ada (yaitu Pembanding).*
  • Abstraksi tingkat tinggi - Spesifikasi dirancang untuk melindungi konsumen dari teknologi implementasi yang mendasarinya.

@karelz @pgolebiowski
Mengganti nama menjadi "Heap" atau istilah lain yang selaras dengan implementasi struktur data tidak akan cocok dengan sebagian besar konvensi BCL untuk koleksi. Secara historis, kelas koleksi .NET telah dirancang untuk tujuan umum daripada berfokus pada struktur/pola data tertentu. Pemikiran awal saya adalah bahwa di awal ekosistem .NET, desainer API dengan sengaja pindah dari "ArrayList" ke "List". Perubahan itu kemungkinan karena kebingungan dengan Array - rata-rata pengembang Anda akan berpikir "ArrayList? Saya hanya ingin daftar, bukan array".

Jika kita menggunakan Heap, hal yang sama mungkin terjadi - banyak pengembang berketerampilan menengah akan (sayangnya) melihat "Heap" dan mengacaukannya untuk tumpukan memori aplikasi (yaitu tumpukan dan tumpukan) alih-alih "menumpuk struktur data umum". Prevalensi System.Collections.Generic akan menyebabkannya muncul di hampir setiap proposal cerdas pengembang .NET, dan mereka akan bertanya-tanya mengapa mereka dapat mengalokasikan tumpukan memori baru :)

PriorityQueue, sebagai perbandingan, jauh lebih mudah ditemukan dan tidak mudah bingung. Anda dapat mengetik "Antrian" dan mendapatkan proposal untuk Antrian Prioritas.

Beberapa proposal dan pertanyaan diajukan tentang prioritas bilangan bulat atau parameter umum untuk prioritas (TKey, TPriority, dll). Menambahkan prioritas eksplisit akan mengharuskan konsumen untuk menulis logika mereka sendiri untuk memetakan prioritas mereka dan meningkatkan kompleksitas API. Menggunakan IComparer bawaanmemanfaatkan fungsionalitas BCL yang ada, dan saya juga mempertimbangkan untuk menambahkan kelebihan ke Pembandingke dalam spesifikasi untuk memudahkan penyediaan ekspresi lambda ad-hoc/fungsi anonim sebagai perbandingan prioritas. Sayangnya, itu bukan konvensi umum di BCL.

Jika entri harus unik, Enqueue() akan memerlukan pencarian keunikan untuk melempar ArgumentException. Selain itu, kemungkinan ada skenario yang valid untuk memungkinkan item diantrekan lebih dari sekali. Desain non-keunikan ini membuat penyediaan operasi Update() menjadi menantang karena tidak akan ada cara untuk mengetahui objek mana yang sedang diperbarui. Seperti yang ditunjukkan oleh beberapa komentar, ini akan mulai masuk ke API yang mengembalikan referensi "simpul" yang pada gilirannya akan (kemungkinan) memerlukan alokasi yang perlu dikumpulkan sampah. Bahkan jika itu berhasil, itu akan meningkatkan konsumsi memori per elemen dari antrian prioritas.

Pada satu titik saya memiliki antarmuka IPriorityQueue khusus di API sebelum saya memposting spesifikasi. Akhirnya saya memutuskan untuk tidak melakukannya - pola penggunaan yang saya tuju adalah Enqueue, Dequeue, dan Iterate. Sudah tercakup oleh set antarmuka yang ada. Memikirkan ini sebagai Antrian yang diurutkan secara internal; selama item memegang posisi mereka sendiri (awal) dalam antrian berdasarkan IComparer mereka, penelepon tidak perlu khawatir tentang bagaimana prioritas direpresentasikan. Dalam implementasi referensi lama yang saya lakukan, (jika saya ingat benar!) Prioritas tidak terwakili sama sekali. Semuanya relatif berdasarkan ICompareratau Perbandingan.

Saya berhutang kepada Anda semua contoh kode pelanggan - rencana awal saya adalah melalui implementasi BCL PriorityQueue yang ada untuk digunakan sebagai dasar untuk contoh.

Dalam api ringkasan, perbandingan disediakan oleh pembanding yang disediakan IComparerpembanding, tidak diperlukan pembungkus. Seringkali prioritas akan menjadi bagian dari tipe misalnya penjadwal waktu akan memiliki waktu eksekusi sebagai properti dari tipe tersebut.

Di bawah OP API yang diusulkan, salah satu dari ini harus dipenuhi untuk menggunakan kelas:

  • Miliki tipe yang sudah sebanding dengan yang Anda inginkan
  • Bungkus tipe dengan kelas lain yang berisi nilai prioritas dan bandingkan seperti yang Anda inginkan
  • Buat IComparer khusus untuk tipe Anda dan berikan melalui konstruktor. Diasumsikan bahwa nilai yang ingin Anda wakili prioritasnya sudah diekspos secara publik oleh tipe Anda.

API tipe ganda bertujuan untuk mengurangi beban dari dua opsi kedua, ya? Bagaimana cara kerja konstruksi PriorityQueue/Heap baru ketika Anda memiliki tipe yang sudah berisi prioritas ? Seperti apa tampilan API-nya?

Saya percaya itu adalah: Dukungan Kurangi-kunci/Tingkatkan-kunci di perpustakaan asli.

public override void Update(ArrayHeapNode<TKey, TValue> node, TKey key) {}
public override void Update(TKey key, TValue value);

Dalam kedua kasus tersebut, item yang terkait dengan prioritas yang diberikan akan diperbarui. Mengapa ArrayHeapNode diperlukan untuk memperbarui? Apa yang dicapai yang tidak dapat dicapai dengan mengambil TKey/TValue secara langsung?

@ianhays

Dalam kedua kasus tersebut, item yang terkait dengan prioritas yang diberikan akan diperbarui. Mengapa ArrayHeapNode diperlukan untuk memperbarui? Apa yang dicapai yang tidak dapat dicapai dengan mengambil TKey/TValue secara langsung?

Setidaknya dengan tumpukan biner (yang paling saya kenal), jika Anda ingin memperbarui prioritas beberapa nilai dan Anda tahu posisinya di tumpukan, Anda dapat melakukannya dengan cepat (dalam waktu O(log n)).

Tetapi jika Anda hanya mengetahui nilai dan prioritasnya, pertama-tama Anda harus mencari nilainya, yaitu lambat (O(n)).

Di sisi lain, jika Anda tidak perlu memperbarui secara efisien, satu alokasi per item di heap adalah overhead murni.

Saya ingin melihat solusi di mana Anda membayar overhead itu hanya ketika Anda membutuhkannya, tetapi itu mungkin tidak dapat diterapkan dan API yang dihasilkan mungkin tidak terlihat bagus.

API tipe ganda bertujuan untuk mengurangi beban dari dua opsi kedua, ya? Bagaimana cara kerja konstruksi PriorityQueue/Heap baru ketika Anda memiliki tipe yang sudah berisi prioritas? Seperti apa tampilan API-nya?

Itu poin yang bagus, ada celah di API asli untuk skenario di mana konsumen sudah memiliki nilai prioritas (yaitu bilangan bulat) yang dipertahankan secara terpisah dari nilai. Sisi sebaliknya adalah bahwagaya API akan memperumit semua jenis yang secara intrinsik sebanding. Bertahun-tahun yang lalu saya menulis komponen penjadwal yang sangat ringan yang memiliki kelas ScheduledTask internalnya sendiri. Setiap ScheduledTask mengimplementasikan IComparer. Masukkan mereka ke dalam antrean prioritas, siap.

Daftar Terurutmenggunakan desain Kunci/Nilai dan saya mendapati diri saya menghindari menggunakannya sebagai hasilnya. Ini membutuhkan kunci untuk menjadi unik, dan persyaratan saya yang biasa adalah "Saya memiliki nilai N yang saya perlukan untuk tetap diurutkan dalam daftar, dan memastikan nilai sesekali yang saya tambahkan tetap diurutkan". "Kunci" bukan bagian dari persamaan itu, daftar bukan kamus.

Bagi saya, prinsip yang sama berlaku untuk antrian. Antrian, secara historis, adalah struktur data satu dimensi.

Pengetahuan perpustakaan C++ std modern saya memang agak berkarat, tetapi bahkan std::priority_queue tampaknya memiliki Push, pop, dan mengambil pembanding sebagai parameter templated (generik). Kru perpustakaan standar C++ sama sensitifnya dengan kinerja yang Anda dapatkan :)

Saya melakukan pemindaian yang sangat cepat dari antrian prioritas dan implementasi tumpukan dalam beberapa bahasa pemrograman sekarang - C++, Java, Rust, dan Go semua bekerja dengan satu jenis (mirip dengan API asli yang diposting di sini). Sekilas tentang implementasi heap/prioritas antrian paling populer di NPM menunjukkan hal yang sama.

@pgolebiowski jangan salah

Namun itu ketika Anda tahu struktur data spesifik apa yang Anda inginkan yang cocok dengan sasaran kinerja yang Anda kejar dan memiliki trade off yang bersedia Anda terima.

Umumnya koleksi kerangka kerja mencakup 90% penggunaan di mana Anda menginginkan perilaku umum. Kemudian jika Anda menginginkan perilaku atau implementasi yang sangat spesifik, Anda mungkin akan menggunakan perpustakaan pihak ke-3; dan semoga diberi nama setelah implementasi sehingga Anda tahu itu sesuai dengan kebutuhan Anda.

Hanya tidak ingin mengikat tipe perilaku umum ke implementasi tertentu; karena itu aneh jika implementasinya berubah karena nama tipe harus tetap sama dan tidak akan cocok.

Ada banyak masalah yang perlu didiskusikan, tetapi mari kita mulai dengan masalah yang saat ini paling berdampak pada bagian yang tidak kita setujui: memperbarui dan menghapus elemen .

Bagaimana Anda ingin mendukung operasi ini? Kita harus memasukkan mereka, itu dasar. Di Jawa, para desainer telah menghilangkannya, dan sebagai hasilnya:

  1. Ada banyak pertanyaan di forum tentang cara melakukan solusi karena fitur yang hilang.
  2. Ada implementasi pihak ketiga dari antrian tumpukan/prioritas untuk menggantikan implementasi pihak pertama, karena itu hampir tidak berguna.

Ini menyedihkan. Apakah ada orang yang benar-benar ingin mengejar cara seperti itu? Saya akan malu untuk merilis struktur data yang dinonaktifkan seperti itu.

@pgolebiowski yakinlah bahwa semua orang di sini memiliki niat terbaik untuk platform ini. Tidak ada yang ingin mengirimkan API yang rusak. Kami ingin belajar dari kesalahan orang lain, jadi tolong terus bawa informasi relevan yang bermanfaat (seperti cerita yang berbatasan dengan Java).

Namun, saya ingin menunjukkan beberapa hal:

  • Jangan mengharapkan perubahan dalam semalam. Ini adalah diskusi desain. Kita perlu menemukan konsensus. Kami tidak terburu-buru menggunakan API. Setiap pendapat harus didengar dan dipertimbangkan, tetapi tidak ada jaminan pendapat semua orang akan dilaksanakan/diterima. Jika Anda memiliki masukan, berikan, dukung dengan data dan bukti. Dengarkan juga argumen orang lain, akui mereka. Berikan bukti terhadap pendapat orang lain jika Anda tidak setuju. Terkadang, setuju pada fakta ada ketidaksepakatan pada beberapa poin. Perlu diingat bahwa SW, termasuk. Desain API bukanlah hal yang hitam-putih/benar-atau-salah.
  • Mari kita menjaga diskusi tetap sipil. Jangan gunakan kata-kata dan pernyataan yang kuat. Mari kita tidak setuju dengan anggun dan mari kita tetap diskusi teknis. Setiap orang dapat merujuk juga ke kode etik kontributor . Kami menyadari dan mendorong semangat tentang .NET, tetapi pastikan kami tidak saling menyinggung.
  • Jika Anda memiliki kekhawatiran/pertanyaan mengenai kecepatan diskusi desain, reaksi, dll., jangan ragu untuk menghubungi saya secara langsung (email saya ada di profil GH saya). Saya dapat membantu mengklarifikasi harapan, asumsi, dan kekhawatiran secara publik atau offline jika diperlukan.

Saya hanya bertanya bagaimana kalian ingin merancang pembaruan / penghapusan jika Anda tidak menyukai pendekatan yang diusulkan ... Ini mendengarkan orang lain dan mencari konsensus, saya percaya.

Saya tidak meragukan niat baik Anda! Terkadang penting bagaimana Anda bertanya - itu memengaruhi cara orang memandang teks di sisi lain. Teks bebas dari emosi, sehingga hal-hal dapat dipahami secara berbeda ketika ditulis. Bahasa Inggris sebagai bahasa kedua semakin memperkeruh banyak hal dan kita semua perlu menyadarinya. Saya akan dengan senang hati mengobrol tentang detail secara offline jika Anda tertarik ... mari arahkan diskusi ke sini kembali ke diskusi teknis ...

Dua sen saya pada debat Heap vs PriorityQueue: kedua pendekatan tersebut valid dan jelas memiliki kelebihan dan kekurangan.

Yang mengatakan, "PriorityQueue" tampaknya jauh lebih konsisten dengan pendekatan .NET yang ada. Hari ini koleksi inti adalah Daftar, Kamus, Tumpukan, Antre, HashSet, Kamus Terurut, dan SortedSet. Ini diberi nama setelah fungsionalitas dan semantik, bukan algoritme. HashSetadalah satu-satunya outlier, tetapi bahkan ini dapat dirasionalisasikan sebagai terkait dengan semantik kesetaraan set (dibandingkan dengan SortedSet). Bagaimanapun, kami sekarang memiliki ImmutableHashSetyang didasarkan pada pohon di bawah tenda.

Akan terasa aneh jika satu koleksi melawan tren di sini.

Saya pikir PriorityQueue dengan konstruktor tambahan: PriorityQueue(IHeap) dapat menjadi solusi. Konstruktor tanpa parameter IHeap dapat menggunakan Heap default.
Dalam hal ini PrioriryQueueakan mewakili tipe data abstrak (seperti kebanyakan koleksi C#) dan mengimplementasikan IPriorityQueueantarmuka tetapi dapat menggunakan implementasi tumpukan yang berbeda seperti yang disarankan @pgolebiowski :

kelas ArrayHeap : IHeap {}
kelas PairingHeap : IHeap {}
kelas FibonacciHeap : IHeap {}
kelas BinomialHeap : IHeap {}

OKE. Ada banyak suara yang berbeda. Saya melakukan diskusi lagi dan memperbaiki pendekatan saya. Saya juga membahas masalah umum. Teks di bawah ini menggunakan kutipan dari posting di atas.

Tujuan kita

Tujuan akhir dari seluruh diskusi ini adalah menyediakan serangkaian fungsi khusus untuk membuat pengguna senang. Ini adalah kasus yang cukup umum bahwa pengguna memiliki banyak elemen, di mana beberapa di antaranya memiliki prioritas lebih tinggi daripada yang lain. Akhirnya, mereka ingin menyimpan kumpulan elemen ini dalam urutan tertentu agar dapat melakukan operasi berikut secara efisien:

  • Ambil elemen dengan prioritas tertinggi (dan dapat menghapusnya).
  • Tambahkan elemen baru ke koleksi.
  • Hapus elemen dari koleksi.
  • Memodifikasi elemen dalam koleksi.
  • Menggabungkan dua koleksi.

Perpustakaan standar lainnya

Penulis perpustakaan standar lainnya juga telah mencoba mendukung fungsi ini. Di bagian ini, saya akan merujuk pada bagaimana hal itu diselesaikan dengan Python , Java , C++ , Go , Swift , dan Rust .

Tak satu StackOverflow . Ini adalah salah satu dari banyak, banyak pertanyaan seperti itu melalui Internet. Para desainer telah gagal.

Hal lain yang perlu diperhatikan adalah bahwa setiap perpustakaan menyediakan fungsionalitas parsial ini melalui penerapan tumpukan biner . Yah, telah ditunjukkan bahwa itu bukan implementasi yang paling berkinerja dalam kasus umum (input acak). Yang paling cocok untuk kasus umum adalah tumpukan kuartener (pohon 4-ary lengkap yang diurutkan secara implisit, disimpan sebagai larik). Secara signifikan kurang diketahui dan ini mungkin alasan mengapa para desainer menggunakan tumpukan biner sebagai gantinya. Tapi tetap saja — pilihan buruk lainnya, meskipun tingkat keparahannya lebih rendah.

Apa yang kita pelajari dari itu?

  • Jika kami ingin pelanggan kami senang dan mencegah mereka mengimplementasikan fungsi ini sendiri sambil mengabaikan struktur data kami yang luar biasa, kami harus memberikan dukungan untuk memodifikasi elemen yang sudah dimasukkan ke dalam koleksi.
  • Kita tidak boleh berasumsi bahwa karena beberapa fungsi disampaikan dalam beberapa cara di beberapa perpustakaan standar, kita harus melakukan hal yang sama, karena sekarang sudah dikenal luas dan pengguna sudah terbiasa dengan itu . Perhatikan bagian terakhir kalimat.

Pendekatan yang diusulkan

Saya sangat merasa bahwa kami harus menyediakan:

  • IHeap<T> antarmuka
  • Heap<T> kelas

Antarmuka IHeap jelas akan menyertakan metode untuk mencapai semua operasi yang dijelaskan di awal posting ini. Kelas Heap , yang diimplementasikan dengan tumpukan kuaterner, akan menjadi solusi masuk dalam 98% kasus. Urutan elemen akan didasarkan pada IComparer<T> diteruskan ke konstruktor atau urutan default jika suatu tipe sudah sebanding.

Pembenaran

  • Pengembang dapat menulis logika mereka terhadap sebuah antarmuka. Saya berasumsi semua orang tahu betapa pentingnya itu dan tidak akan membahasnya secara rinci. Baca: prinsip inversi dependensi , injeksi dependensi , desain berdasarkan kontrak , inversi kontrol .
  • Pengembang dapat memperluas fungsionalitas ini untuk memenuhi kebutuhan khusus mereka dengan menyediakan implementasi heap lainnya. Implementasi tersebut dapat dimasukkan dalam perpustakaan pihak ketiga seperti PowerCollections . Dengan hanya mereferensikan pustaka semacam itu, Anda dapat dengan mudah menyuntikkan tumpukan khusus Anda ke dalam logika apa pun yang menggunakan IHeap sebagai input. Beberapa contoh heap lain yang berperilaku lebih baik daripada heap kuaterner dalam beberapa kondisi tertentu adalah: pairing heap , binomial heap, dan binary heap yang kita cintai.
  • Jika pengembang hanya membutuhkan alat yang menyelesaikan pekerjaan tanpa perlu memikirkan jenis mana yang terbaik, mereka cukup menggunakan implementasi Heap tujuan umum. Ini mengoptimalkan 98% kasus penggunaan.
  • Kami menambahkan nilai luar biasa dari ekosistem .NET dalam hal menawarkan pilihan tunggal dengan karakteristik kinerja yang sangat baik, berguna untuk sebagian besar kasus penggunaan, pada saat yang sama memungkinkan ekstensi berkinerja tinggi bagi mereka yang membutuhkannya dan bersedia untuk menggali lebih dalam dan belajar lebih banyak / membuat pilihan dan pengorbanan yang terdidik.
  • Pendekatan yang diusulkan mencerminkan konvensi saat ini:

    • ISet dan HashSet

    • IList dan List

    • IDictionary dan Dictionary

  • Beberapa orang telah menyatakan bahwa kita harus memberi nama kelas berdasarkan apa yang dilakukan instance mereka, bukan bagaimana mereka melakukannya. Ini tidak sepenuhnya benar. Ini adalah jalan pintas umum untuk mengatakan bahwa kita harus memberi nama kelas setelah perilakunya. Ini memang berlaku dalam banyak kasus. Namun, ada kasus di mana ini bukan pendekatan yang tepat. Contoh yang paling menonjol adalah blok bangunan dasar — ​​seperti tipe primitif, tipe enum, atau struktur data. Prinsipnya adalah memilih nama yang bermakna (yaitu tidak ambigu bagi pengguna). Mempertimbangkan fakta bahwa fungsionalitas yang kita diskusikan selalu disediakan sebagai tumpukan — baik itu Python, Java, C++, Go, Swift, atau Rust. Heap adalah salah satu struktur data paling dasar. Heap memang tidak ambigu dan jelas. Itu juga selaras dengan Stack , Queue , List , dan Array . Pendekatan yang sama dengan penamaan diambil di perpustakaan standar paling modern (Go, Swift, Rust) — mereka mengekspos heap secara eksplisit.

@pgolebiowski Saya tidak melihat bagaimana Heap<T> / IHeap<T> dinamai seperti Stack<T> , Queue<T> , dan/atau List<T> ? Tak satu pun dari nama-nama itu menjelaskan bagaimana mereka diimplementasikan secara internal (array T saat itu terjadi).

@SamuelEnglard

Heap juga tidak mengatakan bagaimana implementasinya secara internal. Saya gagal memahami mengapa bagi banyak orang tumpukan segera mengikuti implementasi tertentu. Ada banyak varian heap yang memiliki API yang sama, mulai dari:

  • tumpukan d-ary,
  • 2-3 tumpukan,
  • tumpukan kiri,
  • tumpukan lembut,
  • tumpukan lemah,
  • B-tumpukan,
  • tumpukan radix,
  • tumpukan miring,
  • tumpukan pasangan,
  • tumpukan Fibonacci,
  • tumpukan binomial,
  • tumpukan pasangan peringkat,
  • tumpukan gempa,
  • tumpukan pelanggaran.

Mengatakan bahwa kita berurusan dengan tumpukan masih sangat abstrak. Sebenarnya, bahkan mengatakan bahwa kita berurusan dengan tumpukan kuartener adalah abstrak -- itu dapat diimplementasikan baik sebagai struktur data implisit berdasarkan larik T (seperti Stack<T> , Queue<T> , dan List<T> ) atau eksplisit (menggunakan node dan pointer).

Singkatnya, Heap<T> sangat mirip dengan Stack<T> , Queue<T> , dan List<T> , karena ini adalah struktur data dasar, blok bangunan abstrak dasar, yang dapat diimplementasikan dengan banyak cara. Selain itu, seperti yang terjadi, semuanya diimplementasikan menggunakan array T bawahnya. Saya menemukan kesamaan ini sangat kuat.

Apakah itu masuk akal?

Sebagai catatan, saya acuh tak acuh tentang penamaan. Orang yang terbiasa menggunakan pustaka standar C++ mungkin akan lebih memilih _priority_queue_. Orang yang telah dididik tentang struktur data mungkin lebih suka _Heap_. Jika saya harus memilih, saya akan memilih _heap_, meskipun itu hampir seperti lemparan koin untuk saya.

@pgolebiowski Saya salah mengucapkan pertanyaan saya, itu salah saya. Ya, Heap<T> tidak mengatakan bagaimana penerapannya secara internal.

Ya Heap adalah struktur data yang valid, tetapi Heap != Antrian Prioritas. Keduanya memperlihatkan permukaan API yang berbeda dan digunakan untuk ide yang berbeda. Heap<T> / IHeap<T> harus merupakan tipe data yang digunakan secara internal oleh (hanya nama teoretis) PriorityQueue<T> / IPriorityQueue<T> .

@SamuelEnglard
Dalam hal bagaimana dunia Ilmu Komputer diatur, ya. Berikut adalah tingkatan abstraksi:

  • implementasi : tumpukan kuaterner implisit berdasarkan array
  • abstraksi : tumpukan kuartener
  • abstraksi : keluarga tumpukan
  • abstraksi : keluarga antrian prioritas

Dan ya, memiliki IHeap dan Heap , implementasi PriorityQueue pada dasarnya adalah:

public class PriorityQueue<T>
{
    private readonly IHeap<T> heap;

    public PriorityQueue(IHeap<T> heap)
    {
        this.heap = heap;
    }

    public void Add<T>(T item) => this.heap.Add(item);

    public void Remove<T>(T item) => this.heap.Remove(item);

    // etc...
}

Mari kita jalankan pohon keputusan di sini.

Antrian prioritas juga dapat diimplementasikan dengan struktur data yang berbeda dari beberapa bentuk tumpukan (secara teoritis). Itu membuat desain PriorityQueue seperti yang di atas sangat jelek, karena hanya ditujukan untuk keluarga tumpukan. Ini juga merupakan pembungkus yang sangat tipis di sekitar IHeap . Ini menghasilkan pertanyaan -- _mengapa tidak menggunakan keluarga tumpukan saja_?

Kami memiliki satu solusi -- memperbaiki antrean prioritas ke implementasi spesifik dari tumpukan kuartener, tanpa ruang untuk antarmuka IHeap . Saya merasa itu melewati terlalu banyak level abstraksi dan membunuh semua manfaat luar biasa dari memiliki antarmuka.

Kami kembali dengan pilihan desain dari tengah diskusi -- have PriorityQueue dan IPriorityQueue . Tapi kemudian kita pada dasarnya akan memiliki:

class BinaryHeap : IPriorityQueue {}
class PairingHeap : IPriorityQueue {}
class FibonacciHeap : IPriorityQueue {}
class BinomialHeap : IPriorityQueue {}

Terasa tidak hanya jelek, tetapi juga salah secara konseptual -- ini bukan jenis antrian prioritas dan tidak berbagi API yang sama (seperti yang sudah dicatat oleh @SamuelEnglard ). Saya pikir kita harus tetap berpegang pada tumpukan, sebuah keluarga yang cukup besar untuk memiliki abstraksi bagi mereka. Kami akan mendapatkan:

class BinaryHeap : IHeap {}
class PairingHeap : IHeap {}
class FibonacciHeap : IHeap {}
class BinomialHeap : IHeap {}

Dan, disediakan oleh kami, class Heap : IHeap {} .


BTW, seseorang mungkin menemukan yang berikut ini bermanfaat:

| Kueri Google | Hasil |
| :--------------------------------------: | :-----: |
| "struktur data" "antrian prioritas" | 172.000 |
| "struktur data" "tumpukan" | 430.000 |
| "struktur data" "antrian" -"antrian prioritas" | 496.000 |
| "struktur data" "antrian" | 530.000 |
| "struktur data" "tumpukan" | 577.000 |

@pgolebiowski Saya merasa diri saya bolak-balik di sini jadi saya akan kebobolan

@karelz @safern Bagaimana perasaan Anda tentang hal di atas? Bisakah kita memperbaiki diri kita sendiri pada pendekatan IHeap + Heap sehingga saya dapat menyajikan proposal API tertentu?

Saya mempertanyakan perlunya antarmuka di sini (baik itu IHeap atau IPriorityQueue ). Ya, ada banyak algoritma berbeda yang secara teoritis dapat digunakan untuk mengimplementasikan struktur data ini. Namun, tampaknya tidak mungkin bahwa kerangka tersebut akan dikirimkan dengan lebih dari satu, dan gagasan bahwa penulis perpustakaan benar-benar membutuhkan antarmuka kanonik sehingga berbagai pihak dapat berkoordinasi dalam penulisan implementasi heap yang kompatibel secara lintas tampaknya tidak mungkin.

Selain itu, setelah antarmuka dirilis, itu tidak akan pernah dapat diubah karena kompatibilitasnya. Itu berarti bahwa antarmuka cenderung tertinggal di belakang kelas konkret dalam hal fungsionalitas (masalah dengan IList dan IDictionary hari ini). Sebaliknya, jika kelas Heap dirilis dan ada tuntutan serius untuk antarmuka IHeap, saya pikir antarmuka dapat ditambahkan ke telepon tanpa masalah.

@madelson Saya setuju bahwa kerangka kerja kemungkinan tidak perlu mengirimkan lebih dari satu implementasi heap. Namun, dengan mendukung implementasi itu melalui sebuah antarmuka, kita yang peduli dengan implementasi lain dapat dengan mudah menukar implementasi lain (buatan kita sendiri atau di perpustakaan eksternal) dan tetap kompatibel dengan kode yang menerima antarmuka.

Saya tidak benar-benar melihat poin negatif untuk pengkodean ke antarmuka. Mereka yang tidak peduli bisa mengabaikannya dan langsung menuju implementasi konkrit. Mereka yang peduli dapat menggunakan implementasi apa pun yang mereka pilih. Ini adalah pilihan. Dan itu adalah pilihan yang saya inginkan.

@pgolebiowski sekarang setelah Anda bertanya, inilah pendapat pribadi saya (saya agak yakin bahwa peninjau/arsitek API lain akan membagikannya karena saya telah memeriksa pendapat dengan beberapa):
Nama harus PriorityQueue , dan kita tidak boleh memperkenalkan antarmuka IHeap . Harus ada tepat satu implementasi (kemungkinan melalui beberapa heap).
Antarmuka IHeap adalah skenario ahli yang sangat canggih - Saya akan menyarankan untuk memindahkannya ke perpustakaan PowerCollections (akhirnya akan dibuat) atau perpustakaan lainnya.
Jika perpustakaan dan antarmuka IHeap menjadi sangat populer, kita dapat berubah pikiran nanti dan menambahkan IHeap berdasarkan permintaan (melalui kelebihan konstruktor Anda), tetapi saya rasa itu tidak berguna/selaras dengan sisa BCL cukup untuk membenarkan komplikasi menambahkan antarmuka baru sekarang. Mulai sederhana, tumbuh menjadi rumit hanya jika benar-benar dibutuhkan.
... hanya 2 sen (pribadi) saya

Mengingat perbedaan pendapat, saya mengusulkan pendekatan berikut untuk memajukan proposal (yang saya sarankan awal minggu ini ke @ianhays , dan karena itu secara tidak langsung ke @safern):

  • Buat 2 proposal alternatif - satu sederhana seperti yang saya jelaskan di atas dan satu dengan Tumpukan seperti yang Anda usulkan, mari bawa ke tinjauan API, diskusikan di sana, dan buat keputusan di sana.
  • Jika saya melihat setidaknya satu orang dalam grup tersebut memberikan suara untuk proposal Heaps, saya akan dengan senang hati mempertimbangkan kembali sudut pandang saya.

... mencoba untuk sepenuhnya transparan tentang pendapat saya (yang Anda minta), tolong jangan anggap itu sebagai kekecewaan atau penolakan - mari kita lihat bagaimana proposal API berjalan.

@karelz

Nama harus PriorityQueue

Ada argumen? Akan lebih baik jika Anda menjawab apa yang saya tulis di atas daripada hanya mengatakan tidak .

Saya pikir Anda menamakannya dengan cukup baik sebelumnya: _Jika Anda memiliki masukan, berikan, dukung dengan data dan bukti. Dengarkan juga argumen orang lain, akui mereka. Berikan bukti terhadap pendapat orang lain jika Anda tidak setuju._

Plus, ini bukan hanya tentang nama . Hal ini tidak dangkal. Silakan baca apa yang saya tulis. Ini tentang bekerja di antara level abstraksi dan kemampuan untuk meningkatkan kode/membangun di atasnya.

Oh, ada argumen dalam diskusi ini mengapa:

Jika kita menggunakan Heap, banyak pengembang berketerampilan menengah akan (sayangnya) melihat "Heap" dan mengacaukannya sebagai tumpukan memori aplikasi (yaitu tumpukan dan tumpukan) alih-alih "menumpuk struktur data umum". [...] mereka akan bertanya-tanya mengapa mereka dapat mengalokasikan tumpukan memori baru.

😛

kita seharusnya tidak memperkenalkan antarmuka IHeap . Antarmuka IHeap adalah skenario ahli yang sangat canggih - saya sarankan untuk memindahkannya ke perpustakaan PowerCollections.

@bendono menulis komentar yang sangat bagus untuk yang satu ini. @safern juga ingin mendukung implementasi dengan antarmuka. Dan beberapa lainnya.

Catatan lain -- Saya tidak yakin bagaimana Anda membayangkan memindahkan antarmuka ke perpustakaan pihak ketiga. Bagaimana mungkin pengembang menulis kode mereka pada antarmuka itu dan menggunakan fungsionalitas kami? Mereka akan dipaksa untuk tetap berpegang pada fungsionalitas non-antarmuka kami atau mengabaikannya sepenuhnya, tidak ada pilihan lain, itu saling eksklusif. Dengan kata lain -- solusi kami tidak akan dapat diperluas sama sekali, sehingga orang-orang menggunakan solusi kami yang dinonaktifkan atau beberapa perpustakaan pihak ketiga, alih-alih mengandalkan inti arsitektur yang sama dalam kedua kasus . Inilah yang kami miliki di perpustakaan standar Java dan solusi pihak ketiga.

Tetapi sekali lagi, Anda mengomentari yang satu ini dengan baik: Tidak ada yang ingin mengirimkan API yang rusak.

Mengingat perbedaan pendapat, saya mengusulkan pendekatan berikut untuk memajukan proposal. [...] Membuat 2 proposal alternatif [...] mari kita bawa ke review API, diskusikan disana dan ambil keputusan disana. Jika saya melihat setidaknya satu orang dalam grup tersebut memberikan suara untuk proposal Heaps, saya akan dengan senang hati mempertimbangkan kembali sudut pandang saya.

Ada banyak diskusi tentang berbagai bagian API di atas. Ini ditangani:

  • PriorityQueue vs Heap
  • Menambahkan antarmuka
  • Mendukung pembaruan / penghapusan elemen dari koleksi

Mengapa orang-orang yang Anda pikirkan tidak dapat berkontribusi pada diskusi ini? Mengapa kita malah harus memulainya lagi?

Jika kita menggunakan Heap, banyak pengembang berketerampilan menengah akan (sayangnya) melihat "Heap" dan mengacaukannya sebagai tumpukan memori aplikasi (yaitu tumpukan dan tumpukan) alih-alih "menumpuk struktur data umum".

Ya, ini aku. Saya sepenuhnya otodidak dalam pemrograman, jadi saya tidak tahu apa yang dikatakan ketika " Heap " memasuki diskusi. Belum lagi bahwa bahkan dalam hal koleksi, "setumpuk barang", bagi saya akan lebih secara intuitif menyiratkan itu tidak teratur dalam setiap mode.

Aku tidak percaya ini benar-benar terjadi ...

Ada argumen? Setidaknya akan lebih baik jika Anda menjawab apa yang saya tulis di atas daripada hanya mengatakan tidak.

Jika Anda membaca jawaban saya, Anda dapat melihat bahwa saya menyebutkan argumen kunci untuk posisi saya:

  • Antarmuka IHeap adalah skenario ahli yang sangat canggih
  • Saya tidak berpikir itu berguna/selaras dengan sisa BCL cukup untuk membenarkan komplikasi menambahkan antarmuka baru sekarang

Argumen yang sama yang telah diulang di utas beberapa kali IMO. Saya baru saja merangkumnya

. Silakan baca apa yang saya tulis.

Saya secara aktif memantau utas ini sepanjang waktu. Saya membaca semua argumen dan poin. Ini adalah ringkasan pendapat saya, meskipun membaca (dan memahami) semua poin Anda dan orang lain.
Anda tampaknya bersemangat tentang topik tersebut. Itu hebat. Namun, saya merasa bahwa kami berada dalam posisi ketika kami hanya mengulangi argumen serupa dari setiap sisi, yang tidak akan mengarah lebih jauh - itulah mengapa saya merekomendasikan 2 proposal dan mendapatkan lebih banyak umpan balik dari kelompok pengalaman yang lebih besar. Peninjau API BCL (BTW: Saya belum menganggap diri saya sebagai peninjau API yang berpengalaman).

Bagaimana mungkin pengembang menulis kode mereka pada antarmuka itu dan menggunakan fungsionalitas kami?

Pengembang yang peduli dengan skenario lanjutan IHeap akan merujuk antarmuka dan implementasi dari perpustakaan pihak ketiga. Seperti yang saya katakan, jika terbukti populer, kami dapat mempertimbangkan untuk memindahkannya ke CoreFX nanti.
Kabar baiknya adalah menambahkan IHeap nanti benar-benar dapat dilakukan - pada dasarnya hanya menambahkan satu konstruktor yang berlebihan pada PriorityQueue .
Ya, tidak ideal dari sudut pandang Anda, tetapi tidak menghalangi inovasi yang Anda anggap penting di masa depan. Saya pikir itu adalah jalan tengah yang masuk akal.

Mengapa orang-orang yang Anda pikirkan tidak dapat berkontribusi pada diskusi ini?

Review API adalah pertemuan dengan diskusi aktif, brainstorming, pembobotan semua sudut. Dalam banyak kasus, ini lebih produktif/efisien daripada bolak-balik pada masalah GitHub. Lihat dotnet/corefx#14354 dan pendahulunya dotnet/corefx#8034 - diskusi terlalu panjang, beberapa pendapat berbeda dengan trilyun tweak yang sulit dilacak, tidak ada kesimpulan, sementara diskusi hebat, juga membuang waktu non-sepele untuk beberapa orang, sampai kami duduk dan membicarakannya dan mencapai konsensus.
Memiliki peninjau API yang memantau setiap masalah API, atau bahkan hanya yang cerewet tidak dapat diskalakan dengan baik.

Mengapa kita malah harus memulainya lagi?

Kami tidak akan memulai lagi. Mengapa Anda berpikir demikian?
Kami akan menyelesaikan tinjauan API di tingkat pertama (tingkat pemilik area) dengan mengirimkan 2 proposal paling populer dengan pro/kontra ke tingkat tinjauan API berikutnya.
Ini adalah pendekatan persetujuan/peninjauan hierarkis. Hal ini mirip dengan tinjauan bisnis - VP/CEO dengan kekuatan keputusan tidak mengawasi setiap diskusi tentang setiap proyek di perusahaan mereka, mereka meminta tim/laporan mereka untuk membawa proposal untuk keputusan yang paling berdampak atau menular untuk berdiskusi lebih lanjut tentang mereka. Tim/laporan harus merangkum masalah dan menyajikan pro/kontra solusi alternatif.

Jika Anda yakin kami belum siap untuk menyajikan 2 proposal terakhir dengan pro dan kontra, karena ada hal-hal yang belum dikatakan di utas ini, mari kita terus berdiskusi, sampai kita hanya memiliki beberapa kandidat teratas untuk ditinjau di berikutnya Tingkat peninjauan API.
Saya mendapat perasaan bahwa semua yang perlu dikatakan telah dikatakan.
Masuk akal?

Nama harus PriorityQueue

Ada argumen?

Jika Anda membaca jawaban saya, Anda dapat melihat bahwa saya menyebutkan argumen kunci untuk posisi saya.

Ya ampun... Saya mengacu pada apa yang saya kutip ( jelas ) -- Anda memutuskan untuk pergi dengan antrian prioritas bukan tumpukan. Dan ya, saya telah membaca jawaban Anda -- ini berisi persis 0% argumen untuk ini.

Saya secara aktif memantau utas ini sepanjang waktu. Saya membaca semua argumen dan poin. Ini adalah ringkasan pendapat saya, meskipun membaca (dan memahami) semua poin Anda dan orang lain.

Anda suka menjadi hiperbolik, saya telah memperhatikan itu sebelumnya. Anda hanya mengakui keberadaan poin.

Bagaimana mungkin pengembang menulis kode mereka pada antarmuka itu dan menggunakan fungsionalitas kami?

Pengembang yang peduli dengan skenario lanjutan IHeap akan merujuk antarmuka dan implementasi dari perpustakaan pihak ke-3. Seperti yang saya katakan, jika terbukti populer, kami dapat mempertimbangkan untuk memindahkannya ke CoreFX nanti.

Sadarkah Anda bahwa Anda hanya mengulangi kata-kata saya di sini dan sama sekali tidak membahas masalah yang saya kemukakan sebagai konsekuensi dari hal di atas? Saya menulis:

Mereka akan dipaksa untuk tetap berpegang pada fungsionalitas non-antarmuka kami atau mengabaikannya sepenuhnya, tidak ada pilihan lain, itu saling eksklusif.

Saya akan membuat ini sangat jelas untuk Anda. Akan ada dua kelompok orang yang terpisah:

  1. Satu grup yang menggunakan fungsionalitas kami. Itu tidak memiliki antarmuka dan tidak dapat diperpanjang, sehingga tidak terhubung ke apa yang disediakan di perpustakaan pihak ketiga.
  2. Grup kedua yang sepenuhnya mengabaikan fungsionalitas kami dan menggunakan solusi pihak ketiga murni.

Masalahnya di sini adalah, seperti yang saya katakan, bahwa mereka adalah kelompok orang yang terpisah-pisah . Dan mereka menghasilkan kode yang tidak bekerja sama , karena tidak ada inti arsitektur yang sama. _ CODEBASE TIDAK SESUAI _. Anda tidak dapat membatalkannya nanti.

Kabar baiknya adalah menambahkan IHeap nanti benar-benar dapat dilakukan - pada dasarnya hanya menambahkan satu kelebihan konstruktor pada PriorityQueue.

Saya sudah menulis mengapa itu menyebalkan: lihat posting ini .

Mengapa orang-orang yang Anda pikirkan tidak dapat berkontribusi pada diskusi ini?

Memiliki peninjau API yang memantau setiap masalah API, atau bahkan hanya yang cerewet tidak dapat diskalakan dengan baik.

Ya, saya bertanya mengapa peninjau API tidak dapat memantau setiap masalah API. Tepatnya, Anda memang merespons dengan tepat.

Masuk akal?

Tidak. Aku bosan dengan diskusi ini, sungguh. Beberapa dari kalian jelas berpartisipasi di dalamnya hanya karena itu adalah pekerjaan Anda dan Anda diperintahkan untuk melakukannya. Beberapa dari Anda membutuhkan bimbingan sepanjang waktu, yang sangat melelahkan. Anda bahkan meminta saya untuk membuktikan mengapa antrian prioritas harus diimplementasikan dengan tumpukan internal, jelas tidak memiliki latar belakang ilmu komputer. Beberapa dari Anda bahkan tidak mengerti apa itu heap sebenarnya, membuat diskusi semakin kacau.

Pergi dengan PriorityQueue Anda yang dinonaktifkan yang tidak memungkinkan pembaruan dan penghapusan elemen. Pergilah dengan desain Anda yang tidak memungkinkan pendekatan OO yang sehat. Gunakan solusi Anda yang tidak mengizinkan penggunaan kembali pustaka standar saat menulis ekstensi. Jalan Jawa.

Dan ini... ini sangat mengejutkan:

Jika kita menggunakan Heap, banyak pengembang berketerampilan menengah akan (sayangnya) melihat "Heap" dan mengacaukannya sebagai tumpukan memori aplikasi (yaitu tumpukan dan tumpukan) alih-alih "menumpuk struktur data umum".

Presentasikan API dengan pendekatan Anda. Saya penasaran.

Aku tidak percaya ini benar-benar terjadi...

Nah, sayangnya saya tidak memiliki kesempatan untuk mendapatkan pendidikan Ilmu Komputer yang layak untuk mengetahui bahwa Heap adalah semacam struktur data yang berbeda dari tumpukan memori.

Intinya masih berdiri. Tumpukan sesuatu tidak menyiratkan apa tentang itu dipesan dalam beberapa jenis. Jika saya membutuhkan koleksi yang memungkinkan saya untuk menyimpan objek untuk diproses, di mana beberapa contoh yang datang kemudian mungkin perlu diproses lebih cepat, saya tidak akan mencari sesuatu yang disebut Heap . PriorityQueue di sisi lain, dengan sempurna mengomunikasikan bahwa ia melakukan hal itu.

Sebagai implementasi pendukung? Tentu, detail implementasi seharusnya tidak menjadi perhatian saya.
Beberapa abstraksi IHeap ? Besar untuk API penulis dan orang-orang yang memiliki CS Mayor tahu apa itu digunakan untuk, tidak ada alasan untuk tidak memiliki.
Memberi sesuatu nama samar yang tidak menyatakan maksudnya dengan baik dan kemudian membatasi kemampuan untuk ditemukan? 👎

Nah, sayangnya saya tidak memiliki kesempatan untuk mendapatkan pendidikan Ilmu Komputer yang layak untuk mengetahui bahwa Heap adalah semacam struktur data yang berbeda dari tumpukan memori.

Ini konyol. Pada saat yang sama Anda ingin berpartisipasi dalam diskusi tentang menambahkan fungsi seperti itu. Kedengarannya seperti trolling.

Tumpukan sesuatu tidak menyiratkan apa-apa tentang itu dipesan dalam beberapa jenis.

Anda salah. Itu dipesan, sebagai tumpukan. Seperti pada gambar yang Anda tautkan.

Sebagai implementasi pendukung? Tentu, detail implementasi seharusnya tidak menjadi perhatian saya.

Saya sudah membahas itu. Keluarga tumpukan sangat besar dan ada dua tingkat abstraksi di atas implementasi. Antrian prioritas adalah lapisan abstraksi ketiga.

Jika saya membutuhkan koleksi yang memungkinkan saya untuk menyimpan objek untuk diproses, di mana beberapa contoh yang datang kemudian mungkin perlu diproses lebih cepat, saya tidak akan mencari sesuatu yang disebut Heap. PriorityQueue di sisi lain, dengan sempurna mengomunikasikan bahwa ia melakukan hal itu.

Dan tanpa latar belakang apa pun, Anda akan meminta Google untuk memberi Anda artikel tentang antrean prioritas? Yah, kita bisa berdebat apa yang lebih atau kurang mungkin dalam pendapat kita. Tapi, seperti yang telah dikatakan dengan sangat baik:

Jika Anda memiliki masukan, berikan, dukung dengan data dan bukti. Berikan bukti terhadap pendapat orang lain jika Anda tidak setuju.

Dan menurut data Anda salah:

Kueri | Hits
:----: |:----:|
| "struktur data" "antrian prioritas" | 172.000 |
| "struktur data" "tumpukan" | 430.000 |

Hampir 3 kali lebih mungkin Anda menemukan tumpukan saat membaca tentang struktur data. Plus, itu adalah nama yang akrab dengan pengembang Swift, Go, Rust, dan Python, karena perpustakaan standar mereka menyediakan struktur data seperti itu.

Kueri | Hits
:----: |:----:|
| "golang" "antrian prioritas" | 3.390 |
| "karat" "antrian prioritas" | 8.630 |
| "cepat" "antrian prioritas" | 18.600 |
| "python" "antrian prioritas" | 72.800 |
| "golang" "tumpukan" | 79.000 |
| "karat" "tumpukan" | 492.000 |
| "cepat" "tumpukan" | 551.000 |
| "python" "tumpukan" | 555.000 |

Sebenarnya ini juga mirip untuk C++, karena struktur data heap diperkenalkan di sana pada abad sebelumnya.

Memberi sesuatu nama samar yang tidak menyatakan maksudnya dengan baik dan kemudian membatasi kemampuan untuk ditemukan? 👎

Tidak ada pendapat. Data. Lihat di atas. Terutama tidak ada pendapat dari seseorang yang tidak memiliki latar belakang. Anda juga tidak akan meng-Google antrian prioritas tanpa membaca tentang struktur data sebelumnya. Dan tumpukan tercakup dalam banyak Struktur Data 101 .

Ini adalah dasar dalam Ilmu Komputer. Ini adalah dasar. Ketika Anda memiliki beberapa semester algoritma dan struktur data, heap adalah sesuatu yang Anda lihat di awal.

Tetapi tetap saja:

  • Pertama -- lihat angka di atas.
  • Kedua -- pikirkan tentang semua bahasa lain di mana heap adalah bagian dari perpustakaan standar.

EDIT: Lihat Google Trends .

Sebagai pengembang otodidak lainnya, saya tidak punya masalah dengan _heap_. Sebagai pengembang yang selalu berusaha untuk meningkatkan, saya telah meluangkan waktu untuk mempelajari dan memahami semua tentang struktur data. Singkatnya, saya tidak setuju dengan implikasi bahwa konvensi penamaan harus menargetkan mereka yang tidak meluangkan waktu untuk memahami leksikon bidang di mana mereka menjadi bagiannya.

Dan saya juga sangat tidak setuju dengan pernyataan seperti "Nama harus PriorityQueue". Jika Anda tidak ingin masukan orang, maka jangan membuatnya open-source dan jangan memintanya.

Biarkan saya memberikan beberapa penjelasan tentang bagaimana kami berpikir tentang penamaan API:

  1. Kami cenderung menyukai konsistensi dalam platform .NET di atas hampir semua hal lainnya. Itu penting agar API terlihat familier dan dapat diprediksi. Terkadang ini berarti kami menerima bahwa sebuah nama tidak 100% benar jika itu adalah istilah yang pernah kami gunakan sebelumnya.

  2. Tujuan kami adalah merancang platform yang dapat didekati oleh berbagai pengembang, beberapa di antaranya tidak memiliki pendidikan formal dalam ilmu komputer. Kami percaya bahwa sebagian alasan .NET secara umum dianggap sangat produktif dan mudah digunakan sebagian disebabkan oleh poin desain tersebut.

Kami biasanya menggunakan "tes mesin telusur" ketika memeriksa seberapa terkenal dan mapan sebuah nama atau terminologi. Jadi saya sangat menghargai penelitian yang telah dilakukan @pgolebiowski . Saya belum melakukan penelitian sendiri tetapi firasat saya adalah bahwa "tumpukan" bukanlah istilah yang akan dicari oleh banyak pakar non-domain.

Oleh karena itu, saya cenderung setuju dengan @karelz bahwa PriorityQueue sepertinya pilihan yang lebih baik. Ini menggabungkan konsep yang ada (antrian) dan menambahkan twist yang mengekspresikan kemampuan yang diinginkan: pengambilan pesanan berdasarkan prioritas. Tapi kami tidak terikat dengan nama itu. Kami sering mengubah nama struktur data dan teknologi berdasarkan umpan balik pelanggan.

Namun, saya ingin menunjukkan bahwa ini:

Jika Anda tidak ingin masukan orang, maka jangan membuatnya open-source dan jangan memintanya.

adalah dikotomi palsu. Bukannya kami tidak menginginkan umpan balik dari ekosistem dan kontributor kami (tentu saja kami menginginkannya). Tetapi pada saat yang sama kami juga harus menghargai bahwa basis pelanggan kami cukup beragam dan kontributor GitHub (atau pengembang di tim kami) tidak selalu merupakan proxy terbaik untuk semua pelanggan kami. Kegunaannya sulit dan kemungkinan akan membutuhkan beberapa iterasi untuk menambahkan konsep baru ke .NET, terutama di area yang sangat populer seperti koleksi.

@pgolebiowski :

Saya sangat menghargai wawasan, data, dan saran Anda. Tapi saya sama sekali tidak menghargai gaya argumentasi Anda. Anda menjadi pribadi terhadap kedua anggota tim saya serta anggota komunitas di utas ini. Hanya karena Anda tidak setuju dengan kami tidak memberi Anda izin untuk menuduh kami tidak memiliki keahlian atau tidak peduli karena itu "hanya pekerjaan kami". Pertimbangkan bahwa banyak dari kita telah benar-benar pindah ke seluruh dunia, meninggalkan keluarga dan teman hanya karena kita ingin melakukan pekerjaan ini. Tidak hanya komentar seperti Anda sangat tidak adil, mereka juga tidak membantu memajukan desain.

Jadi sementara saya suka berpikir saya memiliki kulit tebal, saya tidak memiliki banyak toleransi untuk perilaku semacam itu. Domain kami sudah cukup kompleks; kita tidak perlu menambahkan komunikasi konfrontatif & permusuhan.

Harap hormat. Mengkritik ide dengan penuh semangat adalah permainan yang adil, tetapi menyerang orang tidak. Terima kasih.

Dear semua orang yang merasa putus asa,

Saya minta maaf karena mengurangi tingkat kebahagiaan Anda dengan sikap kasar saya.

@karelz

Saya telah membodohi diri sendiri di sana dengan membuat kesalahan teknis. saya minta maaf. Itu diterima. Namun dilemparkan padaku nanti. Tidak keren IMO.

Saya minta maaf bahwa apa yang saya tulis membuat Anda tidak bahagia. Meskipun tidak seburuk yang Anda gambarkan -- saya hanya memberikan ini sebagai salah satu dari banyak faktor yang berkontribusi pada rasa lelah saya . Ini dari tingkat keparahan yang lebih rendah, saya pikir. Tapi tetap saja, aku minta maaf.

Dan ya, semua orang membuat kesalahan. Tidak apa-apa. Saya juga, misalnya terkadang terbawa suasana.

Yang paling membuat saya tertarik adalah "Anda hanya disuruh melakukan ini, Anda tidak percaya" - ya, itulah mengapa saya melakukannya JUGA di akhir pekan.

Maaf, saya dapat melihat bahwa Anda bekerja keras dan saya sangat menghargai itu. Terlihat bagi saya betapa berdedikasinya Anda pada pencapaian 5/10.

@terrajobst

Hanya karena Anda tidak setuju dengan kami tidak memberi Anda izin untuk menuduh kami tidak memiliki keahlian atau tidak peduli karena itu "hanya pekerjaan kami".

  • Tidak memiliki keahlian -- ditujukan kepada orang-orang tanpa latar belakang ilmu komputer dan tidak memahami konsep tumpukan / antrian prioritas. Jika deskripsi seperti itu berlaku untuk seseorang -- yah, itu berlaku, itu bukan salah saya.
  • Tidak peduli -- ditujukan kepada mereka yang cenderung mengabaikan beberapa poin teknis, sehingga memaksakan argumen yang berulang, sehingga membuat diskusi menjadi kacau dan lebih sulit untuk diikuti oleh orang lain (yang pada gilirannya menyebabkan lebih sedikit masukan).

Komentar seperti milik Anda sangat tidak adil dan juga tidak membantu memajukan desain.

  • Komentar kasar saya adalah akibat dari ketidakefisienan dalam diskusi ini. Inefisiensi = cara diskusi yang kacau, di mana poin tidak dibahas / diselesaikan dan kami bergerak lebih jauh meskipun itu => melelahkan.
  • Juga, sebagai salah satu pendorong utama dalam diskusi, saya sangat merasa telah melakukan banyak hal untuk membantu memajukan desain. Tolong jangan menjelekkan saya, seperti yang Anda coba lakukan di sini dan di media sosial.

Jika ada orang sakit yang ingin "menjilat saya", silakan.


Kami mengalami masalah dan telah mengatasinya. Setiap orang telah belajar sesuatu darinya. Saya dapat melihat bahwa semua orang di sini memperhatikan kualitas kerangka kerja, yang benar-benar luar biasa dan membuat saya termotivasi untuk berkontribusi. Saya berharap untuk terus bekerja di CoreFX dengan Anda. Karena itu, saya akan membahas masukan teknis baru Anda mungkin besok.

@pgolebiowski

Semoga kita bisa bertemu secara langsung suatu saat nanti. Sejujurnya saya percaya bahwa bagian dari tantangan melakukan segala sesuatu secara online adalah bahwa kepribadian terkadang dapat bercampur dengan cara yang buruk tanpa niat dari kedua sisi.

Saya berharap untuk terus bekerja di CoreFX dengan Anda. Karena itu, saya akan membahas masukan teknis baru Anda mungkin besok.

Sama disini. Ini adalah ruang yang menarik dan ada banyak hal menakjubkan yang bisa kita lakukan bersama :-)

@pgolebiowski pertama, terima kasih atas balasan Anda. Ini menunjukkan bahwa Anda peduli dan bermaksud baik (yang secara diam-diam saya harap setiap orang/pengembang di dunia melakukannya, semua konflik hanyalah kesalahpahaman / miskomunikasi). Dan itu membuat saya sangat bahagia - itu membuat saya terus maju dan bersemangat.
Saya akan menyarankan untuk memulai kembali dalam hubungan kita. Mari kita kembali diskusi ke teknis, mari kita semua belajar dari thread ini, bagaimana menangani situasi serupa di masa depan, mari kita asumsikan lagi bahwa pihak lain hanya memiliki kepentingan terbaik untuk platform dalam pikiran.
BTW: Ini adalah salah satu dari beberapa pertemuan/diskusi yang lebih sulit pada repo CoreFX dalam 9 bulan terakhir, dan seperti yang Anda lihat, kami (termasuk/khususnya saya) masih belajar bagaimana menanganinya dengan baik - jadi contoh khusus ini sedang berjalan untuk memberi manfaat bahkan bagi kami dan itu akan membuat kami menjadi lebih baik di masa depan dan membantu kami memahami lebih baik sudut pandang berbeda dari anggota komunitas yang bersemangat. Mungkin itu akan membentuk pembaruan kami pada dokumen kontribusi ...

Komentar kasar saya adalah akibat dari ketidakefisienan dalam diskusi ini. Inefisiensi = cara diskusi yang kacau, di mana poin tidak dibahas / diselesaikan dan kami bergerak lebih jauh meskipun itu => melelahkan.

Pahami frustrasi Anda! Menariknya frustrasi serupa juga di sisi lain dari alasan yang sama ... hampir lucu bagaimana dunia bekerja :).
Sayangnya, diskusi yang sulit adalah bagian dari pekerjaan ketika Anda mendorong keputusan desain. Ini BANYAK pekerjaan. Banyak orang meremehkannya. Keterampilan utama yang dibutuhkan adalah kesabaran dengan semua orang dan kemampuan untuk melangkah di atas pendapat Anda sendiri dan berpikir bagaimana mendorong konsensus bahkan jika itu tidak sesuai keinginan Anda. Itu sebabnya saya menyarankan untuk memiliki 2 proposal dan "meningkatkan" diskusi teknis ke grup peninjau API (terutama karena saya tidak tahu pasti apakah saya benar, meskipun saya diam-diam berharap saya benar seperti setiap pengembang lain di dunia akan ).

SANGAT sulit untuk memiliki pendapat tentang suatu topik DAN mengarahkan diskusi ke KONSENSUS di utas yang sama. Dari perspektif itu, Anda dan saya memiliki kesamaan yang paling umum di utas ini - kami berdua memiliki pendapat, namun kami berdua mencoba mengarahkan diskusi ke penutupan & keputusan. Jadi mari kita bekerja sama erat.

Pendekatan umum saya adalah: Setiap kali saya berpikir seseorang menyerang saya, sedang jahat, malas, membuat saya frustrasi, atau sesuatu. Saya bertanya terlebih dahulu pada diri saya sendiri dan juga orang tertentu: Mengapa? Mengapa Anda mengatakan itu? Apa yang kamu maksud?
Biasanya itu adalah tanda kurangnya pemahaman/komunikasi motif. Atau tanda membaca terlalu banyak yang tersirat dan melihat hinaan/tuduhan/niat buruk padahal sebenarnya tidak.


Sekarang saya tidak takut untuk terus membahas pertanyaan teknis, inilah yang ingin saya tanyakan sebelumnya:

Pergi dengan PriorityQueue Anda yang dinonaktifkan yang tidak memungkinkan pembaruan dan penghapusan elemen.

Ini adalah sesuatu yang saya tidak mengerti. Jika kita meninggalkan IHeap (dalam proposal saya / asli di sini), mengapa itu tidak mungkin?
IMO tidak ada perbedaan antara 2 proposal dari sudut pandang kemampuan kelas, satu-satunya perbedaan adalah - apakah kita menambahkan kelebihan konstruktor PriorityQueue(IHeap) atau tidak (meninggalkan perselisihan nama kelas sebagai masalah independen untuk diselesaikan) .

Penafian (untuk menghindari miskomunikasi): Saya tidak punya waktu untuk membaca artikel dan melakukan penelitian, saya mengharapkan jawaban singkat, menghadirkan argumen elevator-pitch dari siapa pun yang ingin mendorong diskusi teknis. Catatan: Bukan saya yang mengolok-olok. Saya akan menanyakan pertanyaan yang sama kepada siapa pun di tim kami yang membuat klaim ini. Jika Anda tidak memiliki energi untuk menjelaskannya / mengarahkan diskusi (yang akan sangat dimengerti mengingat kesulitan dan investasi waktu dari pihak Anda), katakan saja, tidak ada perasaan sulit. Tolong jangan merasa tertekan oleh saya atau siapa pun (dan itu berlaku untuk semua orang di utas).

Tidak mencoba menambahkan satu lagi komentar yang tidak perlu di sini, utas ini sudah TERLALU PANJANG. Aturan no.1 di era Internet adalah hindari komunikasi teks jika Anda peduli dengan hubungan orang. (Yah, saya yang menciptakannya). Saya percaya beberapa komunitas open source lainnya akan beralih ke Google Hangout untuk diskusi semacam ini jika kebutuhannya jelas. Ketika Anda melihat wajah orang lain, Anda tidak akan pernah mengatakan sesuatu yang "menghina", dan orang-orang menjadi akrab satu sama lain dengan sangat cepat. Mungkin kita bisa mencoba juga?

@karelz Karena panjangnya diskusi di atas, sangat tidak mungkin bagi siapa pun yang baru berkontribusi jika

  • Saya akan melakukan pemungutan suara pada aspek fundamental, satu demi satu. Kami akan mendapat masukan yang jelas dari masyarakat. Idealnya, peninjau API akan datang ke sini juga dan berkomentar segera.
  • "Posting suara" akan berisi informasi yang cukup untuk dapat mengabaikan seluruh dinding teks di atas.
  • Setelah sesi pemungutan suara ini selesai, kami akan mengetahui apa yang diharapkan dari peninjau API dan akan dapat melanjutkan dengan pendekatan tertentu. Ketika kita menyepakati aspek-aspek fundamental, masalah ini akan ditutup, dan yang lain dibuka (yang akan merujuk yang ini). Dalam edisi baru, saya akan merangkum kesimpulan kami dan memberikan proposal API yang mencerminkan keputusan ini. Dan kami akan mengambilnya dari sana.

Apakah itu memiliki peluang masuk akal?

PriorityQueue yang tidak mengizinkan pembaruan dan penghapusan elemen.

Itu tentang proposal asli, yang tidak memiliki kemampuan ini :) Maaf karena tidak menjelaskannya.

Jika Anda tidak memiliki energi untuk menjelaskannya / mengarahkan diskusi (yang akan sangat dimengerti mengingat kesulitan dan investasi waktu dari pihak Anda), katakan saja, tidak ada perasaan sulit. Tolong jangan merasa tertekan oleh saya atau siapa pun (dan itu berlaku untuk semua orang di utas).

Saya tidak akan menyerah. Tidak ada rasa sakit tidak ada keuntungan xD

@xied75

Saya percaya beberapa komunitas open source lainnya akan beralih ke Google Hangout untuk diskusi semacam ini jika kebutuhannya jelas. Mungkin kita bisa mencoba juga?

Kelihatan bagus ;)

Menyediakan antarmuka

Tidak masalah jika kita menggunakan Heap / IHeap atau PriorityQueue / IPriorityQueue (atau yang lainnya), untuk fungsionalitas yang akan kami sediakan...

_apakah Anda ingin memiliki antarmuka, beserta implementasinya?_

Untuk

@bendono

Dengan mendukung implementasi itu dengan sebuah antarmuka, kita yang peduli dengan implementasi lain dapat dengan mudah menukar implementasi lain (buatan kita sendiri atau di perpustakaan eksternal) dan tetap kompatibel dengan kode yang menerima antarmuka.

Mereka yang tidak peduli bisa mengabaikannya dan langsung menuju implementasi konkrit. Mereka yang peduli dapat menggunakan implementasi apa pun yang mereka pilih.

Melawan

@madelson

Ada banyak algoritma berbeda yang secara teoritis dapat digunakan untuk mengimplementasikan struktur data ini. Namun, tampaknya tidak mungkin bahwa kerangka tersebut akan dikirimkan dengan lebih dari satu, dan gagasan bahwa penulis perpustakaan benar-benar membutuhkan antarmuka kanonik sehingga berbagai pihak dapat berkoordinasi dalam penulisan implementasi heap yang kompatibel secara lintas tampaknya tidak mungkin.

Selain itu, setelah antarmuka dirilis, itu tidak akan pernah dapat diubah karena kompatibilitasnya. Itu berarti bahwa antarmuka cenderung tertinggal di belakang kelas konkret dalam hal fungsionalitas (masalah dengan IList dan IDictionary hari ini).

@karelz

Antarmuka adalah skenario ahli yang sangat canggih.

Jika perpustakaan dan antarmuka IHeap menjadi sangat populer, kita dapat berubah pikiran nanti dan menambahkan IHeap berdasarkan permintaan (melalui kelebihan konstruktor), tetapi menurut saya itu tidak berguna/sejajar dengan sisa BCL cukup untuk membenarkan komplikasi penambahan antarmuka baru sekarang. Mulai sederhana, tumbuh menjadi rumit hanya jika benar-benar dibutuhkan.

Dampak keputusan potensial

  • Termasuk antarmuka berarti kami tidak dapat mengubahnya di masa mendatang.
  • Tidak menyertakan antarmuka berarti orang menulis kode yang menggunakan solusi pustaka standar kami atau ditulis terhadap solusi yang diberikan oleh pustaka pihak ketiga (tidak ada antarmuka umum yang akan mengaktifkan kompatibilitas silang).

Gunakan 👍 dan untuk memilih yang ini (masing-masing mendukung dan menentang antarmuka). Atau, tulis komentar. Idealnya, peninjau API akan berpartisipasi.

Saya ingin menambahkan bahwa meskipun mengubah antarmuka itu sulit, dengan metode ekstensi (dan properti yang akan datang) antarmuka lebih mudah untuk diperluas dan/atau digunakan (lihat LINQ)

Saya ingin menambahkan bahwa meskipun mengubah antarmuka itu sulit, dengan metode ekstensi (dan properti yang akan datang) antarmuka lebih mudah untuk diperluas dan/atau digunakan (lihat LINQ)

Mereka hanya dapat bekerja dengan metode yang ditentukan secara publik pada antarmuka; jadi itu berarti melakukannya dengan benar pertama kali.

Saya sarankan menunda antarmuka sebentar sampai kelas digunakan dan telah diselesaikan - lalu perkenalkan antarmuka. (dengan perdebatan tentang bentuk antarmuka menjadi masalah terpisah)

Terus terang, satu-satunya hal yang saya pedulikan adalah antarmuka. Implementasi yang solid akan menyenangkan, tetapi saya (atau siapa pun) selalu dapat membuat sendiri.

Saya ingat beberapa tahun yang lalu bagaimana kami melakukan percakapan yang sama persis dengan HashSet<T> . Microsoft menginginkan HashSet<T> sedangkan komunitas menginginkan ISet<T> . Jika saya ingat dengan benar, kami mendapat HashSet<T> pertama dan ISet<T> kedua. Tanpa antarmuka, penggunaan HashSet<T> cukup terbatas karena sulit (bahkan tidak mungkin) untuk mengubah API publik.

Saya harus mencatat bahwa ada juga SortedSet<T> sekarang, belum lagi banyak implementasi non-BCL dari ISet<T> . Saya telah menggunakan ISet<T> di API publik dan saya berterima kasih untuk itu. Implementasi pribadi saya dapat menggunakan implementasi konkret apa pun yang saya rasa benar. Saya juga dapat dengan mudah menukar implementasi dengan yang lain tanpa merusak apa pun. Ini tidak akan mungkin terjadi tanpa antarmuka.

Bagi mereka yang mengatakan bahwa kita selalu dapat mendefinisikan antarmuka kita sendiri, pertimbangkan ini. Asumsikan sejenak bahwa ISet<T> dalam BCL tidak pernah terjadi. Sekarang saya dapat membuat antarmuka saya sendiri IMySet<T> serta implementasi yang solid. Namun, suatu hari BCL HashSet<T> dirilis. Itu mungkin atau mungkin tidak mengimplementasikan ISet<T> , tetapi tidak mengimplementasikan IMySet<T> . Akibatnya, saya tidak dapat menukar HashSet<T> sebagai implementasi dari IMySet<T> .

Saya khawatir kita akan mengulangi parodi ini lagi.
Jika Anda belum siap untuk berkomitmen pada sebuah antarmuka, maka terlalu dini untuk memperkenalkan kelas konkret.

Saya menemukan perbedaan pendapat yang signifikan. Hanya dengan melihat angkanya, sedikit lebih banyak orang yang memilih antarmuka, tetapi itu tidak banyak memberi tahu kami. Saya akan mencoba bertanya kepada beberapa orang lain yang telah berpartisipasi dalam diskusi sebelumnya tetapi belum mengungkapkan pendapat mereka tentang antarmuka:

@ebickle @svick @horaciojcfilho @paulcbetts @justinvp @akoeplinger @mbeidler @SirCmpwn @andrewgmorris @weshaggard @BrannonKing @NKnusperer @danmosemsft @ianhays @safern @VisualMelon @Joe4evr @jcouv @jcouv @xied75

Untuk yang diberi tahu: _guys, bisakah Anda memberikan masukan? Ini akan sangat membantu, bahkan jika Anda hanya memilih dengan :+1:, :-1:. Anda dapat mulai membaca di komentar masalah ini (4 posting di atas di sini) -- kami sedang mendiskusikan apakah menyediakan antarmuka adalah ide yang bagus (hanya aspek ini untuk saat ini, sampai diselesaikan)._

Mungkin beberapa dari orang-orang ini adalah peninjau API, tetapi saya yakin kami membutuhkan dukungan mereka dalam keputusan mendasar ini sebelum kami melanjutkan. @karelz , @terrajobst , apakah mungkin bagi Anda untuk meminta mereka membantu kami menyelesaikan aspek ini? Masukan mereka akan sangat berharga, karena merekalah yang akan meninjaunya pada akhirnya -- akan sangat membantu untuk mengetahuinya pada saat ini, sebelum berkomitmen dengan pendekatan tertentu (atau menggunakan lebih dari 1 proposal, yang akan melelahkan dan sedikit sia-sia, karena kita dapat mengetahui keputusan mereka sebelumnya).

Secara pribadi, saya untuk antarmuka, tetapi jika keputusannya berbeda, saya senang mengikuti jalan yang berbeda.

Saya tidak ingin menyeret peninjau API ke dalam diskusi -- ini panjang dan berantakan, tidak akan efisien bagi peninjau API untuk membaca ulang semuanya atau bahkan hanya memutuskan apa jawaban penting terakhir (saya tersesat di dalamnya ).
Saya pikir kita berada pada titik ketika kita dapat membuat 2 proposal API formal (lihat contoh 'baik' di sana) dan menyoroti pro/kontra masing-masing. Kami kemudian dapat meninjaunya dalam pertemuan tinjauan API kami dan membuat rekomendasi, dengan mempertimbangkan suara. Bergantung pada diskusi di sana (jika ada banyak pendapat), kami mungkin akan kembali dan memulai polling Twitter / pemungutan suara GH tambahan, dll.

BTW: Rapat tinjauan API terjadi hampir setiap hari Selasa.

Untuk membantu memulainya, seperti inilah tampilan proposal:

Contoh Proposal / Bibit

```c#
namespace System.Collections.Generic
{
Antrian Prioritas kelas publik
: IE dapat dihitung, ICollection, IEnumerable, IReadOnlyCollection
{
Antrian Prioritas publik();
Antrian Prioritas publik (kapasitas int);
Antrian Prioritas publik(IComparerpembanding);
Antrian Prioritas publik (Inumerablekoleksi);
Antrian Prioritas publik (Inumerablekoleksi, IComparerpembanding);
Antrian Prioritas publik (kapasitas int, IComparerpembanding);

    public IComparer<T> Comparer { get; }
    public int Count { get; }

    public void Enqueue(T item);
    public T Dequeue();
    public T Peek();
    public void Clear();
    public bool Contains(T item);

    // Sets the capacity to the actual number of elements
    public void TrimExcess();

    public void CopyTo(T[] array, int arrayIndex);
    void ICollection.CopyTo(Array array, int index);
    public T[] ToArray();

    public Enumerator GetEnumerator();
    IEnumerator<T> IEnumerable<T>.GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator();

    bool ICollection.IsSynchronized { get; }
    object ICollection.SyncRoot { get; }
    public struct Enumerator : IEnumerator<T>
    {
        public T Current { get; }
        object IEnumerator.Current { get; }
        public bool MoveNext();
        public void Reset();
        public void Dispose();
    }
}

}
```

MELAKUKAN:

  • Contoh penggunaan yang hilang (tidak jelas bagi saya bagaimana saya mengekspresikan prioritas item) - mungkin kita harus mendesain ulang untuk memiliki 'int' sebagai input nilai prioritas? Mungkin beberapa abstraksi di atasnya? (Saya pikir itu sudah dibahas di atas, tetapi utasnya terlalu panjang untuk dibaca, itu sebabnya kami membutuhkan proposal yang konkret dan tidak lebih banyak diskusi)
  • Skenario UpdatePriority
  • Adakah hal lain yang dibahas di atas yang hilang di sini?

@karelz OK, akan melakukannya, tetap disini! :senyum:

Baik. Sejauh yang saya tahu, antarmuka atau tumpukan tidak akan lulus tinjauan API. Karena itu saya ingin mengusulkan solusi yang sedikit berbeda, melupakan tumpukan kuaterner misalnya. Struktur data di bawah ini berbeda dalam beberapa hal daripada yang dapat kita temukan di Python , Java , C++ , Go , Swift , dan Rust (setidaknya itu).

Fokus utamanya adalah pada kebenaran, kelengkapan dalam hal fungsionalitas, dan intuisi, sambil mempertahankan kompleksitas yang optimal dan kinerja dunia nyata yang hebat.

@karelz @terrajobst

Usul

Alasan

Ini adalah kasus yang cukup umum bahwa pengguna memiliki banyak elemen, di mana beberapa di antaranya memiliki prioritas lebih tinggi daripada yang lain. Akhirnya, mereka ingin menyimpan kumpulan elemen ini dalam urutan tertentu agar dapat melakukan operasi berikut secara efisien:

  1. Tambahkan elemen baru ke koleksi.
  2. Ambil elemen dengan prioritas tertinggi (dan dapat menghapusnya).
  3. Hapus elemen dari koleksi.
  4. Memodifikasi elemen dalam koleksi.
  5. Menggabungkan dua koleksi.

Penggunaan

Glosarium

  • Nilai — data pengguna.
  • Kunci — objek yang digunakan untuk tujuan pemesanan.

Berbagai jenis data pengguna

Pertama kita akan fokus membangun antrian prioritas (hanya menambahkan elemen). Cara itu dilakukan tergantung pada jenis data pengguna.

skenario 1

  • TKey dan TValue adalah objek yang terpisah.
  • TKey sebanding.
var queue = new PriorityQueue<int, string>();

queue.Enqueue(5, "five");
queue.Enqueue(1, "one");
queue.Enqueue(3, "three");

Skenario 2

  • TKey dan TValue adalah objek yang terpisah.
  • TKey tidak sebanding.
var comparer = Comparer<MyKey>.Create(/* custom logic */);

var queue = new PriorityQueue<MyKey, string>(comparer);

queue.Enqueue(new MyKey(5), "five");
queue.Enqueue(new MyKey(1), "one");
queue.Enqueue(new MyKey(3), "three");

Skenario 3

  • TKey terdapat di dalam TValue .
  • TKey tidak sebanding.
public class MyClass
{
    public MyKey Key { get; set; }
}

Terlepas dari komparator kunci, kami juga membutuhkan pemilih kunci:

var selector = new Func<MyClass, MyKey>(item => item.Key);

var queue = new PriorityQueue<MyKey, MyClass>(selector, comparer);

queue.Enqueue(new MyClass( /* args */ ));
queue.Enqueue(new MyClass( /* args */ ));
queue.Enqueue(new MyClass( /* args */ ));
Catatan
  • Kami menggunakan metode Enqueue yang berbeda di sini. Kali ini hanya membutuhkan satu argumen ( TValue ).
  • Jika pemilih kunci ditentukan, metode Enqueue(TKey, TValue) harus membuang InvalidOperationException .
  • Jika pemilih kunci tidak ditentukan, metode Enqueue(TValue) harus membuang InvalidOperationException .

Skenario 4

  • TKey terdapat di dalam TValue .
  • TKey sebanding.
var queue = new PriorityQueue<MyKey, MyClass>(selector);

queue.Enqueue(new MyClass( /* args */ ));
queue.Enqueue(new MyClass( /* args */ ));
queue.Enqueue(new MyClass( /* args */ ));
Catatan
  • Pembanding untuk TKey diasumsikan Comparer<TKey>.Default , seperti pada Skenario 1 .

Skenario 5

  • TKey dan TValue adalah objek terpisah, tetapi memiliki tipe yang sama.
  • TKey sebanding.
var queue = new PriorityQueue<int, int>();

queue.Enqueue(5, 50);
queue.Enqueue(1, 10);
queue.Enqueue(3, 30);

Skenario 6

  • Data pengguna adalah satu objek, yang sebanding.
  • Tidak ada kunci fisik atau pengguna tidak ingin menggunakannya.
public class MyClass : IComparable<MyClass>
{
    public int CompareTo(MyClass other) => /* custom logic */
}
var queue = new PriorityQueue<MyClass, MyClass>();

queue.Enqueue(new MyClass( /* args */ ));
queue.Enqueue(new MyClass( /* args */ ));
queue.Enqueue(new MyClass( /* args */ ));
Catatan

Pada awalnya, ada ambiguitas.

  • Ada kemungkinan bahwa MyClass adalah objek terpisah dan pengguna hanya ingin memisahkan kunci dan nilai, seperti pada Skenario 5 .
  • Namun, mungkin juga satu objek (seperti dalam kasus ini).

Inilah cara PriorityQueue menangani ambiguitas:

  • Jika pemilih kunci didefinisikan, maka tidak ada ambiguitas. Hanya Enqueue(TValue) yang diperbolehkan. Oleh karena itu, solusi alternatif untuk Skenario 6 adalah dengan mendefinisikan pemilih dan meneruskannya ke konstruktor.
  • Jika pemilih kunci tidak ditentukan, ambiguitas diselesaikan dengan penggunaan pertama metode Enqueue :

    • Jika Enqueue(TKey, TValue) dipanggil pertama kali, kunci dan nilai dianggap sebagai objek yang terpisah ( Skenario 5 ). Sejak saat itu, metode Enqueue(TValue) harus membuang InvalidOperationException .

    • Jika Enqueue(TValue) dipanggil pertama kali, kunci dan nilai dianggap sebagai objek yang sama, pemilih kunci disimpulkan ( Skenario 6 ). Sejak saat itu, metode Enqueue(TKey, TValue) harus membuang InvalidOperationException .

Fungsionalitas lainnya

Kami telah membahas pembuatan antrian prioritas. Kami juga tahu cara menambahkan elemen ke koleksi. Sekarang kita akan fokus pada fungsionalitas yang tersisa.

Elemen prioritas tertinggi

Ada dua operasi yang dapat kita lakukan pada elemen dengan prioritas tertinggi — mengambilnya atau menghapusnya.

var queue = new PriorityQueue<int, string>();

queue.Enqueue(5, "five");
queue.Enqueue(1, "one");
queue.Enqueue(3, "three");

// retrieve the element with the highest priority
var element = queue.Peek();

// remove that element
queue.Dequeue();

Apa yang sebenarnya dikembalikan oleh metode Peek dan Dequeue akan dibahas nanti.

Memodifikasi elemen

Untuk elemen arbitrer, kami mungkin ingin memodifikasinya. Sebagai contoh, jika antrian prioritas digunakan selama masa pakai layanan, ada kemungkinan besar pengembang ingin memiliki kemungkinan memperbarui prioritas.

Anehnya, fungsi seperti itu tidak disediakan oleh struktur data yang setara: di Python , Java , C++ , Go , Swift , dan Rust . Mungkin beberapa yang lain juga, tetapi saya hanya memeriksanya. Hasil? Pengembang yang kecewa:

Kami pada dasarnya memiliki dua opsi di sini:

  • Lakukan dengan cara Java dan jangan berikan fungsi ini. Saya sangat menentang itu. Ini memaksa pengguna untuk menghapus elemen dari koleksi (yang saja tidak berfungsi dengan baik, tetapi saya akan membahasnya nanti) dan kemudian menambahkannya sekali lagi. Ini sangat jelek, tidak berfungsi dalam semua kasus, dan tidak efisien.
  • Perkenalkan konsep baru pegangan .

Menangani

Setiap kali pengguna menambahkan elemen ke antrian prioritas, mereka diberi pegangan:

var handle = queue.Enqueue(42, "forty two");

Handle adalah kelas dengan API publik berikut:

public class PriorityQueueHandle<TKey, TValue>
{
    public TKey Key { get; }
    public TValue Value { get; }
}

Ini adalah referensi ke elemen unik dalam antrian prioritas. Jika Anda khawatir tentang efisiensi, silakan lihat FAQ (dan Anda tidak akan khawatir lagi).

Pendekatan semacam itu memungkinkan kita untuk dengan mudah memodifikasi elemen unik dalam antrian prioritas, dengan cara yang sangat intuitif dan sederhana:

/*
 * User wants to retrieve a server that at any given moment
 * has the lowest average response time.
 * He doesn't want to maintain a separate key object (it is
 * inside his type) and the key is already comparable.
 */

var queue = new PriorityQueue<double, ServerStats>(selector);

/* adding some elements */

var handle = queue.Enqueue(server);

/*
 * Server stats are kept along with handles (e.g. in a dictionary).
 * Whenever there is a need of updating the priority of a certain
 * server, the user simply updates the appropriate ServerStats object
 * and then simply uses the handle associated with it:
 */

queue.Update(handle);

Dalam contoh di atas, karena ada pemilih tertentu yang ditentukan, pengguna dapat memperbarui objek itu sendiri. Antrian prioritas hanya perlu diberi tahu untuk mengatur ulang dirinya sendiri (kami tidak ingin membuatnya dapat diamati).

Sama halnya dengan bagaimana tipe data pengguna memengaruhi cara antrian prioritas dibuat dan diisi dengan elemen, cara pembaruannya juga bervariasi. Saya akan membuat skenario lebih pendek kali ini, karena Anda mungkin sudah tahu apa yang akan saya tulis.

Skenario

Skenario 7
  • TKey dan TValue adalah objek yang terpisah.
var queue = new PriorityQueue<int, string>();

var handle = queue.Enqueue(1, "three");
queue.Enqueue(1, "three");
queue.Enqueue(2, "three");

queue.Update(handle, 3);

Seperti yang Anda lihat, pendekatan semacam itu menyediakan cara sederhana untuk merujuk ke elemen unik dalam antrian prioritas, tanpa ada pertanyaan yang diajukan. Ini sangat membantu dalam skenario di mana kunci dapat diduplikasi. Juga, ini sangat efisien — kita tahu elemen yang akan diperbarui di O(1), dan operasi dapat dilakukan di O(log n).

Sebagai alternatif, pengguna dapat menggunakan metode tambahan yang menelusuri seluruh struktur dalam O(n) dan kemudian memperbarui elemen pertama yang cocok dengan argumen. Ini adalah bagaimana menghapus elemen dilakukan di Jawa. Ini tidak sepenuhnya benar atau efisien, tetapi terkadang lebih sederhana:

var queue = new PriorityQueue<int, string>();

queue.Enqueue(1, "one");
queue.Enqueue(2, "three");
queue.Enqueue(3, "three");

queue.Update("three", 30);

Metode di atas menemukan elemen pertama dengan nilainya sama dengan "three" . Kuncinya akan diperbarui menjadi 30 . Kami tidak tahu mana yang akan diperbarui jika ada lebih dari satu yang memenuhi kondisi.

Itu bisa sedikit lebih aman dengan metode Update(TKey oldKey, TValue, TKey newKey) . Ini menambahkan kondisi lain — kunci lama juga harus cocok. Kedua solusi lebih sederhana, tetapi tidak 100% aman dan kurang berkinerja (O(1 + log n) vs O(n + log n)).

Skenario 8
  • TKey terdapat di dalam TValue .
var queue = new PriorityQueue<int, MyClass>(selector);

/* adding some elements */

queue.Update(handle);

Skenario ini adalah apa yang diberikan sebagai contoh di bagian Menangani .

Di atas dicapai dalam O(log n). Atau, pengguna dapat menggunakan metode Update(TValue) yang menemukan elemen pertama sama dengan yang ditentukan dan melakukan penataan ulang internal. Tentu saja, ini adalah O(n).

Skenario 9
  • TKey dan TValue adalah objek terpisah, tetapi memiliki tipe yang sama.

Menggunakan pegangan, tidak ada ambiguitas, seperti biasa. Dalam hal metode lain yang memungkinkan pembaruan — tentu saja ada. Ini adalah trade-off antara kesederhanaan, kinerja, dan kebenaran (yang tergantung pada apakah data dapat diduplikasi atau tidak).

Skenario 10
  • Data pengguna adalah satu objek.

Dengan pegangan, tidak ada masalah lagi. Memperbarui melalui metode lain mungkin lebih sederhana — tetapi sekali lagi, kinerjanya kurang dan tidak selalu benar (entri yang sama).

Menghapus elemen

Dapat dilakukan dengan sederhana dan benar melalui pegangan. Atau, melalui metode Remove(TValue) dan Remove(TKey, TValue) . Masalah yang sama seperti yang dijelaskan sebelumnya.

Menggabungkan dua koleksi

var queue1 = new PriorityQueue<int, string>();
var queue2 = new PriorityQueue<int, string>();

/* add some elements to both */

queue1.Merge(queue2);

Catatan

  • Setelah penggabungan selesai, antrian berbagi representasi internal yang sama. Pengguna dapat menggunakan salah satu dari keduanya.
  • Jenisnya harus cocok (diperiksa secara statis).
  • Pembanding harus sama, jika tidak InvalidOperationException .
  • Selektor harus sama, jika tidak InvalidOperationException .

API yang Diusulkan

public class PriorityQueue<TKey, TValue>
    : IEnumerable,
    IEnumerable<PriorityQueueHandle<TKey, TValue>>,
    IReadOnlyCollection<PriorityQueueHandle<TKey, TValue>>
    // ICollection not included on purpose
{
    public PriorityQueue();
    public PriorityQueue(IComparer<TKey> comparer);
    public PriorityQueue(Func<TValue, TKey> keySelector);
    public PriorityQueue(Func<TValue, TKey> keySelector, IComparer<TKey> comparer);

    IComparer<TKey> Comparer { get; }
    Func<TValue, TKey> KeySelector { get; }
    public int Count { get; }

    public void Clear();

    public bool Contains(PriorityQueueHandle<TKey, TValue> handle); // O(log n)
    public bool Contains(TValue value); // O(n)
    public bool Contains(TKey key, TValue value); // O(n)

    public PriorityQueueHandle<TKey, TValue> Enqueue(TKey key, TValue value); // O(log n)
    public PriorityQueueHandle<TKey, TValue> Enqueue(TValue value); // O(log n)

    public PriorityQueueHandle<TKey, TValue> Peek(); // O(1)
    public PriorityQueueHandle<TKey, TValue> Dequeue(); // O(log n)

    public void Update(PriorityQueueHandle<TKey, TValue> handle); // O(log n)
    public void Update(PriorityQueueHandle<TKey, TValue> handle, TKey newKey); // O(log n)
    public void Update(TValue value, TKey newKey); // O(n)
    public void Update(TKey oldKey, TValue value, TKey newKey); // O(n)

    public void Remove(PriorityQueueHandle<TKey, TValue> handle); // O(log n)
    public void Remove(TValue value); // O(n)
    public void Remove(TKey key, TValue value); // O(n)

    public void Merge(PriorityQueue<TKey, TValue> other); // O(1)

    public IEnumerator<PriorityQueueHandle<TKey, TValue>> GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator();
}

public class PriorityQueueHandle<TKey, TValue>
{
    public TKey Key { get; }
    public TValue Value { get; }
}

Pertanyaan-pertanyaan terbuka

Predikat sebagai gantinya

Saya sangat menyukai pendekatan dengan pegangan, karena ini efisien, intuitif, dan sepenuhnya benar . Pertanyaannya adalah bagaimana menangani metode yang lebih sederhana tetapi berpotensi tidak aman. Satu hal yang bisa kita lakukan adalah mengganti metode yang lebih sederhana dengan sesuatu seperti ini:

  • UpdateFirst(Func<PriorityQueueHandle<TKey, TValue>, bool> predicate)
  • UpdateAll(Func<PriorityQueueHandle<TKey, TValue>, bool> predicate)

Penggunaannya akan sangat manis dan kuat. Dan benar-benar intuitif. Dan dapat dibaca (sangat dapat diekspresikan). Saya untuk yang itu.

var queue = new PriorityQueue<int, string>();

/* add some elements */

queue.UpdateAll(x => x.Key < 15, 0);

// or for example

queue.UpdateFirst(x => x.Value.State == State.Idle, 100);

Mengatur kunci baru berpotensi menjadi fungsi juga, yang mengambil kunci lama dan mengubahnya entah bagaimana.

Sama untuk Contains dan Remove .

PerbaruiKunci

Jika pemilih kunci tidak didefinisikan (dan dengan demikian kunci dan nilai disimpan secara terpisah), metode ini dapat diberi nama UpdateKey . Ini mungkin lebih bisa diekspresikan. Ketika pemilih kunci ditentukan, Update lebih baik, karena kunci sudah diperbarui dan apa yang perlu dilakukan adalah penataan ulang beberapa elemen dalam antrian prioritas.

FAQ

Bukankah pegangan tidak efisien?

Tidak ada masalah dengan efisiensi dalam hal menggunakan pegangan. Ketakutan yang umum adalah bahwa itu akan membutuhkan alokasi tambahan, karena kami menggunakan heap berdasarkan array secara internal. Jangan takut. Baca terus.

Bagaimana Anda akan menerapkannya kemudian?

Ini akan menjadi pendekatan yang sama sekali berbeda untuk memberikan antrian prioritas. Untuk pertama kalinya, pustaka standar tidak akan menyediakan fungsionalitas ini yang diimplementasikan sebagai tumpukan biner, yang direpresentasikan sebagai larik di bawahnya. Ini akan diimplementasikan dengan heap pasangan , yang secara desain tidak menggunakan array — itu hanya mewakili pohon saja.

Bagaimana performanya?

  • Untuk input acak umum, tumpukan kuartener akan sedikit lebih cepat.
  • Namun, itu harus didasarkan pada array agar lebih cepat. Kemudian, pegangan tidak dapat hanya didasarkan pada node — alokasi tambahan akan diperlukan. Kemudian — kami tidak dapat memperbarui dan menghapus elemen secara wajar.
  • Cara desainnya sekarang, kami memiliki API yang mudah digunakan sementara implementasinya cukup berkinerja.
  • Manfaat tambahan dari memiliki tumpukan pasangan di bawah adalah kemampuan untuk menggabungkan dua antrian prioritas dalam O(1), bukan O(n) seperti dalam tumpukan biner/kuartener.
  • Memasangkan tumpukan masih sangat cepat. Lihat referensi. Terkadang lebih cepat daripada tumpukan kuartener (tergantung pada data input dan operasi yang dilakukan, tidak hanya penggabungan).

Referensi

  • Michael L. Fredman, Robert Sedgewick, Daniel D. Sleator, dan Robert E. Tarjan (1986), The pairing heap: A new form of self-adjusting heap , Algorithmica 1:111-129 .
  • Daniel H. Larkin, Siddhartha Sen, dan Robert E. Tarjan (2014), Sebuah studi empiris back-to-basics antrian prioritas , arXiv:1403.0252v1 [cs.DS].

BTW, ini untuk iterasi ulasan API pertama. Bagian dengan _pertanyaan terbuka_ perlu diselesaikan (saya butuh pendapat Anda [dan peninjau API jika memungkinkan]). Jika iterasi pertama berlalu setidaknya sebagian, untuk sisa diskusi saya ingin membuat masalah baru (tutup dan rujuk yang ini).

Sejauh yang saya tahu, antarmuka atau tumpukan tidak akan lulus tinjauan API.

@pgolebiowski : Kenapa tidak tutup saja masalahnya? Tanpa antarmuka, kelas ini hampir tidak berguna. Satu-satunya tempat yang bisa saya gunakan adalah di implementasi pribadi. Pada titik mana saya bisa membuat (atau menggunakan kembali) antrian prioritas saya sendiri saat dibutuhkan. Saya tidak dapat mengekspos API publik dalam kode saya dengan tipe ini di tanda tangan karena akan rusak segera setelah saya perlu menukarnya dengan implementasi lain.

@bendono
Nah, Microsoft memiliki kata terakhir di sini, tidak sedikit orang yang mengomentari utasnya. Dan kita tahu bahwa:

Saya agak yakin bahwa peninjau/arsitek API lain akan membagikannya karena saya telah memeriksa pendapat dengan beberapa: nama harus PriorityQueue, dan kami tidak boleh memperkenalkan antarmuka IHeap. Harus ada tepat satu implementasi (kemungkinan melalui beberapa heap).

Ini dibagikan oleh @karelz , Manajer Insinyur Perangkat Lunak, dan @terrajobst , Manajer Program dan pemilik repositori ini + beberapa peninjau API.

Meskipun saya jelas menyukai pendekatan dengan antarmuka, seperti yang dinyatakan dengan jelas dalam posting sebelumnya, saya dapat melihatnya cukup sulit mengingat kita tidak memiliki banyak kekuatan dalam diskusi ini. Kami telah membuat poin kami, tetapi kami hanya beberapa komentator. Kode tersebut bukan milik kami. Mau bagaimana lagi?

Mengapa tidak menutup saja masalah itu? Tanpa antarmuka, kelas ini hampir tidak berguna.

Saya telah melakukan cukup banyak -- daripada membenci pekerjaan saya, lakukan sesuatu. Lakukan pekerjaan yang sebenarnya. Mengapa Anda mencoba menyalahkan saya untuk apa pun? Salahkan diri Anda sendiri karena tidak berhasil membujuk orang lain ke sudut pandang Anda.

Dan tolong, lepaskan aku dari retorika seperti itu. Ini benar-benar kekanak-kanakan -- lakukan yang lebih baik.

BTW, proposal ini berfokus pada hal-hal yang umum tidak peduli apakah kita memperkenalkan antarmuka atau tidak, atau apakah kelasnya bernama PriorityQueue atau Heap . Jadi fokuslah pada apa yang benar-benar penting di sini dan tunjukkan kepada kami beberapa bias tindakan jika Anda menginginkan sesuatu.

@pgolebiowski Tentu saja itu adalah keputusan Microsoft. Tetapi lebih baik menyajikan API yang ingin Anda gunakan yang memenuhi kebutuhan Anda. Jika ditolak, maka jadilah itu. Saya hanya tidak melihat perlunya mengkompromikan proposal.

Saya minta maaf jika Anda menafsirkan komentar saya sebagai menyalahkan Anda. Itu tentu bukan niat saya.

@pgolebiowski mengapa tidak menggunakan KeyValuePair<TKey,TValue> untuk pegangan?

@SamuelEnglard

Mengapa tidak menggunakan KeyValuePair<TKey,TValue> untuk pegangan?

  • Nah, PriorityQueueHandle sebenarnya _ memasangkan heap node _. Ini memperlihatkan dua properti -- TKey Key dan TValue Value . Namun, ia memiliki lebih banyak logika di dalamnya, yang hanya internal. Silakan merujuk ke implementasi saya ini . Ini berisi misalnya juga pointer ke node lain di pohon (semuanya akan menjadi internal di CoreFX).
  • KeyValuePair adalah sebuah struct, jadi itu akan disalin setiap kali + tidak dapat diwarisi.
  • Tetapi yang utama adalah PriorityQueueHandle adalah kelas yang agak rumit yang kebetulan mengekspos API publik yang sama dengan KeyValuePair .

@bendono

Tetapi lebih baik menyajikan API yang ingin Anda gunakan yang memenuhi kebutuhan Anda. Jika ditolak, maka jadilah itu. Saya hanya tidak melihat perlunya mengkompromikan proposal.

  • Memang benar, saya akan memikirkannya dan melihat apa yang terjadi. @karelz , bersama dengan proposal, dapatkah Anda juga melewati beberapa posting sebelumnya (mereka tepat sebelum proposal), di mana kami memilih antarmuka? Kami mungkin kembali lagi nanti, setelah iterasi pertama dari tinjauan API.
  • Namun, apa pun solusi yang kami gunakan, fungsinya akan sangat mirip dan akan sangat membantu untuk meninjaunya. Karena jika ide untuk pegangan ditolak dan kami tidak dapat benar-benar memperbarui/menghapus elemen dengan benar (atau kami tidak dapat memisahkan TKey dan TValue ), kelas ini benar-benar hampir tidak berguna - - seperti sekarang di Jawa.
  • Secara khusus, jika kita tidak memiliki antarmuka, perpustakaan AlgoKit saya tidak akan dapat memiliki inti umum untuk tumpukan dengan CoreFX, yang akan sangat menyedihkan bagi saya.
  • Dan ya, sungguh mengejutkan bagi saya bahwa menambahkan antarmuka dianggap sebagai kerugian oleh Microsoft.

Itu tentu bukan niat saya.

Maaf, kesalahan saya saat itu.

Pertanyaan saya tentang desain (penafian: pertanyaan & klarifikasi ini, tidak ada penolakan keras (belum)):

  1. Apakah kita benar-benar membutuhkan PriorityQueueHandle ? Bagaimana jika kita hanya mengharapkan nilai unik dalam antrian?

    • Motivasi: Tampaknya konsep yang agak terlibat. Jika kita memilikinya, saya ingin memahami mengapa kita membutuhkannya? Bagaimana itu membantu? Atau hanya detail implementasi spesifik yang bocor ke permukaan API? Apakah itu akan memberi kita kinerja sebanyak itu untuk membayar komplikasi di API?

  2. Apakah kita membutuhkan Merge ? Apakah koleksi lain memilikinya? Kita tidak boleh menambahkan API, hanya karena mudah diimplementasikan, harus ada beberapa kasus penggunaan umum untuk API.

    • Mungkin hanya menambahkan beberapa inisialisasi dari IEnumerable<KeyValuePair<TKey, TValue>> sudah cukup? + mengandalkan Linq

  3. Apakah kita perlu kelebihan comparer ? Bisakah kita selalu kembali ke default? (penafian: Saya kehilangan pengetahuan/keahlian dalam hal ini, jadi tanyakan saja)
  4. Apakah kita perlu kelebihan keySelector ? Saya pikir kita harus memutuskan apakah kita ingin memiliki prioritas sebagai bagian dari nilai, atau hal yang terpisah. Prioritas terpisah tampaknya sedikit lebih alami bagi saya, tetapi saya tidak memiliki pendapat yang kuat. Apakah kita tahu pro vs kontra?

Poin keputusan terpisah/paralel:

  1. Nama kelas PriorityQueue vs. Heap
  2. Perkenalkan IHeap dan kelebihan konstruktor?

Perkenalkan IHeap dan kelebihan konstruktor?

Sepertinya hal-hal telah cukup tenang bagi saya untuk membuang 2 sen saya .... Saya suka antarmuka, secara pribadi. Ini mengabstraksi detail implementasi dari API (dan fungsionalitas inti yang dijelaskan oleh API itu) dengan cara yang menurut saya menyederhanakan struktur dan memungkinkan kegunaan paling banyak.

Apa yang saya tidak memiliki pendapat yang kuat adalah apakah kita melakukan antarmuka pada saat yang sama dengan PQueue/Heap/ILikeThisItemMoreThanThisItemList atau jika kita menambahkannya nanti. Argumen bahwa API mungkin "sedang berubah" dan karena itu kita harus melepaskannya sebagai kelas terlebih dahulu sampai kita mendapatkan umpan balik tentu saja valid yang tidak saya setujui. Pertanyaannya kemudian menjadi ketika dianggap cukup "stabil" untuk menambahkan antarmuka. Jauh di atas di utas IList dan IDictionary disebutkan tertinggal di belakang API implementasi kanonik mereka yang kami tambahkan sejak lama, jadi periode waktu apa yang dianggap sebagai periode istirahat yang dapat diterima?

Jika kita dapat menentukan periode itu dengan kepastian yang masuk akal dan memastikan itu tidak memblokir secara tidak wajar, maka saya tidak melihat ada masalah dalam mengirimkan struktur data yang menumpuk ini tanpa antarmuka. Kemudian setelah jangka waktu tersebut, kami dapat memeriksa penggunaan dan mempertimbangkan untuk menambahkan antarmuka.

Dan ya, sungguh mengejutkan bagi saya bahwa menambahkan antarmuka dianggap sebagai kerugian oleh Microsoft.

Itu karena dalam banyak hal itu adalah kerugian. Jika kami mengirimkan antarmuka, itu cukup banyak dilakukan. Tidak banyak ruang untuk iterasi pada API itu, jadi kami lebih baik memastikan itu benar untuk pertama kalinya dan akan terus benar selama bertahun-tahun yang akan datang. Kehilangan beberapa fungsionalitas yang bagus untuk dimiliki adalah tempat yang jauh lebih baik daripada terjebak dengan antarmuka yang berpotensi tidak memadai di mana akan sangat menyenangkan untuk memiliki satu perubahan kecil ini yang akan membuat semuanya lebih baik.

Terima kasih atas masukannya, @karelz dan @ianhays!

Mengizinkan duplikat

Apakah kita benar-benar membutuhkan PriorityQueueHandle ? Bagaimana jika kita hanya mengharapkan nilai unik dalam antrian?

Motivasi: Tampaknya konsep yang agak terlibat. Jika kita memilikinya, saya ingin memahami mengapa kita membutuhkannya? Bagaimana itu membantu? Atau hanya detail implementasi spesifik yang bocor ke permukaan API? Apakah itu akan memberi kita kinerja sebanyak itu untuk membayar komplikasi di API?

Tidak, kami tidak membutuhkan itu. API antrian prioritas yang diusulkan di atas cukup kuat dan sangat fleksibel. Hal ini didasarkan pada asumsi bahwa elemen dan prioritas dapat diduplikasi . Karena asumsi itu, memiliki pegangan diperlukan untuk dapat menghapus atau memperbarui simpul yang benar. Namun, jika kita memberikan batasan bahwa elemen harus unik, kita dapat mencapai hasil yang sama seperti di atas dengan API yang lebih sederhana dan tanpa mengekspos kelas internal ( PriorityQueueHandle ), yang memang tidak ideal.

Mari kita asumsikan bahwa kita hanya mengizinkan elemen unik. Kami masih bisa mendukung semua skenario sebelumnya dan menjaga kinerja yang optimal. API yang lebih sederhana:

public class PriorityQueue<TElement, TPriority>
    : IEnumerable,
    IEnumerable<(TElement element, TPriority priority)>,
    IReadOnlyCollection<(TElement element, TPriority priority)>
    // ICollection not included on purpose
{
    public PriorityQueue();
    public PriorityQueue(IComparer<TPriority> comparer);
    public PriorityQueue(Func<TElement, TPriority> prioritySelector);
    public PriorityQueue(Func<TElement, TPriority> prioritySelector, IComparer<TPriority> comparer);

    IComparer<TPriority> Comparer { get; }
    Func<TElement, TPriority> PrioritySelector { get; }
    public int Count { get; }

    public void Clear();
    public bool Contains(TElement element); // O(1)

    public (TElement element, TPriority priority) Peek(); // O(1)
    public (TElement element, TPriority priority) Dequeue(); // O(log n)

    public void Enqueue(TElement element, TPriority priority); // O(log n)
    public void Enqueue(TElement element); // O(log n)

    public void Update(TElement element); // O(log n)
    public void Update(TElement element, TPriority priority); // O(log n)

    public void Remove(TElement element); // O(log n)

    public IEnumerator<(TElement element, TPriority priority)> GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator();
}

Tak lama, akan ada Dictionary<TElement, InternalNode> mendasarinya. Pengujian apakah antrian prioritas berisi elemen dapat dilakukan lebih cepat dari pada pendekatan sebelumnya. Memperbarui dan menghapus elemen disederhanakan secara signifikan, karena kami selalu dapat menunjuk ke elemen langsung dalam antrian.

Mungkin mengizinkan duplikat tidak sepadan dengan kerumitannya dan hal di atas sudah cukup. Saya pikir saya menyukainya. Bagaimana menurutmu?

Menggabungkan

Apakah kita membutuhkan Merge ? Apakah koleksi lain memilikinya? Kita tidak boleh menambahkan API, hanya karena mudah diimplementasikan, harus ada beberapa kasus penggunaan umum untuk API.

Setuju. Kami tidak membutuhkannya. Kami selalu dapat menambahkan API tetapi tidak menghapusnya. Saya setuju dengan menghapus metode ini dan (berpotensi) menambahkannya nanti (jika perlu).

pembanding

Apakah kita membutuhkan kelebihan pembanding? Bisakah kita selalu kembali ke default? (penafian: Saya kehilangan pengetahuan/keahlian dalam hal ini, jadi tanyakan saja)

Ini menurut saya cukup penting, karena dua alasan:

  • Seorang pengguna ingin agar elemen mereka diurutkan dalam urutan menaik atau menurun. Prioritas tertinggi tidak memberi tahu kami bagaimana pemesanan harus dilakukan.
  • Jika kami melewatkan pembanding, kami memaksa pengguna untuk selalu memberikan kami kelas yang mengimplementasikan IComparable .

Plus, ini konsisten dengan API yang ada. Lihat SortedDictionary .

pemilih

Apakah kita memerlukan kelebihan keySelector? Saya pikir kita harus memutuskan apakah kita ingin memiliki prioritas sebagai bagian dari nilai, atau hal yang terpisah. Prioritas terpisah tampaknya sedikit lebih alami bagi saya, tetapi saya tidak memiliki pendapat yang kuat. Apakah kita tahu pro vs kontra?

Saya juga suka prioritas terpisah. Selain itu, lebih mudah untuk mengimplementasikan ini, kinerja & penggunaan memori yang lebih baik, API yang lebih intuitif, lebih sedikit pekerjaan untuk pengguna (tidak perlu mengimplementasikan IComparable ).

Mengenai pemilih sekarang ...

kelebihan

Inilah yang membuat antrian prioritas ini fleksibel. Ini memungkinkan pengguna untuk memiliki:

  • elemen dan prioritasnya sebagai elemen terpisah
  • elemen (kelas kompleks) yang memiliki prioritas di suatu tempat di dalamnya
  • logika eksternal yang mengambil prioritas untuk elemen tertentu
  • elemen yang mengimplementasikan IComparable

Ini memungkinkan hampir semua konfigurasi yang dapat saya pikirkan. Saya merasa ini berguna, karena pengguna hanya bisa "plug n play". Ini juga agak intuitif.

Kontra

  • Ada lebih banyak untuk dipelajari.
  • Lebih banyak API. Dua konstruktor tambahan, satu metode tambahan Enqueue dan Update .
  • Jika kami memutuskan untuk memisahkan atau menggabungkan elemen dan prioritas, kami memaksa beberapa pengguna (yang memiliki data mereka dalam format yang berbeda) untuk mengadaptasi kode mereka untuk menggunakan struktur data ini.

Poin keputusan terpisah/paralel

Nama kelas PriorityQueue vs. Heap

Perkenalkan IHeap dan kelebihan konstruktor.

  • Rasanya PriorityQueue harus menjadi bagian dari CoreFX, daripada Heap .
  • Mengenai antarmuka IHeap — karena antrian prioritas dapat diimplementasikan dengan sesuatu yang berbeda dari heap, mungkin kami tidak ingin mengeksposnya dengan cara ini. Namun, kita mungkin membutuhkan IPriorityQueue .

Jika kami mengirimkan antarmuka, itu cukup banyak dilakukan. Tidak banyak ruang untuk iterasi pada API itu, jadi kami lebih baik memastikan itu benar untuk pertama kalinya dan akan terus benar selama bertahun-tahun yang akan datang. Kehilangan beberapa fungsionalitas yang bagus untuk dimiliki adalah tempat yang jauh lebih baik daripada terjebak dengan antarmuka yang berpotensi tidak memadai di mana akan sangat menyenangkan untuk memiliki satu perubahan kecil ini yang akan membuat semuanya lebih baik.

Setuju!

Elemen yang sebanding

Jika kita menambahkan asumsi lain: bahwa elemen harus sebanding, maka API menjadi lebih sederhana. Tapi sekali lagi, itu kurang fleksibel.

kelebihan

  • Tidak perlu IComparer .
  • Tidak perlu pemilih.
  • Satu metode Enqueue dan Update .
  • Satu tipe generik, bukan dua.

Kontra

  • Kita tidak dapat memiliki elemen dan prioritas sebagai objek yang terpisah. Pengguna perlu menyediakan kelas pembungkus baru jika mereka memilikinya dalam format itu.
  • Pengguna selalu perlu mengimplementasikan IComparable sebelum mereka dapat menggunakan antrian prioritas ini.
  • Jika kita memiliki elemen dan prioritas yang terpisah, kita dapat mengimplementasikannya dengan lebih mudah, dengan kinerja dan penggunaan memori yang lebih baik -- kamus internal <TElement, InternalNode> dan InternalNode berisi TPriority .
public class PriorityQueue<T> : IEnumerable, IEnumerable<T>, IReadOnlyCollection<T>
    where T : IComparable<T>
    // ICollection not included on purpose
{
    public PriorityQueue();
    // some other constructors like building it from a collection, or initial capacity if we have an array beneath

    public int Count { get; }

    public void Clear();
    public bool Contains(T element);

    public T Peek();
    public T Dequeue();

    public void Enqueue(T element);
    public void Update(T element);
    public void Remove(T element);

    public IEnumerator<T> GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator();
}

Semuanya adalah trade-off. Kami menghapus beberapa fitur, kehilangan beberapa fleksibilitas, tetapi mungkin kami tidak membutuhkannya untuk memuaskan 95% pengguna kami.

Saya juga menyukai pendekatan antarmuka karena lebih fleksibel dan memberikan lebih banyak penggunaan, tetapi saya juga setuju dengan @karelz dan @ianhays bahwa kita harus menunggu sampai API kelas baru digunakan dan kita mendapatkan umpan balik sampai kita benar-benar mengirimkan antarmuka dan kemudian kita penuh dengan versi itu dan tidak dapat mengubahnya tanpa melanggar pelanggan yang sudah menggunakannya.

Juga tentang pembanding, saya pikir ini mengikuti api BCL lainnya dan saya tidak suka fakta bahwa pengguna perlu menyediakan kelas pembungkus baru. Saya sangat suka konstruktor yang kelebihan menerima pendekatan pembanding dan menggunakan pembanding itu di dalam semua perbandingan yang perlu dilakukan secara internal, jika pembanding tidak disediakan atau pembanding adalah nol, maka gunakan pembanding default.

@pgolebiowski terima kasih atas proposal API yang terperinci dan deskriptif dan menjadi sangat aktif dan energik untuk mendapatkan API ini disetujui dan ditambahkan ke CoreFX 👍 ketika diskusi ini selesai dan kami menganggapnya siap untuk ditinjau Saya akan menggabungkan semua input dan permukaan API akhir menjadi satu komentar dan perbarui komentar masalah utama di bagian atas karena itu akan membuat hidup pengulas lebih mudah.

OK, saya yakin tentang pembanding, masuk akal dan konsisten.
Saya masih bingung pada pemilih - IMO kita harus mencoba tanpanya - mari kita bagi menjadi 2 varian.

```c#
Antrian Prioritas kelas publik
: IE dapat dihitung,
IEnumerable<(Elemen elemen, prioritas TPrioritas)>,
IReadOnlyCollection<(elemen TElement, prioritas TPriority)>
// Koleksi tidak disertakan dengan sengaja
{
Antrian Prioritas publik();
Antrian Prioritas publik(IComparerpembanding);

public IComparer<TPriority> Comparer { get; }
public int Count { get; }

public void Clear();
public bool Contains(TElement element); // O(1)

public (TElement element, TPriority priority) Peek(); // O(1)
public (TElement element, TPriority priority) Dequeue(); // O(log n)

public void Enqueue(TElement element, TPriority priority); // O(log n)
public void Update(TElement element, TPriority priority); // O(log n)

public void Remove(TElement element); // O(log n)

public IEnumerator<(TElement element, TPriority priority)> GetEnumerator();
IEnumerator IEnumerable.GetEnumerator();

//
// Bagian pemilih
//
Antrian Prioritas publik(FuncprioritasPemilih);
Antrian Prioritas publik(FuncprioritySelector, IComparerpembanding);

public Func<TElement, TPriority> PrioritySelector { get; }

public void Enqueue(TElement element); // O(log n)
public void Update(TElement element); // O(log n)

}
````

Pertanyaan-pertanyaan terbuka:

  1. Nama kelas PriorityQueue vs. Heap
  2. Perkenalkan IHeap dan kelebihan konstruktor? (Haruskah kita menunggu nanti?)
  3. Perkenalkan IPriorityQueue ? (Haruskah kita menunggu nanti - IDictionary contoh)
  4. Gunakan pemilih (prioritas yang disimpan di dalam nilai) atau tidak (perbedaan 5 API)
  5. Gunakan tupel (TElement element, TPriority priority) vs. KeyValuePair<TPriority, TElement>

    • Haruskah Peek dan Dequeue lebih suka memiliki argumen out daripada Tuple?

Saya akan mencoba menjalankannya oleh grup tinjauan API besok untuk umpan balik awal.

Memperbaiki nama bidang tuple Peek dan Dequeue menjadi priority (terima kasih @pgolebiowski telah menunjukkannya).
Top-post diperbarui dengan proposal terbaru di atas.

Saya mendukung komentar @pgolebiowski karena telah mendorongnya ke depan!

Pertanyaan-pertanyaan terbuka:

Saya pikir kita bisa memiliki satu lagi di sini:

  1. Batasi diri kita hanya pada elemen unik (jangan izinkan duplikat).

Poin bagus, ditangkap sebagai 'Asumsi' di pos teratas, daripada pertanyaan terbuka. Kebalikannya akan sedikit merusak API.

Haruskah kita mengembalikan bool dari Remove ? Dan juga prioritas sebagai out priority arg? (Saya percaya kami menambahkan kelebihan yang serupa baru-baru ini ke dalam struktur data lain)

Mirip dengan Contains - Saya yakin seseorang akan menginginkan prioritas darinya. Kami mungkin ingin menambahkan kelebihan dengan out priority .

Saya tetap berpendapat (meskipun saya belum menyuarakannya) bahwa Heap akan menyiratkan implementasi, dan akan mendukung pemanggilan ini PriorityQueue . (Saya bahkan akan mendukung proposal untuk kelas Heap yang lebih ramping yang memungkinkan elemen duplikat, dan tidak mengizinkan pembaruan, dll. (lebih sesuai dengan proposal asli) tetapi saya tidak berharap itu terjadi).

KeyValuePair<TPriority, TElement> tentu saja tidak boleh digunakan, karena prioritas duplikat diharapkan, dan saya pikir KeyValuePair<TElement, TPriority> juga membingungkan, jadi saya akan mendukung untuk tidak menggunakan KeyValuePair sama sekali, dan juga menggunakan tupel biasa, atau parameter keluar untuk prioritas (secara pribadi saya suka params, tapi saya tidak sibuk).

Jika kita melarang duplikat, kita perlu memutuskan perilaku mencoba menambahkannya kembali dengan prioritas yang berbeda/berubah.

Saya tidak mendukung proposal pemilih, karena alasan sederhana yang menyiratkan redundansi, dan sepatutnya akan menciptakan kembali kebingungan. Jika Prioritas Elemen disimpan dengannya, maka itu akan disimpan di dua tempat, dan jika mereka menjadi tidak sinkron (yaitu seseorang lupa memanggil metode Update(TElement) tampak tidak berguna) maka banyak penderitaan akan memastikan. Jika pemilih adalah komputer, maka kami terbuka untuk orang yang sengaja menambahkan elemen, kemudian mengubah nilai yang mereka hitung, dan sekarang jika mereka mencoba menambahkannya kembali, ada banyak hal yang bisa salah tergantung pada keputusan tentang apa yang terjadi ketika ini terjadi. Pada tingkat yang sedikit lebih tinggi, mencoba meskipun dapat menghasilkan penambahan salinan Elemen yang diubah, karena tidak lagi sama dengan sebelumnya (ini adalah masalah umum dengan kunci yang bisa berubah, tapi saya pikir memisahkan Prioritas dan Elemen akan membantu untuk menghindari potensi masalah).

Pemilih itu sendiri bertanggung jawab untuk mengubah perilaku, yang merupakan cara lain agar pengguna dapat merusak segalanya tanpa berpikir. Jauh lebih baik, saya pikir, untuk membuat pengguna secara eksplisit memberikan prioritas. Masalah besar yang saya lihat dengan ini, adalah ia menyampaikan bahwa entri duplikat diperbolehkan, karena kami mendeklarasikan pasangan, bukan Elemen. Namun, dokumentasi sebaris yang masuk akal dan nilai pengembalian bool pada Enqueue seharusnya dapat mengatasi masalah ini dengan mudah. Lebih baik daripada Boolean mungkin mengembalikan prioritas elemen lama/baru (misalnya jika Enqueue menggunakan prioritas yang baru diberikan, atau minimum dari keduanya, atau prioritas lama), tetapi saya pikir Enqueue seharusnya gagal jika Anda mencoba untuk menambahkan kembali sesuatu, dan karena itu hanya mengembalikan bool menunjukkan keberhasilan. Ini membuat Enqueue dan Update benar-benar terpisah dan terdefinisi dengan baik.

Saya akan mendukung untuk tidak menggunakan KeyValuePair sama sekali, dan juga menggunakan tupel biasa

Aku bersamamu di tupel.

Jika kita melarang duplikat, kita perlu memutuskan perilaku mencoba menambahkannya kembali dengan prioritas yang berbeda/berubah.

  • Saya melihat kesamaan dengan pengindeks dalam Dictionary . Di sana, ketika Anda melakukan dictionary["something"] = 5 maka itu akan diperbarui jika "something" adalah kunci di sana sebelumnya. Jika tidak ada, itu hanya akan ditambahkan.
  • Namun, metode Enqueue bagi saya analog dengan metode Add dalam kamus. Yang berarti itu harus melempar pengecualian.
  • Dengan mempertimbangkan poin di atas, kami dapat mempertimbangkan untuk menambahkan pengindeks ke antrian prioritas untuk mendukung perilaku yang Anda pikirkan.
  • Tetapi pada gilirannya, pengindeks mungkin bukan sesuatu yang bekerja dengan konsep antrian.
  • Yang membawa kita pada kesimpulan bahwa metode Enqueue seharusnya hanya mengeluarkan pengecualian jika seseorang ingin menambahkan elemen duplikat. Demikian pula, metode Update harus mengeluarkan pengecualian jika seseorang ingin memperbarui prioritas elemen yang tidak ada.
  • Yang membawa kita ke solusi baru -- tambahkan metode TryUpdate yang memang mengembalikan bool .

Saya tidak mendukung proposal pemilih, karena alasan sederhana yang menyiratkan redundansi

Bukankah kuncinya tidak disalin secara fisik (jika ada), tetapi pemilih tetap merupakan fungsi yang dipanggil ketika prioritas perlu dievaluasi? Di mana redundansinya?

Saya pikir memisahkan Prioritas dan Elemen akan membantu menghindari potensi masalah

Satu-satunya masalah adalah kasus ketika pelanggan tidak memiliki prioritas fisik. Dia tidak bisa berbuat banyak saat itu.

Jauh lebih baik, saya pikir, untuk membuat pengguna secara eksplisit memberikan prioritas. Masalah besar yang saya lihat dengan ini, adalah ia menyampaikan bahwa entri duplikat diperbolehkan, karena kami mendeklarasikan pasangan, bukan Elemen.

Saya melihat beberapa masalah dengan solusi itu, tetapi belum tentu mengapa ini menunjukkan bahwa entri duplikat diizinkan. Saya pikir logika "tanpa duplikat" harus diterapkan hanya pada TElement -- prioritasnya hanyalah nilai di sini.

var queue = new PriorityQueue<string, double>();

queue.Enqueue("first", 0.1);
queue.Enqueue("first", 0.5); // should be unsuccessful IMO

@VisualMelon apakah itu masuk akal?

@pgolebiowski

Saya sepenuhnya setuju dengan menambahkan TryUpdate , dan melempar Update masuk akal bagi saya. Saya lebih memikirkan ISet sehubungan dengan Enqueue (daripada IDictionary ). Melempar juga masuk akal, saya pasti melewatkan poin itu, tetapi saya pikir pengembalian bool menyampaikan 'kesetiaan' dari jenisnya. Mungkin TryEnqueue akan diurutkan juga (dengan Add melempar)? TryRemove juga akan dihargai (gagal jika kosong). Mengenai poin terakhir Anda, ya, itulah perilaku yang saya pikirkan juga. Saya kira analogi dengan IDictionary lebih baik daripada ISet pada refleksi, dan itu seharusnya cukup jelas. (Singkatnya: Saya akan mendukung semua yang dilemparkan sesuai saran Anda, tetapi memiliki Try* adalah suatu keharusan jika itu masalahnya; Saya juga setuju dengan pernyataan Anda mengenai kondisi kegagalan).

Mengenai pengindeks, saya pikir Anda benar bahwa itu tidak benar-benar 'sesuai' dengan konsep antrian, saya tidak akan mendukungnya. Jika ada, metode yang diberi nama baik untuk Antrian atau Pembaruan akan dilakukan.

Bukankah kuncinya tidak disalin secara fisik (jika ada), tetapi pemilih tetap merupakan fungsi yang dipanggil ketika prioritas perlu dievaluasi? Di mana redundansinya?

Anda benar, saya salah membaca pembaruan proposal (sedikit tentang menyimpannya di Elemen). Dengan asumsi bahwa Pemilih dipanggil sesuai permintaan (yang harus diperjelas dalam dokumentasi apa pun, karena dapat memiliki implikasi kinerja), poinnya masih tetap bahwa hasil fungsi dapat berubah tanpa struktur data merespons, meninggalkan dua tidak sinkron kecuali Update dipanggil. Lebih buruk lagi, jika pengguna mengubah prioritas efektif beberapa Elemen, dan hanya memperbarui salah satunya, maka struktur data akan berakhir tergantung pada perubahan 'tidak terikat' ketika mereka dipilih dari Elemen 'tidak diperbarui' (Saya belum melihat implementasi DataStructure yang diusulkan secara rinci, tetapi saya pikir ini pasti menjadi masalah untuk setiap Pembaruan O(<n) ). Memaksa pengguna untuk secara eksplisit memperbarui prioritas apa pun memperbaikinya, dengan perlu mengambil struktur data dari satu keadaan konsisten ke keadaan lain.

Perhatikan bahwa semua keluhan saya sejauh ini dengan proposal Selector adalah tentang kekokohan API: Saya pikir pemilih membuatnya mudah digunakan secara tidak benar. Namun, selain kegunaan, secara konseptual, Elemen dalam Antrian tidak harus mengetahui prioritasnya. Ini berpotensi tidak relevan bagi mereka, dan jika pengguna akhirnya membungkus Elemen mereka dalam struct Queable<T>{} atau sesuatu maka itu tampak seperti kegagalan dalam menyediakan API tanpa gesekan (sama seperti saya menyakiti saya untuk menggunakan istilah tersebut).

Anda tentu saja dapat berargumen bahwa tanpa pemilih, beban ada pada pengguna untuk memanggil pemilih, tetapi saya pikir jika Elemen mengetahui prioritas mereka, mereka akan (semoga) diekspos dengan rapi (yaitu properti), dan jika mereka tidak' t, maka tidak ada pemilih yang perlu dikhawatirkan (menghasilkan Prioritas dengan cepat, dll.). Ada lebih banyak pipa ledeng yang tidak berarti yang diperlukan untuk mendukung pemilih jika Anda tidak menginginkannya, kemudian meneruskan prioritas jika Anda sudah memiliki 'pemilih' yang terdefinisi dengan baik. Pemilih membujuk pengguna untuk mengekspos informasi ini (mungkin tidak membantu), sementara prioritas terpisah menyediakan antarmuka yang sangat transparan yang tidak dapat saya bayangkan akan memengaruhi keputusan desain.

Satu-satunya argumen yang benar-benar dapat saya pikirkan untuk memilih pemilih adalah bahwa itu berarti Anda dapat menggunakan pass PriorityQueue , dan bagian lain dari program dapat menggunakannya tanpa mengetahui bagaimana prioritas dihitung. Saya harus memikirkan ini sedikit lebih lama, tetapi mengingat kesesuaian 'ceruk', ini menurut saya sebagai hadiah kecil untuk overhead yang cukup berat dalam kasus yang lebih umum.

_Edit: Setelah memikirkannya lagi, akan lebih baik untuk memiliki PriorityQueue s mandiri yang dapat Anda lempar Elemen, tetapi saya berpendapat bahwa biaya untuk mengatasi ini akan menjadi besar, sementara biaya pengenalan akan jauh lebih sedikit._

Saya telah melakukan sedikit pekerjaan mencari melalui open source .NET codebase untuk melihat contoh dunia nyata tentang bagaimana antrian prioritas digunakan. Masalah rumitnya adalah menyaring proyek siswa dan kode sumber pelatihan.

Penggunaan 1: Layanan Pemberitahuan Roslyn
https://github.com/dotnet/roslyn/
Kompiler Roslyn menyertakan implementasi antrean prioritas pribadi yang disebut "PriorityQueue" yang tampaknya memiliki pengoptimalan yang sangat spesifik di dalamnya - antrean objek yang menggunakan kembali objek dalam antrean untuk menghindari pengumpulan sampah. Metode Enqueue_NoLock melakukan evaluasi pada current.Value.MinimumRunPointInMS < entry.Value.MinimumRunPointInMS untuk menentukan di mana dalam antrian untuk menempatkan node baru. Salah satu dari dua desain antrian prioritas utama yang diusulkan di sini (fungsi pembanding/delegasi vs prioritas eksplisit) akan sesuai dengan skenario penggunaan ini.

Penggunaan 2: Lucene.net
https://github.com/Apache/lucenenet
Ini bisa dibilang contoh penggunaan terbesar untuk PriorityQueue yang bisa saya temukan di .NET. Apache Lucene.net adalah port .net lengkap dari perpustakaan mesin pencari Lucene yang populer. Kami menggunakan versi Java di perusahaan saya, dan menurut situs web Apache beberapa nama besar menggunakan versi .NET. Ada banyak garpu proyek .NET di Github.

Lucene menyertakan implementasi PriorityQueue-nya sendiri yang disubklasifikasikan oleh sejumlah antrian prioritas "khusus": HitQueue, TopOrdAndFloatQueue, PhraseQueue, dan SuggestWordQueue. Selain itu, proyek secara langsung membuat PriorityQueue di sejumlah tempat.

Implementasi PriorityQueue Lucene, yang ditautkan di atas, sangat mirip dengan API antrian prioritas asli yang diposting di edisi ini. Ini didefinisikan sebagai "PriorityQueue" dan menerima IComparerparameter dalam konstruktornya. Metode dan Properti termasuk Hitung, Hapus, Penawaran (enqueue/push), Poll (dequeue/pop), Peek, Hapus (menghapus item yang cocok pertama ditemukan dalam antrian), Add (sinonim untuk Penawaran). Menariknya, mereka juga memberikan dukungan enumerasi untuk antrian.

Prioritas dalam Lucene's PriorityQueue ditentukan oleh pembanding yang diteruskan ke konstruktor, dan jika tidak ada yang diteruskan, diasumsikan bahwa objek yang dibandingkan mengimplementasikan IComparabledan menggunakan antarmuka itu untuk melakukan perbandingan. Desain API asli yang diposting di sini serupa, kecuali bahwa ia bekerja dengan tipe nilai juga.

Ada banyak contoh penggunaan melalui basis kode mereka, SloppyPhraseScorer menjadi salah satunya.

Konstruktor SloppyPhraseScorer membuat instance PhraseQueue (pq) baru, yang merupakan salah satu subkelas khusus PriorityQueue. Kumpulan PhrasePositions dihasilkan, yang tampaknya menjadi pembungkus untuk satu set posting, posisi, dan satu set istilah. Metode FillQueue menghitung posisi frase dan mengantrekannya. PharseFreq() memanggil fungsi AdvancePP dan, pada tingkat tinggi, muncul untuk dequeue, memperbarui prioritas item, dan kemudian enqueue lagi. Prioritas ditentukan secara relatif (menggunakan pembanding) daripada secara eksplisit (prioritas tidak "diteruskan" sebagai parameter kedua selama enqueue).

Anda dapat melihat bahwa berdasarkan implementasi PhraseQueue, nilai perbandingan yang diteruskan melalui konstruktor (misalnya integer) mungkin tidak memotongnya. Fungsi perbandingannya ("LessThan") mengevaluasi tiga bidang berbeda: PhrasePositions.doc, PhrasePositions.position, dan PhrasePositions.offset.

Penggunaan 3: Pengembangan Game
Saya belum selesai mencari melalui contoh penggunaan di ruang ini, tapi saya melihat beberapa contoh custom .NET PriorityQueue yang digunakan dalam pengembangan game. Dalam pengertian yang sangat umum, ini cenderung mengelompok di sekitar pencarian jalan sebagai kasus penggunaan utama (milik Dijkstra). Anda dapat menemukan banyak orang bertanya bagaimana menerapkan algoritma pathfinding di .NET karena Unity 3D .

Masih perlu menggali melalui area ini; melihat beberapa contoh dengan prioritas eksplisit sedang diantrekan dan beberapa contoh menggunakan Pembanding/IComparable.

Pada catatan terpisah, ada beberapa diskusi seputar elemen unik, menghapus elemen eksplisit, dan menentukan apakah elemen tertentu ada.

Antrian, sebagai struktur data, umumnya mendukung Enqueuing dan Dequeuing. Jika kita menuju ke jalur penyediaan operasi seperti set/daftar lainnya, saya bertanya-tanya apakah kita benar-benar merancang struktur data yang berbeda sama sekali - sesuatu yang mirip dengan daftar tupel yang diurutkan. Jika penelepon memiliki kebutuhan selain Enqueue, Dequeue, Peek, mungkin mereka membutuhkan sesuatu selain antrian prioritas? Antrian, menurut definisi, menyiratkan penyisipan ke dalam antrian dan memerintahkan penghapusan dari antrian; tidak banyak lagi.

@ebickle

Saya menghargai upaya Anda dalam menelusuri repositori lain dan memeriksa bagaimana fungsionalitas antrian prioritas disampaikan di sana. Namun, IMO diskusi ini akan mendapat manfaat dari penyediaan proposal desain khusus . Utas ini sudah sulit untuk diikuti dan menceritakan kisah yang panjang tanpa kesimpulan membuatnya semakin sulit.

Antrian [...] umumnya mendukung Enqueuing dan Dequeuing. [...] Jika penelepon memiliki kebutuhan selain Enqueue, Dequeue, Peek, mungkin mereka membutuhkan sesuatu selain antrian prioritas? Antrian, menurut definisi, menyiratkan penyisipan ke dalam antrian dan memerintahkan penghapusan dari antrian; tidak banyak lagi.

  • Apa kesimpulannya? Usul?
  • Ini sangat jauh dari kebenaran. Bahkan algoritma Dijkstra yang Anda sebutkan menggunakan pembaruan prioritas elemen. Dan apa yang diperlukan untuk memperbarui elemen tertentu juga diperlukan untuk menghapus elemen tertentu.

Diskusi yang bagus. Penelitian @ebickle sangat berguna IMO!
@ebickle apakah Anda memiliki kesimpulan tentang [2] lucene.net - apakah proposal terbaru kami sesuai dengan penggunaan atau tidak? (Saya harap saya tidak melewatkannya dalam deskripsi terperinci Anda)

Sepertinya kita membutuhkan Try* varian dari atas + IsEmpty + TryPeek / TryDequeue + EnqueueOrUpdate ? Pikiran?
```c#
bool publik IsEmpty();

public bool TryPeek(ref TElement element, ref TPriority priority); // false if empty
public bool TryDequeue(ref TElement element, ref TPriority priority); // false if empty

public bool TryEnqueue(TElement element, TPriority priority); // false if it is duplicate (doe NOT update it)
public void EnqueueOrUpdate(TElement element, TPriority priority); // TODO: Should return bool status for enqueued vs. updated?
public bool TryUpdate(TElement element, TPriority priority); // false if element does not exist (does NOT add it)

public bool TryRemove(TElement element); // false if element does not exist

```

@karelz

Sepertinya kita membutuhkan varian Try* dari atas

Iya benar sekali.

Haruskah mengembalikan status bool untuk enqueued vs. diperbarui?

Jika kita ingin mengembalikan status di mana-mana, maka di mana-mana. Untuk metode khusus ini, saya pikir itu harus benar jika salah satu operasi berhasil ( Enqueue atau Update ).

Selebihnya -- saya setuju saja :smile:

Hanya satu pertanyaan -- mengapa ref bukannya out ? Saya tidak yakin:

  • Kita tidak perlu menginisialisasinya sebelum memasuki fungsi.
  • Itu tidak digunakan untuk metode (itu hanya padam).

Jika kita ingin mengembalikan status di mana-mana, maka di mana-mana. Untuk metode khusus ini, saya pikir itu harus benar jika salah satu operasi berhasil ( Enqueue atau Update ).

Itu akan selalu kembali benar, itu bukan ide yang baik. Kita harus menggunakan void dalam kasus seperti itu IMO. Kalau tidak, orang akan bingung dan akan memeriksa nilai kembalian dan menambahkan kode yang tidak akan pernah dieksekusi yang tidak berguna. (Kecuali saya melewatkan sesuatu)

ref vs out setuju, saya memperdebatkannya sendiri. Saya tidak memiliki pendapat yang kuat / pengalaman yang cukup untuk membuat keputusan sendiri. Kami dapat meminta peninjau API / menunggu lebih banyak komentar.

Itu akan selalu kembali benar

Anda benar, buruk saya. Maaf.

Saya tidak memiliki pendapat yang kuat / pengalaman yang cukup untuk membuat keputusan sendiri.

Mungkin saya melewatkan sesuatu, tetapi saya merasa cukup sederhana. Jika kita menggunakan ref , pada dasarnya kita mengatakan bahwa Peek dan Dequeue ingin menggunakan entah bagaimana TElement dan TPriority diteruskan ke sana ( Maksud saya baca bidang itu). Yang sebenarnya tidak demikian -- metode kita seharusnya hanya memberikan nilai ke variabel tersebut (dan sebenarnya mereka diharuskan melakukannya oleh kompiler).

Posting teratas diperbarui dengan API Try*
Menambahkan 2 pertanyaan terbuka:

  • [6] Apakah melempar Peek dan Dequeue berguna sama sekali?
  • [7] TryPeek dan TryDequeue - harus menggunakan ref atau out args?

ref vs. keluar - Anda benar. Saya mengoptimalkan untuk menghindari inisialisasi jika kami mengembalikan false. Itu bodoh & buta dari saya - optimasi prematur. Saya akan mengubahnya menjadi keluar dan menghapus pertanyaan.

6: Saya mungkin melewatkan sesuatu, tetapi jika tidak memberikan pengecualian, apa yang harus dilakukan Peek atau Dequeue jika antrian kosong? Saya kira ini mulai menimbulkan pertanyaan apakah struktur data harus menerima null (saya lebih suka tidak, tetapi tidak ada pendapat tegas). Bahkan jika kita mengizinkan null , Tipe Nilai tidak memiliki null ( default tentu saja tidak dihitung), jadi Peek dan Dequeue memiliki tidak ada cara untuk menyampaikan hasil yang tidak berarti, dan sepatutnya saya pikir harus mengeluarkan pengecualian (sehingga menghilangkan kekhawatiran tentang parameter out !). Saya tidak melihat alasan untuk tidak mengikuti contoh Queue.Dequeue

Saya hanya akan menambahkan bahwa Dijkstra (alias. Pencarian Heuristik tanpa Heuristik) seharusnya tidak memerlukan perubahan Prioritas. Saya tidak pernah ingin memperbarui prioritas apa pun, dan saya selalu melakukan pencarian heuristik. (inti dari algoritme adalah bahwa setelah Anda menjelajahi keadaan, Anda tahu bahwa Anda telah menjelajahi rute terbaik ke sana, jika tidak, risikonya menjadi tidak optimal, maka Anda tidak akan pernah dapat meningkatkan prioritas, dan Anda tentu tidak ingin menguranginya (yaitu mempertimbangkan rute yang lebih buruk) _Mengabaikan kasus di mana Anda sengaja memilih heuristik sehingga tidak optimal, dalam hal ini Anda tentu saja bisa mendapatkan hasil yang tidak optimal, tetapi Anda tetap tidak akan pernah perbarui prioritas_)

jika tidak melempar pengecualian, apa yang harus dilakukan Peek atau Dequeue jika antrian kosong?

Benar.

sehingga menghilangkan kekhawatiran tentang parameter keluar!

Bagaimana? Nah, parameter out adalah untuk metode TryPeek dan TryDequeue . Yang mengeluarkan pengecualian adalah Peek dan Dequeue .

Saya hanya akan menambahkan bahwa Dijkstra seharusnya tidak memerlukan perubahan Prioritas.

Saya mungkin salah, tetapi sepengetahuan saya, algoritma Dijkstra menggunakan operasi DecreaseKey . Lihat misalnya ini . Apakah ini efisien atau tidak adalah aspek yang berbeda. Faktanya, Fibonacci heap dirancang sedemikian rupa sehingga mencapai operasi DecreaseKey secara asimtotik di O(1) (untuk meningkatkan Dijkstra).

Tapi tetap saja, yang penting dalam diskusi kita -- kemampuan untuk memperbarui elemen dalam antrian prioritas sangat membantu dan ada orang yang mencari fungsi seperti itu (lihat pertanyaan yang ditautkan sebelumnya di StackOverflow). Saya sendiri juga beberapa kali menggunakannya.

Maaf, ya, saya melihat masalah dengan params out sekarang, salah membaca lagi. Dan tampaknya varian Dijkstra (nama yang tampaknya diterapkan lebih luas daripada yang saya yakini ...) dapat diimplementasikan mungkin lebih efisien (jika antrian Anda sudah berisi Elemen, mungkin ada manfaat untuk pencarian berulang) dengan prioritas yang dapat diperbarui. Itulah yang saya dapatkan karena menggunakan kata dan nama yang panjang. Perhatikan bahwa saya tidak mengusulkan agar kita menjatuhkan Update , akan lebih baik juga untuk memiliki Heap lebih ramping atau lainnya (yaitu proposal asli) tanpa batasan yang diberikan oleh kemampuan ini (sama mulianya kemampuan seperti yang saya hargai).

7: Ini seharusnya out . Masukan apa pun tidak akan pernah memiliki arti apa pun, jadi seharusnya tidak ada (yaitu kita tidak boleh menggunakan ref ). Dengan parameter out , kami tidak akan meningkatkan pengembalian nilai default . Inilah yang dilakukan Dictionary.TryGetValue , dan saya tidak melihat alasan untuk melakukan sebaliknya. _Dikatakan, Anda dapat memperlakukan ref sebagai nilai atau default, tetapi jika Anda tidak memiliki default yang berarti, maka itu akan membuat frustrasi._

Diskusi tinjauan API:

  • Kami perlu mengadakan pertemuan desain yang tepat (2 jam) untuk membahas semua pro/kontra, 30 menit yang kami investasikan hari ini tidak cukup untuk mencapai konsensus / menutup pertanyaan terbuka.

    • Kami kemungkinan akan mengundang sebagian besar anggota komunitas yang berinvestasi - @pgolebiowski , siapa lagi?

Berikut adalah catatan mentah utama:

  • Eksperimen - kita harus melakukan eksperimen (dalam CoreFxLabs), merilisnya sebagai paket NuGet pra-rilis dan meminta umpan balik dari konsumen (melalui posting blog). Kami tidak yakin kami dapat menggunakan API tanpa loop umpan balik.

    • Kami melakukan hal serupa untuk ImmutableCollections di masa lalu (siklus rilis pratinjau cepat adalah kunci & membantu dalam membentuk API).

    • Kita dapat menggabungkan eksperimen ini dengan MultiValueDictionary yang sudah ada di CoreFxLab. TODO: Periksa apakah kami memiliki lebih banyak kandidat, kami tidak ingin memposting blog masing-masing secara terpisah.

    • Paket NuGet eksperimental akan di-nuked setelah eksperimen berakhir dan kami akan memindahkan kode sumber ke CoreFX atau CoreFXExtensions (akan diputuskan nanti).

  • Ide: Kembalikan hanya TElement dari Peek & Dequeue , jangan kembalikan TPriority (pengguna dapat menggunakan metode Try* untuk itu).
  • Stabilitas (untuk prioritas yang sama) - item dengan prioritas yang sama harus dikembalikan sesuai urutan dimasukkan ke dalam antrian (perilaku antrian umum)
  • Kami ingin mengaktifkan duplikat (mirip dengan koleksi lain):

    • Singkirkan Update - pengguna dapat Remove dan kemudian Enqueue item kembali dengan prioritas yang berbeda.

    • Remove harus menghapus item pertama yang ditemukan (seperti yang dilakukan List ).

  • Ide: Bisakah kita mengabstraksi antarmuka IQueue ke abstrak Queue dan PriorityQueue ( Peek dan Dequeue mengembalikan hanya TElement membantu di sini)

    • Catatan: Ini mungkin terbukti tidak mungkin, tetapi kita harus menjelajahinya sebelum menetapkan API

Kami memiliki diskusi panjang tentang pemilih - kami masih belum memutuskan (mungkin diperlukan oleh IQueue atas?). Mayoritas tidak menyukai pemilih, tetapi hal-hal dapat berubah dalam pertemuan tinjauan API mendatang.

Pertanyaan terbuka lainnya tidak dibahas.

Proposal terbaru terlihat cukup bagus bagi saya. Pendapat/pertanyaan saya:

  1. Jika Peek dan Dequeue menggunakan parameter out untuk priority , maka mereka juga dapat memiliki kelebihan yang tidak mengembalikan prioritas sama sekali, yang menurut saya akan menyederhanakan penggunaan umum. Padahal prioritas juga bisa diabaikan dengan membuang, yang membuat ini kurang penting.
  2. Saya tidak suka bahwa versi pemilih dibedakan dengan menggunakan konstruktor yang berbeda dan mengaktifkan serangkaian metode yang berbeda. Mungkin harus ada PriorityQueue<T> ? Atau satu set metode statis dan ekstensi yang bekerja dengan PriorityQueue<T, T> ?
  3. Apakah itu didefinisikan dalam urutan apa elemen dikembalikan saat menghitung? Dugaan saya adalah itu tidak terdefinisi, untuk membuatnya efisien.
  4. Haruskah ada cara untuk menghitung hanya item dalam antrian prioritas, mengabaikan prioritas? Atau harus menggunakan sesuatu seperti priorityQueue.Select(t => t.element) dapat diterima?
  5. Jika antrian prioritas secara internal menggunakan Dictionary pada tipe elemen, haruskah ada opsi untuk melewatkan IEqualityComparer<TElement> ?
  6. Melacak elemen menggunakan Dictionary adalah overhead yang tidak perlu jika saya tidak perlu memperbarui prioritas. Haruskah ada opsi untuk menonaktifkannya? Padahal ini bisa ditambahkan nanti, kalau ternyata bermanfaat.

@karelz

Stabilitas (untuk prioritas yang sama) - item dengan prioritas yang sama harus dikembalikan sesuai urutan dimasukkan ke dalam antrian (perilaku antrian umum)

Saya pikir ini berarti bahwa secara internal, prioritas harus seperti sepasang (priority, version) , di mana version bertambah untuk setiap penambahan. Saya pikir itu akan secara signifikan mengasapi penggunaan memori dari antrian prioritas, terutama mengingat bahwa version kemungkinan harus 64 bit. Saya tidak yakin itu akan berharga.

Kami tidak ingin mencegah nilai duplikat (mirip dengan koleksi lain):
Singkirkan Update - pengguna dapat Remove dan kemudian Enqueue item kembali dengan prioritas yang berbeda.

Bergantung pada implementasinya, Update kemungkinan akan jauh lebih efisien daripada Remove diikuti oleh Enqueue . Misalnya, dengan tumpukan biner (saya pikir tumpukan kuaterner memiliki kompleksitas waktu yang sama), Update (dengan nilai unik dan kamus) adalah O(log n ), sedangkan Remove (dengan nilai duplikat dan tidak ada kamus) adalah O( n ).

@pgolebiowski
Sepenuhnya setuju kita harus tetap fokus pada proposal di sini; Saya membuat proposal API asli dan posting asli. Kembali pada bulan Januari @karelz meminta beberapa contoh penggunaan khusus; yang membuka pertanyaan yang jauh lebih luas mengenai kebutuhan spesifik dan penggunaan API PriorityQueue. Saya memiliki implementasi PriorityQueue saya sendiri dan menggunakannya dalam beberapa proyek dan merasa sesuatu yang serupa akan berguna di BCL.

Apa yang saya kurang dalam posting asli adalah survei luas penggunaan antrian yang ada; contoh dunia nyata akan membantu menjaga desain tetap membumi dan memastikannya dapat digunakan secara luas.

@karelz
Lucene.net berisi setidaknya satu fungsi perbandingan antrian prioritas (mirip dengan Perbandingan) yang mengevaluasi beberapa bidang untuk menentukan prioritas. Pola perbandingan "implisit" Lucene tidak akan dipetakan dengan baik ke dalam parameter TPriority eksplisit proposal API saat ini. Beberapa jenis pemetaan akan diperlukan - menggabungkan beberapa bidang menjadi satu atau ke dalam struktur data 'sebanding' yang dapat diteruskan sebagai TPriority.

Usul:
1) Antrian Prioritaskelas berdasarkan proposal asli saya di atas (tercantum di bawah judul Proposal Asli). Berpotensi menambahkan fungsi kenyamanan Perbarui (T), Hapus (T), dan Berisi (T). Sesuai dengan sebagian besar contoh penggunaan yang ada dari komunitas open source.
2) Antrian Prioritasvarian dari dotnet/corefx#1. Dequeue() dan Peek() mengembalikan TElement alih-alih Tuple. Tidak ada fungsi Coba*, Hapus() mengembalikan bool alih-alih melempar agar sesuai dengan Daftarpola. Bertindak sebagai 'tipe kenyamanan' sehingga pengembang yang memiliki nilai prioritas eksplisit tidak perlu membuat tipe pembanding mereka sendiri.

Kedua jenis mendukung elemen duplikat. Perlu menentukan apakah kami menjamin FIFO untuk elemen dengan prioritas yang sama atau tidak; kemungkinan tidak jika kami mendukung pembaruan prioritas.

Bangun kedua varian di CoreFxLabs seperti yang disarankan @karelz dan

Pertanyaan:

public void Enqueue(TElement element, TPriority priority); // Throws if it is duplicate`
public bool TryEnqueue(TElement element, TPriority priority); // Returns false if it is duplicate (does NOT update it)

Mengapa tidak ada duplikat?

public (TElement element, TPriority priority) Peek(); // Throws if empty
public (TElement element, TPriority priority) Dequeue(); // Throws if empty
public void Remove(TElement element); // Throws if element does not exist

Jika metode ini diinginkan, apakah metode ini sebagai metode ekstensi?

public void Enqueue(TElement element);
public void Update(TElement element);

Harus melempar; tetapi mengapa tidak Coba varian, juga metode ekstensi?

Enumerator berbasis struktur?

@benaadams throw on dupes awalnya disarankan (oleh saya) untuk menghindari tipe pegangan yang jelek. Pembaruan terbaru dari tinjauan API mengembalikannya.

Kita tidak boleh menambahkan metode sebagai metode ekstensi ketika kita dapat menambahkannya pada tipe. Metode ekstensi adalah cadangan jika kami tidak dapat menambahkannya pada jenisnya (misalnya antarmuka, atau kami ingin mengirimkannya ke .NET Framework lebih cepat).

Dupes: Jika Anda memiliki beberapa entri yang sama, Anda hanya perlu memperbarui satu? Kemudian mereka menjadi berbeda?

Metode melempar: pada dasarnya adalah pembungkus dan akan menjadi?

public void Remove(TElement element)
{
    if (!TryRemove(element))
    {
        throw new Exception();
    }
}

Meskipun kontrolnya mengalir melalui pengecualian?

@benaadams periksa balasan saya dengan catatan ulasan API: https://github.com/dotnet/corefx/issues/574#issuecomment -308206064

  • Kami ingin mengaktifkan duplikat (mirip dengan koleksi lain):

    • Singkirkan Update - pengguna dapat Remove dan kemudian Enqueue item kembali dengan prioritas yang berbeda.

    • Remove harus menghapus item pertama yang ditemukan (seperti yang dilakukan List ).

Meskipun kontrolnya mengalir melalui pengecualian?

Tidak yakin apa yang kamu maksud. Sepertinya pola yang cukup umum di BCL.

Meskipun kontrolnya mengalir melalui pengecualian?

Tidak yakin apa yang kamu maksud.

Jika Anda hanya memiliki metode TryX

  • jika Anda peduli dengan hasilnya; Anda memeriksanya
  • kalo gak peduli hasilnya gak usah di cek

Tidak terkecuali keterlibatan; tapi nama mendorong Anda untuk membuat keputusan untuk membuang kembali.

Jika Anda memiliki metode melempar pengecualian non-Coba

  • jika Anda peduli dengan hasilnya; Anda juga harus memeriksa terlebih dahulu atau menggunakan coba/tangkap untuk mendeteksi
  • jika Anda tidak peduli dengan hasilnya; Anda juga harus mengosongkan metode catch, atau mendapatkan pengecualian yang tidak terduga

Jadi Anda berada dalam anti-pola menggunakan penanganan pengecualian untuk flow-control . Mereka hanya tampak sedikit berlebihan; selalu bisa menambahkan lemparan jika salah untuk dibuat ulang.

Sepertinya pola yang cukup umum di BCL.

Metode TryX bukanlah pola umum di BCL asli; sampai tipe Concurrent (walaupun berpikir Dictionary.TryGetValue di 2.0 mungkin merupakan contoh pertama?)

misalnya metode TryX baru saja ditambahkan ke Core for Queue dan Stack dan belum menjadi bagian dari Framework.

Stabilitas

  • Stabilitas tidak datang secara gratis. Ini datang dengan overhead dalam kinerja dan memori. Untuk penggunaan biasa, stabilitas dalam antrian prioritas tidak penting.
  • Kami mengekspos IComparer . Jika pelanggan ingin memiliki stabilitas, mereka dapat dengan mudah menambahkannya sendiri. Akan mudah untuk membangun StablePriorityQueue di atas implementasi kita — menggunakan pendekatan biasa dengan menyimpan "usia" elemen dan menggunakannya selama perbandingan.

IQueue

Ada konflik API kemudian. Sekarang mari kita pertimbangkan IQueue<T> sehingga berfungsi dengan Queue<T> :

public interface IQueue<T> :
    IEnumerable,
    IEnumerable<T>,
    IReadOnlyCollection<T>
{
    void Enqueue(T element);

    T Peek();
    T Dequeue();

    bool TryPeek(out T element);
    bool TryDequeue(out T element);
}

Kami memiliki dua opsi untuk antrian prioritas:

  1. Terapkan IQueue<TElement> .
  2. Terapkan IQueue<(TElement, TPriority)> .

Saya akan menulis implikasi dari kedua jalur hanya menggunakan angka (1) dan (2).

antrian

Dalam antrian prioritas, kita perlu mengantrekan elemen dan prioritasnya (solusi saat ini). Di tumpukan biasa, kami menambahkan hanya satu elemen.

  1. Dalam kasus pertama, kami mengekspos Enqueue(TElement) , yang cukup aneh untuk antrian prioritas jika kami memasukkan elemen tanpa prioritas. Kami kemudian dipaksa untuk melakukan ... apa? Asumsikan default(TPriority) ? Nah...
  2. Dalam kasus kedua, kami mengekspos Enqueue((TElement, TPriority) element) . Kami pada dasarnya menambahkan dua argumen dengan menerima tupel yang dibuat dari mereka. Bukan API yang ideal mungkin.

Intip & Dequeue

Anda ingin metode tersebut mengembalikan TElement saja.

  1. Bekerja dengan apa yang ingin Anda capai.
  2. Tidak bekerja dengan apa yang ingin Anda capai — mengembalikan (TElement, TPriority) .

Menghitung

  1. Pelanggan tidak dapat menulis kueri LINQ apa ​​pun yang menggunakan prioritas elemen. Ini salah.
  2. Berhasil.

Kami dapat mengurangi beberapa masalah dengan memberikan PriorityQueue<T> , di mana tidak ada prioritas fisik yang terpisah.

  • Pengguna pada dasarnya dipaksa untuk menulis kelas pembungkus agar kode mereka dapat menggunakan kelas kita. Ini berarti bahwa dalam banyak kasus mereka juga ingin mengimplementasikan IComparable . Yang menambahkan banyak kode boilerplate yang cukup (dan file baru dalam kode sumbernya, kemungkinan besar).
  • Kami jelas dapat membahas kembali ini, jika Anda mau. Saya juga memberikan pendekatan alternatif di bawah ini.

Dua antrian prioritas

Jika kami memberikan dua antrian prioritas, solusi keseluruhan akan lebih kuat dan fleksibel. Ada beberapa catatan yang perlu diperhatikan:

  • Kami mengekspos dua kelas untuk menyediakan fungsionalitas yang sama, tetapi hanya untuk berbagai jenis format input. Tidak terasa terbaik.
  • PriorityQueue<T> berpotensi mengimplementasikan IQueue<T> .
  • PriorityQueue<TElement, TPriority> akan mengekspos API aneh jika mencoba mengimplementasikan antarmuka IQueue .

Yah ... itu akan berhasil . Tidak ideal sekalipun.

Memperbarui dan menghapus

Mengingat asumsi bahwa kami ingin mengizinkan duplikat dan kami tidak ingin menggunakan konsep pegangan:

  • Tidak mungkin untuk menyediakan fungsionalitas memperbarui prioritas elemen sepenuhnya dengan benar (memperbarui node tertentu saja). Analoginya, ini berlaku untuk menghilangkan elemen.
  • Selain itu, kedua operasi harus dilakukan dalam O(n), yang cukup menyedihkan, karena dimungkinkan untuk melakukan keduanya dalam O(log n).

Ini pada dasarnya mengarah pada apa yang ada di Java, di mana pengguna harus menyediakan implementasi mereka sendiri dari seluruh struktur data jika mereka ingin dapat memperbarui prioritas dalam antrian prioritas mereka tanpa mengulangi seluruh koleksi secara naif setiap saat.

Pegangan alternatif

Dengan asumsi bahwa kita tidak ingin menambahkan tipe pegangan baru, kita dapat melakukannya secara berbeda untuk dapat memiliki dukungan penuh untuk memperbarui dan menghapus elemen (ditambah melakukannya secara efisien). Solusinya adalah dengan menambahkan metode alternatif:

void Enqueue(TElement element, TPriority priority, out object handle);

void Update(object handle, TPriority priority);

void Remove(object handle);

Ini akan menjadi metode bagi mereka yang ingin memiliki kontrol yang tepat dalam memperbarui dan menghapus elemen (dan tidak melakukannya dalam O(n), seolah-olah itu adalah List :stuck_out_tongue_winking_eye :).

Tapi lebih baik, mari kita pertimbangkan ...

Pendekatan alternatif

Atau, kita dapat menghapus fungsionalitas ini dari antrean prioritas seluruhnya dan menambahkannya di heap sebagai gantinya. Ini memiliki banyak manfaat:

  • Operasi yang lebih kuat dan efisien tersedia dengan menggunakan Heap .
  • API sederhana dan penggunaan langsung tersedia dengan menggunakan PriorityQueue -- untuk orang yang ingin kodenya berfungsi .
  • Kami tidak mengalami masalah Jawa.
  • Kita bisa membuat PriorityQueue stabil sekarang -- ini bukan trade-off lagi.
  • Solusinya selaras dengan perasaan bahwa orang-orang dengan latar belakang ilmu komputer yang lebih kuat akan menyadari keberadaan Heap . Mereka juga akan menyadari keterbatasan PriorityQueue dan dengan demikian dapat menggunakan Heap sebagai gantinya (misalnya jika mereka ingin memiliki kontrol lebih besar atas pembaruan/penghapusan elemen atau tidak ingin data struktur menjadi stabil dengan mengorbankan kecepatan dan penggunaan memori).
  • Antrian prioritas dan tumpukan dapat dengan mudah mengizinkan duplikat tanpa mengorbankan fungsinya (karena tujuannya berbeda).
  • Akan lebih mudah untuk membuat antarmuka IQueue -- karena kekuatan dan fungsionalitas akan dilempar ke bidang heap. API PriorityQueue dapat difokuskan untuk membuatnya kompatibel dengan Queue melalui abstraksi.
  • Kami tidak perlu menyediakan antarmuka IPriorityQueue lagi (kami lebih fokus menjaga fungsionalitas PriorityQueue dan Queue serupa). Sebagai gantinya, kita dapat menambahkannya di area heap -- dan pada dasarnya memiliki IHeap sana. Ini bagus, karena memungkinkan orang untuk membangun perpustakaan pihak ketiga di atas apa yang ada di perpustakaan standar. Dan rasanya benar -- karena sekali lagi, kami menganggap heaps lebih maju daripada antrian prioritas , jadi ekstensi akan disediakan oleh area itu. Ekstensi seperti itu juga tidak akan terpengaruh oleh pilihan yang akan kita lakukan di PriorityQueue , karena akan terpisah.
  • Kita tidak perlu mempertimbangkan konstruktor IHeap untuk PriorityQueue lagi.
  • Antrian prioritas akan menjadi kelas yang berguna untuk digunakan secara internal di CoreFX. Namun, jika kami menambahkan fitur seperti stabilitas dan menghapus beberapa fungsi lainnya, kemungkinan besar kami akan membutuhkan sesuatu yang lebih kuat daripada solusi itu. Untungnya, akan ada Heap yang lebih kuat dan berkinerja lebih baik di perintah kita!

Pada dasarnya, antrian prioritas akan difokuskan terutama pada kemudahan penggunaan, dengan mengorbankan: kinerja, daya, dan fleksibilitas. Heap akan ditujukan bagi mereka yang menyadari kinerja dan implikasi fungsional dari keputusan kami dalam antrean prioritas. Kami mengurangi banyak masalah dengan trade-off.

Jika kita untuk membuat percobaan, saya pikir itu mungkin sekarang. Mari kita tanyakan kepada masyarakat. Tidak semua orang membaca utas ini -- kami mungkin menghasilkan komentar dan skenario penggunaan berharga lainnya. Bagaimana menurutmu? Secara pribadi, saya akan menyukai solusi seperti itu. Saya pikir itu akan membuat semua orang bahagia.

Catatan penting: jika kita menginginkan pendekatan seperti itu, kita perlu merancang antrian dan tumpukan prioritas bersama-sama . Karena tujuan mereka akan berbeda dan satu solusi akan memberikan apa yang tidak akan diberikan solusi lainnya.

Menyampaikan IQueue kalau begitu

Dengan pendekatan yang disajikan di atas, agar antrian prioritas dapat mengimplementasikan IQueue<T> (sehingga masuk akal), dan dengan asumsi bahwa kita menghapus dukungan untuk pemilih di sana, itu harus memiliki satu tipe generik. Meskipun itu berarti pengguna perlu menyediakan pembungkus jika mereka memiliki (user data, priority) terpisah, solusi seperti itu masih intuitif. Dan yang paling penting, ini memungkinkan semua format input (itulah mengapa itu harus dilakukan dengan cara ini jika kita menjatuhkan pemilih). Tanpa pemilih, Enqueue(TElement, TPriority) tidak akan mengizinkan jenis yang sudah sebanding. Jenis generik tunggal juga penting untuk enumerasi -- sehingga metode ini dapat disertakan dalam IQueue<T> .

Aneka ragam

@svick

Apakah itu didefinisikan dalam urutan apa elemen dikembalikan saat menghitung? Dugaan saya adalah itu tidak terdefinisi, untuk membuatnya efisien.

Untuk memiliki pesanan saat menghitung, pada dasarnya kita perlu mengurutkan koleksi. Itu sebabnya ya, itu harus tidak ditentukan, karena pelanggan dapat menjalankan OrderBy sendiri dan mencapai kinerja yang hampir sama (tetapi beberapa orang tidak membutuhkannya).

Ide: dalam antrian prioritas ini dapat dipesan, di tumpukan tidak berurutan. Rasanya lebih baik. Entah bagaimana antrian prioritas terasa seperti mengulanginya secara berurutan. Sebuah tumpukan pasti tidak. Manfaat lain dari pendekatan di atas.

@pgolebiowski
Itu semua tampak sangat waras. Maukah Anda mengklarifikasi, di bagian Delivering IQueue then , apakah Anda menyarankan T : IComparable<T> untuk "satu tipe generik" sebagai alternatif untuk pemilih yang diberikan kelonggaran elemen duplikat?

Saya akan mendukung memiliki dua jenis yang terpisah.

Saya tidak mengerti alasan di balik penggunaan object sebagai tipe pegangan: apakah ini hanya untuk menghindari membuat tipe baru? Mendefinisikan tipe baru akan memberikan overhead implementasi minimal, sementara membuat API lebih sulit untuk disalahgunakan (apa yang menghentikan saya mencoba meneruskan string ke Remove(object) ?), dan lebih mudah digunakan (apa yang menghentikan saya mencoba untuk meneruskan Elemen itu sendiri ke Remove(object) , dan siapa yang bisa menyalahkan saya karena mencoba?).

Saya mengusulkan tambahan tipe dummy yang diberi nama yang sesuai, untuk menggantikan object dalam metode pegangan, untuk kepentingan antarmuka yang lebih ekspresif.

Jika kemampuan debug mengalahkan overhead memori, tipe pegangan bahkan dapat menyertakan informasi tentang antrian miliknya (harus menjadi Generik, yang akan meningkatkan keamanan tipe antarmuka), memberikan pengecualian yang berguna di sepanjang baris ("Pegangan yang disediakan telah dibuat oleh Antrian yang berbeda"), atau apakah sudah digunakan ("Elemen yang dirujuk oleh Handle telah dihapus").

Jika ide pegangan berjalan, saya akan mengusulkan bahwa jika informasi ini dianggap berguna, maka subset dari pengecualian ini juga akan dilemparkan oleh metode TryRemove dan TryUpdate sesuai, kecuali di mana elemen tidak ada lagi, baik karena telah dihapus atau dihapus oleh pegangan. Ini akan memerlukan tipe pegangan yang tidak terlalu membosankan, generik, dan diberi nama yang sesuai.

@VisualMelon

Maukah Anda mengklarifikasi, di bagian Delivering IQueue then , apakah Anda menyarankan T : IComparable<T> untuk "satu tipe generik" sebagai alternatif untuk pemilih yang diberikan kelonggaran elemen duplikat?

Maaf karena tidak menjelaskan ini.

  • Maksud saya mengirimkan PriorityQueue<T> , tanpa batasan pada T .
  • Itu masih akan menggunakan IComparer<T> .
  • Jika kebetulan T sudah sebanding, maka Comparer<T>.Default akan diasumsikan (dan Anda dapat memanggil konstruktor default dari antrian prioritas tanpa argumen).
  • Pemilih memiliki tujuan yang berbeda — untuk dapat menggunakan semua jenis data pengguna. Ada beberapa konfigurasi:

    1. Data pengguna terpisah dari prioritas (dua contoh fisik).
    2. Data pengguna berisi prioritas.
    3. Data pengguna adalah prioritas.
    4. Kasus langka: prioritas dapat diperoleh melalui beberapa logika lain (berada di objek yang berbeda dari data pengguna).

    Kasus yang jarang terjadi tidak akan mungkin dalam PriorityQueue<T> , tetapi itu tidak terlalu menjadi masalah. Yang penting sekarang kita bisa menangani (1), (2), dan (3). Namun, jika kita memiliki dua tipe generik, kita harus memiliki metode seperti Enqueue(TElement, TPrioriity) . Itu akan membatasi kita untuk (1) saja. (2) akan menyebabkan redundansi. (3) akan sangat jelek. Ada sedikit lebih banyak tentang ini di bagian IQueue > Enqueue di atas (metode Enqueue dan default(TPriority ).

Saya harap sekarang lebih jelas.

BTW, dengan asumsi solusi seperti itu, mendesain API PriorityQueue<T> dan IQueue<T> akan menjadi hal yang sepele. Ambil saja beberapa metode di Queue<T> , masukkan ke dalam IQueue<T> dan buat PriorityQueue<T> mengimplementasikannya. Tadaa! 😄

Saya tidak mengerti alasan di balik penggunaan object sebagai tipe pegangan: apakah ini hanya untuk menghindari membuat tipe baru?

  • Ya, tepatnya. Mengingat asumsi bahwa kami tidak ingin mengekspos tipe seperti itu, itu satu-satunya solusi untuk tetap menggunakan konsep pegangan (dan dengan demikian memiliki lebih banyak kekuatan dan kecepatan). Saya setuju itu tidak ideal - itu lebih merupakan klarifikasi apa yang harus terjadi jika kita tetap berpegang pada pendekatan saat ini dan ingin memiliki lebih banyak kekuatan & efisiensi (yang saya lawan).
  • Mengingat bahwa kita akan menggunakan pendekatan alternatif (antrian dan tumpukan prioritas terpisah), ini lebih mudah. Kita bisa membiarkan PriorityQueue<T> berada di System.Collections.Generic , sedangkan fungsi heap akan berada di System.Collections.Specialized . Di sana, kami akan memiliki peluang lebih tinggi untuk memperkenalkan tipe seperti itu, yang pada akhirnya memiliki deteksi kesalahan waktu kompilasi yang luar biasa.
  • Tetapi sekali lagi -- sangat penting untuk merancang antrean prioritas dan fungsionalitas heap bersama-sama jika kita ingin memiliki pendekatan seperti itu. Karena satu solusi memberikan apa yang tidak disediakan oleh solusi lainnya.

Jika kemampuan debug mengalahkan overhead memori, tipe pegangan bahkan dapat menyertakan informasi tentang antrian miliknya (harus menjadi Generik, yang akan meningkatkan keamanan tipe antarmuka), memberikan pengecualian yang berguna di sepanjang baris ("Pegangan yang disediakan telah dibuat oleh Antrian yang berbeda"), atau apakah sudah digunakan ("Elemen yang dirujuk oleh Handle telah dihapus").

Jika ide pegangan berjalan, saya akan mengusulkan bahwa jika informasi ini dianggap berguna ...

Pasti lebih mungkin kemudian : mengedipkan mata:. Terutama untuk memperluas semua itu oleh komunitas kami di perpustakaan pihak ketiga.

@karelz @safern @ianhays @terrajobst @bendono @svick @alexey-dvortsov @SamuelEnglard @xied75 dan lainnya -- menurut Anda pendekatan seperti itu masuk akal (seperti yang dijelaskan dalam this dan this post)? Apakah itu akan memenuhi semua harapan dan kebutuhan Anda?

Saya pikir ide membuat dua kelas sangat masuk akal dan memecahkan banyak masalah. Hanya meskipun saya punya adalah mungkin masuk akal untuk PriorityQueue<T> menggunakan Heap<T> internal dan hanya "menyembunyikan" fungsionalitas lanjutannya.

Pada dasarnya...

IQueue<T>

public interface IQueue<T> :
    IEnumerable,
    IEnumerable<T>,
    IReadOnlyCollection<T>
{
    int Count { get; }

    void Clear();
    bool Contains(T element);
    bool IsEmpty();

    void Enqueue(T element);

    T Peek();
    T Dequeue();

    bool TryPeek(out T element);
    bool TryDequeue(out T element);
}

Catatan

  • Dalam System.Collections.Generic .
  • Ide: kita juga bisa menambahkan metode untuk menghapus elemen di sini ( Remove dan TryRemove ). Hal ini tidak di Queue<T> sekalipun. Tapi itu tidak perlu.

PriorityQueue<T>

public class PriorityQueue<T> : IQueue<T>
{
    public PriorityQueue();
    public PriorityQueue(IComparer<T> comparer);
    public PriorityQueue(IEnumerable<T> collection);
    public PriorityQueue(IEnumerable<T> collection, IComparer<T> comparer);

    public IComparer<T> Comparer { get; }
    public int Count { get; }

    public bool IsEmpty();
    public void Clear();
    public bool Contains(T element);

    public void Enqueue(T element);

    public T Peek();
    public T Dequeue();

    public bool TryPeek(out T element);
    public bool TryDequeue(out T element);

    public void Remove(T element);
    public bool TryRemove(T element);

    public IEnumerator<T> GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator();
}

Catatan

  • Dalam System.Collections.Generic .
  • Jika IComparer<T> tidak terkirim, Comparer<T>.Default dipanggil.
  • Ini stabil.
  • Memungkinkan duplikat.
  • Remove dan TryRemove hanya menghapus kemunculan pertama (jika mereka menemukannya).
  • Pencacahan dipesan.

tumpukan

Tidak menulis semuanya di sini sekarang, tetapi:

  • Dalam System.Collections.Specialized .
  • Itu tidak akan stabil (dan dengan demikian lebih cepat dan lebih efisien dalam hal memori).
  • Menangani dukungan, memperbarui dan menghapus dengan benar

    • dilakukan dengan cepat di O(log n) alih-alih O(n)

    • dilakukan dengan benar

  • Pencacahan tidak berurutan (lebih cepat).
  • Memungkinkan duplikat.

Setuju dengan IQueueusul. Saya akan mengajukan hal yang sama hari ini, rasanya seperti tingkat abstraksi yang tepat untuk dimiliki di tingkat antarmuka. "Antarmuka ke struktur data yang dapat menambahkan item ke dalamnya dan memerintahkan item dihapus darinya."

  • Spesifikasi untuk IQueuekelihatan bagus.

  • Pertimbangkan untuk menambahkan "int Count { get; }" ke IQueuesehingga jelas Hitungan diinginkan terlepas dari apakah kita mewarisi dari IReadOnlyCollection.

  • Di pagar tentang TryPeek, TryDequeue di IQueuemengingat mereka tidak dalam Antrian, tetapi helper itu mungkin harus ditambahkan ke Queue dan Stack juga.

  • IsEmpty tampak seperti outlier; tidak banyak jenis koleksi lain di BCL yang memilikinya. Untuk menambahkannya ke antarmuka, kita harus menganggapnya akan ditambahkan ke Antrian, dan sepertinya agak aneh menambahkannya ke Antriandan tidak ada lagi. Merekomendasikan kami menjatuhkannya dari antarmuka, dan mungkin juga kelas.

  • Jatuhkan TryRemove dan ubah Hapus menjadi "bool Hapus". Menjaga keselarasan dengan kelas koleksi lain akan menjadi penting di sini - pengembang akan memiliki banyak otot-memori yang mengatakan "hapus () dalam koleksi tidak membuang". Ini adalah area yang tidak akan diuji dengan baik oleh banyak pengembang dan akan menyebabkan banyak kejutan jika perilaku normal diubah.

Dari kutipan Anda sebelumnya @pgolebiowski

  1. Data pengguna terpisah dari prioritas (dua contoh fisik).
  2. Data pengguna berisi prioritas.
  3. Data pengguna adalah prioritas.
  4. Kasus langka: prioritas dapat diperoleh melalui beberapa logika lain (berada di objek yang berbeda dari data pengguna).

Juga merekomendasikan mempertimbangkan 5. Data pengguna berisi prioritas di beberapa bidang (seperti yang kita lihat di Lucene.net)

Di pagar tentang TryPeek, TryDequeue di IQueue mengingat mereka tidak ada di Queue

Mereka adalah System/Collections/Generic/Queue.cs#L253-L295

Di sisi lain Antrian tidak memiliki
c# public void Remove(T element); public bool TryRemove(T element);

Pertimbangkan untuk menambahkan "int Count { get; }" ke IQueue sehingga jelas Count diinginkan terlepas dari apakah kita mewarisi dari IReadOnlyCollection atau tidak.

OKE. Akan memodifikasinya.

IsEmpty tampak seperti outlier; tidak banyak jenis koleksi lain di BCL yang memilikinya.

Yang ini ditambahkan oleh @karelz , saya baru saja menyalinnya. Saya menyukainya, mungkin dipertimbangkan dalam ulasan API :)

Jatuhkan TryRemove dan ubah Hapus menjadi "bool Hapus".

Saya pikir Remove dan TryRemove konsisten dengan metode lain seperti itu ( Peek dan TryPeek atau Dequeue dan TryDequeue ).

  1. Data pengguna berisi prioritas di beberapa bidang

Ini adalah poin yang valid, tetapi ini sebenarnya juga dapat ditangani dengan pemilih (ini fungsi apa pun) -- tapi itu untuk heap.

IsEmpty tampak seperti outlier; tidak banyak jenis koleksi lain di BCL yang memilikinya.

FWIW, bool IsEmpty { get; } adalah sesuatu yang saya harap kami tambahkan ke IProducerConsumerCollection<T> , dan saya menyesal karena tidak ada di sana beberapa kali sejak itu. Tanpa itu, pembungkus sering kali perlu melakukan yang setara dengan Count == 0 , yang untuk beberapa koleksi secara signifikan kurang efisien untuk diterapkan, khususnya pada sebagian besar koleksi bersamaan.

@pgolebiowski Bagaimana perasaan Anda tentang ide membuat repositori github untuk meng-host kontrak API saat ini dan satu atau dua file .md yang berisi alasan desain. Setelah stabil, dapat digunakan sebagai tempat untuk membangun implementasi awal sebelum melakukan PR hingga CoreFxLabs saat siap?

@svick

Jika Peek dan Dequeue menggunakan parameter out untuk prioritas, maka mereka juga dapat memiliki kelebihan yang tidak mengembalikan prioritas sama sekali, yang menurut saya akan menyederhanakan penggunaan umum

Sepakat. Mari kita tambahkan.

Haruskah ada cara untuk menghitung hanya item dalam antrian prioritas, mengabaikan prioritas?

Pertanyaan bagus. Apa yang akan Anda usulkan? IEnumerable<TElement> Elements { get; } ?

Stabilitas - Saya pikir ini berarti bahwa secara internal, prioritas harus berupa sepasang (priority, version)

Saya pikir kita dapat menghindarinya dengan menganggap Update sebagai logis Remove + Enqueue . Kami akan selalu menambahkan item di akhir item dengan prioritas yang sama (pada dasarnya pertimbangkan hasil pembanding 0 sebagai -1). IMO yang seharusnya berfungsi.


@benaadams

Metode TryX bukanlah pola umum di BCL asli; sampai tipe Concurrent (walaupun berpikir Dictionary.TryGetValue di 2.0 mungkin merupakan contoh pertama?)
misalnya metode TryX baru saja ditambahkan ke Core for Queue dan Stack dan belum menjadi bagian dari Framework.

Saya akui saya masih baru di BCL. Dari pertemuan tinjauan API dan dari fakta kami menambahkan banyak metode Try* baru-baru ini, saya mendapat kesan bahwa ini adalah pola umum untuk waktu yang lebih lama .
Either way, itu adalah pola umum sekarang dan kita tidak perlu takut untuk menggunakannya. Belum adanya pola di .NET Framework seharusnya tidak menghentikan kita untuk berinovasi di .NET Core -- itulah tujuan utamanya, untuk berinovasi lebih cepat.


@pgolebiowski

Atau, kami dapat menghapus fungsionalitas ini dari antrean prioritas sepenuhnya dan menambahkannya di heap sebagai gantinya. Ini memiliki banyak manfaat

Hmm, ada yang memberitahuku bahwa kamu mungkin punya agenda di sini
Sekarang serius, itu sebenarnya arah yang baik yang kami tuju sepanjang waktu - PriorityQueue tidak pernah dimaksudkan untuk menjadi alasan untuk TIDAK melakukan Heap . Jika kita semua setuju dengan fakta bahwa Heap mungkin tidak berhasil masuk ke CoreFX dan mungkin tetap "hanya" di repo CoreFXExtensions sebagai struktur data lanjutan bersama PowerCollections, saya setuju dengan itu,

Catatan penting: jika kita menginginkan pendekatan seperti itu, kita perlu merancang antrian prioritas dan tumpukan bersama-sama. Karena tujuan mereka akan berbeda dan satu solusi akan memberikan apa yang tidak akan diberikan solusi lainnya.

Saya tidak mengerti mengapa kita perlu melakukannya bersama-sama. IMO kita bisa fokus pada PriorityQueue dan menambahkan "benar" Heap secara paralel/nanti. Saya tidak keberatan jika seseorang melakukannya bersama-sama, tetapi saya tidak melihat alasan kuat mengapa keberadaan PriorityQueue harus memengaruhi desain "performa tinggi" lanjutan Heap keluarga.

IQueue

Terima kasih atas tulisannya. Mengingat poin Anda, saya tidak berpikir kita harus berusaha keras untuk menambahkan IQueue . IMO itu bagus untuk dimiliki. Jika kita memiliki pemilih, maka itu akan menjadi alami. Namun, saya tidak suka pendekatan pemilih karena membawa keanehan dan kerumitan dalam menggambarkan ketika pemilih dipanggil oleh PriorityQueue (hanya pada Enqueue dan Update .

Gagang alternatif (objek)

Itu sebenarnya bukan ide yang buruk (walaupun agak jelek) untuk memiliki IMO yang berlebihan. Kami harus dapat mendeteksi bahwa pegangannya salah PriorityQueue , yaitu O(log n).
Saya merasa bahwa peninjau API akan menolaknya, tetapi IMO patut dicoba untuk bereksperimen dengannya ...

Stabilitas

Saya tidak berpikir stabilitas datang dengan overhead perf/memori (dengan asumsi kita sudah menjatuhkan Update atau bahwa kita memperlakukan Update sebagai logis Remove + Enqueue , jadi kami pada dasarnya mengatur ulang usia elemen). Perlakukan saja hasil pembanding 0 sebagai -1 dan semuanya baik-baik saja ... Atau apakah saya melewatkan sesuatu?

Pemilih dan IQueue<T>

Mungkin ide yang baik untuk memiliki 2 proposal (dan kami berpotensi mengambil keduanya):

  • PriorityQueue<T,U> tanpa pemilih dan tanpa IQueue (yang akan merepotkan)
  • PriorityQueue<T> dengan pemilih dan IQueue

Cukup banyak orang yang mengisyaratkan hal itu di atas.

Re: Proposal terbaru dari @pgolebiowski - https://github.com/dotnet/corefx/issues/574#issuecomment -308427321

IQueue<T> - kita dapat menambahkan Remove dan TryRemove , tetapi Queue<T> tidak memilikinya.

Apakah masuk akal untuk ditambahkan ke Queue<T> ? ( @ebickle setuju) Jika ya, kita harus membundel tambahannya juga.
Mari tambahkan di antarmuka dan sebut mereka dipertanyakan / perlu tambahan Queue<T> juga.
Sama untuk IsEmpty -- apapun yang membutuhkan tambahan Queue<T> dan Stack<T> , mari kita tandai di antarmuka (akan lebih mudah untuk meninjau & mencerna).
@pgolebiowski dapatkah Anda menambahkan komentar ke IQueue<T> dengan daftar kelas yang menurut kami akan diimplementasikan?

PriorityQueue<T>

Mari kita tambahkan struct enumerator (saya pikir ini adalah pola BCL yang umum belakangan ini ). Itu dipanggil beberapa kali di utas, lalu dijatuhkan/dilupakan.

tumpukan

Pilihan namespace: Jangan buang waktu untuk keputusan namespace. Jika Heap berakhir di CoreFxExtensions, kami belum tahu ruang nama seperti apa yang akan kami izinkan di sana. Mungkin Community.* atau semacamnya. Itu tergantung pada hasil diskusi tujuan/mode operasi CoreFxExtensions.
Catatan: Satu ide untuk repo CoreFxExtensions adalah memberikan izin Menulis kepada anggota komunitas yang telah terbukti, dan membiarkannya didorong terutama oleh komunitas dengan tim .NET yang memberikan saran saja (termasuk keahlian tinjauan API) & tim .NET/MS menjadi penengah bila diperlukan. Jika itu tempat kita mendarat, kemungkinan besar kita tidak menginginkannya di System.* atau Microsoft.* namespace. (Penafian: pemikiran awal, tolong jangan langsung menyimpulkan, ada ide tata kelola alternatif lain dalam penerbangan)

Jatuhkan TryRemove dan ubah Remove menjadi bool Remove . Menjaga keselarasan dengan kelas koleksi lain akan menjadi penting di sini - pengembang akan memiliki banyak otot-memori yang mengatakan "hapus () dalam koleksi tidak membuang". Ini adalah area yang tidak akan diuji dengan baik oleh banyak pengembang dan akan menyebabkan banyak kejutan jika perilaku normal diubah.

👍 Setidaknya kita harus mempertimbangkan untuk menyelaraskannya dengan koleksi lain. Dapatkah seseorang memindai koleksi lain apa pola Remove ?

@ebickle Bagaimana perasaan Anda tentang ide membuat repositori github untuk meng-host kontrak API saat ini dan satu atau dua file .md yang berisi alasan desain.

Mari kita host langsung di CoreFxLabs . 👍

@karelz

Haruskah ada cara untuk menghitung hanya item dalam antrian prioritas, mengabaikan prioritas?

Pertanyaan bagus. Apa yang akan Anda usulkan? IEnumerable<TElement> Elements { get; } ?

Ya, baik itu atau properti yang mengembalikan semacam ElementsCollection mungkin adalah pilihan terbaik. Terutama karena mirip dengan Dictionary<K, V>.Values .

Kami akan selalu menambahkan item di akhir item dengan prioritas yang sama (pada dasarnya pertimbangkan hasil pembanding 0 sebagai -1). IMO yang seharusnya berfungsi.

Itu bukan cara kerja heap, tidak ada "akhir dari item dengan prioritas yang sama". Item dengan prioritas yang sama dapat tersebar di seluruh heap (kecuali jika mendekati nilai minimum saat ini).

Atau, dengan kata lain, untuk menjaga stabilitas, Anda harus mempertimbangkan hasil pembanding dari 0 menjadi -1 tidak hanya saat memasukkan, tetapi juga ketika item dipindahkan di tumpukan sesudahnya. Dan saya pikir Anda harus menyimpan sesuatu seperti version bersama dengan priority untuk melakukannya dengan benar.

IQueue- kita bisa menambahkan Hapus dan Coba Hapus, tapi Antriantidak memilikinya.

Apakah masuk akal untuk ditambahkan ke Antrian?? ( @ebickle setuju) Jika ya, kita harus membundel tambahannya juga.

Saya tidak berpikir Remove harus ditambahkan ke IQueue<T> ; dan saya bahkan menyarankan Contains cerdik; itu membatasi kegunaan antarmuka dan jenis Antrian apa yang dapat digunakan kecuali Anda mulai juga melempar NotSupportedExceptions. yaitu apa ruang lingkupnya?

Apakah hanya untuk antrean vanilla, antrean pesan, antrean terdistribusi, antrean ServiceBus, Antrean Penyimpanan Azure, Antrean Handal ServiceFabric, dll..

Itu bukan cara kerja heap, tidak ada "akhir dari item dengan prioritas yang sama". Item dengan prioritas yang sama dapat tersebar di seluruh heap (kecuali jika mendekati nilai minimum saat ini).

Poin yang adil. Saya memikirkannya sebagai pohon pencarian biner. Harus menyikat dasar-dasar ds saya, saya kira :)
Baik, kami menerapkannya dengan ds yang berbeda (array, pohon pencarian biner (atau apa pun nama resminya) - @stephentoub punya ide/saran), atau kami menerima pesanan acak. Saya tidak berpikir mempertahankan bidang version atau age sepadan dengan jaminan stabilitas.

@ebickle Bagaimana perasaan Anda tentang ide membuat repositori github untuk meng-host kontrak API saat ini dan satu atau dua file .md yang berisi alasan desain.

@karelz Mari kita host langsung di CoreFxLabs.

Silakan lihat dokumen ini .

Mungkin ide yang baik untuk memiliki 2 proposal (dan kami berpotensi mengambil keduanya):

  • Antrian Prioritastanpa pemilih dan tanpa IQueue (yang akan merepotkan)
  • Antrian Prioritasdengan pemilih dan IQueue

Saya menyingkirkan pemilih sama sekali. Hal ini diperlukan hanya jika kita ingin memiliki satu kesatuan PriorityQueue<T, U> . Jika kita memiliki dua kelas -- yah, pembanding sudah cukup.


Tolong beri tahu saya jika Anda menemukan sesuatu yang layak ditambahkan/diubah/dihapus.

@pgolebiowski

Dokumen terlihat bagus. Mulai terasa seperti solusi solid yang saya harapkan di perpustakaan inti .NET :)

  • Harus menambahkan kelas Enumerator bersarang. Tidak yakin apakah situasinya telah berubah, tetapi bertahun-tahun yang lalu memiliki struktur anak menghindari alokasi pengumpul sampah yang disebabkan oleh meninju hasil GetEnumerator(). Lihat https://github.com/dotnet/coreclr/issues/1579 misalnya.
    public class PriorityQueue<T> // ... { public struct Enumerator : IEnumerator<T> { public T Current { get; } object IEnumerator.Current { get; } public bool MoveNext(); public void Reset(); public void Dispose(); } }

  • Antrian Prioritasharus memiliki struct Enumerator bersarang juga.

  • Saya masih memilih untuk memiliki "public bool Remove(T element)" tanpa TryRemove karena ini adalah pola lama dan mengubahnya membuat kesalahan pengembang sangat mungkin terjadi. Kami dapat membiarkan kru Peninjau API ikut campur, tetapi ini adalah pertanyaan terbuka di benak saya.

  • Apakah ada nilai dalam menentukan kapasitas awal dalam konstruktor atau memiliki fungsi TrimExcess, atau apakah itu optimasi mikro saat ini - terutama mengingat IEnumerableparameter konstruktor?

@pgolebiowski terima kasih telah memasukkannya ke dalam dokumen. Bisakah Anda mengirimkan dokumen Anda sebagai PR terhadap cabang master CoreFxLab? Tag @ianhays @safern mereka akan dapat menggabungkan PR.
Kami kemudian dapat menggunakan masalah CoreFxLab untuk diskusi lebih lanjut tentang poin desain tertentu - seharusnya lebih mudah daripada masalah besar ini (saya baru saja memulai email untuk membuat area-PriorityQueue di sana).

Tolong taruh tautan ke permintaan tarik CoreFxLab di sini?

  • @ebickle @jnm2 -- ditambahkan
  • @karelz @SamuelEnglard -- direferensikan

Jika API disetujui dalam bentuknya saat ini (dua kelas), saya akan membuat PR lain ke CoreFXLab dengan implementasi untuk keduanya menggunakan tumpukan kuarter ( lihat implementasi ). PriorityQueue<T> hanya akan menggunakan satu larik di bawahnya, sedangkan PriorityQueue<TElement, TPriority> akan menggunakan dua -- dan menyusun ulang elemennya bersama-sama. Ini dapat menghemat alokasi tambahan dan seefisien mungkin. Saya akan menambahkan bahwa setelah ada lampu hijau.

Apakah ada rencana untuk membuat versi thread-safe seperti ConcurrentQueue?

Saya akan :heart: untuk melihat versi bersamaan dari ini, mengimplementasikan IProducerConsumerCollection<T> , sehingga dapat digunakan dengan BlockingCollection<T> dll.

@aobatact @khellang Kedengarannya seperti utas yang sama sekali berbeda:

Saya setuju itu sangat berharga :wink:!

public bool IsEmpty();

Mengapa ini sebuah metode? Setiap koleksi lain pada kerangka kerja yang memiliki IsEmpty memilikinya sebagai properti.

Saya memperbarui proposal di posting teratas dengan IsEmpty { get; } .
Saya juga telah menambahkan tautan ke dokumen proposal terbaru di corefxlab repo.

Halo semua,
Saya pikir ada banyak suara yang berdebat akan lebih baik untuk mendukung pembaruan jika memungkinkan. Saya pikir tidak ada yang akhirnya meragukan itu adalah fitur yang berguna untuk pencarian grafik.

Tapi ada beberapa keberatan yang diajukan di sana-sini. Jadi saya dapat memeriksa pemahaman saya, sepertinya keberatan utamanya adalah:
-tidak jelas bagaimana pembaruan harus bekerja ketika ada elemen duplikat.
-ada beberapa argumen tentang apakah elemen duplikat bahkan diinginkan untuk didukung dalam antrian prioritas
-ada beberapa kekhawatiran bahwa mungkin tidak efisien untuk memiliki struktur data pencarian tambahan untuk menemukan elemen dalam struktur data pendukung hanya untuk memperbarui prioritasnya. Khusus untuk skenario di mana pembaruan tidak pernah dilakukan! Dan/atau mempengaruhi jaminan kinerja kasus terburuk...

Ada yang saya lewatkan?

OK saya kira satu lagi - telah diklaim bahwa memperbarui/menghapus secara semantik berarti 'updateFirst' atau 'removeFirst' mungkin aneh. :)

Di versi .Net Core mana kita bisa mulai menggunakan PriorityQueue?

@memoryfraction .NET Core sendiri belum memiliki PriorityQueue (ada proposal - tetapi kami belum punya waktu untuk menghabiskannya baru-baru ini). Tidak ada alasan mengapa itu harus dalam distribusi Microsoft .NET Core. Siapa pun di komunitas dapat memasangnya di NuGet. Mungkin seseorang tentang masalah ini dapat menyarankannya.

@memoryfraction .NET Core sendiri belum memiliki PriorityQueue (ada proposal - tetapi kami belum punya waktu untuk menghabiskannya baru-baru ini). Tidak ada alasan mengapa itu harus dalam distribusi Microsoft .NET Core. Siapa pun di komunitas dapat memasangnya di NuGet. Mungkin seseorang tentang masalah ini dapat menyarankannya.

Terima kasih atas balasan dan saran Anda.
Ketika saya menggunakan C# untuk berlatih leetcode, dan perlu menggunakan SortedSet untuk menangani set dengan elemen duplikat. Saya harus membungkus elemen dengan ID unik untuk menyelesaikan masalah.
Jadi, saya lebih suka memiliki Antrian Prioritas di .NET Core di masa depan karena akan lebih nyaman.

Bagaimana keadaan saat ini dari masalah ini? Saya baru saja menemukan diri saya baru-baru ini membutuhkan PriorityQueue

Proposal ini tidak mengaktifkan inisialisasi koleksi secara default. PriorityQueue<T> tidak memiliki metode Add . Saya pikir inisialisasi koleksi adalah fitur bahasa yang cukup besar untuk menjamin penambahan metode duplikat.

Jika seseorang memiliki tumpukan biner cepat yang ingin mereka bagikan, saya ingin membandingkannya dengan apa yang saya tulis.
Saya sepenuhnya menerapkan API seperti yang diusulkan. Namun, alih-alih tumpukan, ia menggunakan array yang diurutkan sederhana. Saya menyimpan item dengan nilai terendah di bagian akhir sehingga diurutkan dalam kebalikan dari urutan biasa. Ketika jumlah item kurang dari 32, saya menggunakan pencarian linier sederhana untuk menemukan tempat untuk memasukkan nilai baru. Setelah itu, saya menggunakan pencarian biner. Menggunakan data acak, saya menemukan pendekatan ini lebih cepat daripada tumpukan biner dalam kasus sederhana mengisi antrian dan kemudian mengurasnya.
Jika saya punya waktu, saya akan meletakkannya di repo git publik sehingga orang dapat mengkritiknya dan memperbarui komentar ini dengan lokasinya.

@SunnyWar Saat ini saya menggunakan: https://github.com/BlueRaja/High-Speed-Priority-Queue-for-C-Sharp
yang memiliki kinerja yang baik. Saya pikir masuk akal untuk menguji implementasi Anda terhadap GenericPriorityQueue di sana

Tolong abaikan postingan saya sebelumnya. Saya menemukan kesalahan dalam tes. Setelah diperbaiki, tumpukan biner melakukan yang terbaik.

@karelz Di mana proposal ini saat ini di ICollection<T> ? Saya tidak mengerti mengapa itu tidak akan didukung, meskipun koleksinya jelas tidak dimaksudkan untuk dibaca saja?

@karelz kembali pertanyaan terbuka dari urutan dequeue: Saya berpendapat mendukung nilai prioritas terendah dihapus terlebih dahulu. Lebih mudah untuk algoritma jalur terpendek dan juga alami untuk implementasi antrian timer. Dan itu adalah dua skenario utama yang saya pikir akan digunakan. Saya tidak benar-benar menyadari apa argumen lawannya, kecuali jika itu adalah argumen linguistik yang terkait dengan prioritas 'tinggi', dan nilai numerik 'tinggi'.

@karelz kembali urutan pencacahan ... bagaimana dengan memiliki metode Sort() pada tipe yang akan menempatkan tumpukan data internal koleksi ke dalam urutan yang diurutkan di tempat (di O (n log n)). Dan memanggil Enumerate() setelah Sort() menghitung koleksi dalam O(n). Dan jika Sort() TIDAK dipanggil, ia mengembalikan elemen dalam urutan yang tidak diurutkan dalam O(n)?

Triase: pindah ke masa depan karena tampaknya tidak ada konsensus tentang apakah kita harus menerapkan ini.

Itu sangat mengecewakan untuk didengar. Saya masih harus menggunakan solusi pihak ketiga untuk ini.

Apa alasan utama Anda tidak suka menggunakan solusi pihak ketiga?

struktur data heap adalah KEHARUSAN untuk melakukan leetcode
lebih banyak leetcode, lebih banyak wawancara kode c# yang berarti lebih banyak pengembang c#.
lebih banyak pengembang berarti ekosistem yang lebih baik.
ekosistem yang lebih baik berarti jika kita masih bisa memprogram di c# besok.

Singkatnya: ini bukan hanya fitur, tetapi juga masa depan. itulah mengapa masalah diberi label 'masa depan'.

Apa alasan utama Anda tidak suka menggunakan solusi pihak ketiga?

Karena ketika tidak ada standar, setiap orang menciptakan standar mereka sendiri, masing-masing dengan keunikannya sendiri.

Ada banyak waktu ketika saya menginginkan System.Collections.Generic.PriorityQueue<T> karena itu relevan dengan sesuatu yang sedang saya kerjakan. Proposal ini sudah ada selama 5 tahun sekarang. Mengapa masih belum terjadi?

Mengapa masih belum terjadi?

Kelaparan prioritas, tidak ada permainan kata-kata yang dimaksudkan. Merancang koleksi itu berhasil karena jika kita menempatkannya di BCL kita harus memikirkan bagaimana mereka bertukar, antarmuka apa yang mereka implementasikan, apa semantiknya, dll. Semua ini bukan ilmu roket, tetapi butuh beberapa saat. Selain itu, Anda perlu satu set kasus pengguna dan pelanggan konkret untuk menilai desain, yang semakin sulit karena koleksi semakin terspesialisasi. Selama ini selalu ada pekerjaan lain yang dianggap lebih penting dari itu.

Mari kita lihat contoh terbaru: koleksi yang tidak dapat diubah. Mereka dirancang oleh seseorang yang tidak bekerja di tim BCL untuk kasus penggunaan di VS yang seputar kekekalan. Kami bekerja dengannya untuk mendapatkan API "terifikasi BCL". Dan ketika Roslyn online, kami mengganti salinan mereka dengan milik kami di sebanyak mungkin tempat dan kami banyak mengubah desain (dan implementasi) berdasarkan umpan balik mereka. Tanpa skenario "pahlawan" ini sulit.

Karena ketika tidak ada standar, setiap orang menciptakan standar mereka sendiri, masing-masing dengan keunikannya sendiri.

@masonwheeler apakah ini yang Anda lihat untuk PriorityQueue<T> ? Bahwa ada beberapa opsi pihak ke-3 yang tidak dapat dipertukarkan dan tidak ada "perpustakaan terbaik untuk sebagian besar" yang diterima dengan jelas? (Saya belum melakukan penelitian jadi saya tidak tahu jawabannya)

@eiriktsarpalis Bagaimana tidak ada konsensus tentang apakah akan diterapkan?

@terrajobst Apakah Anda benar-benar membutuhkan skenario pahlawan ketika ini adalah pola terkenal dengan aplikasi terkenal? Sangat sedikit inovasi yang dibutuhkan di sini. Bahkan sudah ada spesifikasi antarmuka yang cukup berkembang. Menurut pendapat saya alasan sebenarnya utilitas ini tidak ada bukanlah karena terlalu sulit, juga tidak ada permintaan atau kasus penggunaan untuk itu. Alasan sebenarnya adalah bahwa untuk setiap proyek perangkat lunak nyata, lebih mudah untuk membangunnya sendiri daripada mencoba melakukan kampanye politik agar ini dimasukkan ke dalam perpustakaan kerangka kerja.

Apa alasan utama Anda tidak suka menggunakan solusi pihak ketiga?

@terrajobst
Saya pribadi membuat pengalaman bahwa solusi pihak ke-3 tidak selalu berfungsi seperti yang diharapkan/tidak menggunakan fitur bahasa saat ini. Dengan versi standar saya (sebagai pengguna) bisa cukup yakin bahwa kinerja terbaik yang bisa Anda dapatkan.

@danmosemsft

@masonwheeler apakah ini yang Anda lihat untuk PriorityQueue<T> ? Bahwa ada beberapa opsi pihak ke-3 yang tidak dapat dipertukarkan dan tidak ada "perpustakaan terbaik untuk sebagian besar" yang diterima dengan jelas? (Saya belum melakukan penelitian jadi saya tidak tahu jawabannya)

Ya. Hanya google "antrian prioritas C#"; halaman pertama penuh dengan:

  1. Implementasi antrian prioritas di Github dan situs hosting lainnya
  2. Orang-orang bertanya mengapa di dunia tidak ada antrian prioritas resmi di Collections.Generic
  3. Tutorial tentang cara membuat implementasi antrian prioritas Anda sendiri

@terrajobst Apakah Anda benar-benar membutuhkan skenario pahlawan ketika ini adalah pola terkenal dengan aplikasi terkenal? Sangat sedikit inovasi yang dibutuhkan di sini.

Dalam pengalaman saya ya. Setan ada dalam detailnya dan setelah kami mengirimkan API, kami tidak dapat benar-benar membuat perubahan yang merusak. Dan ada banyak pilihan implementasi yang terlihat oleh pengguna. Kami dapat mempratinjau API sebagai OOB untuk sementara waktu, tetapi kami juga telah mempelajari bahwa meskipun kami pasti dapat mengumpulkan umpan balik, kurangnya skenario pahlawan berarti Anda tidak memiliki pemutus ikatan, yang sering mengakibatkan situasi di mana ketika pahlawan skenario tiba, struktur data tidak memenuhi persyaratannya.

@masonwheeler Saya berasumsi tautan Anda adalah ini Sekarang ada di kepala saya.

Meskipun seperti yang dikatakan @terrajobst , masalah utama kami di sini adalah sumber daya/perhatian (dan Anda dapat menyalahkan kami untuk itu jika Anda mau), kami juga ingin memperkuat ekosistem non-Microsoft sehingga kemungkinan besar Anda dapat menemukan perpustakaan yang kuat untuk apa pun skenario.

[diedit untuk kejelasan]

@danmosemsft Nah, jika saya akan memilih lagu itu, saya akan membuat versi Shrek 2.

Kandidat aplikasi pahlawan #1: bagaimana kalau menggunakannya di TimerQueue.Portable?

Sudah dipertimbangkan, dibuat prototipe, dan dibuang. Itu membuat kasus yang sangat umum dari timer yang dibuat dan dihancurkan dengan cepat (misalnya untuk timeout) kurang efisien.

@ stephentoub Saya berasumsi maksud Anda itu kurang efisien untuk beberapa skenario di mana ada sejumlah kecil penghitung waktu. Tapi bagaimana skalanya?

Saya berasumsi maksud Anda itu kurang efisien untuk beberapa skenario di mana ada sejumlah kecil pengatur waktu. Tapi bagaimana skalanya?

Tidak, maksud saya kasus umum adalah Anda memiliki banyak pengatur waktu setiap saat, tetapi sangat sedikit yang menyala. Inilah yang terjadi ketika timer digunakan untuk timeout. Dan yang penting adalah kecepatan di mana Anda dapat menambahkan dan menghapus dari struktur data... Anda ingin itu menjadi 0(1) dan dengan overhead yang sangat rendah. Jika itu menjadi O(log N), itu masalah.

Memiliki antrian prioritas pasti akan membuat C# lebih ramah wawancara.

Memiliki antrian prioritas pasti akan membuat C# lebih ramah wawancara.

Ya itu benar.
Saya mencarinya untuk alasan yang sama.

@stephentoub Untuk batas waktu yang tidak pernah benar-benar terjadi, itu sangat masuk akal bagi saya. Tapi saya bertanya-tanya apa yang terjadi pada sistem ketika tiba-tiba banyak timeout mulai terjadi, karena tiba-tiba ada packet loss atau server yang tidak responsif atau apa? Juga apakah penghitung waktu berulang ala System.Timer menggunakan implementasi yang sama? Di sana, batas waktu yang berakhir akan menjadi 'jalur bahagia'.

Tapi saya bertanya-tanya apa yang terjadi pada sistem ketika tiba-tiba banyak timeout mulai terjadi

Cobalah. :)

Kami melihat banyak beban kerja nyata yang menderita dengan implementasi sebelumnya. Dalam setiap kasus yang kami lihat, yang baru (yang tidak menggunakan antrian prioritas tetapi hanya memiliki pemisahan sederhana antara pengatur waktu segera dan tidak segera) telah memperbaiki masalah, dan kami belum melihat yang baru dengan dia.

Juga apakah penghitung waktu berulang ala System.Timer menggunakan implementasi yang sama?

Ya. Tapi mereka umumnya tersebar, seringkali cukup merata, dalam aplikasi dunia nyata.

5 tahun kemudian, masih belum ada PriorityQueue.

5 tahun kemudian, masih belum ada PriorityQueue.

Tidak harus menjadi prioritas yang cukup tinggi...

@stephentoub tapi mungkin sebenarnya @eiriktsarpalis apakah kita benar-benar memiliki daya tarik untuk ini sekarang? Jika kami memiliki desain API yang sudah selesai, saya akan bersedia untuk mengerjakannya

Saya belum melihat deklarasi ini diselesaikan dan saya tidak yakin apakah akan ada desain api akhir tanpa aplikasi pembunuh yang ditunjuk. Tetapi...
dengan asumsi kandidat aplikasi pembunuh teratas adalah kontes pemrograman / pengajaran / wawancara, saya pikir desain Eric di atas terlihat cukup berguna ... dan saya masih memiliki kontra-proposal saya (baru direvisi, masih belum ditarik!)

https://github.com/TimLovellSmith/corefxlab/blob/priorityQueueSpecSolved/docs/specs/priority-queue.md

Perbedaan utama yang saya perhatikan di antara mereka adalah apakah itu harus mencoba berbicara seperti kamus, dan apakah penyeleksi harus menjadi sesuatu.

Saya mulai lupa persis mengapa saya menginginkannya menjadi kamus. Memiliki pemberi tugas yang diindeks untuk memperbarui prioritas, dan dapat menghitung kunci dengan prioritasnya, tampak seperti kebaikan dan sangat mirip kamus bagi saya. Mampu memvisualisasikannya sebagai kamus di debugger juga bisa menjadi manis.

Saya pikir penyeleksi sebenarnya secara drastis mengubah antarmuka kelas yang diharapkan, FWIW. Selector harus menjadi sesuatu yang dipanggang pada waktu konstruksi, dan jika Anda memilikinya, itu menghilangkan kebutuhan bagi siapa pun untuk memberikan nilai prioritas kepada orang lain -- mereka hanya perlu memanggil pemilih jika mereka ingin mengetahui prioritasnya. Jadi, Anda sama sekali tidak ingin memiliki parameter prioritas dalam tanda tangan metode apa pun kecuali cukup banyak pemilih. Jadi dalam hal ini menjadi semacam kelas 'PrioritySet' yang lebih khusus. [Untuk pemilih yang tidak murni mana yang merupakan masalah bug yang mungkin terjadi!]

@TimLovellSmith Saya mengerti keinginan untuk perilaku ini, tetapi karena ini telah menjadi pertanyaan 5 tahun, bukankah masuk akal untuk hanya mengimplementasikan tumpukan biner berbasis array dengan pemilih dan berbagi area permukaan API yang sama dengan Queue.cs ? Saya pikir sejauh ini yang dibutuhkan sebagian besar pengguna. Menurut pendapat saya struktur data ini belum diimplementasikan karena kita tidak dapat menyetujui desain API, tetapi jika kita membuat PriorityQueue Saya yakin kita harus meniru desain api Queue .

@Jlalond Oleh karena itu proposal di sini, ya? Saya tidak keberatan dengan implementasi array berbasis heap. Sekarang saya ingat keberatan terbesar yang saya miliki terhadap penyeleksi, adalah tidak cukup mudah untuk melakukan pembaruan prioritas dengan benar . Antrian lama biasa tidak memiliki operasi pembaruan prioritas, tetapi memperbarui prioritas elemen adalah operasi penting untuk banyak algoritma antrian prioritas.
Orang-orang harus selamanya berterima kasih jika kita menggunakan API yang tidak mengharuskan mereka untuk men-debug struktur antrian prioritas yang rusak.

@TimLovellSmith Maaf Tim, saya seharusnya mengklarifikasi warisan dari IDictionary .

Apakah kita memiliki kasus penggunaan di mana prioritas akan berubah?

Saya suka implementasi Anda, tetapi saya pikir kami dapat mengurangi replikasi Perilaku IDictionary. Saya pikir kita tidak boleh mewarisi dari IDictionary<> tetapi hanya ICollection, karena menurut saya pola akses kamus tidak intuitif,

Namun saya benar-benar berpikir mengembalikan T dan prioritas terkaitnya akan masuk akal, tetapi saya tidak tahu kasus penggunaan di mana saya perlu mengetahui prioritas elemen dalam struktur data saat mengantrekan atau menghapusnya.

@Jlalond
Jika kita memiliki antrian prioritas, maka itu harus mendukung semua operasi yang dapat dilakukan secara sederhana dan efisien, _dan_ itu
'diharapkan' berada di sana oleh orang-orang yang akrab dengan struktur data / abstraksi semacam ini.

Prioritas pembaruan termasuk dalam API karena termasuk dalam kedua kategori tersebut. Prioritas pembaruan merupakan pertimbangan yang cukup penting dalam banyak algoritme bahwa kerumitan melakukan operasi dengan struktur data heap mempengaruhi kompleksitas algoritme keseluruhan, dan itu diukur secara teratur, lihat:
https://en.wikipedia.org/wiki/Priority_queue#Specialized_heaps

TBH terutama penurunan_key-nya yang menarik untuk algoritme dan efisiensi, dan diukur, jadi saya pribadi baik-baik saja karena API hanya memiliki ReducePriority(), dan sebenarnya tidak memiliki UpdatePriority.
Tetapi untuk kenyamanan tampaknya lebih baik untuk tidak mengganggu pengguna API dengan mengkhawatirkan apakah setiap perubahan adalah peningkatan atau penurunan. Sehingga pengembang joe memiliki 'pembaruan' di luar kotak tanpa harus bertanya-tanya mengapa itu hanya mendukung penurunan dan bukan peningkatan, (dan mana yang digunakan kapan), saya pikir yang terbaik adalah memiliki API prioritas pembaruan umum, tetapi dokumentasikan itu saat menggunakannya untuk mengurangi biayanya X, saat menggunakannya untuk menambah biayanya Y karena diimplementasikan sama dengan Hapus+Tambah.

kamus RE Saya sudah setuju. Menghapusnya dari proposal saya atas nama lebih sederhana lebih baik.
https://github.com/TimLovellSmith/corefxlab/blob/priorityQueueSpecSolved/docs/specs/priority-queue.md

Saya tidak tahu kasus penggunaan di mana saya perlu mengetahui prioritas elemen dalam struktur data saat mengantrekan atau menghapusnya.

Saya tidak yakin tentang apa komentar ini. Anda tidak perlu mengetahui prioritas suatu elemen untuk menghilangkannya. Tetapi Anda perlu mengetahui prioritasnya saat Anda mengantrekannya. Anda mungkin ingin mempelajari prioritas akhir suatu elemen saat Anda menghapusnya, karena nilai tersebut dapat memiliki arti misalnya jarak.

@TimLovellSmith

Saya tidak yakin tentang apa komentar ini. Anda tidak perlu mengetahui prioritas suatu elemen untuk menghilangkannya. Tetapi Anda perlu mengetahui prioritasnya saat Anda mengantrekannya. Anda mungkin ingin mempelajari prioritas akhir suatu elemen saat Anda menghapusnya, karena nilai tersebut dapat memiliki arti misalnya jarak.

Maaf, saya salah memahami apa yang dimaksud TPriorty dalam pasangan nilai kunci Anda.

Oke, dua pertanyaan terakhir, mengapa mereka KeyValuePair dengan TPriority, dan waktu terbaik yang bisa saya lihat untuk prioritas ulang adalah N log N, saya setuju itu berharga, tetapi juga seperti apa tampilan API itu?

waktu terbaik yang bisa saya lihat untuk memprioritaskan ulang adalah N log N,

Itu waktu terbaik untuk memprioritaskan ulang N item sebagai lawan dari satu item, bukan?
Saya akan mengklarifikasi dokumen: "UpdatePriority | O(log n)" harus membaca "UpdatePriority (item tunggal) | O(log n)".

@TimLovellSmith Masuk akal, tetapi bukankah pembaruan prioritas benar-benar ne O2 * (log n), karena kita harus menghapus elemen dan kemudian memasukkannya kembali? Mendasarkan asumsi saya dari ini menjadi tumpukan biner

@TimLovellSmith Masuk akal, tetapi bukankah pembaruan prioritas benar-benar ne O2 * (log n), karena kita harus menghapus elemen dan kemudian memasukkannya kembali? Mendasarkan asumsi saya dari ini menjadi tumpukan biner

Faktor konstan seperti itu 2 umumnya diabaikan dalam analisis kompleksitas karena sebagian besar menjadi tidak relevan seiring bertambahnya ukuran N.

Dipahami, sebagian besar ingin tahu apakah Tim punya ide menarik untuk dilakukan
satu operasi :)

Pada Selasa, 18 Agustus 2020, 23:51 masonwheeler [email protected] menulis:

@TimLovellSmith https://github.com/TimLovellSmith Masuk akal, tapi
tidak akan ada pembaruan prioritas sebenarnya ne O2 * (log n), seperti yang kita perlukan
hapus elemen lalu masukkan kembali? Mendasarkan asumsi saya dari ini
menjadi tumpukan biner

Faktor konstan seperti itu 2 umumnya diabaikan dalam analisis kompleksitas
karena mereka menjadi sebagian besar tidak relevan dengan bertambahnya ukuran N.


Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/dotnet/runtime/issues/14032#issuecomment-675887763 ,
atau berhenti berlangganan
https://github.com/notifications/unsubscribe-auth/AF76XTI3YK4LRUVTOIQMVHLSBNZARANCNFSM4LTSQI6Q
.

@Jlalond
Tidak ada ide baru, saya hanya mengabaikan faktor konstan, tetapi penurunan juga berbeda dari peningkatan

Jika pembaruan prioritas adalah penurunan, Anda tidak perlu menghapusnya dan menambahkannya kembali, itu hanya menggelembung, jadi 1 * O(log n) = O(log n).
Jika pembaruan prioritas adalah peningkatan, Anda mungkin harus menghapusnya dan menambahkannya kembali, jadi 2 * O(log n) = masih O (log n).

Orang lain mungkin telah menemukan struktur data/algoritma yang lebih baik untuk meningkatkan prioritas daripada menghapus + readd, tetapi saya belum mencoba menemukannya, O(log n) tampaknya cukup baik.

KeyValuePair menyusup ke antarmuka saat masih berupa kamus, tetapi selamat dari penghapusan kamus, terutama agar memungkinkan untuk mengulangi koleksi _elemen dengan prioritasnya_. Dan juga agar Anda dapat menghapus elemen dengan prioritasnya. Tapi mungkin untuk penyederhanaan lagi, dequeue dengan prioritas seharusnya hanya di versi 'advanced' dari Dequeue API. (CobaDequeue :D)

Aku akan membuat perubahan itu.

@TimLovellSmith Keren, saya menantikan proposal revisi Anda. Bisakah kami mengirimkannya untuk ditinjau sehingga saya dapat mulai mengerjakan ini? Selain itu, saya tahu ada beberapa dorongan untuk antrian pemasangan untuk meningkatkan waktu penggabungan, tetapi saya masih berpikir tumpukan biner berbasis array akan menjadi kinerja paling keseluruhan. Pikiran?

@Jlalond
Saya telah membuat perubahan kecil untuk menyederhanakan api Peek + Dequeue itu.
Beberapa ICollectionapis yang terkait dengan KeyValuePair masih ada, karena saya tidak melihat sesuatu yang lebih baik untuk menggantikannya.
Hubungan PR yang sama. Apakah ada cara lain saya harus mengirimkannya untuk ditinjau?

https://github.com/TimLovellSmith/corefxlab/blob/priorityQueueSpecSolved/docs/specs/priority-queue.md

Saya tidak keberatan implementasi mana yang digunakannya selama itu secepat tumpukan berbasis array atau lebih baik.

@TimLovellSmith Saya sebenarnya tidak tahu apa-apa tentang tinjauan API, tetapi saya membayangkan @danmosemsft dapat mengarahkan kita ke arah yang benar.

Pertanyaan pertama saya adalah apa yang akanmenjadi? Saya berasumsi itu akan menjadi int, atau angka floating point, tetapi bagi saya menyatakan angka yang "internal" ke antrian tampak aneh bagi saya

Dan saya sebenarnya lebih suka tumpukan biner, tetapi ingin memastikan kita semua baik-baik saja menuju ke arah itu

@eiriktsarpalis @layomia adalah pemilik area dan harus menggembalakan ini melalui tinjauan API. Saya belum mengikuti diskusi, apakah kita sudah mencapai titik mufakat?

Seperti yang telah kita bahas di masa lalu, perpustakaan inti bukanlah tempat terbaik untuk semua koleksi, terutama yang berpendirian dan/atau khusus. Misalnya, kami mengirim setiap tahun - kami tidak dapat bergerak secepat yang dapat dilakukan perpustakaan. Kami memiliki tujuan untuk membangun komunitas di sekitar paket koleksi untuk mengisi celah itu. Tapi 84 acungan jempol untuk yang satu ini menunjukkan ada kebutuhan luas untuk ini di perpustakaan inti.

@Jlalond Maaf, saya tidak mengerti apa yang seharusnya ada di pertanyaan pertama. Apakah Anda bertanya mengapa TPriority bersifat generik? Atau harus nomor? Saya ragu banyak orang akan menggunakan tipe prioritas non-numerik. Tetapi mereka mungkin lebih suka menggunakan byte, int, long, float, double, atau enum.

@TimLovellSmith Ya, hanya ingin tahu apakah itu generik

@danmosemsft Terima kasih Dan. Jumlah keberatan/komentar yang belum terselesaikan yang saya lihat pada proposal saya[1] kira-kira nol, dan ini menjawab pertanyaan penting apa pun yang dibiarkan terbuka oleh proposal @ebickle di atas (yang semakin mirip dengannya).

Jadi saya mengklaim itu melewati pemeriksaan kewarasan sejauh ini. Masih harus ada beberapa tinjauan yang diperlukan, kita dapat berbicara tentang apakah berguna untuk mewarisi IReadOnlyCollection (kedengarannya tidak terlalu berguna tetapi saya harus tunduk pada para ahli) dan seterusnya -- saya rasa itulah gunanya proses tinjauan api! @eiriktsarpalis @layomia bolehkah saya meminta Anda untuk melihat ini?

[1] https://github.com/TimLovellSmith/corefxlab/blob/priorityQueueSpecSolved/docs/specs/priority-queue.md

[PS - berdasarkan sentimen utas, aplikasi pembunuh yang diusulkan saat ini adalah kompetisi pengkodean, pertanyaan wawancara, dll, di mana Anda hanya ingin API sederhana yang bagus untuk digunakan yang berfungsi baik kelas atau struct plus tipe numerik favorit Anda hari ini, dengan O yang tepat (n) dan implementasi yang tidak terlalu lambat - dan saya senang menjadi kelinci percobaan untuk menerapkannya pada beberapa masalah. Maaf itu bukan sesuatu yang lebih menarik.]

Hanya berbicara karena saya menggunakan antrian prioritas sepanjang waktu untuk proyek 'nyata', terutama untuk pencarian grafik (misalnya optimasi masalah, pencarian rute), dan ekstraksi yang dipesan (dengan potongan (misalnya tetangga terdekat quad-tree), penjadwalan (yaitu seperti diskusi Timer di atas)). Saya memiliki beberapa proyek yang dapat saya terapkan langsung untuk pemeriksaan/pengujian kewarasan, jika itu membantu. Proposal saat ini terlihat bagus bagi saya (akan bekerja untuk semua tujuan saya, jika tidak cocok). Saya memiliki beberapa pengamatan kecil:

  • TElement muncul beberapa kali di mana seharusnya TKey
  • Implementasi saya sendiri sering kali menyertakan bool EnqueueOrUpdateIfHigherPriority untuk kemudahan penggunaan (dan dalam beberapa kasus efisiensi), yang sering kali menjadi satu-satunya metode enqueueing/pembaruan yang digunakan (misalnya dalam pencarian grafik). Jelas itu tidak penting dan akan menambah kerumitan tambahan pada API, tetapi itu adalah hal yang sangat bagus untuk dimiliki.
  • Ada dua salinan Enqueue (maksud saya bukan Add ): apakah yang dimaksudkan adalah bool TryEnqueue ?
  • "// menghitung koleksi dalam urutan sewenang-wenang, tetapi dengan elemen terkecil terlebih dahulu": Saya tidak berpikir bahwa bit terakhir berguna; Saya lebih suka enumerator tidak harus melakukan perbandingan apa pun, tetapi saya kira ini akan 'gratis' sehingga tidak akan mengganggu saya
  • Penamaan EqualityComparer agak tidak terduga: Saya mengharapkan KeyComparer untuk pergi dengan PriorityComparer .
  • Saya tidak mengerti catatan tentang kerumitan CopyTo dan ToArray .

@VisualMelon
Terima kasih banyak atas ulasannya!

Saya suka saran penamaan KeyComparer. API prioritas penurunan terdengar sangat berguna. Bagaimana kalau kita menambahkan satu atau kedua API tersebut. Karena kita menggunakan model 'paling tidak prioritas yang diutamakan', apakah Anda hanya akan menggunakan penurunan? Atau apakah Anda ingin meningkatkan juga?

    public void EnqueueOrIncreasePriority(TKey key, TPriority priority); // doesn't throw, only updates priority of keys already in the collection if new priority is higher
    public void EnqueueOrDeccreasePriority(TKey key, TPriority priority); // doesn't throw, only updates priority of keys already in the collection new priroity is lower

Alasan saya berpikir enumerasi mengembalikan elemen terkecil terlebih dahulu adalah untuk konsisten dengan model mental yang Peek() mengembalikan elemen di depan koleksi. Dan juga tidak ada perbandingan yang diperlukan untuk mengimplementasikannya jika ini adalah tumpukan biner, jadi sepertinya gratis. Tapi mungkin itu kurang gratis dari yang saya kira - kompleksitas yang tidak beralasan? Saya senang untuk mengeluarkannya karena alasan itu.

Dua Enqueues tidak disengaja. Kami memiliki EnqueueOrUpdate, di mana prioritas selalu diperbarui. Saya kira TryUpdate adalah kebalikan logis dari itu, di mana prioritas tidak boleh diperbarui untuk item yang sudah ada dalam koleksi. Saya dapat melihatnya mengisi celah logis itu, tetapi saya tidak yakin apakah itu adalah API yang diinginkan orang dalam praktiknya.

Saya hanya bisa membayangkan menggunakan varian yang menurun: ini adalah operasi alami dalam pencarian rute untuk ingin mengantrekan node atau mengganti node yang ada dengan yang memiliki prioritas lebih rendah; Saya tidak bisa langsung menyarankan penggunaan sebaliknya. Parameter pengembalian boolean dapat berguna jika Anda perlu melakukan operasi apa pun saat item diantrekan/tidak diantrekan.

Saya tidak mengasumsikan tumpukan biner, tetapi jika gratis maka saya tidak memiliki masalah dengan elemen pertama yang selalu menjadi minimum, itu sepertinya kendala yang aneh.

@VisualMelon @TimLovellSmith Apakah satu API akan baik-baik saja?
EnqueueOrUpdatePriority ? Saya membayangkan kemampuan untuk memposisikan ulang sebuah node dalam antrian sangat berharga untuk algoritma traversal bahkan jika itu menambah atau mengurangi prioritas

@Jlalond ya, saya menyebutkannya karena dapat membantu efisiensi tergantung pada implementasinya, dan secara langsung mengkodekan kasus penggunaan hanya ingin mengantri sesuatu jika itu meningkatkan apa yang sudah Anda miliki dalam antrian. Saya tidak akan berani mengatakan apakah kompleksitas tambahan itu sesuai untuk alat tujuan umum seperti itu, tetapi tentu saja itu tidak perlu.

Diperbarui untuk menghapus EnqueueOrIncreasePriority, menjaga EnqueueOrUpdate dan EnqueueOrDecrease. Membiarkan mereka mengembalikan bool saat item baru ditambahkan ke koleksi, seperti HashSet.Add().

@Jlalond Saya mohon maaf atas kesalahan di atas (penghapusan komentar terbaru Anda menanyakan kapan masalah ini akan ditinjau).

Saya hanya ingin menyebutkan bahwa @eiriktsarpalis akan kembali minggu depan dan kami akan membahas memprioritaskan fitur ini dan meninjau API kemudian.

Ini adalah sesuatu yang kami pertimbangkan tetapi belum berkomitmen untuk .NET 6.

Saya melihat bahwa utas ini dihidupkan kembali dengan memulai dari awal dan bercabang, meskipun kami telah berdiskusi mendalam tentang topik ini pada tahun 2017 selama beberapa bulan (sebagian besar utas besar ini) dan secara kolektif menghasilkan proposal ini — gulir ke atas untuk melihat. Ini juga proposal yang ditautkan @karelz di baris pertama dari posting pertama untuk visibilitas:

Lihat Proposal TERBARU di repo corefxlab.

Pada 2016-06-16, kami menunggu tinjauan API yang tidak pernah terjadi dan statusnya adalah:

Jika API disetujui dalam bentuknya saat ini (dua kelas), saya akan membuat PR lain ke CoreFXLab dengan implementasi untuk keduanya menggunakan tumpukan kuarter ( lihat implementasi ). PriorityQueue<T> hanya akan menggunakan satu larik di bawahnya, sedangkan PriorityQueue<TElement, TPriority> akan menggunakan dua -- dan menyusun ulang elemennya bersama-sama. Ini dapat menghemat alokasi tambahan dan seefisien mungkin. Saya akan menambahkan bahwa setelah ada lampu hijau.

Saya merekomendasikan untuk melanjutkan iterasi pada proposal yang kami buat pada tahun 2017, dimulai dari tinjauan formal yang belum terjadi. Ini akan memungkinkan kita untuk menghindari forking dan iterasi di belakang proposal yang disatukan setelah ratusan posting dipertukarkan oleh anggota komunitas, juga untuk menghormati upaya yang dilakukan oleh semua orang yang terlibat. Saya senang mendiskusikannya jika ada umpan balik.

@pgolebiowski Terima kasih telah kembali ke diskusi. Saya ingin meminta maaf karena telah membuat proposal lebih jauh secara efektif, yang bukan merupakan cara yang paling efektif untuk berkolaborasi. Itu adalah langkah impulsif di pihak saya. (Saya mendapat kesan bahwa diskusi telah benar-benar terhenti karena terlalu banyak pertanyaan terbuka dalam proposal, dan hanya membutuhkan proposal yang lebih beropini.)

Bagaimana jika kita mencoba untuk bergerak maju dalam hal ini, dengan setidaknya melupakan sementara isi kode PR saya, dan melanjutkan membahas di sini 'masalah terbuka' yang teridentifikasi? Saya ingin memberikan pendapat saya tentang mereka semua.

  1. Nama kelas PriorityQueue vs. Heap <-- Saya pikir itu sudah dibahas dan konsensus adalah bahwa PriorityQueue lebih baik.

  2. Perkenalkan IHeap dan kelebihan konstruktor? <-- Saya tidak melihat banyak nilai. Saya pikir untuk 95% dunia tidak ada alasan kuat (misalnya delta kinerja) untuk memiliki beberapa implementasi tumpukan, dan 5% lainnya mungkin harus menulis sendiri.

  3. Perkenalkan IPriorityQueue? <-- Saya juga tidak berpikir itu diperlukan. Saya berasumsi bahasa dan kerangka kerja lain baik-baik saja tanpa antarmuka ini di perpustakaan standar mereka. Beri tahu saya jika itu salah.

  4. Gunakan pemilih (prioritas yang disimpan di dalam nilai) atau tidak (perbedaan 5 API) <-- Saya tidak melihat argumen kuat yang mendukung pemilih yang mendukung dalam antrian prioritas. Saya merasa IComparer<> sudah berfungsi sebagai mekanisme ekstensibilitas minimal yang sangat baik untuk membandingkan prioritas item, dan penyeleksi tidak menawarkan peningkatan yang signifikan. Juga orang cenderung bingung tentang bagaimana menggunakan penyeleksi dengan prioritas yang bisa berubah (atau bagaimana TIDAK menggunakannya).

  5. Gunakan tupel (elemen TE, prioritas TPriority) vs. KeyValuePair<-- Secara pribadi saya pikir KeyValuePair adalah opsi yang lebih disukai untuk antrian prioritas di mana item memiliki prioritas yang dapat diperbarui, karena item diperlakukan sebagai kunci dalam set/kamus.

  6. Haruskah Peek dan Dequeue lebih suka berdebat daripada Tuple? <-- Saya tidak yakin dengan semua pro dan kontra, terutama kinerja. Haruskah kita memilih salah satu yang berkinerja lebih baik dalam tes benchmark sederhana?

  7. Apakah melempar Peek dan Dequeue berguna sama sekali? <-- Ya... Inilah yang seharusnya terjadi pada algoritma yang salah berasumsi bahwa masih ada item dalam antrian . Jika Anda tidak ingin algoritme berasumsi demikian, hal terbaik yang harus dilakukan adalah tidak menyediakan api Peek dan Dequeue sama sekali, tetapi cukup sediakan TryPeek dan TryDequeue. [Saya kira ada algoritme yang terbukti dapat dengan aman memanggil Peek atau Dequeue dalam kasus tertentu, dan memiliki Peek/Dequeue adalah sedikit peningkatan kinerja + kegunaan untuk itu.]

Selain itu saya juga memiliki beberapa saran untuk dipertimbangkan untuk ditambahkan ke proposal yang sedang dipertimbangkan:

  1. Antrian Prioritasseharusnya hanya bekerja dengan item unik, yang merupakan pegangan mereka sendiri. Itu harus mendukung IEqualityComparer khusus, jika ingin membandingkan objek yang tidak dapat diperluas (seperti string) dengan cara tertentu untuk melakukan pembaruan prioritas.

  2. Antrian Prioritasharus mendukung berbagai operasi seperti 'hapus', 'kurangi prioritas jika lebih kecil', dan terutama 'item antrean atau kurangi prioritas jika operasi lebih kecil', semuanya dalam O(log n) - untuk menerapkan skenario pencarian grafik secara efisien dan sederhana.

  3. Akan lebih mudah bagi programmer yang malas jika PriorityQueuemenyediakan operator indeks[] untuk mendapatkan/mengatur prioritas item yang ada. Seharusnya O(1) untuk get, O(log n) untuk set.

  4. Prioritas terkecil pertama adalah yang terbaik. Karena antrian prioritas yang digunakan untuk banyak adalah masalah optimasi, dengan 'biaya minimal'.

  5. Pencacahan tidak boleh memberikan jaminan pemesanan apa pun.

Buka masalah - menangani:
Antrian Prioritasitem dan prioritas tampaknya dimaksudkan untuk memungkinkan bekerja dengan nilai duplikat. Nilai-nilai itu perlu dianggap sebagai 'tidak dapat diubah', atau harus ada metode untuk memanggil untuk memberi tahu koleksi ketika prioritas item berubah, mungkin dengan pegangan, sehingga dapat spesifik tentang prioritas duplikat mana yang diubah (skenario apa yang perlu dilakukan ini??)... Saya ragu tentang semua yang berguna ini, dan apakah ini ide yang bagus, tetapi jika kami memiliki prioritas pembaruan dalam koleksi seperti itu, mungkin bagus jika biaya yang dikeluarkan dengan metode itu dapat dikeluarkan dengan malas, jadi bahwa ketika Anda hanya mengerjakan item yang tidak dapat diubah, dan tidak ingin menggunakan tidak ada biaya tambahan ... bagaimana cara mengatasinya? Mungkinkah dengan memiliki metode kelebihan beban yang menggunakan pegangan dan yang tidak? Atau haruskah kita tidak memiliki pegangan item yang berbeda dari item itu sendiri (digunakan sebagai kunci hash untuk pencarian)?

Sebuah pemikiran untuk membantu memindahkan ini lebih cepat, mungkin masuk akal untuk memindahkan jenis ini ke perpustakaan "Koleksi Komunitas" yang sedang dibahas di https://github.com/dotnet/runtime/discussions/42205

Saya setuju bahwa prioritas terkecil pertama adalah yang terbaik. Antrian prioritas juga berguna dalam pengembangan game berbasis giliran, dan dapat memiliki prioritas menjadi penghitung yang meningkat secara monoton ketika setiap elemen mendapat giliran berikutnya sangat membantu.

Kami sedang mempertimbangkan untuk membawa ini untuk tinjauan API ASAP (meskipun kami belum berkomitmen untuk memberikan implementasi dalam jangka waktu .NET 6).

Namun sebelum kami mengirimkan ini untuk ditinjau, kami perlu menyelesaikan beberapa pertanyaan terbuka tingkat tinggi. Saya pikir @TimLovellSmith 's write up adalah baik titik awal menuju mencapai tujuan itu.

Beberapa komentar tentang poin-poin itu:

  • Konsensus pada pertanyaan 1-3 sudah lama terbentuk, saya pikir kita bisa memperlakukannya sebagai terselesaikan.

  • _Gunakan pemilih (prioritas disimpan di dalam nilai) atau tidak_ -- Setuju dengan komentar Anda. Masalah lain dengan desain itu adalah bahwa penyeleksi bersifat opsional, yang berarti Anda harus berhati-hati untuk tidak menyebut Enqueue (atau berisiko mendapatkan InvalidOperationException ).

  • Saya lebih suka menggunakan Tuple daripada KeyValuePair . Menggunakan properti .Key untuk mengakses nilai TPriority terasa aneh bagi saya. Tuple memungkinkan Anda menggunakan .Priority yang memiliki properti dokumentasi diri yang lebih baik.

  • _Haruskah Peek dan Dequeue lebih memilih argumen daripada Tuple?_ -- Saya pikir kita harus mengikuti konvensi yang sudah ada dalam metode serupa di tempat lain. Jadi mungkin gunakan argumen out .

  • _Apakah Peek dan Dequeue throw berguna sama sekali?_ -- Setuju 100% dengan komentar Anda.

  • _Prioritas terkecil pertama adalah yang terbaik_ -- Setuju.

  • _Pencacahan seharusnya tidak memberikan jaminan pemesanan apa pun_ -- Mungkinkah ini tidak melanggar harapan pengguna? Apa trade-offnya? NB kami mungkin dapat menunda detail seperti ini untuk diskusi tinjauan API.

Saya juga ingin mengulangi beberapa pertanyaan terbuka lainnya:

  • Apakah kami mengirim PriorityQueue<T> , PriorityQueue<TElement, TPriority> atau keduanya? -- Secara pribadi saya pikir kita hanya harus menerapkan yang terakhir, karena tampaknya memberikan solusi yang lebih bersih dan lebih umum. Pada prinsipnya prioritas tidak boleh menjadi properti intrinsik untuk elemen antrian, jadi kita tidak boleh memaksa pengguna untuk merangkumnya dalam jenis pembungkus.

  • Apakah kita membutuhkan keunikan elemen, hingga kesetaraan? -- Koreksi saya jika saya salah, tetapi saya merasa keunikan adalah batasan buatan, yang dipaksakan kepada kami oleh persyaratan untuk mendukung skenario pembaruan tanpa menggunakan pendekatan pegangan. Ini juga memperumit permukaan API, karena kita sekarang juga perlu khawatir tentang elemen yang memiliki semantik kesetaraan yang tepat (apa persamaan yang tepat ketika elemen adalah DTO besar?). Saya dapat melihat tiga kemungkinan jalur di sini:

    1. Memerlukan keunikan/kesetaraan dan mendukung skenario pembaruan dengan meneruskan elemen asli (hingga kesetaraan).
    2. Tidak memerlukan keunikan/kesetaraan dan mendukung skenario pembaruan menggunakan pegangan. Ini dapat diperoleh dengan menggunakan varian metode Enqueue opsional untuk pengguna yang membutuhkannya. Jika menangani alokasi menjadi perhatian yang cukup besar, saya bertanya-tanya apakah itu bisa diamortisasi la ValueTask?
    3. Tidak memerlukan keunikan/kesetaraan dan tidak mendukung pembaruan prioritas.
  • Apakah kami mendukung penggabungan antrian? -- Konsensus tampaknya tidak, karena kami hanya mengirimkan satu implementasi (mungkin menggunakan tumpukan array) di mana penggabungan tidak efisien.

  • Antarmuka apa yang harus diimplementasikan? -- Saya melihat beberapa proposal yang merekomendasikan IQueue<T> , tetapi ini terasa seperti abstraksi yang bocor. Saya pribadi lebih suka membuatnya sederhana dan hanya mengimplementasikan ICollection<T> .

cc @layomia @safern

@eiriktsarpalis Sebaliknya - tidak ada yang artifisial tentang batasan untuk mendukung pembaruan sama sekali!

Algoritma dengan antrian prioritas sering memperbarui prioritas elemen. Pertanyaannya adalah, apakah Anda menyediakan API berorientasi objek, yang 'berfungsi' untuk memperbarui prioritas objek biasa...
a) memahami model pegangan
b) menyimpan data tambahan di memori, seperti properti tambahan atau kamus eksternal, untuk melacak pegangan objek, di sisinya, hanya agar mereka dapat melakukan pembaruan (harus menggunakan kamus untuk kelas yang tidak dapat mereka ubah? atau tingkatkan objek menjadi tupel dll)
c) 'pengelolaan memori', atau 'pengumpulan sampah' struktur eksternal yaitu membersihkan pegangan untuk item yang tidak lagi dalam antrian, saat menggunakan pendekatan kamus
d) tidak membingungkan pegangan buram dalam konteks dengan banyak antrian karena mereka hanya bermakna dalam konteks antrian prioritas tunggal

Plus ada pertanyaan filosofis di sekitar alasan mengapa ada orang yang menginginkan antrian yang _melacak objek berdasarkan prioritas_ untuk berperilaku seperti ini: mengapa objek yang sama (sama dengan pengembalian benar) memiliki dua prioritas _berbeda_? Jika mereka benar-benar seharusnya memiliki prioritas yang berbeda, mengapa mereka tidak dimodelkan sebagai objek yang berbeda ? (atau diubah menjadi tupel, dengan pembeda?)

Juga untuk pegangan, harus ada beberapa tabel pegangan internal dalam antrian prioritas agar pegangan benar-benar berfungsi. Saya pikir ini adalah pekerjaan yang setara dengan menyimpan kamus untuk mencari objek dalam antrian.

PS: setiap objek .net sudah mendukung konsep kesetaraan/keunikan, jadi tidak terlalu sulit untuk 'mewajibkannya'.

Mengenai KeyValuePair , saya setuju itu tidak ideal (meskipun dalam proposal, Key adalah untuk elemen, Value untuk prioritas, yang konsisten dengan berbagai Sorted tipe data di BCL), dan kesesuaiannya akan tergantung pada keputusan mengenai keunikan. Secara pribadi, saya akan _banyak_ lebih memilih struct khusus bernama baik daripada Tuple di mana saja di API publik, terutama untuk input.

Mengenai keunikan, itu adalah perhatian mendasar, dan saya rasa tidak ada hal lain yang bisa diputuskan sampai itu. Saya akan menyukai keunikan elemen seperti yang didefinisikan oleh pembanding (opsional) (sesuai dengan proposal dan saran yang ada i) jika tujuannya adalah API tunggal yang mudah digunakan dan tujuan umum. Unique vs non-unik adalah celah besar, dan saya menggunakan kedua jenis untuk tujuan yang berbeda. Yang pertama 'lebih sulit' untuk diterapkan, dan mencakup sebagian besar kasus penggunaan (dan lebih umum) (hanya pengalaman saya) sementara lebih sulit untuk disalahgunakan. Kasus-kasus penggunaan yang _require_ non-uniqueness harus (IMO) dilayani oleh tipe yang berbeda (misalnya tumpukan biner lama yang polos), dan saya akan menghargai keduanya tersedia. Ini pada dasarnya adalah apa yang disediakan oleh proposal asli yang ditautkan oleh _Edit: Tidak, itu tidak akan mendukung prioritas terikat_

Sebaliknya - tidak ada batasan yang dibuat-buat untuk mendukung pembaruan sama sekali!

Maaf, saya tidak bermaksud menunjukkan bahwa dukungan untuk pembaruan adalah buatan; melainkan persyaratan untuk keunikan diperkenalkan secara artifisial untuk mendukung pembaruan.

PS: setiap objek .net sudah mendukung konsep kesetaraan/keunikan, jadi bukan pertanyaan besar untuk 'mewajibkannya'

Tentu, tetapi kadang-kadang semantik kesetaraan yang datang dengan tipe mungkin bukan yang diinginkan (misalnya kesetaraan referensi, kesetaraan struktural besar-besaran, dll.). Saya hanya menunjukkan bahwa kesetaraan itu sulit dan memaksanya ke dalam desain membawa kelas baru dari bug pengguna potensial.

@eiriktsarpalis Terima kasih telah mengklarifikasi itu. Tapi apakah itu benar-benar buatan? Saya tidak berpikir itu. Ini hanya solusi alami lainnya.

Api harus _didefinisikan dengan baik_. Anda tidak dapat menyediakan API pembaruan tanpa mengharuskan pengguna untuk secara spesifik tentang apa yang ingin mereka perbarui . Menangani, dan kesetaraan objek hanyalah dua pendekatan berbeda untuk membangun API yang terdefinisi dengan baik.

Menangani pendekatan: setiap kali Anda menambahkan objek ke koleksi, Anda harus memberinya 'nama', yaitu 'pegangan', sehingga Anda dapat merujuk ke objek yang tepat tanpa ambiguitas nanti dalam percakapan.

Pendekatan keunikan objek: setiap kali Anda menambahkan objek ke koleksi, itu harus menjadi objek yang berbeda, atau Anda harus menentukan cara menangani kasus objek yang sudah ada.

Sayangnya, hanya pendekatan objek yang benar-benar memungkinkan Anda untuk mendukung beberapa properti metode API tingkat tinggi yang paling berguna, seperti 'EnqueueIfNotExists' atau 'EnqueueOrDecreasePriroity(item)' - mereka tidak masuk akal untuk disediakan dalam desain yang berpusat pada pegangan, karena Anda tidak dapat mengetahui apakah item tersebut sudah ada dalam antrian (karena tugas Anda untuk melacaknya, dengan pegangan).

Salah satu kritik paling jitu dari pendekatan pegangan, atau menjatuhkan batasan keunikan kepada saya, adalah bahwa itu membuat semua jenis skenario dengan prioritas yang dapat diperbarui jauh lebih rumit untuk diterapkan:

misalnya

  1. gunakan Antrian Prioritasuntuk string yang mewakili pesan/sentimen/tag/nama pengguna yang mendapat upvoted/skor diperbarui, nilai unik memiliki prioritas yang berubah
  2. gunakan Antrian Prioritas, double> untuk memesan tupel unik [apakah mereka memiliki perubahan prioritas atau tidak] - harus melacak pegangan tambahan di suatu tempat
  3. gunakan PriorityQueueuntuk memprioritaskan indeks grafik, atau id objek basis data, sekarang Anda harus menaburkan pegangan melalui implementasi Anda

PS

Tentu, tetapi kadang-kadang semantik kesetaraan yang datang dengan tipe mungkin bukan yang diinginkan

Harus ada jalan keluar, seperti IEqualityComparer, atau konversi ke tipe yang lebih kaya.

Terima kasih atas umpan baliknya Akan memperbarui proposal selama akhir pekan, dengan mempertimbangkan semua masukan baru, dan membagikan revisi baru untuk putaran berikutnya. ETA 2020-09-20.

Proposal antrian prioritas (v2.0)

Ringkasan

Komunitas .NET Core mengusulkan untuk menambahkan fungsionalitas _priority queue_ library sistem, sebuah struktur data di mana setiap elemen tambahan memiliki prioritas yang terkait dengannya. Secara khusus, kami mengusulkan untuk menambahkan PriorityQueue<TElement, TPriority> ke ruang nama System.Collections.Generic .

Prinsip

Dalam desain kami, kami dipandu oleh prinsip-prinsip berikut (kecuali Anda tahu yang lebih baik):

  • Cakupan yang luas. Kami ingin menawarkan kepada pelanggan .NET Core struktur data berharga yang cukup fleksibel untuk mendukung berbagai macam kasus penggunaan.
  • Belajar dari kesalahan yang diketahui. Kami berusaha untuk memberikan fungsionalitas antrian prioritas yang akan bebas dari masalah yang dihadapi pelanggan yang ada dalam kerangka kerja dan bahasa lain, misalnya Java, Python, C++, Rust. Kami akan menghindari membuat pilihan desain yang diketahui membuat pelanggan tidak senang dan mengurangi kegunaan antrian prioritas.
  • Sangat hati-hati dengan keputusan pintu 1 arah. Setelah API diperkenalkan, itu tidak dapat dimodifikasi atau dihapus, hanya diperpanjang. Kami akan dengan hati-hati menganalisis pilihan desain untuk menghindari solusi suboptimal yang akan membuat pelanggan kami terjebak selamanya.
  • Hindari kelumpuhan desain. Kami menerima bahwa mungkin tidak ada solusi yang sempurna. Kami akan menyeimbangkan trade-off dan bergerak maju dengan pengiriman, untuk akhirnya memberikan pelanggan kami fungsionalitas yang telah mereka tunggu selama bertahun-tahun.

Latar belakang

Dari sudut pandang pelanggan

Secara konseptual, antrian prioritas adalah kumpulan elemen, di mana setiap elemen memiliki prioritas terkait. Fungsi terpenting dari antrian prioritas adalah menyediakan akses yang efisien ke elemen dengan prioritas tertinggi dalam koleksi, dan opsi untuk menghapus elemen itu. Perilaku yang diharapkan juga dapat mencakup: 1) kemampuan untuk mengubah prioritas elemen yang sudah ada dalam koleksi; 2) kemampuan untuk menggabungkan beberapa antrian prioritas.

Latar belakang ilmu komputer

Antrian prioritas adalah struktur data abstrak, yaitu konsep dengan karakteristik perilaku tertentu, seperti yang dijelaskan pada bagian sebelumnya. Implementasi antrian prioritas yang paling efisien didasarkan pada tumpukan. Namun, bertentangan dengan kesalahpahaman umum, tumpukan juga merupakan struktur data abstrak, dan dapat diwujudkan dengan berbagai cara, masing-masing menawarkan manfaat dan kerugian yang berbeda.

Sebagian besar insinyur perangkat lunak hanya mengenal implementasi tumpukan biner berbasis array — ini adalah yang paling sederhana, tetapi sayangnya bukan yang paling efisien. Untuk input acak umum, dua contoh tipe heap yang lebih efisien adalah: heap kuartener dan heap berpasangan . Untuk informasi lebih lanjut tentang tumpukan, silakan merujuk ke Wikipedia dan makalah ini .

Mekanisme pembaruan adalah tantangan desain utama

Diskusi kami telah menunjukkan bahwa area yang paling menantang dalam desain, dan pada saat yang sama dengan dampak terbesar pada API, adalah mekanisme pembaruan. Secara khusus, tantangannya adalah untuk menentukan apakah dan bagaimana produk yang ingin kami tawarkan kepada pelanggan harus mendukung pembaruan prioritas elemen yang sudah ada dalam koleksi.

Kemampuan tersebut diperlukan untuk mengimplementasikan misalnya algoritma jalur terpendek Dijkstra atau penjadwal pekerjaan yang perlu menangani perubahan prioritas. Mekanisme pembaruan tidak ada di Java, yang terbukti mengecewakan bagi para insinyur, misalnya dalam tiga pertanyaan StackOverflow yang dilihat lebih dari 32 ribu kali: example , example , example . Untuk menghindari pengenalan API dengan nilai terbatas seperti itu, kami percaya bahwa persyaratan mendasar untuk fungsionalitas antrian prioritas yang kami berikan adalah untuk mendukung kemampuan memperbarui prioritas untuk elemen yang sudah ada dalam koleksi.

Untuk memberikan mekanisme pembaruan, kami harus memastikan bahwa pelanggan dapat secara spesifik tentang apa yang sebenarnya ingin mereka perbarui. Kami telah mengidentifikasi dua cara untuk mengirimkan ini: a) melalui pegangan; dan b) melalui penegakan keunikan elemen dalam koleksi. Masing-masing datang dengan manfaat dan biaya yang berbeda.

Opsi (a): Menangani. Dalam pendekatan ini, setiap kali elemen ditambahkan ke antrian, struktur data menyediakan pegangan uniknya. Jika pelanggan ingin menggunakan mekanisme pembaruan, mereka perlu melacak pegangan tersebut sehingga nantinya mereka dapat dengan jelas menentukan elemen mana yang ingin mereka perbarui. Biaya utama dari solusi ini adalah bahwa pelanggan perlu mengelola petunjuk ini. Namun, itu tidak berarti bahwa perlu ada alokasi internal untuk mendukung pegangan dalam antrian prioritas — heap berbasis non-array didasarkan pada node, di mana setiap node secara otomatis menanganinya sendiri. Sebagai contoh, lihat API metode PairingHeap.Update .

Opsi (b): Keunikan. Pendekatan ini menempatkan dua batasan tambahan pada pelanggan: i) elemen dalam antrian prioritas harus sesuai dengan semantik kesetaraan tertentu, yang membawa kelas baru bug pengguna potensial; ii) dua elemen yang sama tidak dapat disimpan dalam antrian yang sama. Dengan membayar biaya ini, kami mendapatkan manfaat dari mendukung mekanisme pembaruan tanpa menggunakan pendekatan pegangan. Namun, implementasi apa pun yang memanfaatkan keunikan/kesetaraan untuk menentukan elemen yang akan diperbarui akan memerlukan pemetaan internal ekstra, sehingga dilakukan di O(1) dan bukan di O(n).

Rekomendasi

Kami merekomendasikan untuk menambahkan ke perpustakaan sistem kelas PriorityQueue<TElement, TPriority> yang mendukung mekanisme pembaruan melalui pegangan. Implementasi yang mendasarinya adalah tumpukan pasangan.

public class PriorityQueueNode<TElement, TPriority>
{
    public TElement Element { get; }
    public TPriority Priority { get; }
}

public class PriorityQueue<TElement, TPriority> :
    IEnumerable<PriorityQueueNode<TElement, TPriority>>
{
    public PriorityQueue();
    public PriorityQueue(IComparer<TPriority> comparer);

    public IComparer<TPriority> Comparer { get; }
    public int Count { get; }

    public bool IsEmpty { get; }
    public void Clear();
    public bool Contains(TElement element); // O(n)
    public bool TryGetNode(TElement element, out PriorityQueueNode<TElement, TPriority> node); // O(n)

    public PriorityQueueNode<TElement, TPriority> Enqueue(TElement element, TPriority priority); //O(log n)

    public PriorityQueueNode<TElement, TPriority> Peek(); // O(1)
    public bool TryPeek(out PriorityQueueNode<TElement, TPriority> node); // O(1)

    public void Dequeue(out TElement element); // O(log n)
    public bool TryDequeue(out TElement element, out TPriority priority); // O(log n)

    public void Update(PriorityQueueNode<TElement, TPriority> node, TPriority priority); // O(log n)
    public void Remove(PriorityQueueNode<TElement, TPriority> node); // O(log n)

    public void Merge(PriorityQueue<TElement, TPriority> other) // O(1)

    public IEnumerator<PriorityQueueNode<TElement, TPriority>> GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator();

    public struct Enumerator : IEnumerator<PriorityQueueNode<TElement, TPriority>>
    {
        public PriorityQueueNode<TElement, TPriority> Current { get; }
        object IEnumerator.Current { get; }
        public bool MoveNext() => throw new NotImplementedException();
        public void Reset() => throw new NotImplementedException();
        public void Dispose() => throw new NotImplementedException();
    }
}

Contoh penggunaan

1) Pelanggan yang tidak peduli dengan mekanisme pembaruan

var queue = new PriorityQueue<Job, double>();
queue.Enqueue(firstJob, 10);
queue.Enqueue(secondJob, 5);
queue.Enqueue(thirdJob, 40);

var withHighestPriority = queue.Peek(); // { element: secondJob, priority: 5 }

2) Pelanggan yang peduli dengan mekanisme pembaruan

var queue = new PriorityQueue<Job, double>();
var mapping = new Dictionary<Job, PriorityQueueNode<Job, double>>();

mapping[job] = queue.Enqueue(job, priority);

queue.Update(mapping[job], newPriority);

FAQ

Dalam urutan apa antrian prioritas menghitung elemen?

Dalam urutan yang tidak ditentukan, sehingga enumerasi dapat terjadi di O(n), sama seperti dengan HashSet . Tidak ada implementasi yang efisien yang akan menyediakan kemampuan untuk menghitung tumpukan dalam waktu linier sambil memastikan bahwa elemen dicacah secara berurutan - ini akan membutuhkan O(n log n). Karena pemesanan pada koleksi dapat dicapai dengan mudah dengan .OrderBy(x => x.Priority) dan tidak setiap pelanggan peduli dengan pencacahan dengan pesanan ini, kami percaya lebih baik memberikan perintah pencacahan yang tidak ditentukan.

Lampiran

Lampiran A: Bahasa lain dengan fungsi antrian prioritas

| Bahasa | Ketik | Catatan |
|:-:|:-:|:-:|
| Jawa | Antrian Prioritas | Memperluas kelas abstrak AbstractQueue dan mengimplementasikan antarmuka Queue . |
| karat | BinaryHeap | |
| Cepat | CFBinaryHeap | |
| C++ | prioritas_antrian | |
| Python | tumpukan | |
| Pergi | tumpukan | Ada antarmuka tumpukan. |

Lampiran B: Dapat Ditemukan

Perhatikan bahwa ketika membahas struktur data, istilah _heap_ digunakan 4 kali lebih sering daripada _priority queue_.

  • "array" AND "data structure" — 17.400.000 hasil
  • "stack" AND "data structure" — 12.100.000 hasil
  • "queue" AND "data structure" — 3.850.000 hasil
  • "heap" AND "data structure" — 1.830.000 hasil
  • "priority queue" AND "data structure" — 430.000 hasil
  • "trie" AND "data structure" — 335.000 hasil

Silakan tinjau, saya senang menerima umpan balik dan terus mengulangi :) Saya merasa kita sedang bertemu! Juga senang menerima pertanyaan — akan menambahkannya ke FAQ!

@pgolebiowski Saya tidak cenderung menggunakan API berbasis pegangan (saya berharap untuk membungkus ini sesuai contoh 2 jika saya memasang kembali kode yang ada) tetapi sebagian besar terlihat bagus untuk saya. Saya mungkin mencoba implementasi yang Anda sebutkan untuk melihat bagaimana rasanya, tetapi berikut adalah beberapa komentar:

  • Tampaknya aneh bahwa TryDequeue mengembalikan sebuah simpul, karena itu sebenarnya bukan pegangan pada saat itu (saya lebih suka dua parameter terpisah)
  • Apakah akan stabil jika dua elemen diantrekan dengan prioritas yang sama mereka dequeue dalam urutan yang dapat diprediksi? (baik untuk memiliki reproduktifitas; dapat diimplementasikan dengan cukup mudah oleh konsumen jika tidak)
  • Parameter untuk Merge seharusnya PriorityQueue'2 bukan PriorityQueueNode'2 , dan dapatkah Anda mengklarifikasi perilakunya? Saya tidak akrab dengan tumpukan pasangan, tetapi mungkin kedua tumpukan itu tumpang tindih setelahnya
  • Saya bukan penggemar nama Contains untuk metode 2 parameter: itu bukan nama yang saya kira untuk metode gaya TryGet
  • Haruskah kelas mendukung IEqualityComparer<TElement> untuk tujuan Contains ?
  • Sepertinya tidak ada cara untuk secara efisien menentukan apakah sebuah simpul masih ada di tumpukan (tidak yakin kapan saya akan menggunakan ini, pikir)
  • Aneh bahwa Remove return adalah bool ; Saya berharap itu disebut TryRemove atau dilemparkan (yang saya asumsikan Update melakukannya jika simpul tidak ada di heap).

@VisualMelon terima kasih banyak atas umpan baliknya! Akan dengan cepat menyelesaikan ini, pasti setuju:

  • Tampaknya aneh bahwa TryDequeue mengembalikan sebuah simpul, karena itu sebenarnya bukan pegangan pada saat itu (saya lebih suka dua parameter terpisah)
  • Parameter untuk Merge seharusnya PriorityQueue'2 bukan PriorityQueueNode'2 , dan dapatkah Anda mengklarifikasi perilakunya? Saya tidak akrab dengan tumpukan pasangan, tetapi mungkin kedua tumpukan itu tumpang tindih setelahnya
  • Aneh bahwa Remove return adalah bool ; Saya berharap itu disebut TryRemove atau dilemparkan (yang saya asumsikan Update melakukannya jika simpul tidak ada di heap).
  • Saya bukan penggemar nama Contains untuk metode 2 parameter: itu bukan nama yang saya kira untuk metode gaya TryGet

Klarifikasi untuk keduanya:

  • Apakah akan stabil jika dua elemen diantrekan dengan prioritas yang sama mereka dequeue dalam urutan yang dapat diprediksi? (baik untuk memiliki reproduktifitas; dapat diimplementasikan dengan cukup mudah oleh konsumen jika tidak)
  • Sepertinya tidak ada cara untuk secara efisien menentukan apakah sebuah simpul masih ada di tumpukan (tidak yakin kapan saya akan menggunakan ini, pikir)

Untuk poin pertama, jika tujuannya adalah reproduktifitas, maka implementasinya akan bersifat deterministik, ya. Jika tujuannya adalah _jika dua elemen yang ditempatkan ke dalam antrian dengan prioritas yang sama, maka mereka akan keluar dalam urutan yang sama di mana mereka ditempatkan ke dalam antrian_ — Saya tidak yakin apakah implementasinya dapat disesuaikan dengan mencapai properti ini, jawaban cepatnya adalah "mungkin tidak".

Untuk poin kedua, ya, tumpukan tidak baik untuk memeriksa apakah ada elemen dalam koleksi, pelanggan harus melacak ini secara terpisah untuk mencapai ini di O(1) atau menggunakan kembali pemetaan yang mereka gunakan untuk pegangan, jika mereka menggunakan mekanisme pembaruan. Jika tidak, O(n).

  • Haruskah kelas mendukung IEqualityComparer<TElement> untuk tujuan Contains ?

Hmm... Saya mulai berpikir bahwa menempatkan Contains ke dalam tanggung jawab antrian prioritas ini mungkin terlalu banyak, dan menggunakan metode dari Linq mungkin sudah cukup ( Contains harus diterapkan pada pencacahan pula).

@pgolebiowski terima kasih atas klarifikasinya

Untuk poin pertama, jika tujuannya adalah reproduktifitas, maka implementasinya akan deterministik, ya

Tidak terlalu deterministik, seperti yang dijamin selamanya (misalnya bahkan implementasinya berubah, perilakunya tidak), jadi saya pikir jawaban yang saya cari adalah 'tidak'. Tidak apa-apa: konsumen dapat menambahkan ID urutan ke prioritas jika mereka membutuhkan ini, meskipun pada saat itu SortedSet akan melakukan pekerjaan itu.

Untuk poin kedua, ya, tumpukan tidak baik untuk memeriksa apakah ada elemen dalam koleksi, pelanggan harus melacak ini secara terpisah untuk mencapai ini di O(1) atau menggunakan kembali pemetaan yang mereka gunakan untuk pegangan, jika mereka menggunakan mekanisme pembaruan. Jika tidak, O(n).

Apakah itu tidak memerlukan subset dari pekerjaan yang diperlukan untuk Remove ? Saya mungkin tidak jelas: Maksud saya diberi PriorityQueueNode , memeriksa apakah itu ada di heap (bukan TElement ).

Hmm... Aku mulai berpikir bahwa menempatkan Berisi ke dalam tanggung jawab antrian prioritas ini mungkin terlalu berlebihan

Saya tidak akan mengeluh jika Contains tidak ada: itu juga jebakan bagi orang-orang yang tidak menyadari bahwa mereka harus menggunakan pegangan.

@pgolebiowski Anda tampaknya sangat mendukung pegangan, apakah saya benar dalam memikirkannya karena alasan efisiensi?

Dari sudut pandang efisiensi, saya menduga pegangan benar-benar yang terbaik, untuk kedua skenario dengan keunikan dan tanpa, jadi saya setuju dengan itu sebagai solusi utama yang ditawarkan oleh kerangka kerja.

Sama sekali:
Dari sudut pandang kegunaan, saya pikir elemen duplikat jarang menjadi apa yang saya inginkan, yang masih membuat saya bertanya-tanya apakah penting bagi kerangka kerja untuk memberikan dukungan untuk kedua model. Tapi... kelas "PrioritySet" ramah-elemen unik setidaknya akan mudah ditambahkan nanti sebagai pembungkus untuk PriorityQueue diusulkan, misalnya sebagai tanggapan atas permintaan lanjutan untuk API yang lebih ramah. (jika permintaan ada. Seperti yang saya pikir mungkin!)

Untuk API saat ini yang diusulkan sendiri, beberapa pemikiran/pertanyaan:

  • bagaimana kalau juga memberikan kelebihan TryPeek(out TElement element, out TPriority priority) ?
  • jika Anda memiliki kunci duplikat yang dapat diperbarui, saya khawatir jika 'dequeue' tidak mengembalikan simpul antrian prioritas, lalu bagaimana Anda memeriksa apakah menghapus simpul yang tepat dari sistem pelacakan pegangan Anda? Karena Anda dapat memiliki lebih dari satu salinan elemen dengan prioritas yang sama.
  • Apakah Remove(PriorityQueueNode) melempar jika simpul tidak ditemukan? atau kembali palsu?
  • Haruskah ada versi TryRemove() yang tidak melempar jika Hapus melempar?
  • Saya tidak yakin Contains() api sering kali berguna? 'Berisi' tampaknya menjadi perhatian yang melihatnya, terutama untuk skenario dengan elemen 'duplikat' dengan prioritas berbeda, atau fitur berbeda lainnya! Dalam hal ini, pengguna akhir mungkin harus melakukan pencarian mereka sendiri. Tapi setidaknya bisa berguna untuk skenario tanpa duplikat.

@pgolebiowski Terima kasih telah meluangkan waktu untuk menyusun proposal baru! Beberapa komentar di sisi saya:

  • Menggemakan komentar @TimLovellSmith , saya tidak yakin Contains() atau TryGetNode() harus ada sama sekali di API dalam bentuk yang diusulkan saat ini. Mereka menyiratkan kesetaraan untuk TElement adalah signifikan, yang mungkin merupakan salah satu hal yang coba dihindari oleh pendekatan berbasis pegangan.
  • Saya mungkin akan mengubah kata public void Dequeue(out TElement element); menjadi public TElement Dequeue();
  • Mengapa metode TryDequeue() perlu mengembalikan prioritas?
  • Bukankah kelas juga harus mengimplementasikan ICollection<T> atau IReadOnlyCollection<T> ?
  • Apa yang harus terjadi jika saya mencoba memperbarui PriorityQueueNode yang dikembalikan dari instance PriorityQueue yang berbeda?
  • Apakah kita ingin mendukung operasi penggabungan yang efisien? AFAICT ini menyiratkan bahwa kita tidak dapat menggunakan representasi berbasis array. Bagaimana hal ini akan berdampak pada implementasi dalam hal alokasi?

Sebagian besar insinyur perangkat lunak hanya mengenal implementasi tumpukan biner berbasis array — ini adalah yang paling sederhana, tetapi sayangnya bukan yang paling efisien. Untuk input acak umum, dua contoh tipe heap yang lebih efisien adalah: heap kuartener dan heap berpasangan.

Apa trade-off untuk memilih pendekatan yang terakhir? Apakah mungkin menggunakan implementasi berbasis array untuk itu?

Itu tidak dapat mengimplementasikan ICollection<T> atau IReadOnlyCollection<T> ketika tidak memiliki tanda tangan yang tepat untuk 'Tambah' dan 'Hapus' dll.

Ini jauh lebih dekat dalam semangat/bentuknya dengan ICollection<KeyValuePair<T,Priority>>

Ini jauh lebih dekat dalam semangat/bentuknya dengan ICollection<KeyValuePair<T,Priority>>

Bukankah KeyValuePair<TPriority, TElement> , karena pemesanan dilakukan oleh TPriority, yang secara efektif merupakan mekanisme penguncian?

Oke, sepertinya kami secara keseluruhan mendukung penghapusan metode Contains dan TryGet . Akan menghapusnya di revisi berikutnya dan menjelaskan alasan penghapusan di FAQ.

Adapun antarmuka yang diimplementasikan — bukankah IEnumerable<PriorityQueueNode<TElement, TPriority>> cukup? Apa jenis fungsi yang hilang?

Pada KeyValuePair - ada beberapa suara bahwa tuple atau struct dengan .Element dan .Priority lebih diinginkan. Saya pikir saya mendukung ini.

Bukankah KeyValuePair<TPriority, TElement> , karena pemesanan dilakukan oleh TPriority, yang secara efektif merupakan mekanisme penguncian?

Ada argumen yang bagus untuk kedua belah pihak. Di satu sisi, ya, persis seperti yang baru saja Anda katakan. Di sisi lain, kunci dalam koleksi KVP umumnya diharapkan unik, dan sangat valid untuk memiliki beberapa elemen dengan prioritas yang sama.

Di sisi lain, kunci dalam koleksi KVP umumnya diharapkan unik

Saya tidak setuju dengan pernyataan ini - kumpulan pasangan nilai kunci hanya itu; persyaratan keunikan apa pun berlapis di atasnya.

bukankah IEnumerable<PriorityQueueNode<TElement, TPriority>> cukup? Apa jenis fungsi yang hilang?

Saya pribadi mengharapkan IReadOnlyCollection<PQN<TElement, TPriority>> untuk diimplementasikan, karena API yang disediakan sebagian besar sudah memenuhi antarmuka itu. Plus, itu akan konsisten dengan jenis koleksi lainnya.

Mengenai antarmuka:

```
bool publik TryGetNode (elemen TElement, keluar PriorityQueueNodesimpul); // Pada)

What's the point of it if I can just do enumerate collection and do comparison of elements? And why only Try version?

public PriorityQueueNode<TElement, TPriority> Peek(); // O(1)
public void Dequeue(out TElement element); // O(log n)
I find it a bit strange to have discrepancy between Dequeue and Peek. They do pretty much same thing expect one is removing element from queue and other is not, it's looks weird for me if one returns priority and element and other just element.

public void Remove(PriorityQueueNode<TElement, TPriority> node); // O(log n)
`Queue` doesn't have `Remove` method, why `PriorityQueue` should ?


I would also like to see constructor

Antrian Prioritas publik (Inumerable>koleksi);
```

Saya juga ingin PriorityQueue mengimplementasikan antarmuka IReadonlyCollection<> .

Melompat ke hal lain selain permukaan API publik.

Kemampuan tersebut diperlukan untuk mengimplementasikan misalnya algoritma jalur terpendek Dijkstra atau penjadwal pekerjaan yang perlu menangani perubahan prioritas. Mekanisme pembaruan tidak ada di Java, yang terbukti mengecewakan bagi para insinyur, misalnya dalam tiga pertanyaan StackOverflow yang dilihat lebih dari 32 ribu kali: contoh, contoh, contoh. Untuk menghindari pengenalan API dengan nilai terbatas seperti itu, kami percaya bahwa persyaratan mendasar untuk fungsionalitas antrian prioritas yang kami berikan adalah untuk mendukung kemampuan memperbarui prioritas untuk elemen yang sudah ada dalam koleksi.

Saya ingin tidak setuju. Terakhir kali saya menulis Dijkstra di C++ std::priority_queue sudah cukup dan tidak menangani pembaruan prioritas. Konsensus umum AFAIK untuk kasus itu adalah menambahkan elemen palsu ke dalam antrian dengan prioritas dan nilai yang diubah dan monitor apakah kita memproses nilai atau tidak. Hal yang sama dapat dilakukan dengan penjadwal pekerjaan.

Sejujurnya saya tidak yakin seperti apa Dijkstra dengan proposal antrian saat ini. Bagaimana saya bisa melacak node yang saya perlukan prioritas pembaruan? Dengan TryGetNode()? Atau punya koleksi node lain? Ingin melihat kode untuk proposal saat ini.

Jika Anda melihat ke Wikipedia, tidak ada asumsi memperbarui prioritas untuk antrian prioritas. Sama untuk semua bahasa lain yang tidak memiliki fungsi itu dan lolos begitu saja. Saya tahu "berusaha menjadi lebih baik", tetapi apakah benar-benar ada permintaan untuk itu?

Untuk input acak umum, dua contoh tipe heap yang lebih efisien adalah: heap kuartener dan heap berpasangan. Untuk informasi lebih lanjut tentang tumpukan, silakan merujuk ke Wikipedia dan makalah ini.

Saya telah melihat ke dalam kertas dan ini adalah kutipan darinya:

Hasilnya menunjukkan bahwa pilihan implementasi yang optimal sangat bergantung pada input. Lebih lanjut, ini menunjukkan bahwa perhatian harus diberikan untuk mengoptimalkan kinerja cache, terutama pada penghalang L1-L2. Ini menunjukkan bahwa struktur yang rumit dan tidak memperhatikan cache tidak mungkin berkinerja baik dibandingkan dengan struktur yang lebih sederhana dan sadar-cache.

Dari tampilannya, proposal antrian saat ini akan dikunci di belakang implementasi pohon daripada larik, dan sesuatu memberi tahu saya apa kemungkinan simpul pohon yang tersebar di sekitar memori tidak seperforma larik elemen.

Saya pikir memiliki tolok ukur untuk membandingkan tumpukan biner sederhana berdasarkan array dan memasangkan tumpukan akan ideal untuk membuat keputusan yang tepat, sebelum itu, saya tidak berpikir cerdas untuk mengunci desain di belakang implementasi tertentu (Saya melihat Anda Merge metode).

Melompat ke topik lain, saya pribadi lebih suka memiliki KeyValuePairdari kelas kustom baru.

  • Lebih sedikit permukaan API
  • Saya dapat melakukan sesuatu seperti ini: `Queue Prioritas baru(Kamus baru() { { 1, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 }, { 5, 5 } }); Saya tahu itu dibatasi oleh keunikan kuncinya. Juga saya pikir itu akan membawa sinergi yang bagus untuk mengkonsumsi koleksi berbasis IDictionary.
  • Ini adalah struct daripada kelas jadi tidak ada pengecualian NullReference
  • Pada titik tertentu, PrioirtyQueue perlu diserialisasi/diserialisasi dan saya pikir akan lebih mudah untuk melakukan apa dengan objek yang sudah ada.

Kelemahannya adalah perasaan campur aduk melihat TPriority Key tetapi saya pikir dokumentasi yang baik dapat menyelesaikannya.
`

Sebagai solusi untuk Dijkstra, menambahkan elemen palsu ke dalam antrian berfungsi, dan jumlah total tepi grafik yang Anda proses tidak berubah. Tetapi jumlah node sementara yang tinggal di heap yang dihasilkan oleh tepi pemrosesan berubah, dan itu dapat memengaruhi penggunaan memori dan efisiensi enqueue dan dequeue.

Saya salah karena tidak dapat melakukan IReadOnlyCollection, itu seharusnya baik-baik saja. Tidak ada Add() dan Remove() di antarmuka itu, hah! (Apa yang saya pikirkan ...)

@Ivanidzo4ka Komentar Anda semakin meyakinkan saya bahwa masuk akal untuk memiliki dua jenis terpisah: satu tumpukan biner sederhana (yaitu tanpa pembaruan), dan yang kedua seperti yang dijelaskan oleh salah satu proposal:

  • Binary Heaps sederhana, mudah diterapkan, memiliki API kecil, dan diketahui berkinerja baik dalam praktik di banyak skenario penting.
  • Antrian prioritas yang lengkap memberikan manfaat teoretis untuk beberapa skenario (yaitu mengurangi kompleksitas ruang, dan mengurangi kompleksitas waktu untuk pencarian pada grafik yang terhubung secara padat), dan menyediakan API alami untuk lebih banyak algoritma/skenario.

Di masa lalu, saya sebagian besar lolos dengan tumpukan biner yang saya tulis bagian terbaik dari satu dekade yang lalu, dan kombinasi SortedSet / Dictionary , dan ada skenario di mana saya telah menggunakan kedua jenis dalam peran terpisah (KSSP muncul di pikiran).

Ini adalah struct daripada kelas jadi tidak ada pengecualian NullReference

Saya berpendapat sebaliknya: jika seseorang memberikan nilai default, saya ingin mereka melihat NRE; namun, saya pikir kita perlu memperjelas di mana kita mengharapkan hal-hal ini digunakan: node/handle mungkin harus kelas, tetapi jika kita hanya membaca/mengembalikan pasangan, maka saya setuju itu harus berupa struct.


Saya tergoda untuk menyarankan bahwa mungkin untuk memperbarui elemen serta prioritas dalam proposal berbasis pegangan. Anda dapat mencapai efek yang sama hanya dengan menghapus pegangan dan menambahkan yang baru, tetapi ini adalah operasi yang berguna, dan mungkin memiliki manfaat kinerja tergantung pada implementasinya (misalnya beberapa tumpukan dapat mengurangi prioritas sesuatu yang relatif murah). Perubahan tersebut akan membuatnya lebih rapi untuk menerapkan banyak hal (misalnya ini agak mimpi buruk menginduksi contoh berdasarkan AlgoKit ParingHeap yang ada), terutama yang beroperasi di wilayah yang tidak diketahui dari ruang negara.

Proposal antrian prioritas (v2.1)

Ringkasan

Komunitas .NET Core mengusulkan untuk menambahkan fungsionalitas _priority queue_ library sistem, sebuah struktur data di mana setiap elemen tambahan memiliki prioritas yang terkait dengannya. Secara khusus, kami mengusulkan untuk menambahkan PriorityQueue<TElement, TPriority> ke ruang nama System.Collections.Generic .

Prinsip

Dalam desain kami, kami dipandu oleh prinsip-prinsip berikut (kecuali Anda tahu yang lebih baik):

  • Cakupan yang luas. Kami ingin menawarkan kepada pelanggan .NET Core struktur data berharga yang cukup fleksibel untuk mendukung berbagai macam kasus penggunaan.
  • Belajar dari kesalahan yang diketahui. Kami berusaha untuk memberikan fungsionalitas antrian prioritas yang akan bebas dari masalah yang dihadapi pelanggan yang ada dalam kerangka kerja dan bahasa lain, misalnya Java, Python, C++, Rust. Kami akan menghindari membuat pilihan desain yang diketahui membuat pelanggan tidak senang dan mengurangi kegunaan antrian prioritas.
  • Sangat hati-hati dengan keputusan pintu 1 arah. Setelah API diperkenalkan, itu tidak dapat dimodifikasi atau dihapus, hanya diperpanjang. Kami akan dengan hati-hati menganalisis pilihan desain untuk menghindari solusi suboptimal yang akan membuat pelanggan kami terjebak selamanya.
  • Hindari kelumpuhan desain. Kami menerima bahwa mungkin tidak ada solusi yang sempurna. Kami akan menyeimbangkan trade-off dan bergerak maju dengan pengiriman, untuk akhirnya memberikan pelanggan kami fungsionalitas yang telah mereka tunggu selama bertahun-tahun.

Latar belakang

Dari sudut pandang pelanggan

Secara konseptual, antrian prioritas adalah kumpulan elemen, di mana setiap elemen memiliki prioritas terkait. Fungsi terpenting dari antrian prioritas adalah menyediakan akses yang efisien ke elemen dengan prioritas tertinggi dalam koleksi, dan opsi untuk menghapus elemen itu. Perilaku yang diharapkan juga dapat mencakup: 1) kemampuan untuk mengubah prioritas elemen yang sudah ada dalam koleksi; 2) kemampuan untuk menggabungkan beberapa antrian prioritas.

Latar belakang ilmu komputer

Antrian prioritas adalah struktur data abstrak, yaitu konsep dengan karakteristik perilaku tertentu, seperti yang dijelaskan pada bagian sebelumnya. Implementasi antrian prioritas yang paling efisien didasarkan pada tumpukan. Namun, bertentangan dengan kesalahpahaman umum, tumpukan juga merupakan struktur data abstrak, dan dapat diwujudkan dengan berbagai cara, masing-masing menawarkan manfaat dan kerugian yang berbeda.

Sebagian besar insinyur perangkat lunak hanya mengenal implementasi tumpukan biner berbasis array — ini adalah yang paling sederhana, tetapi sayangnya bukan yang paling efisien. Untuk input acak umum, dua contoh tipe heap yang lebih efisien adalah: heap kuartener dan heap berpasangan . Untuk informasi lebih lanjut tentang tumpukan, silakan merujuk ke Wikipedia dan makalah ini .

Mekanisme pembaruan adalah tantangan desain utama

Diskusi kami telah menunjukkan bahwa area yang paling menantang dalam desain, dan pada saat yang sama dengan dampak terbesar pada API, adalah mekanisme pembaruan. Secara khusus, tantangannya adalah untuk menentukan apakah dan bagaimana produk yang ingin kami tawarkan kepada pelanggan harus mendukung pembaruan prioritas elemen yang sudah ada dalam koleksi.

Kemampuan tersebut diperlukan untuk mengimplementasikan misalnya algoritma jalur terpendek Dijkstra atau penjadwal pekerjaan yang perlu menangani perubahan prioritas. Mekanisme pembaruan tidak ada di Java, yang terbukti mengecewakan bagi para insinyur, misalnya dalam tiga pertanyaan StackOverflow yang dilihat lebih dari 32 ribu kali: example , example , example . Untuk menghindari pengenalan API dengan nilai terbatas seperti itu, kami percaya bahwa persyaratan mendasar untuk fungsionalitas antrian prioritas yang kami berikan adalah untuk mendukung kemampuan memperbarui prioritas untuk elemen yang sudah ada dalam koleksi.

Untuk memberikan mekanisme pembaruan, kami harus memastikan bahwa pelanggan dapat secara spesifik tentang apa yang sebenarnya ingin mereka perbarui. Kami telah mengidentifikasi dua cara untuk mengirimkan ini: a) melalui pegangan; dan b) melalui penegakan keunikan elemen dalam koleksi. Masing-masing datang dengan manfaat dan biaya yang berbeda.

Opsi (a): Menangani. Dalam pendekatan ini, setiap kali elemen ditambahkan ke antrian, struktur data menyediakan pegangan uniknya. Jika pelanggan ingin menggunakan mekanisme pembaruan, mereka perlu melacak pegangan tersebut sehingga nantinya mereka dapat dengan jelas menentukan elemen mana yang ingin mereka perbarui. Biaya utama dari solusi ini adalah bahwa pelanggan perlu mengelola petunjuk ini. Namun, itu tidak berarti bahwa perlu ada alokasi internal untuk mendukung pegangan dalam antrian prioritas — heap berbasis non-array didasarkan pada node, di mana setiap node secara otomatis menanganinya sendiri. Sebagai contoh, lihat API metode PairingHeap.Update .

Opsi (b): Keunikan. Pendekatan ini menempatkan dua batasan tambahan pada pelanggan: i) elemen dalam antrian prioritas harus sesuai dengan semantik kesetaraan tertentu, yang membawa kelas baru bug pengguna potensial; ii) dua elemen yang sama tidak dapat disimpan dalam antrian yang sama. Dengan membayar biaya ini, kami mendapatkan manfaat dari mendukung mekanisme pembaruan tanpa menggunakan pendekatan pegangan. Namun, implementasi apa pun yang memanfaatkan keunikan/kesetaraan untuk menentukan elemen yang akan diperbarui akan memerlukan pemetaan internal ekstra, sehingga dilakukan di O(1) dan bukan di O(n).

Rekomendasi

Kami merekomendasikan untuk menambahkan ke perpustakaan sistem kelas PriorityQueue<TElement, TPriority> yang mendukung mekanisme pembaruan melalui pegangan. Implementasi yang mendasarinya adalah tumpukan pasangan.

public class PriorityQueueNode<TElement, TPriority>
{
    public TElement Element { get; }
    public TPriority Priority { get; }
}

public class PriorityQueue<TElement, TPriority> :
    IEnumerable<PriorityQueueNode<TElement, TPriority>>,
    IReadOnlyCollection<PriorityQueueNode<TElement, TPriority>>
{
    public PriorityQueue();
    public PriorityQueue(IComparer<TPriority> comparer);

    public IComparer<TPriority> Comparer { get; }
    public int Count { get; }

    public bool IsEmpty { get; }
    public void Clear();

    public PriorityQueueNode<TElement, TPriority> Enqueue(TElement element, TPriority priority); //O(log n)

    public PriorityQueueNode<TElement, TPriority> Peek(); // O(1)
    public bool TryPeek(out PriorityQueueNode<TElement, TPriority> node); // O(1)
    public bool TryPeek(out TElement element, out TPriority priority); // O(1)
    public bool TryPeek(out TElement element); // O(1)

    public PriorityQueueNode<TElement, TPriority> Dequeue(); // O(log n)
    public void Dequeue(out TElement element, out TPriority priority); // O(log n)
    public bool TryDequeue(out PriorityQueueNode<TElement, TPriority> node); // O(log n)
    public bool TryDequeue(out TElement element, out TPriority priority); // O(log n)
    public bool TryDequeue(out TElement element); // O(log n)

    public void Update(PriorityQueueNode<TElement, TPriority> node, TPriority priority); // O(log n)
    public void Remove(PriorityQueueNode<TElement, TPriority> node); // O(log n)

    public void Merge(PriorityQueue<TElement, TPriority> other) // O(1)

    public IEnumerator<PriorityQueueNode<TElement, TPriority>> GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator();

    public struct Enumerator : IEnumerator<PriorityQueueNode<TElement, TPriority>>
    {
        public PriorityQueueNode<TElement, TPriority> Current { get; }
        object IEnumerator.Current { get; }
        public bool MoveNext() => throw new NotImplementedException();
        public void Reset() => throw new NotImplementedException();
        public void Dispose() => throw new NotImplementedException();
    }
}

Contoh penggunaan

1) Pelanggan yang tidak peduli dengan mekanisme pembaruan

var queue = new PriorityQueue<Job, double>();
queue.Enqueue(firstJob, 10);
queue.Enqueue(secondJob, 5);
queue.Enqueue(thirdJob, 40);

var withHighestPriority = queue.Peek(); // { element: secondJob, priority: 5 }

2) Pelanggan yang peduli dengan mekanisme pembaruan

var queue = new PriorityQueue<Job, double>();
var mapping = new Dictionary<Job, PriorityQueueNode<Job, double>>();

mapping[job] = queue.Enqueue(job, priority);

queue.Update(mapping[job], newPriority);

FAQ

1. Dalam urutan apa antrian prioritas menghitung elemen?

Dalam urutan yang tidak ditentukan, sehingga enumerasi dapat terjadi di O(n), sama seperti dengan HashSet . Tidak ada implementasi yang efisien yang akan menyediakan kemampuan untuk menghitung tumpukan dalam waktu linier sambil memastikan bahwa elemen dicacah secara berurutan - ini akan membutuhkan O(n log n). Karena pemesanan pada koleksi dapat dicapai dengan mudah dengan .OrderBy(x => x.Priority) dan tidak setiap pelanggan peduli dengan pencacahan dengan pesanan ini, kami percaya lebih baik memberikan perintah pencacahan yang tidak ditentukan.

2. Mengapa tidak ada metode Contains atau TryGet ?

Menyediakan metode tersebut memiliki nilai yang dapat diabaikan, karena menemukan elemen dalam heap memerlukan enumerasi seluruh koleksi, yang berarti bahwa metode Contains atau TryGet akan menjadi pembungkus di sekitar enumerasi. Selanjutnya, untuk memeriksa apakah ada elemen dalam koleksi, antrian prioritas harus mengetahui bagaimana melakukan pemeriksaan kesetaraan objek TElement , yang bukan sesuatu yang kami yakini harus menjadi tanggung jawab antrian prioritas.

3. Mengapa ada kelebihan Dequeue dan TryDequeue yang mengembalikan PriorityQueueNode ?

Ini untuk pelanggan yang ingin menggunakan metode Update atau Remove dan melacak pegangannya. Saat melepaskan elemen dari antrian prioritas, mereka akan menerima pegangan yang dapat mereka gunakan untuk menyesuaikan status sistem pelacakan pegangan mereka.

4. Apa yang terjadi ketika metode Update atau Remove menerima node dari antrian yang berbeda?

Antrian prioritas akan mengeluarkan pengecualian. Setiap node mengetahui antrean prioritas yang dimilikinya, sama seperti LinkedListNode<T> mengetahui LinkedList<T> miliknya.

Lampiran

Lampiran A: Bahasa lain dengan fungsi antrian prioritas

| Bahasa | Ketik | Catatan |
|:-:|:-:|:-:|
| Jawa | Antrian Prioritas | Memperluas kelas abstrak AbstractQueue dan mengimplementasikan antarmuka Queue . |
| karat | BinaryHeap | |
| Cepat | CFBinaryHeap | |
| C++ | prioritas_antrian | |
| Python | tumpukan | |
| Pergi | tumpukan | Ada antarmuka tumpukan. |

Lampiran B: Dapat Ditemukan

Perhatikan bahwa ketika membahas struktur data, istilah _heap_ digunakan 4 kali lebih sering daripada _priority queue_.

  • "array" AND "data structure" — 17.400.000 hasil
  • "stack" AND "data structure" — 12.100.000 hasil
  • "queue" AND "data structure" — 3.850.000 hasil
  • "heap" AND "data structure" — 1.830.000 hasil
  • "priority queue" AND "data structure" — 430.000 hasil
  • "trie" AND "data structure" — 335.000 hasil

Terima kasih semua atas tanggapannya! Saya telah memperbarui proposal ke v2.1. Catatan perubahan:

  • Menghapus metode Contains dan TryGet .
  • Menambahkan FAQ #2: _Mengapa tidak ada metode Contains atau TryGet ?_
  • Menambahkan antarmuka IReadOnlyCollection<PriorityQueueNode<TElement, TPriority>> .
  • Menambahkan kelebihan bool TryPeek(out TElement element, out TPriority priority) .
  • Menambahkan kelebihan bool TryPeek(out TElement element) .
  • Menambahkan kelebihan void Dequeue(out TElement element, out TPriority priority) .
  • Mengubah void Dequeue(out TElement element) menjadi PriorityQueueNode<TElement, TPriority> Dequeue() .
  • Menambahkan kelebihan bool TryDequeue(out TElement element) .
  • Menambahkan kelebihan bool TryDequeue(out PriorityQueueNode<TElement, TPriority> node) .
  • Menambahkan FAQ #3: _Mengapa ada kelebihan Dequeue dan TryDequeue yang mengembalikan PriorityQueueNode ?_
  • Menambahkan FAQ #4: _Apa yang terjadi ketika metode Update atau Remove menerima node dari antrian yang berbeda?_

Terima kasih atas perubahan catatan ;)

Permintaan kecil untuk klarifikasi:

  • Bisakah kita memenuhi syarat FAQ 4 sedemikian rupa sehingga berlaku untuk elemen yang tidak ada dalam antrian? (yaitu yang dihapus)
  • Bisakah kita menambahkan FAQ tentang stabilitas, yaitu jaminan (jika ada) ketika elemen dequeuing dengan prioritas yang sama (pemahaman saya adalah tidak ada rencana untuk membuat jaminan, yang penting untuk diketahui misalnya penjadwalan).

@pgolebiowski Mengenai metode Merge diusulkan:

public void Merge(PriorityQueue<TElement, TPriority> other); // O(1)

Jelas operasi seperti itu tidak akan menyalin semantik, jadi saya bertanya-tanya apakah akan ada gotcha di sekitar membuat perubahan pada this dan other _after_ penggabungan telah dilakukan (mis. untuk memenuhi properti heap).

@eiriktsarpalis @VisualMelon — Terima kasih! Akan membahas poin yang diangkat, ETA 2020-10-04.

Jika orang lain memiliki umpan balik/pertanyaan/masalah/pemikiran lain — silakan bagikan 😊

Proposal antrian prioritas (v2.2)

Ringkasan

Komunitas .NET Core mengusulkan untuk menambahkan fungsionalitas _priority queue_ library sistem, sebuah struktur data di mana setiap elemen tambahan memiliki prioritas yang terkait dengannya. Secara khusus, kami mengusulkan untuk menambahkan PriorityQueue<TElement, TPriority> ke ruang nama System.Collections.Generic .

Prinsip

Dalam desain kami, kami dipandu oleh prinsip-prinsip berikut (kecuali Anda tahu yang lebih baik):

  • Cakupan yang luas. Kami ingin menawarkan kepada pelanggan .NET Core struktur data berharga yang cukup fleksibel untuk mendukung berbagai macam kasus penggunaan.
  • Belajar dari kesalahan yang diketahui. Kami berusaha untuk memberikan fungsionalitas antrian prioritas yang akan bebas dari masalah yang dihadapi pelanggan yang ada dalam kerangka kerja dan bahasa lain, misalnya Java, Python, C++, Rust. Kami akan menghindari membuat pilihan desain yang diketahui membuat pelanggan tidak senang dan mengurangi kegunaan antrian prioritas.
  • Sangat hati-hati dengan keputusan pintu 1 arah. Setelah API diperkenalkan, itu tidak dapat dimodifikasi atau dihapus, hanya diperpanjang. Kami akan dengan hati-hati menganalisis pilihan desain untuk menghindari solusi suboptimal yang akan membuat pelanggan kami terjebak selamanya.
  • Hindari kelumpuhan desain. Kami menerima bahwa mungkin tidak ada solusi yang sempurna. Kami akan menyeimbangkan trade-off dan bergerak maju dengan pengiriman, untuk akhirnya memberikan pelanggan kami fungsionalitas yang telah mereka tunggu selama bertahun-tahun.

Latar belakang

Dari sudut pandang pelanggan

Secara konseptual, antrian prioritas adalah kumpulan elemen, di mana setiap elemen memiliki prioritas terkait. Fungsi terpenting dari antrian prioritas adalah menyediakan akses yang efisien ke elemen dengan prioritas tertinggi dalam koleksi, dan opsi untuk menghapus elemen itu. Perilaku yang diharapkan juga dapat mencakup: 1) kemampuan untuk mengubah prioritas elemen yang sudah ada dalam koleksi; 2) kemampuan untuk menggabungkan beberapa antrian prioritas.

Latar belakang ilmu komputer

Antrian prioritas adalah struktur data abstrak, yaitu konsep dengan karakteristik perilaku tertentu, seperti yang dijelaskan pada bagian sebelumnya. Implementasi antrian prioritas yang paling efisien didasarkan pada tumpukan. Namun, bertentangan dengan kesalahpahaman umum, tumpukan juga merupakan struktur data abstrak, dan dapat diwujudkan dengan berbagai cara, masing-masing menawarkan manfaat dan kerugian yang berbeda.

Sebagian besar insinyur perangkat lunak hanya mengenal implementasi tumpukan biner berbasis array — ini adalah yang paling sederhana, tetapi sayangnya bukan yang paling efisien. Untuk input acak umum, dua contoh tipe heap yang lebih efisien adalah: heap kuartener dan heap berpasangan . Untuk informasi lebih lanjut tentang tumpukan, silakan merujuk ke Wikipedia dan makalah ini .

Mekanisme pembaruan adalah tantangan desain utama

Diskusi kami telah menunjukkan bahwa area yang paling menantang dalam desain, dan pada saat yang sama dengan dampak terbesar pada API, adalah mekanisme pembaruan. Secara khusus, tantangannya adalah untuk menentukan apakah dan bagaimana produk yang ingin kami tawarkan kepada pelanggan harus mendukung pembaruan prioritas elemen yang sudah ada dalam koleksi.

Kemampuan tersebut diperlukan untuk mengimplementasikan misalnya algoritma jalur terpendek Dijkstra atau penjadwal pekerjaan yang perlu menangani perubahan prioritas. Mekanisme pembaruan tidak ada di Java, yang terbukti mengecewakan bagi para insinyur, misalnya dalam tiga pertanyaan StackOverflow yang dilihat lebih dari 32 ribu kali: example , example , example . Untuk menghindari pengenalan API dengan nilai terbatas seperti itu, kami percaya bahwa persyaratan mendasar untuk fungsionalitas antrian prioritas yang kami berikan adalah untuk mendukung kemampuan memperbarui prioritas untuk elemen yang sudah ada dalam koleksi.

Untuk memberikan mekanisme pembaruan, kami harus memastikan bahwa pelanggan dapat secara spesifik tentang apa yang sebenarnya ingin mereka perbarui. Kami telah mengidentifikasi dua cara untuk mengirimkan ini: a) melalui pegangan; dan b) melalui penegakan keunikan elemen dalam koleksi. Masing-masing datang dengan manfaat dan biaya yang berbeda.

Opsi (a): Menangani. Dalam pendekatan ini, setiap kali elemen ditambahkan ke antrian, struktur data menyediakan pegangan uniknya. Jika pelanggan ingin menggunakan mekanisme pembaruan, mereka perlu melacak pegangan tersebut sehingga nantinya mereka dapat dengan jelas menentukan elemen mana yang ingin mereka perbarui. Biaya utama dari solusi ini adalah bahwa pelanggan perlu mengelola petunjuk ini. Namun, itu tidak berarti bahwa perlu ada alokasi internal untuk mendukung pegangan dalam antrian prioritas — heap berbasis non-array didasarkan pada node, di mana setiap node secara otomatis menanganinya sendiri. Sebagai contoh, lihat API metode PairingHeap.Update .

Opsi (b): Keunikan. Pendekatan ini menempatkan dua batasan tambahan pada pelanggan: i) elemen dalam antrian prioritas harus sesuai dengan semantik kesetaraan tertentu, yang membawa kelas baru bug pengguna potensial; ii) dua elemen yang sama tidak dapat disimpan dalam antrian yang sama. Dengan membayar biaya ini, kami mendapatkan manfaat dari mendukung mekanisme pembaruan tanpa menggunakan pendekatan pegangan. Namun, implementasi apa pun yang memanfaatkan keunikan/kesetaraan untuk menentukan elemen yang akan diperbarui akan memerlukan pemetaan internal ekstra, sehingga dilakukan di O(1) dan bukan di O(n).

Rekomendasi

Kami merekomendasikan untuk menambahkan ke perpustakaan sistem kelas PriorityQueue<TElement, TPriority> yang mendukung mekanisme pembaruan melalui pegangan. Implementasi yang mendasarinya adalah tumpukan pasangan.

public class PriorityQueueNode<TElement, TPriority>
{
    public TElement Element { get; }
    public TPriority Priority { get; }
}

public class PriorityQueue<TElement, TPriority> :
    IEnumerable<PriorityQueueNode<TElement, TPriority>>,
    IReadOnlyCollection<PriorityQueueNode<TElement, TPriority>>
{
    public PriorityQueue();
    public PriorityQueue(IComparer<TPriority> comparer);

    public IComparer<TPriority> Comparer { get; }
    public int Count { get; }

    public bool IsEmpty { get; }
    public void Clear();

    public PriorityQueueNode<TElement, TPriority> Enqueue(TElement element, TPriority priority); //O(log n)

    public PriorityQueueNode<TElement, TPriority> Peek(); // O(1)
    public bool TryPeek(out PriorityQueueNode<TElement, TPriority> node); // O(1)
    public bool TryPeek(out TElement element, out TPriority priority); // O(1)
    public bool TryPeek(out TElement element); // O(1)

    public PriorityQueueNode<TElement, TPriority> Dequeue(); // O(log n)
    public void Dequeue(out TElement element, out TPriority priority); // O(log n)
    public bool TryDequeue(out PriorityQueueNode<TElement, TPriority> node); // O(log n)
    public bool TryDequeue(out TElement element, out TPriority priority); // O(log n)
    public bool TryDequeue(out TElement element); // O(log n)

    public void Update(PriorityQueueNode<TElement, TPriority> node, TPriority priority); // O(log n)
    public void Remove(PriorityQueueNode<TElement, TPriority> node); // O(log n)

    public IEnumerator<PriorityQueueNode<TElement, TPriority>> GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator();

    public struct Enumerator : IEnumerator<PriorityQueueNode<TElement, TPriority>>
    {
        public PriorityQueueNode<TElement, TPriority> Current { get; }
        object IEnumerator.Current { get; }
        public bool MoveNext() => throw new NotImplementedException();
        public void Reset() => throw new NotImplementedException();
        public void Dispose() => throw new NotImplementedException();
    }
}

Contoh penggunaan

1) Pelanggan yang tidak peduli dengan mekanisme pembaruan

var queue = new PriorityQueue<Job, double>();
queue.Enqueue(firstJob, 10);
queue.Enqueue(secondJob, 5);
queue.Enqueue(thirdJob, 40);

var withHighestPriority = queue.Peek(); // { element: secondJob, priority: 5 }

2) Pelanggan yang peduli dengan mekanisme pembaruan

var queue = new PriorityQueue<Job, double>();
var mapping = new Dictionary<Job, PriorityQueueNode<Job, double>>();

mapping[job] = queue.Enqueue(job, priority);

queue.Update(mapping[job], newPriority);

FAQ

1. Dalam urutan apa antrian prioritas menghitung elemen?

Dalam urutan yang tidak ditentukan, sehingga enumerasi dapat terjadi di O(n), sama seperti dengan HashSet . Tidak ada implementasi yang efisien yang akan menyediakan kemampuan untuk menghitung tumpukan dalam waktu linier sambil memastikan bahwa elemen dicacah secara berurutan - ini akan membutuhkan O(n log n). Karena pemesanan pada koleksi dapat dicapai dengan mudah dengan .OrderBy(x => x.Priority) dan tidak setiap pelanggan peduli dengan pencacahan dengan pesanan ini, kami percaya lebih baik memberikan perintah pencacahan yang tidak ditentukan.

2. Mengapa tidak ada metode Contains atau TryGet ?

Menyediakan metode tersebut memiliki nilai yang dapat diabaikan, karena menemukan elemen dalam heap memerlukan enumerasi seluruh koleksi, yang berarti bahwa metode Contains atau TryGet akan menjadi pembungkus di sekitar enumerasi. Selanjutnya, untuk memeriksa apakah ada elemen dalam koleksi, antrian prioritas harus mengetahui bagaimana melakukan pemeriksaan kesetaraan objek TElement , yang bukan sesuatu yang kami yakini harus menjadi tanggung jawab antrian prioritas.

3. Mengapa ada kelebihan Dequeue dan TryDequeue yang mengembalikan PriorityQueueNode ?

Ini untuk pelanggan yang ingin menggunakan metode Update atau Remove dan melacak pegangannya. Saat melepaskan elemen dari antrian prioritas, mereka akan menerima pegangan yang dapat mereka gunakan untuk menyesuaikan status sistem pelacakan pegangan mereka.

4. Apa yang terjadi ketika metode Update atau Remove menerima node dari antrian yang berbeda?

Antrian prioritas akan mengeluarkan pengecualian. Setiap node mengetahui antrean prioritas yang dimilikinya, sama seperti LinkedListNode<T> mengetahui LinkedList<T> miliknya. Lebih lanjut, jika sebuah node telah dihapus dari antrian, mencoba untuk memanggil Update atau Remove di atasnya juga akan menghasilkan pengecualian.

5. Mengapa tidak ada metode Merge ?

Menggabungkan dua antrian prioritas dapat dicapai dalam waktu yang konstan, yang menjadikannya fitur yang menggoda untuk ditawarkan kepada pelanggan. Namun, kami tidak memiliki data untuk menunjukkan bahwa ada permintaan untuk fungsi seperti itu, dan kami tidak dapat membenarkan memasukkannya ke dalam API publik. Lebih lanjut, desain fungsi semacam itu tidak sepele dan, mengingat bahwa fitur ini mungkin tidak diperlukan, hal itu dapat memperumit permukaan dan implementasi API yang tidak perlu.

Namun demikian, tidak menyertakan metode Merge sekarang adalah pintu 2 arah — jika di masa mendatang pelanggan benar-benar tertarik untuk memiliki fungsionalitas penggabungan yang didukung, dimungkinkan untuk memperluas jenis PriorityQueue . Karena itu, kami menyarankan untuk tidak menyertakan metode Merge , dan melanjutkan peluncuran.

6. Apakah koleksi memberikan jaminan stabilitas?

Koleksi tidak akan memberikan jaminan stabilitas di luar kotak, yaitu jika dua elemen diantrekan dengan prioritas yang sama, pelanggan tidak akan dapat berasumsi bahwa mereka akan di-dequeued dalam urutan tertentu. Namun, jika pelanggan ingin mencapai stabilitas menggunakan PriorityQueue , mereka dapat menentukan TPriority dan IComparer<TPriority> yang sesuai yang akan memastikannya. Selanjutnya, pengumpulan data akan bersifat deterministik, yaitu untuk urutan operasi tertentu, akan selalu berperilaku dengan cara yang sama, memungkinkan untuk reproduktifitas.

Lampiran

Lampiran A: Bahasa lain dengan fungsi antrian prioritas

| Bahasa | Ketik | Catatan |
|:-:|:-:|:-:|
| Jawa | Antrian Prioritas | Memperluas kelas abstrak AbstractQueue dan mengimplementasikan antarmuka Queue . |
| karat | BinaryHeap | |
| Cepat | CFBinaryHeap | |
| C++ | prioritas_antrian | |
| Python | tumpukan | |
| Pergi | tumpukan | Ada antarmuka tumpukan. |

Lampiran B: Dapat Ditemukan

Perhatikan bahwa ketika membahas struktur data, istilah _heap_ digunakan 4 kali lebih sering daripada _priority queue_.

  • "array" AND "data structure" — 17.400.000 hasil
  • "stack" AND "data structure" — 12.100.000 hasil
  • "queue" AND "data structure" — 3.850.000 hasil
  • "heap" AND "data structure" — 1.830.000 hasil
  • "priority queue" AND "data structure" — 430.000 hasil
  • "trie" AND "data structure" — 335.000 hasil

Catatan perubahan:

  • Menghapus metode void Merge(PriorityQueue<TElement, TPriority> other) // O(1) .
  • Menambahkan FAQ #5: Mengapa tidak ada metode Merge ?
  • Modifikasi FAQ #4 untuk juga menahan node yang telah dihapus dari antrian prioritas.
  • Menambahkan FAQ #6: Apakah koleksi memberikan jaminan stabilitas?

FAQ baru tampak hebat. Saya bermain-main dengan pengkodean Dijkstra terhadap API yang diusulkan, dengan kamus pegangan, dan pada dasarnya tampaknya baik-baik saja.

Satu pelajaran kecil yang saya dapatkan dari melakukan ini adalah bahwa kumpulan nama/kelebihan metode saat ini tidak berfungsi dengan baik untuk pengetikan implisit variabel out . Apa yang ingin saya lakukan dengan kode C# adalah TryDequeue(out var node) - tetapi sayangnya saya perlu memberikan tipe eksplisit variabel out sebagai PriorityQueueNode<> jika tidak, kompiler tidak tahu apakah saya menginginkan node antrian prioritas atau elemen.

    var shortestDistances = new Dictionary<int, int>();
    var queue = new PriorityQueue<int, int>();
    var handles = new Dictionary<int, PriorityQueueNode<int, int>>();
    handles[startNode] = queue.Enqueue(startNode, 0);
    while (queue.TryDequeue(out PriorityQueueNode<int, int> nextQueueNode))
    {
        int nodeToExploreFrom = nextQueueNode.Element, minDistance = nextQueueNode.Priority;
        shortestDistances.Add(nodeToExploreFrom, minDistance);
        handles[nodeToExploreFrom] = null; // so it is clearly already visited
        foreach (int nextNode in nodes)
        {
            int candidatePathDistance = minDistance + edgeDistances[nodeToExploreFrom, nextNode];
            if (handles.TryGetValue(nextNode, out var nextNodeHandle))
            {
                if (nextNodeHandle != null && candidatePathDistance < nextNodeHandle.Priority)
                {
                    queue.Update(nextNodeHandle, candidatePathDistance);
                }
                // or else... we already got there
            }
            else
            {
                handles[nextNode] = queue.Enqueue(nextNode, candidatePathDistance);
            }
        }
    }

Pertanyaan desain utama yang belum terselesaikan dalam pandangan saya adalah apakah implementasi harus mendukung pembaruan prioritas atau tidak. Saya dapat melihat tiga kemungkinan jalur di sini:

  1. Memerlukan keunikan/kesetaraan dan mendukung pembaruan dengan meneruskan elemen asli.
  2. Mendukung pembaruan prioritas menggunakan pegangan.
  3. Tidak mendukung pembaruan prioritas.

Pendekatan ini saling eksklusif dan memiliki set trade-off mereka sendiri, masing-masing:

  1. Pendekatan berbasis kesetaraan memperumit kontrak API dengan memaksakan keunikan dan membutuhkan pembukuan tambahan di bawah tenda. Ini terjadi apakah pengguna membutuhkan pembaruan prioritas atau tidak.
  2. Pendekatan berbasis pegangan akan menyiratkan setidaknya satu alokasi tambahan per elemen enqueued. Meskipun tidak memaksakan keunikan, kesan saya adalah bahwa untuk skenario yang membutuhkan pembaruan invarian ini hampir pasti tersirat (misalnya perhatikan bagaimana kedua contoh yang tercantum di atas menangani toko dalam kamus eksternal yang diindeks oleh elemen itu sendiri).
  3. Pembaruan tidak didukung sama sekali, atau akan memerlukan traversal linear dari heap. Dalam kasus elemen duplikat, pembaruan bisa menjadi ambigu.

Mengidentifikasi aplikasi PriorityQueue umum

Penting bagi kami untuk mengidentifikasi pendekatan mana di atas yang dapat memberikan nilai terbaik bagi sebagian besar pengguna kami. Jadi saya telah mempelajari basis kode .NET, baik internal maupun publik Microsoft, untuk contoh implementasi Heap/PriorityQueue untuk lebih memahami pola penggunaan yang paling umum:

Sebagian besar aplikasi mengimplementasikan variasi dari heap sort atau penjadwalan pekerjaan. Beberapa contoh digunakan untuk algoritma seperti pengurutan topologi atau pengkodean Huffman. Angka yang lebih kecil digunakan untuk menghitung jarak dalam grafik. Dari 80 implementasi PriorityQueue yang diperiksa, hanya 9 yang ditemukan telah mengimplementasikan beberapa bentuk pembaruan prioritas.

Pada saat yang sama, tidak ada implementasi Heap/PriorityQueue di pustaka inti Python, Java, C++, Go, Swift, atau Rust yang mendukung pembaruan prioritas di API mereka.

Rekomendasi

Mengingat data ini, jelas bagi saya bahwa . memerlukan implementasi PriorityQueue dasar yang mengekspos API penting (heapify/push/peek/pop), menawarkan jaminan kinerja tertentu (misalnya tidak ada alokasi tambahan per enqueue) dan tidak menerapkan kendala keunikan. Ini menyiratkan bahwa implementasi _tidak akan mendukung pembaruan prioritas O(log n)_.

Kami juga harus mempertimbangkan untuk menindaklanjuti dengan implementasi heap terpisah yang mendukung _O(log n)_ pembaruan/penghapusan, dan menggunakan pendekatan berbasis kesetaraan. Karena ini akan menjadi tipe khusus, persyaratan keunikan seharusnya tidak menjadi masalah.

Saya telah mengerjakan pembuatan prototipe kedua implementasi, dan akan segera menindaklanjuti dengan proposal API.

Terima kasih banyak atas analisisnya, @eiriktsarpalis! Saya menghargai khususnya waktu untuk menganalisis basis kode Microsoft internal untuk menemukan kasus penggunaan yang relevan dan mendukung diskusi kita dengan data.

Pendekatan berbasis pegangan akan menyiratkan setidaknya satu alokasi tambahan per elemen enqueued.

Asumsi ini salah, Anda tidak memerlukan alokasi tambahan di tumpukan berbasis simpul. Heap pasangan lebih berkinerja untuk input acak umum daripada heap biner berbasis array, yang berarti bahwa Anda akan memiliki insentif untuk menggunakan heap berbasis node ini bahkan untuk antrian prioritas yang tidak mendukung pembaruan. Anda bisa melihat benchmark di makalah yang saya rujuk tadi .

Dari 80 implementasi PriorityQueue yang diperiksa, hanya 9 yang ditemukan telah mengimplementasikan beberapa bentuk pembaruan prioritas.

Bahkan dengan sampel kecil ini, itu adalah 11-12% dari semua penggunaan. Juga, ini mungkin kurang terwakili di domain tertentu seperti misalnya video game, di mana saya berharap persentase ini lebih tinggi.

Berdasarkan data ini, jelas bagi saya bahwa [...]

Saya tidak berpikir kesimpulan seperti itu jelas, mengingat salah satu asumsi utama salah dan dapat diperdebatkan apakah 11-12% pelanggan adalah kasus penggunaan yang cukup penting atau tidak. Apa yang saya lewatkan dalam penilaian Anda adalah evaluasi dampak biaya dari "pembaruan pendukung struktur data" untuk pelanggan yang tidak peduli dengan mekanisme ini — yang bagi saya, biaya ini dapat diabaikan, karena mereka dapat menggunakan struktur data tanpa terpengaruh oleh mekanisme pegangan.

Pada dasarnya:

| | 11-12% kasus penggunaan | 88-89% kasus penggunaan |
|:-:|:-:|:-:|
| peduli dengan pembaruan | ya | tidak |
| terpengaruh secara negatif oleh pegangan | T/A (diinginkan) | tidak |
| dipengaruhi secara positif oleh pegangan | ya | tidak |

Bagi saya, ini adalah pilihan yang mendukung 100% kasus penggunaan, tidak hanya 88-89%.

Asumsi ini salah, Anda tidak memerlukan alokasi tambahan di tumpukan berbasis simpul

Jika prioritas dan item adalah tipe nilai (atau jika keduanya adalah tipe referensi yang tidak Anda miliki dan/atau tidak dapat mengubah tipe dasar), dapatkah Anda menautkan ke implementasi yang menunjukkan tidak ada alokasi tambahan yang diperlukan (atau jelaskan saja bagaimana pencapaiannya)? Itu akan sangat membantu untuk dilihat. Terima kasih.

Akan sangat membantu jika Anda bisa menjelaskan lebih lanjut, atau hanya mengatakan apa yang ingin Anda katakan. Saya perlu memperjelas, akan ada ping-pong, dan itu akan menjadi diskusi panjang. Atau, kita bisa mengatur panggilan.

Saya katakan kita ingin menghindari operasi Enqueue yang memerlukan alokasi, baik pada bagian pemanggil atau bagian dari implementasi (alokasi internal diamortisasi baik-baik saja, misalnya untuk memperluas array yang digunakan dalam implementasi). Saya mencoba memahami bagaimana itu mungkin dengan tumpukan berbasis simpul (misalnya jika objek simpul tersebut diekspos ke pemanggil, yang melarang penyatuan oleh implementasi karena kekhawatiran seputar penggunaan kembali/alias yang tidak tepat). Saya ingin bisa menulis:
C# pq.Enqueue(42, 84);
dan memiliki yang tidak mengalokasikan. Bagaimana implementasi yang Anda rujuk untuk mencapai itu?

atau katakan saja apa yang ingin Anda katakan

Saya pikir saya.

kami ingin menghindari operasi Enqueue yang memerlukan alokasi [...] Saya ingin dapat menulis: pq.Enqueue(42, 84); dan tidak mengalokasikannya.

Dari mana datangnya keinginan ini? Ini bagus untuk memiliki efek samping dari solusi, bukan persyaratan yang harus dipenuhi oleh pelanggan 99,9%. Saya tidak mengerti mengapa Anda memilih dimensi berdampak rendah ini untuk membuat pilihan desain di antara solusi.

Kami tidak membuat pilihan desain berdasarkan pengoptimalan untuk 0,1% pelanggan jika hal ini berdampak negatif pada 12% pelanggan di dimensi lain. "tidak peduli tentang alokasi" + "berurusan dengan dua jenis nilai" adalah kasus tepi.

Saya menemukan dimensi perilaku/fungsi yang didukung jauh lebih penting, terutama ketika merancang struktur data serbaguna tujuan umum untuk khalayak luas dan berbagai kasus penggunaan.

Dari mana datangnya keinginan ini?

Dari menginginkan tipe koleksi inti dapat digunakan dalam skenario yang peduli dengan kinerja. Anda mengatakan solusi berbasis simpul akan mendukung 100% kasus penggunaan: itu tidak terjadi jika setiap enqueue mengalokasikan, seperti List<T> , Dictionary<TKey, TValue> , HashSet<T> , dan seterusnya on akan menjadi tidak dapat digunakan dalam banyak situasi jika mereka dialokasikan pada setiap Add.

Mengapa Anda yakin hanya "0,1%" yang peduli dengan alokasi overhead metode ini? Dari mana data itu berasal?

"tidak peduli tentang alokasi" + "berurusan dengan dua jenis nilai" adalah kasus tepi

Bukan itu. Ini juga bukan hanya "dua tipe nilai". Seperti yang saya pahami, solusi yang diusulkan akan membutuhkan a) alokasi pada setiap enqueue terlepas dari Ts yang terlibat, atau b) akan membutuhkan tipe elemen untuk diturunkan dari beberapa tipe dasar yang diketahui yang pada gilirannya melarang sejumlah besar kemungkinan penggunaan untuk menghindari alokasi ekstra.

@eiriktsarpalis
Jadi Anda jangan lupa opsi apa pun, saya pikir ada opsi 4 yang layak untuk ditambahkan ke opsi 1, 2, dan 3, dalam daftar Anda yang merupakan kompromi:

  1. implementasi yang mendukung kasus penggunaan 12%, sementara juga hampir mengoptimalkan untuk 88% lainnya dengan mengizinkan pembaruan ke elemen yang dapat disamakan, dan hanya _lazily_ membangun tabel pencarian yang diperlukan untuk melakukan pembaruan tersebut saat pertama kali metode pembaruan dipanggil ( dan memperbaruinya pada pembaruan+penghapusan selanjutnya). Oleh karena itu menimbulkan lebih sedikit biaya untuk aplikasi yang tidak menggunakan fungsi tersebut.

Kami mungkin masih memutuskan bahwa karena ada kinerja ekstra yang tersedia untuk 88% atau 12% dari implementasi yang tidak memerlukan struktur data yang dapat diperbarui, atau dioptimalkan untuk satu di tempat pertama, lebih baik memberikan opsi 2 dan 3, daripada opsi 4. Tapi saya pikir kita tidak boleh melupakan opsi lain yang ada.

[Atau saya kira Anda bisa melihat ini sebagai opsi 1 yang lebih baik dan memperbarui deskripsi 1 untuk mengatakan bahwa pembukuan tidak dipaksakan, tetapi malas, dan perilaku persamaan yang benar hanya diperlukan ketika pembaruan digunakan ...]

@stephentoub Itulah yang ada dalam pikiran saya tentang mengatakan apa yang ingin Anda katakan, terima kasih :)

Mengapa Anda yakin bahwa hanya 0,1% yang peduli dengan alokasi overhead dari metode ini? Dari mana data itu berasal?

Dari intuisi, yaitu sumber yang sama berdasarkan yang Anda yakini lebih penting untuk memprioritaskan "tidak ada alokasi tambahan" daripada "kemampuan untuk melakukan pembaruan". Setidaknya untuk mekanisme pembaruan, kami memiliki data bahwa 11-12% pelanggan perlu mendukung perilaku ini. Saya tidak berpikir pelanggan yang dekat dari jarak jauh akan peduli dengan "tidak ada alokasi tambahan".

Dalam kedua kasus, Anda untuk beberapa alasan memilih untuk terpaku pada dimensi memori, melupakan dimensi lain, misalnya kecepatan mentah, yang merupakan trade-off lain untuk pendekatan pilihan Anda. Implementasi berbasis array yang memberikan "tidak ada alokasi tambahan" akan lebih lambat daripada implementasi berbasis node. Sekali lagi, saya pikir itu sewenang-wenang di sini untuk memprioritaskan memori daripada kecepatan.

Mari kita mundur selangkah dan fokus pada apa yang diinginkan pelanggan. Kami memiliki pilihan desain yang mungkin atau mungkin tidak membuat struktur data tidak dapat digunakan untuk 12% pelanggan. Saya pikir kita harus sangat berhati-hati dengan memberikan alasan mengapa kita memilih untuk tidak mendukung ini.

Implementasi berbasis array yang memberikan "tidak ada alokasi tambahan" akan lebih lambat daripada implementasi berbasis node.

Silakan bagikan dua implementasi C# yang Anda gunakan untuk melakukan perbandingan itu dan tolok ukur yang digunakan untuk sampai pada kesimpulan itu. Makalah teoretis tentu berharga tetapi hanya satu bagian kecil dari teka-teki. Yang lebih penting adalah ketika karet memenuhi jalan, mempertimbangkan detail platform yang diberikan dan implementasi yang diberikan, dan Anda dapat memvalidasi pada platform tertentu dengan implementasi spesifik dan kumpulan data/pola penggunaan yang khas/diharapkan. Bisa jadi pernyataan Anda benar. Ini juga mungkin tidak. Saya ingin melihat implementasi/data untuk lebih memahami.

Silakan bagikan dua implementasi C # yang Anda gunakan untuk melakukan perbandingan itu dan tolok ukur yang digunakan untuk sampai pada kesimpulan itu

Ini adalah poin yang valid, makalah yang saya kutip hanya membandingkan dan membandingkan implementasi dalam C++. Ini melakukan beberapa tolok ukur dengan kumpulan data dan pola penggunaan yang berbeda. Saya cukup yakin ini akan dapat ditransfer ke C#, tetapi jika Anda yakin ini adalah sesuatu yang perlu kami gandakan, saya pikir tindakan terbaik adalah meminta rekan kerja untuk melakukan penelitian semacam itu.

@pgolebiowski Saya akan tertarik untuk lebih memahami sifat keberatan Anda. Proposal menganjurkan untuk dua jenis yang terpisah, apakah itu tidak memenuhi kebutuhan Anda?

  1. implementasi yang mendukung kasus penggunaan 12%, sementara juga hampir mengoptimalkan untuk 88% lainnya dengan mengizinkan pembaruan ke elemen yang dapat disamakan, dan hanya dengan malas membangun tabel pencarian yang diperlukan untuk melakukan pembaruan tersebut saat pertama kali metode pembaruan dipanggil ( dan memperbaruinya pada pembaruan+penghapusan berikutnya). Oleh karena itu menimbulkan lebih sedikit biaya untuk aplikasi yang tidak menggunakan fungsi tersebut.

Saya mungkin akan mengklasifikasikannya sebagai pengoptimalan kinerja untuk opsi 1, namun saya melihat beberapa masalah dengan pendekatan khusus itu:

  • Pembaruan sekarang menjadi _O(n)_, yang dapat menghasilkan kinerja yang tidak terduga tergantung pada pola penggunaan.
  • Tabel pencarian juga diperlukan untuk memvalidasi keunikan. Mengantrekan elemen yang sama dua kali _sebelum_ memanggil Pembaruan akan diterima dan bisa dibilang membawa antrian ke keadaan yang tidak konsisten.

@eiriktsarpalis Ini hanya O(n) sekali, dan O(1) sesudahnya, yaitu O(1) diamortisasi. Dan Anda dapat menunda validasi keunikan hingga pembaruan pertama. Tapi mungkin ini terlalu pintar. Dua kelas lebih mudah dijelaskan.

Saya telah menghabiskan beberapa hari terakhir membuat prototipe dua implementasi PriorityQueue: implementasi dasar tanpa dukungan pembaruan dan implementasi yang mendukung pembaruan menggunakan kesetaraan elemen. Saya telah menamai yang pertama PriorityQueue dan yang terakhir, karena tidak ada nama yang lebih baik, PrioritySet . Tujuan saya adalah mengukur ergonomi API dan membandingkan kinerja.

Implementasinya dapat ditemukan di repo ini . Kedua kelas diimplementasikan menggunakan tumpukan quad berbasis array. Implementasi yang dapat diperbarui juga menggunakan kamus yang memetakan elemen ke indeks heap internal.

Antrian Prioritas Dasar

namespace System.Collections.Generic
{
    public class PriorityQueue<TElement, TPriority> : IReadOnlyCollection<(TElement Element, TPriority Priority)>
    {
        // Constructors
        public PriorityQueue();
        public PriorityQueue(int initialCapacity);
        public PriorityQueue(IComparer<TPriority>? comparer);
        public PriorityQueue(int initialCapacity, IComparer<TPriority>? comparer);
        public PriorityQueue(IEnumerable<(TElement Element, TPriority Priority)> values);
        public PriorityQueue(IEnumerable<(TElement Element, TPriority Priority)> values, IComparer<TPriority>? comparer);

        // Properties
        public int Count { get; }
        public IComparer<TPriority> Comparer { get; }

        // O(log(n)) push operation
        public void Enqueue(TElement element, TPriority priority);
        // O(1) peek operations
        public TElement Peek();
        public bool TryPeek(out TElement element, out TPriority priority);
        // O(log(n)) pop operations
        public TElement Dequeue();
        public bool TryDequeue(out TElement element, out TPriority priority);
        // Combined push/pop, generally more efficient than sequential Enqueue();Dequeue() calls.
        public TElement EnqueueDequeue(TElement element, TPriority priority);

        public void Clear();

        public Enumerator GetEnumerator();
        public struct Enumerator : IEnumerator<(TElement Element, TPriority Priority)>, IEnumerator;
    }
}

Berikut adalah contoh dasar menggunakan tipe

var queue = new PriorityQueue<string, int>();

queue.Enqueue("John", 1940);
queue.Enqueue("Paul", 1942);
queue.Enqueue("George", 1943);
queue.Enqueue("Ringo", 1940);

Assert.Equal("John", queue.Dequeue());
Assert.Equal("Ringo", queue.Dequeue());
Assert.Equal("Paul", queue.Dequeue());
Assert.Equal("George", queue.Dequeue());

Antrian Prioritas yang Dapat Diperbarui

namespace System.Collections.Generic
{
    public class PrioritySet<TElement, TPriority> : IReadOnlyCollection<(TElement Element, TPriority Priority)> where TElement : notnull
    {
        // Constructors
        public PrioritySet();
        public PrioritySet(int initialCapacity);
        public PrioritySet(IComparer<TPriority> comparer);
        public PrioritySet(int initialCapacity, IComparer<TPriority>? priorityComparer, IEqualityComparer<TElement>? elementComparer);
        public PrioritySet(IEnumerable<(TElement Element, TPriority Priority)> values);
        public PrioritySet(IEnumerable<(TElement Element, TPriority Priority)> values, IComparer<TPriority>? comparer, IEqualityComparer<TElement>? elementComparer);

        // Members shared with baseline PriorityQueue implementation
        public int Count { get; }
        public IComparer<TPriority> Comparer { get; }
        public void Enqueue(TElement element, TPriority priority);
        public TElement Peek();
        public bool TryPeek(out TElement element, out TPriority priority);
        public TElement Dequeue();
        public bool TryDequeue(out TElement element, out TPriority priority);
        public TElement EnqueueDequeue(TElement element, TPriority priority);

        // Update methods and friends
        public bool Contains(TElement element); // O(1)
        public bool TryRemove(TElement element); // O(log(n))
        public bool TryUpdate(TElement element, TPriority priority); // O(log(n))
        public void EnqueueOrUpdate(TElement element, TPriority priority); // O(log(n))

        public void Clear();
        public Enumerator GetEnumerator();
        public struct Enumerator : IEnumerator<(TElement Element, TPriority Priority)>, IEnumerator;
    }
}

Perbandingan Kinerja

Saya menulis benchmark heapsort sederhana yang membandingkan dua implementasi dalam aplikasi paling dasar mereka. Saya juga menyertakan semacam benchmark yang menggunakan Linq untuk perbandingan:

BenchmarkDotNet=v0.12.1, OS=ubuntu 20.04
AMD EPYC 7452, 1 CPU, 4 logical and 2 physical cores
.NET Core SDK=5.0.100-rc.2.20479.15
  [Host]     : .NET Core 5.0.0 (CoreCLR 5.0.20.47505, CoreFX 5.0.20.47505), X64 RyuJIT
  DefaultJob : .NET Core 5.0.0 (CoreCLR 5.0.20.47505, CoreFX 5.0.20.47505), X64 RyuJIT

| Metode | Ukuran | Berarti | Kesalahan | StdDev | Rasio | RasioSD | Gen 0 | Gen 1 | Gen 2 | Dialokasikan |
|-------------- |------ |-------------:|-----------: |-----------:|------:|--------:|--------:|-------- :|--------:|----------:|
| LinqSort | 30 | 1.439 kami | 0,0072 kita | 0,0064 kami | 1,00 | 0,00 | 0,0095 | - | - | 672 B |
| Antrian Prioritas | 30 | 1.450 kita | 0,0085 kami | 0,0079 kami | 1.01 | 0,01 | - | - | - | - |
| Set Prioritas | 30 | 2,778 kami | 0,0217 kami | 0,0192 kami | 1.93 | 0,02 | - | - | - | - |
| | | | | | | | | | | |
| LinqSort | 300 | 24.727 kami | 0.1032 kami | 0,0915 kami | 1,00 | 0,00 | 0,0305 | - | - | 3912 B |
| Antrian Prioritas | 300 | 29.510 kami | 0,0995 kami | 0,0882 kami | 1.19 | 0,01 | - | - | - | - |
| Set Prioritas | 300 | 47.715 kami | 0,4455 kami | 0,4168 kami | 1.93 | 0,02 | - | - | - | - |
| | | | | | | | | | | |
| LinqSort | 3000 | 412.015 kami | 1,5495 kami | 1.3736 kami | 1,00 | 0,00 | 0,4883 | - | - | 36312 B |
| Antrian Prioritas | 3000 | 491.722 kami | 4.1463 kami | 3.8785 kami | 1.19 | 0,01 | - | - | - | - |
| Set Prioritas | 3000 | 677.959 kami | 3.1996 kami | 2.4981 kami | 1.64 | 0,01 | - | - | - | - |
| | | | | | | | | | | |
| LinqSort | 30000 | 5.223.560 kami | 11.9077 kami | 9.9434 kami | 1,00 | 0,00 | 93.7500 | 93.7500 | 93.7500 | 360910 B |
| Antrian Prioritas | 30000 | 5.688,625 kami | 53.0746 kami | 49.6460 kami | 1.09 | 0,01 | - | - | - | 2 B |
| Set Prioritas | 30000 | 8.124,306 kami | 39.9498 kami | 37.3691 kami | 1.55 | 0,01 | - | - | - | 4 B |

Seperti yang diharapkan, overhead lokasi elemen pelacakan menambahkan hit kinerja yang signifikan, kira-kira 40-50% lebih lambat dibandingkan dengan implementasi dasar.

Saya menghargai semua upaya, saya melihat itu menghabiskan banyak waktu dan energi.

  1. Saya benar-benar tidak melihat alasan untuk 2 struktur data yang hampir identik, di mana yang satu adalah versi yang lebih rendah dari yang lain.
  2. Selanjutnya, bahkan jika Anda ingin memiliki 2 versi antrian prioritas seperti itu, saya tidak melihat bagaimana versi "unggul" lebih baik daripada proposal antrian Prioritas (v2.2) dari 20 hari yang lalu.

tl; dr:

  • Usulan ini menarik!! Tapi itu belum sesuai dengan kasus penggunaan berkinerja tinggi saya.
  • >90% dari beban kinerja geometri/perencanaan gerak komputasi saya adalah PQ enqueue/dequeue karena itulah N^m LogN yang dominan dalam suatu algoritme.
  • Saya mendukung implementasi PQ yang terpisah. Saya biasanya tidak memerlukan pembaruan prioritas, dan kinerja 2x lebih buruk tidak dapat diterima.
  • PrioritySet adalah nama yang membingungkan dan tidak dapat ditemukan vs PriorityQueue
  • Menyimpan prioritas dua kali (sekali dalam elemen saya, sekali dalam antrian) terasa mahal. Salinan struktural & penggunaan memori.
  • Jika prioritas komputasi mahal, saya hanya akan menyimpan Tuple (priority: ComputePriority(element), element) ke dalam PQ, dan fungsi pengambil prioritas saya akan menjadi tuple => tuple.priority .
  • Performa harus di-benchmark per operasi atau sebagai alternatif pada kasus penggunaan dunia nyata (mis., pencarian multi-akhir multi-awal yang dioptimalkan pada grafik)
  • Perilaku enumerasi tidak berurutan tidak terduga. Lebih suka semantik urutan Dequeue()-seperti Antrian?
  • Pertimbangkan untuk mendukung operasi Klon & operasi gabungan.
  • Operasi dasar harus 0-alloc dalam penggunaan kondisi mapan. Saya akan mengumpulkan antrian ini.
  • Pertimbangkan untuk mendukung EnqueueMany yang melakukan heapify untuk membantu penyatuan.

Saya bekerja dalam pencarian kinerja tinggi (perencanaan gerak) & kode geometri komputasi (misalnya algoritma sweepline) yang relevan dengan robotika dan game, saya menggunakan banyak antrian prioritas gulung tangan khusus. Kasus penggunaan umum lainnya yang saya miliki adalah kueri Top-K di mana prioritas yang dapat diperbarui tidak berguna.

Beberapa umpan balik mengenai debat dua implementasi (ya vs tidak ada dukungan pembaruan).

Penamaan:

  • PrioritySet menyiratkan set semantik, tetapi antrian tidak mengimplementasikan ISet.
  • Anda menggunakan nama UpdateablePriorityQueue yang lebih mudah ditemukan jika saya mencari PriorityQueue.

Pertunjukan:

  • Kinerja Antrian Prioritas hampir selalu menjadi hambatan kinerja saya (>90%) dalam algoritme geometri/perencanaan saya
  • Pertimbangkan untuk melewatkan Funcatau Perbandinganuntuk ctor daripada menyalin TPriority sekitar (mahal!). Jika prioritas komputasi mahal, saya akan memasukkan (prioritas, elemen) ke dalam PQ dan memberikan perbandingan yang melihat prioritas cache saya.
  • Sejumlah besar algoritme saya tidak memerlukan pembaruan PQ. Saya akan mempertimbangkan untuk menggunakan PQ bawaan yang tidak mendukung pembaruan, tetapi jika sesuatu memiliki biaya 2x perf untuk mendukung fitur yang tidak saya perlukan (memperbarui) maka itu tidak berguna bagi saya.
  • Untuk analisis / pengorbanan kinerja, penting untuk mengetahui biaya waktu dinding relatif enqueue/dequeue per operasi @eiriktsarpalis -- "pelacakan 2x lebih lambat" tidak cukup untuk mengevaluasi apakah PQ berguna.
  • Saya senang melihat konstruktor Anda melakukan Heapify. Pertimbangkan konstruktor yang mengambil IList, Daftar, atau Array untuk menghindari alokasi pencacahan.
  • Pertimbangkan untuk mengekspos EnqueueMany yang melakukan Heapify jika PQ awalnya kosong, karena dalam performa tinggi itu umum untuk mengumpulkan koleksi.
  • Pertimbangkan untuk membuat Hapus bukan nol array jika elemen tidak mengandung referensi.
  • Alokasi dalam enqueue/dequeue tidak dapat diterima. Algoritme saya adalah alokasi nol karena alasan kinerja, dengan kumpulan kumpulan utas-lokal.

Lebah:

  • Mengkloning antrian prioritas adalah operasi sepele dengan implementasi Anda dan sering kali berguna.

    • Terkait: Haruskah menghitung Antrian Prioritas memiliki semantik seperti antrian? Saya mengharapkan koleksi dequeue-order, mirip dengan apa yang Queue lakukan. Saya berharap Daftar baru(myPriorityQueue) tidak mengubah priorityQueue, tetapi berfungsi seperti yang baru saja saya jelaskan.

  • Seperti disebutkan di atas, lebih baik mengambil Func<TElement, TPriority> daripada memasukkan dengan prioritas. Jika prioritas komputasi mahal, saya cukup memasukkan (priority, element) dan menyediakan fungsi tuple => tuple.priority
  • Menggabungkan dua antrian prioritas terkadang berguna.
  • Aneh bahwa Peek mengembalikan TItem tetapi Enumeration & Enqueue memiliki (TItem, TPriority).

Karena itu, untuk sejumlah besar algoritme saya, item antrian prioritas saya berisi prioritasnya, dan menyimpannya dua kali (sekali di PQ, sekali di item) terdengar tidak efisien. Ini terutama terjadi jika saya memesan dengan banyak kunci (kasus penggunaan serupa dengan OrderBy.thenBy.thenBy). API ini juga membersihkan banyak inkonsistensi di mana Sisipkan diprioritaskan tetapi Peek tidak mengembalikannya.

Akhirnya, perlu dicatat bahwa saya sering memasukkan indeks array ke dalam Antrian Prioritas, daripada elemen array itu sendiri . Ini didukung oleh semua API yang dibahas sejauh ini. Sebagai contoh, jika saya memproses awal/akhir interval pada garis sumbu x, saya mungkin memiliki acara antrian prioritas (x, isStartElseEnd, intervalId) dan memesan dengan x kemudian oleh isStartElseEnd. Ini sering karena saya memiliki struktur data lain yang memetakan dari indeks ke beberapa data yang dihitung.

@pgolebiowski Saya mengambil kebebasan untuk memasukkan implementasi heap pasangan yang Anda usulkan ke tolok ukur, supaya kami dapat membandingkan contoh ketiga pendekatan secara langsung. Berikut adalah hasilnya:

| Metode | Ukuran | Berarti | Kesalahan | StdDev | Median | Gen 0 | Gen 1 | Gen 2 | Dialokasikan |
|-------------- |-------- |-------------------:|---- -------------:|------------------:|---------------- ---:|----------:|------:|------:|-----------:|
| Antrian Prioritas | 10 | 774,7 ns | 3,30 n | 3,08 n | 773,2 ns | - | - | - | - |
| Set Prioritas | 10 | 1,643,0 ns | 3,89 n | 3,45 ns | 1.642.8 ns | - | - | - | - |
| PairingHeap | 10 | 1.660.2 ns | 14,11 ns | 12,51 ns | 1.657,2 ns | 0,0134 | - | - | 960 B |
| Antrian Prioritas | 50 | 6.413,0 ns | 14,95 ns | 13,99 n | 6.409,5 ns | - | - | - | - |
| Set Prioritas | 50 | 12,193,1 ns | 35,41 ns | 29,57 ns | 12.188,3 ns | - | - | - | - |
| PairingHeap | 50 | 13.955,8 ns | 193,36 ns | 180,87 ns | 13.989,2 n | 0,0610 | - | - | 4800 B |
| Antrian Prioritas | 150 | 27,402,5 ns | 76,52 ns | 71,58 ns | 27.410,2 ns | - | - | - | - |
| Set Prioritas | 150 | 48.485,8 ns | 160,22 ns | 149,87 ns | 48.476,3 ns | - | - | - | - |
| PairingHeap | 150 | 56.951,2 ns | 190,52 ns | 168,89 ns | 56,953,6 ns | 0.1831 | - | - | 14400 B |
| Antrian Prioritas | 500 | 124.933,7 ns | 429,20 ns | 380,48 ns | 124.824,4 ns | - | - | - | - |
| Set Prioritas | 500 | 206.310,0 ns | 433,97 ns | 338,81 ns | 206.319.0 ns | - | - | - | - |
| PairingHeap | 500 | 229.423.9 ns | 3.213,33 ns | 2.848,53 ns | 230,398,7 n | 0,4883 | - | - | 48000 B |
| Antrian Prioritas | 1000 | 284.481,8 ns | 475,91 ns | 445,16 ns | 284.445.6 ns | - | - | - | - |
| Set Prioritas | 1000 | 454.989,4 ns | 3.712,11 ns | 3,472,31 ns | 455.354,0 ns | - | - | - | - |
| PairingHeap | 1000 | 459.049,3 ns | 1.706,28 ns | 1.424,82 ns | 459.364.9 ns | 0,9766 | - | - | 96000 B |
| Antrian Prioritas | 10.000 | 3.788.802,4 ns | 11.715,81 ns | 10.958,98 n | 3.787.811,9 ns | - | - | - | 1 B |
| Set Prioritas | 10.000 | 5.963.100,4 ns | 26.669,04 ns | 22.269,86 ns | 5,950,915,5 ns | - | - | - | 2 B |
| PairingHeap | 10.000 | 6.789.719.0 ns | 134.453,01 ns | 265.397,13 ns | 6.918.392.9 ns | 7.8125 | - | - | 960002 B |
| Antrian Prioritas | 1000000 | 595.059.170,7 ns | 4.001.349,38 ns | 3.547.092.00 n | 595.716.610.5 ns | - | - | - | 4376 B |
| Set Prioritas | 1000000 | 1.592.037.780,9 ns | 13.925.896,05 ns | 12.344.944,12 ns | 1.591.051.886,5 ns | - | - | - | 288 B |
| PairingHeap | 1000000 | 1.858.670.560,7 ns | 36.405.433,20 ns | 59.815.170,76 ns | 1.838.721.629,0 ns | 1000.0000 | - | - | 96000376 B |

Takeaways kunci

  • ~ Implementasi heap pasangan secara asimtotik berkinerja jauh lebih baik daripada rekan-rekan yang didukung array. Namun bisa sampai 2x lebih lambat untuk ukuran tumpukan kecil (<50 elemen), mengejar sekitar 1000 elemen, tetapi hingga 2x lebih cepat untuk tumpukan ukuran 10^6~.
  • Seperti yang diharapkan, tumpukan pasangan menghasilkan sejumlah besar alokasi tumpukan.
  • Implementasi "PrioritySet" secara konsisten lambat ~ yang paling lambat dari ketiga pesaing ~, jadi kami mungkin tidak ingin mengejar pendekatan itu.

~Mengingat hal di atas, saya masih percaya ada trade-off yang valid antara tumpukan array dasar dan pendekatan tumpukan berpasangan~.

EDIT: memperbarui hasil setelah perbaikan bug di tolok ukur saya, terima kasih @VisualMelon

@eiriktsarpalis Patokan Anda untuk PairingHeap , menurut saya, salah: parameter ke Add adalah sebaliknya. Saat Anda menukarnya, ini adalah cerita yang berbeda: https://Gist.github.com/VisualMelon/00885fe50f7ab0f4ae5cd1307312109f

(Saya melakukan hal yang persis sama ketika saya pertama kali menerapkannya)

Perhatikan bahwa ini tidak berarti tumpukan pasangan lebih cepat atau lebih lambat, tetapi tampaknya sangat bergantung pada distribusi/urutan data yang disediakan.

@eiriktsarpalis re: kegunaan PrioritySet...
Kami seharusnya tidak mengharapkan pembaruan menjadi apa pun selain lebih lambat untuk heapsort, karena tidak memiliki pembaruan prioritas dalam skenario. (Juga untuk heapsort, kemungkinan Anda bahkan ingin menyimpan duplikat, satu set tidak sesuai.)

Tes lakmus untuk melihat apakah PrioritySet berguna harus merupakan algoritma pembandingan yang menggunakan pembaruan prioritas, versus implementasi non-pembaruan dari algoritma yang sama, mengantrekan nilai duplikat dan mengabaikan duplikat saat dequeueing.

Terima kasih @VisualMelon , saya memperbarui hasil dan komentar saya setelah perbaikan yang Anda sarankan.

melainkan tampaknya sangat bergantung pada distribusi/urutan data yang diberikan.

Saya percaya itu mungkin mendapat manfaat dari fakta bahwa prioritas yang diantrekan monoton.

Tes lakmus untuk melihat apakah PrioritySet berguna harus merupakan algoritma pembandingan yang menggunakan pembaruan prioritas, versus implementasi non-pembaruan dari algoritma yang sama, mengantrekan nilai duplikat dan mengabaikan duplikat saat dequeueing.

@TimLovellSmith tujuan saya di sini adalah untuk mengukur kinerja untuk aplikasi PriorityQueue yang paling umum: daripada mengukur kinerja pembaruan, saya ingin melihat dampaknya untuk kasus di mana pembaruan tidak diperlukan sama sekali. Namun mungkin masuk akal untuk menghasilkan tolok ukur terpisah yang membandingkan tumpukan pasangan dengan pembaruan "PrioritySet".

@miyu terima kasih atas umpan balik terperinci Anda, sangat dihargai!

@TimLovellSmith Saya menulis tolok ukur sederhana yang menggunakan pembaruan:

| Metode | Ukuran | Berarti | Kesalahan | StdDev | Median | Gen 0 | Gen 1 | Gen 2 | Dialokasikan |
|------------ |-------- |---------------:|---------- -----:|---------------:|---------------:|-------:| ------:|------:|-----------:|
| Set Prioritas | 10 | 1.052 kita | 0,0106 kami | 0,0099 kami | 1.055 kami | - | - | - | - |
| PairingHeap | 10 | 1.055 kami | 0,0042 kami | 0,0035 kami | 1.055 kami | 0,0057 | - | - | 480 B |
| Set Prioritas | 50 | 7.394 kami | 0,0527 kita | 0,0493 kami | 7.380 kami | - | - | - | - |
| PairingHeap | 50 | 8.587 kami | 0.1678 kami | 0.1570 kami | 8.634 kami | 0,0305 | - | - | 2400 B |
| Set Prioritas | 150 | 27,522 kami | 0,0459 kami | 0,0359 kami | 27.523 kami | - | - | - | - |
| PairingHeap | 150 | 32.045 kami | 0.1076 kita | 0.1007 kami | 32.019 kami | 0,0610 | - | - | 7200 B |
| Set Prioritas | 500 | 109.097 kami | 0.6548 kami | 0,6125 kami | 109.162 kami | - | - | - | - |
| PairingHeap | 500 | 131.647 kita | 0,5401 kami | 0.4510 kami | 131.588 kami | 0.2441 | - | - | 24000 B |
| Set Prioritas | 1000 | 238.184 kami | 1.0282 kami | 0.9618 kami | 238.457 kami | - | - | - | - |
| PairingHeap | 1000 | 293.236 kami | 0.9396 kami | 0,8789 kami | 293.257 kami | 0,4883 | - | - | 48000 B |
| Set Prioritas | 10.000 | 3.035,982 kami | 12.2952 kami | 10.8994 kami | 3.036.985 kami | - | - | - | 1 B |
| PairingHeap | 10.000 | 3,388.685 kami | 16.0675 kami | 38.1861 kami | 3.374.565 kami | - | - | - | 480002 B |
| Set Prioritas | 1000000 | 841,406.888 kami | 16.788.4775 kami | 15,703,9522 kami | 840.888.389 kami | - | - | - | 288 B |
| PairingHeap | 1000000 | 989.966.501 kami | 19.722.6687 kami | 30.705.8191 kami | 996,075.410 kami | - | - | - | 48000448 B |

Pada catatan terpisah, apakah diskusi/umpan balik mereka tentang kurangnya stabilitas menjadi masalah (atau bukan masalah) untuk kasus penggunaan orang?

apakah diskusi/umpan balik mereka tentang kurangnya stabilitas menjadi masalah (atau bukan masalah) untuk kasus penggunaan orang?

Tidak ada implementasi yang menjamin stabilitas, namun seharusnya cukup mudah bagi pengguna untuk mendapatkan stabilitas dengan menambah ordinal dengan urutan penyisipan:

var pq = new PriorityQueue<string, (int priority, int insertionCount)>();
int insertionCount = 0;

foreach (string element in elements)
{
    int priority = 42;
    pq.Enqueue(element, (priority, insertionCount++));
}

Untuk meringkas beberapa posting saya sebelumnya, saya telah mencoba mengidentifikasi seperti apa antrian prioritas .NET yang populer, jadi saya melihat data berikut:

  • Pola penggunaan antrean prioritas umum dalam kode sumber .NET.
  • Implementasi PriorityQueue di pustaka inti dari kerangka kerja yang bersaing.
  • Tolok ukur dari berbagai prototipe antrian prioritas .NET.

Yang menghasilkan takeaways berikut:

  • 90% dari kasus penggunaan antrian prioritas tidak memerlukan pembaruan prioritas.
  • Mendukung pembaruan prioritas menghasilkan kontrak API yang lebih rumit (memerlukan pegangan atau keunikan elemen).
  • Dalam tolok ukur saya, implementasi yang mendukung pembaruan prioritas 2-3x lebih lambat dibandingkan dengan yang tidak.

Langkah selanjutnya

Ke depan, saya mengusulkan agar kami mengambil tindakan berikut untuk .NET 6:

  1. Perkenalkan kelas System.Collections.Generic.PriorityQueue yang sederhana, memenuhi sebagian besar kebutuhan pengguna kami dan seefisien mungkin. Ini akan menggunakan tumpukan kuaterner yang didukung array dan tidak akan mendukung pembaruan prioritas. Sebuah prototipe implementasi dapat ditemukan di sini . Saya akan segera membuat masalah terpisah yang merinci proposal API.

  2. Kami menyadari perlunya heap yang mendukung pembaruan prioritas yang efisien, jadi kami akan terus berupaya memperkenalkan kelas khusus yang membahas persyaratan ini. Kami mengevaluasi beberapa prototipe [ 1 , 2 ] masing-masing dengan set trade-off mereka sendiri. Rekomendasi saya adalah untuk memperkenalkan jenis ini pada tahap selanjutnya, karena lebih banyak pekerjaan diperlukan untuk menyelesaikan desain.

Pada saat ini, saya ingin mengucapkan terima kasih kepada para kontributor thread ini, khususnya @pgolebiowski dan @TimLovellSmith. Umpan balik Anda telah memainkan peran besar dalam memandu proses desain kami. Saya berharap untuk terus menerima masukan Anda saat kami menyelesaikan desain untuk antrean prioritas yang dapat diperbarui.

Ke depan, saya mengusulkan agar kami mengambil tindakan berikut untuk .NET 6: [...]

Kedengarannya bagus :)

Perkenalkan kelas System.Collections.Generic.PriorityQueue yang sederhana, memenuhi sebagian besar kebutuhan pengguna kami dan seefisien mungkin. Ini akan menggunakan tumpukan kuaterner yang didukung array dan tidak akan mendukung pembaruan prioritas.

Jika kami memiliki keputusan oleh pemilik basis kode bahwa arah ini disetujui dan diinginkan, dapatkah saya terus memimpin desain API untuk bagian itu dan memberikan implementasi akhir?

Pada saat ini, saya ingin mengucapkan terima kasih kepada para kontributor thread ini, khususnya @pgolebiowski dan @TimLovellSmith. Umpan balik Anda telah memainkan peran besar dalam memandu proses desain kami. Saya berharap untuk terus menerima masukan Anda saat kami menyelesaikan desain untuk antrean prioritas yang dapat diperbarui.

Perjalanan yang cukup melelahkan :D

API untuk System.Collections.Generic.PriorityQueue<TElement, TPriority> baru saja disetujui. Saya telah membuat masalah terpisah untuk melanjutkan percakapan kita tentang implementasi heap potensial yang mendukung pembaruan prioritas.

Saya akan menutup masalah ini, terima kasih atas kontribusi Anda!

Mungkin seseorang bisa menulis tentang perjalanan ini! 6 tahun penuh untuk satu API. :) Adakah kesempatan untuk memenangkan Guinness?

Apakah halaman ini membantu?
0 / 5 - 0 peringkat